Click here to Skip to main content
15,867,453 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
for my cards game I can add and show PlayingCards (which are images created fron templates) on the Mainwindow only like this:
For Each cardShuffled0 In handCards0
     Panel0.Children.Add(cardShuffled0)
Next

Cannot add an Observable Collection to Panel0.Children

If i try data binding, the panel does not show the cards.

The HandCards class is this:

Public Class HandCards
    Implements INotifyPropertyChanged

    Private _MyHand0 As New ObservableCollection(Of PlayingCard)

    Public Event PropertyChanged As PropertyChangedEventHandler
    Private Event INotifyPropertyChanged_PropertyChanged As
        PropertyChangedEventHandler

    Implements INotifyPropertyChanged.PropertyChanged

    Public Property GetHand0 As ObservableCollection(Of PlayingCard)
        Get
            Return _MyHand0
        End Get
        Set(value As ObservableCollection(Of PlayingCard))
            _MyHand0 = value
            OnPropertyChanged("GetHand0")
        End Set
    End Property

    ' Create the OnPropertyChanged method to raise the event
    Protected Sub OnPropertyChanged(GetHand0)
        RaiseEvent PropertyChanged(Me, New
            PropertyChangedEventArgs("GetHand0"))
        RaiseEvent PropertyChanged(Me, New
            PropertyChangedEventArgs("AddCard"))
    End Sub

    Public Sub AddCard(card As PlayingCard)

        If _MyHand0.Contains(card) Then
            Throw New InvalidOperationException($"Hand already contains
                card {card}.")
        End If
        _MyHand0.Add(card)
        OnPropertyChanged("AddCard")
    End Sub

End Class


What I have tried:

For Each cardShuffled0 In handCards0
    MyHand0.AddCard(cardShuffled0)
Next

<StackPanel x:Name="MyPanel">
    <ItemsControl  x:Name="Panel0" VerticalAlignment="Top"
                       HorizontalAlignment="Center"
                       DockPanel.Dock="Top" Height="150" Width="800"
                       Margin="0,40,0,0"
                       ItemsSource="{Binding GetHand0}" >
        <ItemsControl.DataContext>
            <local:HandCards/>
        </ItemsControl.DataContext>
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <DockPanel />
            </DataTemplate>
        </ItemsControl.ItemTemplate>
Posted
Updated 9-Jan-23 13:26pm
v2

1 solution

There are a number of issues with the code that you are sharing.

As this is related to the previous question[^] I will modify code from my previous answer[^] to be suitable for WPF.

1. We can simplify the implementation of the INotifyPropertyChanged interface with a base class:
VB.NET
Imports System.ComponentModel
Imports System.Runtime.CompilerServices

Public MustInherit Class ObservableObject : Implements INotifyPropertyChanged

    Public Sub SetValue(Of TValue) _
        (
            ByRef field As TValue,
            newValue As TValue,
            <CallerMemberName> Optional propertyName As String = ""
        )

        If field IsNot Nothing Then
            If Not EqualityComparer(Of TValue).Default.Equals(field, Nothing) And
               field.Equals(newValue) Then
                Return
            End If
        End If

        field = newValue

        OnPropertyChanged(propertyName)

    End Sub

    Public Event PropertyChanged As PropertyChangedEventHandler _
    Implements INotifyPropertyChanged.PropertyChanged

    Public Sub OnPropertyChanged(propertyName As String)
        RaiseEvent PropertyChanged(Me, _
            New PropertyChangedEventArgs(propertyName))
    End Sub

End Class

2. Now we need to modify the CardDeck class for DataBinding:
VB.NET
Imports System.Collections.ObjectModel
Imports System.Collections.Specialized

Public Class CardDeck : Inherits ObservableObject : Implements IDisposable

    Public Sub New()
        Cards = New ObservableCollection(Of Card)
        AddHandler Cards.CollectionChanged, AddressOf CardCollectionChanged
        Reset()
    End Sub

    Public ReadOnly Property Cards As ObservableCollection(Of Card)

    Public ReadOnly Property Count As Integer
        Get
            Return Cards.Count
        End Get
    End Property

    Private Sub CardCollectionChanged(sender As Object, e As NotifyCollectionChangedEventArgs)
        OnPropertyChanged(NameOf(Count))
    End Sub

    Public Sub Reset()

        Dim cardCollection = [Enum].GetValues(GetType(Suit)) _
            .Cast(Of Suit)() _
            .SelectMany(Function(__) [Enum].GetValues(GetType(Kind)) _
                           .Cast(Of Kind)(),
                        Function(suit, kind) New With {suit, kind}) _
            .Select(Function(card) New Card(card.kind, card.suit))

        Cards.Clear()

        For Each card As Card In cardCollection
            Cards.Add(card)
        Next

    End Sub

    Public Function DrawCardAt(index As Integer) As Card
        If index < 0 OrElse index >= Count Then
            Throw New ArgumentOutOfRangeException(NameOf(index))
        End If
        Dim card As Card = Cards(index)
        Cards.RemoveAt(index)
        Return card
    End Function

    Public Function DrawTopCard() As Card
        Return DrawCardAt(0)
    End Function

    Public Function DrawBottomCard() As Card
        Return DrawCardAt(Count - 1)
    End Function

    Public Function DrawRandomCard() As Card
        Dim random As Random = New Random()
        Dim index As Integer = random.[Next](Count)
        Return DrawCardAt(index)
    End Function

    Public Sub AddCardOnTop(card As Card)
        If Cards.Contains(card) Then
            Throw New InvalidOperationException($"Deck already contains card {card}.")
        End If
        Cards.Insert(0, card)
    End Sub

    Public Sub AddCardOnBottom(card As Card)
        If Not Cards.Contains(card) Then
            Cards.Add(card)
            Return
        End If

        Throw New InvalidOperationException($"Deck already contains card {card}.")
    End Sub

    Public Sub Shuffle()
        ReorderCards(Cards.OrderBy(Function(x) Guid.NewGuid()).ToList())
    End Sub

    Public Sub Sort()
        Dim sorted = New List(Of Card)(Cards.ToList())
        sorted.Sort()
        ReorderCards(sorted)
    End Sub

    Public Sub Sort(comparer As IComparer(Of Card))
        Dim sorted = New List(Of Card)(Cards.ToList())
        sorted.Sort(comparer)
        ReorderCards(sorted)
    End Sub

    Private Sub ReorderCards(ordered As List(Of Card))
        For index As Integer = 0 To ordered.Count - 1
            Cards.Move(Cards.IndexOf(ordered(index)), index)
        Next
    End Sub

    Public Sub Dispose() Implements IDisposable.Dispose
        RemoveHandler Cards.CollectionChanged, AddressOf CardCollectionChanged
    End Sub

End Class

Notes:
* Cards collection is marked as ReadOnly to prevent renewing and breaking data binding. Instead, we call the Clear method on the ObservableCollection class. We also do not need to raise the PropertyChanged event as the ObservableCollection class uses the CollectionChanged event. Ther Data Binding will be listening for this event.
* As we are working with the ObservableCollection class, we need to manually move the items around - see the ReorderCards method to see how this is done.

3. We need to do the same to the Hand Class:
VB.NET
Imports System.Collections.ObjectModel
Imports System.Collections.Specialized

Public Class Hand : Inherits ObservableObject : Implements IDisposable

    Private nameValue As String

    Public Sub New(name As String)
        Me.Name = name
        Me.Cards = New ObservableCollection(Of Card)
        AddHandler Cards.CollectionChanged, AddressOf CardCollectionChanged
    End Sub

    Public ReadOnly Property Cards As ObservableCollection(Of Card)

    Public Property Name As String
        Get
            Return nameValue
        End Get
        Set
            SetValue(nameValue, Value)
        End Set
    End Property

    Public ReadOnly Property Count As Integer
        Get
            Return Cards.Count
        End Get
    End Property

    Private Sub CardCollectionChanged(sender As Object, e As NotifyCollectionChangedEventArgs)
        OnPropertyChanged(NameOf(Count))
    End Sub

    Public Sub Reset()
        Cards.Clear()
    End Sub

    Public Sub AddCard(card As Card)
        If Cards.Contains(card) Then
            Throw New InvalidOperationException($"Hand already contains card {card}.")
        End If
        Cards.Add(card)
    End Sub

    Public Sub RemoveCard(card As Card)
        If Not Cards.Contains(card) Then
            Throw New InvalidOperationException($"Hand does not contain card {card}.")
        End If
        Cards.Add(card)
    End Sub

    Public Sub Dispose() Implements IDisposable.Dispose
        RemoveHandler Cards.CollectionChanged, AddressOf CardCollectionChanged
    End Sub

End Class

4. Now we are ready to implement our logic in the MainWindow code-behind:
VB.NET
Class MainWindow
    Sub New()
        InitData()
        InitializeComponent()
    End Sub

    Property Deck As CardDeck
    Property Hand As Hand

    Private dealer As Dealer

    Private Sub OnShuffleClick(sender As Object, e As RoutedEventArgs)
        Deck.Shuffle()
    End Sub

    Private Sub OnSortClick(sender As Object, e As RoutedEventArgs)
        Deck.Sort()
    End Sub

    Private Sub OnResetClick(sender As Object, e As RoutedEventArgs)
        Deck.Reset()
        Hand.Reset()
    End Sub

    Private Sub OnDrawClick(sender As Object, e As RoutedEventArgs)
        dealer.DealCard(Hand)
    End Sub

    Private Sub OnClearClick(sender As Object, e As RoutedEventArgs)
        Hand.Reset()
    End Sub

    Private Sub InitData()
        Deck = New CardDeck()
        Hand = New Hand("Fred")
        dealer = New Dealer(Deck)
    End Sub

End Class

5. Lastly, the MainWindow XAML/UI:
XML
<Window x:Class="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfCardUIVB"
        mc:Ignorable="d" x:Name="Window"
        Title="MainWindow" Height="450" Width="800">

    <Grid DataContext="{Binding ElementName=Window}">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>

        <TextBlock HorizontalAlignment="Center">
            <Run Text="Count: "/>
            <Run Text="{Binding Deck.Count, Mode=OneWay}"/>
        </TextBlock>

        <ListBox ItemsSource="{Binding Deck.Cards}"
                 Margin="10 10 10 0"
                 Grid.Row="1"/>

        <Grid Grid.Row="2" Margin="10">
            <Grid.ColumnDefinitions>
                <ColumnDefinition/>
                <ColumnDefinition/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <Grid.Resources>
                <Style TargetType="Button">
                    <Setter Property="Width" Value="100" />
                    <Setter Property="Padding" Value="20 10" />
                </Style>
            </Grid.Resources>
            <Button Content="Shuffle" Click="OnShuffleClick" />
            <Button Content="Sort" Click="OnSortClick" Grid.Column="1" />
            <Button Content="Reset" Click="OnResetClick" Grid.Column="2" />
        </Grid>

        <Grid Grid.Column="1">
            <Grid.ColumnDefinitions>
                <ColumnDefinition/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>

            <TextBlock HorizontalAlignment="Center">
                <Run Text="Name: "/>
                <Run Text="{Binding Hand.Name, Mode=OneWay}"/>
            </TextBlock>

            <TextBlock HorizontalAlignment="Center" Grid.Column="1">
                <Run Text="Count: "/>
                <Run Text="{Binding Hand.Count, Mode=OneWay}"/>
            </TextBlock>

        </Grid>

        <ListBox ItemsSource="{Binding Hand.Cards}"
                 Margin="10 10 10 0"
                 Grid.Row="1" Grid.Column="1"/>

        <Grid Grid.Row="2" Grid.Column="1" Margin="10">
            <Grid.ColumnDefinitions>
                <ColumnDefinition/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <Grid.Resources>
                <Style TargetType="Button">
                    <Setter Property="Width" Value="100" />
                    <Setter Property="Padding" Value="20 10" />
                </Style>
            </Grid.Resources>
            <Button Content="Draw" Click="OnDrawClick" />
            <Button Content="Clear" Click="OnClearClick" Grid.Column="1" />
        </Grid>

    </Grid>
</Window>

When the app starts, the left side will show the Deck and the right side the Hand. Both have card counts, the Hand with a player name. The Dealer class is used to draw cards from the top of the deck.

If you want to shuffle cards before the next draw card, press the Shuffle button.

As you click on the Draw button, the top card is removed from the Deck and appended to the Hand, and the counts will update.

The Clear button will clear the Hand and not change the Deck.

The Reset button will reset the Deck and clear the Hand.

This is a data-first method. All updates to the XAML/UI are managed via the built-in Data Binding.
 
Share this answer
 
v2
Comments
Jo_vb.net 10-Jan-23 13:57pm    
Thanks for your hard work!

But after more than 6 hours doing changes and testing I gave up.

I can verify via the properties that 8 cards are in the ObervableCollection (Count) and that PropertyChanged fires each time when a card is added (using a MsgBox to check this) but Count on the panel shows 0 and the listbox ist empty all the time.

Even using UpdateSourceTrigger=PropertyChanged does not help.

Perhaps you can use your framework for writing a nice article.
Graeme_Grant 10-Jan-23 16:34pm    
*sigh* ... I did not give you a broken solution. If you implemented the code as-is above, in its own project, not in your own, it will work. This link will help you better understand DataBinding: Introduction to WPF data binding - The complete WPF tutorial[^]

Here is a downloadable working version, the same code as above: CardDeck.zip - Google Drive[^]
Jo_vb.net 11-Jan-23 4:56am    
Thanks- your project runs fine!

Found my mistake:

Used Friend Cards As New HandCards
instead of your declaration as property
Property Hand As HandCards

And your gif animation of cards looks great.

Thanks for your help!
Graeme_Grant 11-Jan-23 5:04am    
Glad to hear that you fixed your issue.
Jo_vb.net 11-Jan-23 12:43pm    
This allows me to use parts of your framework for a redesign of my cards game.

What I do not understand why you allow to sort the cards deck but not the hand cards.

In my game the computer controls 3 players which play against the user.
The problem is to code the game rules in good style (OOP?).

Do you know where to find a VB.Net example with rules for games like Schafkopf, Bridge or Romme?

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900