Search code examples
c#xamlwinui-3

Unable to bind Checkbox to Field of Class in WinUI3


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

  1. 1st one with DataTemplate and a checkbox
  2. With DataGridCheckBoxColumn

Looks like this:

enter image description here

  • The first column with DataTemplate and check box works fine on display side but does not bind i.e. changes IsChecked when Binding value (isBowling) is changed progmatically.
  • The second column with DataGridCheckBox remains Disabled, I have no idea why ?

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.


Solution

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