DataGrid from CommunityToolkit.WinUI.Controls.DataGrid not updating when bound ObservableCollection changes

639 Views Asked by At

I have a dropdown with three filter options: All, Active, and Inactive. Changing the selected value raises an event that adjusts the observable collection property ProjectDbUsers in the ViewModel, which a Datagrid showing application users is bound to. The code successfully changes the ProjectDbUsers property when I debug, but the Datagrid is not refreshing/updating to match the changed object collection.

Note: I'm using the CommunityToolkit.WinUI.Controls.DataGrid package in the main UI project and the CommunityToolkit.Mvvm package in the business layer project that contains the ViewModel.

The code:

In the Manage Users page XAML, showing the Combobox and Datagrid:

        <ComboBox x:Name="BasicUserFilter"  Grid.Row="0" Grid.Column="1"
                   Height="30" Width="Auto" Margin="20,10,10,10"
                  ItemsSource="{x:Bind ViewModel.Filters, Mode=OneWay}" 
                  SelectedItem="{x:Bind ViewModel.SelectedFilter, Mode=TwoWay}"                      
                  SelectionChanged="BasicUserFilter_SelectionChanged"/>

<controls:DataGrid x:Name="dataGrid1" Grid.Row="2"
Height="600" Margin="12"
AutoGenerateColumns="False"
ItemsSource="{x:Bind ViewModel.ProjectDbUsers, Mode=OneWay}">
    <controls:DataGrid.Columns>
        <controls:DataGridTextColumn 
            Header="Name" 
            Width="250"
            Binding="{Binding UserAddisplayName}" 
            FontSize="14" />
            ...
    </controls:DataGrid.Columns>
</controls:DataGrid> 

The code behind where the BasicUserFilter_SelectionChanged event fires when changing the filter Combobox value.

private void BasicUserFilter_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    ViewModel.UpdateGridBasedOnFilter();
}

Excerpts from the ViewModel class, showing the bound property definitions and the method called when the filter dropdown selection has changed:

[ObservableProperty]
private string _selectedFilter;   

public ObservableCollection<string> Filters { get; } = new ObservableCollection<string>(new List<string>() { "All", "Active", "Inactive" });

public ObservableCollection<UserAd> ProjectDbUsers { get; private set; }

public void UpdateGridBasedOnFilter()
{
    using (UnitOfWorkDbGlobal unitOfWorkDbGlobal = new(new MyDBContext()))
    {
        List<UserAd> users = new();
        switch (SelectedFilter)
        {
            case "All":
                users = unitOfWorkDbGlobal.UserAds.GetAllUsers().ToList();
                break;
            case "Active":
                users = unitOfWorkDbGlobal.UserAds.GetAllActiveUsers().ToList();
                break;
            case "Inactive":
                users = unitOfWorkDbGlobal.UserAds.GetAllInactiveUsers().ToList();
                break;
        }

        ProjectDbUsers = new ObservableCollection<UserAd>(users);
    }
}

What might be missing in order to allow the Datagrid to update when the ProjectDbUsers changes?

1

There are 1 best solutions below

1
On BEST ANSWER

Since you are reinstantiating the projectDbUsers property itself, you need to make it ObservableProperty.

Not directly related to your question, but instead of calling BasicUserFilter_SelectionChanged() in code-behind, you can use partial methods generated by the CommunityToolkit.Mvvm.

See this sample code.

using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;

namespace DataGridTest;

public enum Filters
{
    All,
    Active,
    Inactive,
}

public class UserAd
{
    public string Name { get; set; } = string.Empty;
}

public partial class MainPageViewModel : ObservableObject
{
    private List<UserAd> userAdSource = new()
    {
        new UserAd() { Name = "User A - Active" },
        new UserAd() { Name = "User B - Inactive" },
        new UserAd() { Name = "User C - Active" },
        new UserAd() { Name = "User D - Inactive" },
        new UserAd() { Name = "User E - Active" },
    };

    // The CommunityToolkit.Mvvm auto-generates 
    // partial void OnSelectedFilterChanged(Filters value).
    [ObservableProperty]
    private Filters selectedFilter = Filters.All;

    // ObservableCollections notifies the UI when you add/remove elements.
    // Since you are reinstantiating the projectDbUsers property itself, 
    // you need to make it ObservableProperty.
    //public ObservableCollection<UserAd> ProjectDbUsers { get; set; }
    [ObservableProperty]
    private ObservableCollection<UserAd>? projectDbUsers;

    public MainPageViewModel()
    {
        IEnumerable<UserAd> filteredUserAds = GetFilteredUserAds(SelectedFilter);
        ProjectDbUsers = new ObservableCollection<UserAd>(filteredUserAds);
    }

    public List<Filters> FilterList { get; } = new(Enum.GetValues<Filters>());

    // This "partial method" is auto-generated by the CommunityTookit.Mvvm.
    async partial void OnSelectedFilterChanged(Filters value)
    {
        IEnumerable<UserAd> filteredUserAds = await GetFilteredUserAds(value);
        ProjectDbUsers = new ObservableCollection<UserAd>(filteredUserAds);
    }

    private Task<IEnumerable<UserAd>> GetFilteredUserAds(Filters filter)
    {
        return await Task.Run(() =>
        {
            return filter is Filters.All
                ? this.userAdSource
                : this.userAdSource.Where(x => x.Name.Contains(filter.ToString()));
        });
    }
}

MainPage.xaml

<Page
    x:Class="DataGridTest.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:DataGridTest"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
    mc:Ignorable="d">

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <ComboBox
            Grid.Row="0"
            ItemsSource="{x:Bind ViewModel.FilterList}"
            SelectedItem="{x:Bind ViewModel.SelectedFilter, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />

        <controls:DataGrid
            Grid.Row="1"
            AutoGenerateColumns="False"
            ItemsSource="{x:Bind ViewModel.ProjectDbUsers, Mode=OneWay}">
            <controls:DataGrid.Columns>
                <controls:DataGridTextColumn
                    Width="250"
                    Binding="{Binding Name}"
                    FontSize="14"
                    Header="Name" />
            </controls:DataGrid.Columns>
        </controls:DataGrid>
    </Grid>
</Page>