Home > Software engineering >  How to call events using CommunityToolkit.Mvvm
How to call events using CommunityToolkit.Mvvm

Time:07-12

I am "attempting" to learn the MvvM architecture using C# within a WPF application. I quickly realized that I was writing a tonne of duplication boilerplate thus I decided to install the NuGet CoummunityToolkit.Mvvm to handle the vast majority for me.

I then started to create a very simple application which would just display items for me to learn the basics of MvvM. Therefore my View looks like such...

<UserControl.DataContext>
    <viewmodel:DrawingMenuViewModel/>
</UserControl.DataContext>

<StackPanel>
    <TextBox Text="{Binding SearchFilter}"/>

    <DataGrid x:Name="ContactsResult"
            ItemsSource="{Binding DrawingsCollectionView}"
            SelectedItem="{Binding SelectedModel}"
            AutoGenerateColumns="False" CanUserAddRows="False"
            IsReadOnly="True" Height="300">

        <DataGrid.Columns>
            <DataGridTextColumn Header="DRAWING NO"
                            Binding="{Binding Path = DNumber}"/>
            <DataGridTextColumn Header="DESCRIPTION"
                            Binding="{Binding Path = Description}"
                            Width="*"/>
            <DataGridTextColumn Header="REV"
                            Binding="{Binding Path = Revision}"
                            Width="200"/>
        </DataGrid.Columns>

    </DataGrid>

    <Button Content="Click" Command="{Binding OpenDrawingCommand}"/>

</StackPanel>

Then I started to rig up the backend ViewModel which works perfectly so far...

public partial class DrawingMenuViewModel : ObservableObject
{
    public ObservableCollection<DrawingModel> _Drawings;
    [ObservableProperty] ICollectionView drawingsCollectionView;
    [ObservableProperty] DrawingModel selectedModel;

    [ObservableProperty] string searchFilter = "D";

    public DrawingMenuViewModel() => PopulateDatagrid();

    #region Populating DataGrid
    public void PopulateDatagrid()
    {
        // Initialize Data into the DataGrid Control
        List<DrawingModel> testD = new List<DrawingModel>
        {
            new DrawingModel { DNumber = "D1294", Description = "Test 1", Revision = "A"},
            new DrawingModel { DNumber = "D1295", Description = "Test 2", Revision = "A"},
            new DrawingModel { DNumber = "D1296", Description = "Test 3", Revision = "A"},
            new DrawingModel { DNumber = "D1297", Description = "Test 4", Revision = "A"},
        };

        // Hook-up data to view and set filter for search capability
        _Drawings = new ObservableCollection<DrawingModel>(testD);
        DrawingsCollectionView = CollectionViewSource.GetDefaultView(_Drawings);
        DrawingsCollectionView.Filter = FilterDatagrid;
    }

    private bool FilterDatagrid(object obj)
    {
        if (obj is DrawingModel d)
        {
            return d.DNumber.Contains(searchFilter, StringComparison.OrdinalIgnoreCase) ||
                d.Description.Contains(searchFilter, StringComparison.OrdinalIgnoreCase) ||
                d.Revision.Contains(searchFilter, StringComparison.OrdinalIgnoreCase);
        }

        return false;
    }
    #endregion

    [ICommand]
    async Task OpenDrawing()
    {
        if (SelectedModel != null)
        {
            MessageBox.Show(SelectedModel.DNumber);
        }
    }
}

Where my issues begin is that I am struggling to understand how I can rig events such as a TextChanged event from the SearchFilter textbox which would run a command to filter the datagridview using the following statement...

CollectionViewSource.GetDefaultView(DrawingsCollectionView).Refresh(); 

What I have tried without success is something like...

<TextBox Text="{Binding SearchFilter}" TextChanged="{Binding OpenDrawingCommand}"/>

Which results in error XLS0523 "Event 'TextChanged' can only be bound to properties of delegate type 'TextChangedEventHandler'.". So I attempted to try calling the event like so...

XAML
<TextBox Text="{Binding SearchFilter}" TextChanged="{Binding OpenDrawing}"/>

C#
public void OpenDrawing(object sender, TextChangedEventArgs e)
{
    CollectionViewSource.GetDefaultView(DrawingsCollectionView).Refresh();
}

Resulting in the following error...

System.Windows.Markup.XamlParseException: ''Provide value on 'System.Windows.Data.Binding' threw an exception.' Line number '15' and line position '48'.'

CodePudding user response:

One solution is the use partial methods that are generated for just this purpose.

Here's what generally generated when you apply ObservableProperty to a field.

You can view this in the Project>Dependencies>Analyzers>CommunityToolkit.Mvvm.SourceGenerators project node.

public string SearchFilter
{
    get => searchFilter;
    set
    {
        if (!EqualityComparer<string>.Default.Equals(searchFilter, value))
        {
            OnSearchFilterChanging(value);
            OnPropertyChanging(new PropertyChangedEventArgs(nameof(SearchFilter)));
            searchFilter = value;
            OnSearchFilterChanged(value);
            OnPropertyChanged(new PropertyChangedEventArgs(nameof(SearchFilter)));
        }
    }
}

partial void OnSearchFilterChanging(string value);
partial void OnSearchFilterChanged(string value);

You can learn more about partial methods here but the TLDR is that the implementation is left up to you but it's also not required that you implement it.

All you need to do is implement the method and it will call those partial methods before and after the property changes.
If you type partial then space, you will get intellisense for partial methods just like when you type override.

partial void OnSearchFilterChanged(string value)
{
    DrawingsCollectionView.Refresh();
}

You might also want to update your textbox binding to be more (but not too) responsive.

<TextBox Text="{Binding SearchFilter, UpdateSourceTrigger=PropertyChanged, Delay=250}"/>
  • Related