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:
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:
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:
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:
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:
<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
.