I saw question WinUI 3 Datagrid Checkbox not Bound when using Template Column and Cell/Data Template but it is unanswered, while I am unable to get even to basic levels of that question.
In DataGrid,I am trying to create a set of check boxes which will select a max 2 items while remaining will be unchecked/greyed out till user unselects any one. I plan to do the uncheck and disable check boxes from C# code and thats why I need it to be bound Two way.
To test I created two columns, though I need only one working
Looks like this:
my XAML code:
<controls:DataGrid.Columns >
<controls:DataGridTemplateColumn Header="Select Two">
<controls:DataGridTemplateColumn.CellTemplate>
<DataTemplate >
<CheckBox IsChecked="{Binding isBowling ,Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Checked="CheckBox_Checked" />
</DataTemplate>
</controls:DataGridTemplateColumn.CellTemplate>
</controls:DataGridTemplateColumn>
<controls:DataGridCheckBoxColumn IsReadOnly="False" Header="Select 2" Binding="{Binding isBowling, Mode=TwoWay}"/>
<controls:DataGridTextColumn IsReadOnly="False" FontSize="10" Header="Name" Binding="{Binding Name}" />
</controls:DataGrid.Columns>
</controls:DataGrid>
I tried using INotifyPropertyChanged in the Class.
The Bowler Class which is binded to DataGrid
public class Bowler : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string PlayerID;
public string Name { get; set; }
public int Stamina { get; set; }
public double Overs { get; set; }
public int Maiden { get; set; }
public int Runs { get; set; }
public int Wickets { get; set; }
public int NoBall { get; set; }
public int Wides { get; set; }
public string LastBall { get; set; }
public bool isBowling
{
get { return isBowling; }
set
{
isBowling = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(isBowling)));
}
}
public override string ToString()
{
return JsonConvert.SerializeObject(this);
}
}
When I use INotifyPropertyChanged, I get
Stack Overflow Error at isBowling = value;
when I am creating the ObservableCollection using this method, the Stack Overflow Exception happens saying the setting of value was done multiple times.
private void FillObservableListBowling(ObservableCollection<Bowler> bowlingTeam, List<Player> teamPlayers)
{
Debug.WriteLine("Size of bowler list is " + teamPlayers.Count);
foreach(Player player in teamPlayers)
{
if (!player.BOWLERTYPE.Equals("NA"))
{
Bowler bowler = new Bowler();
bowler.PlayerID = player.PLAYERID;
bowler.Name = player.PLAYERDISPLAYNAME;
try
{
bowler.Stamina = player.STAMINACARD.Equals("NA") || player.STAMINACARD.Equals("") ? 0 : int.Parse(player.STAMINACARD);
}
catch (FormatException ex)
{
Debug.WriteLine("Exception happened for " + player.ToString());
}
bowler.Overs = 0;
bowler.Maiden = 0;
bowler.Runs = 0;
bowler.Wickets = 0;
bowler.NoBall = 0;
bowler.Wides = 0;
bowler.LastBall = "";
bowler.isBowling = false;
bowlingTeam.Add(bowler);
}
}
}
I tried using Mode =Two Way, UpdateSourceTrigger=PropertyChanged at all places but somehow neither of the two(DataTemplate and DataGridCheckBox) seems to work for me.
Any help would be highly appreciated to make Check Boxes (either) working fine i.e. when changed the value of Class variable, it should reflect here in the checkbox.
You need to use a field like this:
private bool isBowling;
public bool IsBowling
{
get => _isBowling;
set
{
if (_isBowling == value)
{
return;
}
_isBowling = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsBowling)));
}
}
But I'd recommend to use the CommunityToolkit.Mvvm NuGet package for this. It uses source generators that creates boilerplate code for you.
In your case, your code would be something like this:
Bowler.cs
public partial class Bowler : ObservableObject
{
[ObservableProperty]
private string name = string.Empty;
[ObservableProperty]
private bool isEnabled = true;
[ObservableProperty]
private bool isBowling;
}
MainPageViewModel.cs
public partial class MainPageViewModel : ObservableObject
{
public ObservableCollection<Bowler> Bowlers { get; } = new();
public MainPageViewModel()
{
int itemsCount = 10;
for (int i = 0; i < itemsCount; i++)
{
Bowler bowler = new();
bowler.PropertyChanged += Bowler_PropertyChanged;
Bowlers.Add(bowler);
}
}
private void Bowler_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName is not nameof(Bowler.IsBowling))
{
return;
}
if (Bowlers.Count(x => x.IsBowling is true) < 2)
{
foreach (Bowler bowler in Bowlers)
{
bowler.IsEnabled = true;
}
return;
}
foreach (Bowler bowler in Bowlers)
{
bowler.IsEnabled = bowler.IsBowling is true;
}
}
}
MainPage.xaml.cs
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
public MainPageViewModel ViewModel { get; } = new();
}
MainPage.xaml
<Page
x:Class="DataGridExample.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:CommunityToolkit.WinUI.UI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:DataGridExample"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Name="RootPage"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
mc:Ignorable="d">
<Grid RowDefinitions="Auto,*">
<StackPanel Orientation="Horizontal" />
<controls:DataGrid
Grid.Row="1"
AutoGenerateColumns="False"
HeadersVisibility="Column"
IsReadOnly="False"
ItemsSource="{x:Bind ViewModel.Bowlers, Mode=OneWay}">
<controls:DataGrid.Columns>
<controls:DataGridTemplateColumn>
<controls:DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="local:Bowler">
<CheckBox
IsChecked="{x:Bind IsBowling, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
IsEnabled="{x:Bind IsEnabled, Mode=OneWay}" />
</DataTemplate>
</controls:DataGridTemplateColumn.CellTemplate>
</controls:DataGridTemplateColumn>
</controls:DataGrid.Columns>
</controls:DataGrid>
</Grid>
</Page>
NOTE: Avoid using the DataGridCheckBoxColumn
. It has some binding or focusing issues.