Search code examples
.netxamldatagridwinui-3winui

In a Datagrid: How to display change in cell B after changing value in cell A


I have a winui3 project using the CommunityToolkit.WinUI.UI.Controls.DataGrid. I have an ObservableCollection<ExampleClass> that I bind to a DataGrid. The relevant variables of the ExampleClass are: Amount int, Price int, TotalPrice int. I added an event to my datagrid, CellEditEnded, which updates the TotalPrice to Amount * Price. After the user changes Amount or Price in the datagrid at runtime, but the following problem is still there with any kind of change to the value of TotalPrice. Price is == 10 at the start of the app. When the user changes Amount to let's say 2, the Totalprice Cell of that ExampleClass instance still shows 0 (0 amount * 10 price). I have checked and the TotalPrice value has correctly changed in the Observablecollection. The problem is that the DataGrid won't show the change. When I reload the page, the change is finally made correctly. Alternatively when the user starts editing the TotalPrice cell, the correct value suddenly shows up and stays in the cell. Funny also when I try to change the totalprice value manually, it always goes back to the correct value.

With this problem the user, after editing amount or price, always has to start editing all the outdated totalprice cells to see the correct value. OR he reloads the page to see all correct values.

The DataGrid seems not to update cell B (TotalPrice) when cell A (Amount or Price) is changed.

Amount = Anzahl, Price = Preis

<controls:DataGrid 
    AutoGenerateColumns="False"
    GridLinesVisibility="Horizontal"
    ItemsSource="{x:Bind ViewModel.kgr.Tabelle1, Mode=OneWay}"
    Grid.Column="0"
    Grid.Row="0"
    CellEditEnded="DataGrid_CellEditEnded>
    <controls:DataGrid.Resources>
        <SolidColorBrush x:Key="DataGridColumnHeaderBackgroundColor" Color="Transparent" />
    </controls:DataGrid.Resources>
    <controls:DataGrid.Columns>
        <controls:DataGridTextColumn Binding="{Binding Name}" Header="Bereich" />
        <controls:DataGridTextColumn Binding="{Binding Anzahl}" Header="Fläche in m^2" />
        <controls:DataGridTextColumn Binding="{Binding Preis}" Header="€ pro m^2" />
        <controls:DataGridTextColumn Binding="{Binding TotalPreis}" Header="Gesamt"/>
        <!-- IsReadOnly="True" -->
    </controls:DataGrid.Columns>
</controls:DataGrid>

I focused on the xaml file because the datasource is working all well. There I tried alternatives to the celleditevent, Binding Modes in the itemsource, other xaml properties in the: itemsource, datagrid itself, totalprice column, totalprice binding property etc etc. many of these properties had promising names (like UpdateSourceTrigger, Bindmode = twoway), but I tried alot of them and none seem to work. I am probably missing something but the mediocre documentation and young age of winui3 doesn't help, and I can't imagine that this feature wouldn't exist in a UI framework of Microsoft.

WPF devs use propertychanged, I looked for something with that name and found its a property of binding inside Datagridtextcolumn or a property inside itemsource, but the documentation is this https://learn.microsoft.com/en-us/dotnet/api/communitytoolkit.winui.ui.controls.datagridboundcolumn.binding?view=win-comm-toolkit-dotnet-7.0#communitytoolkit-winui-ui-controls-datagridboundcolumn-binding

Even a temporary inelegant fix will do, like reloading the datagrid, I just need something.

Hoping for some help.


Solution

  • My first guess is that you are not implementing INotifyPropertyChange on your properties.

    This is a sample code the uses the CommunityToolkit.Mvvm NuGet package the will generate code that implements INotifyPropertyChanged for you.

    Item.cs

    public partial class Item : ObservableObject
    {
        [ObservableProperty]
        private string name = string.Empty;
    
        [ObservableProperty]
        [NotifyPropertyChangedFor(nameof(Total))]
        private int price;
    
        [ObservableProperty]
        [NotifyPropertyChangedFor(nameof(Total))]
        private int quantity;
    
        public int Total => Price * Quantity;
    }
    

    MainPageViewModel.cs

    public partial class MainPageViewModel : ObservableObject
    {
        [ObservableProperty]
        private ObservableCollection<Item> items = new();
    
        public MainPageViewModel()
        {
            Items.Add(new Item { Name = "A", Price = 100, Quantity = 1 });
            Items.Add(new Item { Name = "B", Price = 200, Quantity = 2 });
            Items.Add(new Item { Name = "C", Price = 300, Quantity = 3 });
            Items.Add(new Item { Name = "D", Price = 400, Quantity = 4 });
            Items.Add(new Item { Name = "E", Price = 500, Quantity = 5 });
        }
    }
    

    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"
        Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
        mc:Ignorable="d">
        <Page.DataContext>
            <local:MainPageViewModel x:Name="ViewModel" />
        </Page.DataContext>
    
        <Grid>
            <controls:DataGrid
                AutoGenerateColumns="False"
                ItemsSource="{x:Bind ViewModel.Items, Mode=OneWay}">
                <controls:DataGrid.Columns>
                    <controls:DataGridTextColumn
                        Binding="{Binding Name}"
                        Header="Name"
                        IsReadOnly="True" />
                    <controls:DataGridTextColumn
                        Binding="{Binding Price}"
                        Header="Price" />
                    <controls:DataGridTextColumn
                        Binding="{Binding Quantity}"
                        Header="Quantity" />
                    <controls:DataGridTextColumn
                        Binding="{Binding Total}"
                        Header="Total"
                        IsReadOnly="True" />
                </controls:DataGrid.Columns>
            </controls:DataGrid>
        </Grid>
    </Page>