4

I'm trying to update actual fields in an ObservableCollection that is bound to a CollectionView.

The CollectionView updates fine on adding, removing items, but not if I programmatically change an item in the list.

I understand from this post Observable collection not updated that I need to implement INotifyPropertyChanged.

I am using the CommunityToolkit.Mvvm and had hoped that this kind of magic would be automatically done, but it appears not. I don't have the C# knowledge to know how to do what I want. Could someone help me please :)

There is a repo at https://github.com/gfmoore/TestCollectionBinding

and here is my current code:

Views.MyItem.cs


namespace TestCollectionBinding.Views;

public class MyItem
{
  public string Name { get; set; }
  public string Device { get; set; }
}

ViewModels.MainPageViewModel.cs

using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using TestCollectionBinding.Views;

namespace TestCollectionBinding.ViewModels;

public partial class MainPageViewModel : ObservableObject
{
  [ObservableProperty]
  public ObservableCollection<MyItem> myItems = new()
  {
    new MyItem
    {
      Name = "Heart Rate Monitor",
      Device = "12:12:12:12:AB"
    },

    new MyItem
    {
      Name = "Cadence",
      Device = "34:34:34:34:CD"
    }
  };

  //show details
  public ICommand ChangeCommand => new Command(ChangeControl);
  public void ChangeControl()
  {
    //change device
    foreach (MyItem q in MyItems)
    {
      q.Device = "***********";
    }
    Console.WriteLine($"Change device");
  }
}

and the MainPage.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:TestCollectionBinding.ViewModels"
             x:Class="TestCollectionBinding.MainPage">

  <ContentPage.BindingContext>
    <local:MainPageViewModel />
  </ContentPage.BindingContext>

  <StackLayout>
    <Label Text="Items" 
           FontSize="20"
           TextColor="Blue"/>

    <CollectionView x:Name="MyItemsList"
                    ItemsSource="{Binding MyItems}">
      <CollectionView.ItemTemplate>
        <DataTemplate>
          <Grid
            Margin="10, 0, 10, 10"
            ColumnDefinitions="200, 200">
            <Label Grid.Column="0" 
                   Text="{Binding Name}" 
                   FontSize="20"/>

            <Label Grid.Column="1" 
                   Text="{Binding Device}" 
                   FontSize="20"/>            
          </Grid>
   
        </DataTemplate>
      </CollectionView.ItemTemplate>
    </CollectionView>

    <Button
      Text="Change device names"
      FontFamily="20"
      WidthRequest="150"
      Command="{Binding ChangeCommand}" />
    
  </StackLayout>

</ContentPage>

So the MainPage displays two items in the list and when I hit the button the command cycles through the list and just replaces the Device property with "*********".

I expect to see these changes in the displayed list.

Oh for the days of ... dBase II lol

G

gfmoore
  • 976
  • 18
  • 31
  • `MyItem` also needs to be an `ObservableObject`, since it is the class that contains the properties you want to update in the UI – Jason Jun 14 '22 at 22:15
  • 1
    Okay I sort of see what you are getting at, but though I've tried making my (now partial) MyItem class inherit from ObservableObject I think I need to do more than that, but I just don't know what. I tried using [ObservableProperty] on the fields, but it wouldn't have it. Is there some tutorial that explains how this stuff works and how to use it because I'm stumped. – gfmoore Jun 14 '22 at 22:34

3 Answers3

3

from the docs

public class MyItem : ObservableObject
{
    private string name;

    public string Name
    {
        get => name;
        set => SetProperty(ref name, value);
    }
}
Jason
  • 86,222
  • 15
  • 131
  • 146
3

I just struggled with why my list wasn't updating upon being programatically updated. A much easier way I discovered (at least for me) is to configure it as follows, while using the CommunityToolkit from Microsoft. For the Model, have the class inherit from "ObservableObject". Then mark the Property with "[ObservableProperty]".

namespace TestCollectionBinding.Views;

public partial class MyItem : ObservableObject
{
[ObservableProperty]  
string name;
[ObservableProperty]
string device;
}

On your ViewModel "ViewModels.MainPageViewModel.cs", set the collection to be displayed as follows. For others who don't know better, do not use a "List myItems", but set it to "ObservableCollection myItems" as this will not update programmatically. I'd love for someone to explain why the list doesn't update, despite being raised by OnPropertyChanged("ListName").

[ObservableProperty]
ObservableCollection<MyItem> myItems = new()
  {
    new MyItem
    {
      Name = "Heart Rate Monitor",
      Device = "12:12:12:12:AB"
    },

    new MyItem
    {
      Name = "Cadence",
      Device = "34:34:34:34:CD"
    }
  };

You'll want to configure your VIEW to use the CommunityToolkit on your "MainPage.xaml". I added the reference to: xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
      xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
      xmlns:local="clr-namespace:TestCollectionBinding.ViewModels"
      xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
      x:Class="TestCollectionBinding.MainPage">

I just spent an entire day trying to figure out why my LIST wasn't updating the VIEW. Figured I'd post a solution for anyone else who stumbles across this question. In summary, tag the MODEL PROPERTY with "[ObservableProperty]". Utilize "ObservableProperty" in the VIEWMODEL. And then make sure the VIEW is set to utilize the CommunityToolkit from Microsoft, which you can get from Github.

Makes for MUCH cleaner plumbing.

RAB
  • 317
  • 3
  • 12
2

Following the helpful hint from @Jason (ta) I searched a bit harder and found this amazingly helpful explanation at the source: https://learn.microsoft.com/en-us/windows/communitytoolkit/mvvm/observableobject

My MyItem.cs now looks like:

public partial class MyItem : ObservableObject
{
  private string name;
  private string device;

  public string Name { 
    get => name; 
    set => SetProperty(ref name, value); 
  }

  public string Device { 
    get => device; 
    set => SetProperty(ref device, value); 
  }
}

It's more complex than I expected it to look... I have to ask though, why is this stuff so impenetrable, I thought coding was supposed to get easier over the years? I would have expected that if a device is connected to a list then it should just (magically) work without all the plumbing that should be hidden from view. If you don't want anything to change make it readonly! :sigh. One day when I'm dead and buried...

gfmoore
  • 976
  • 18
  • 31
  • 2
    what if the object had 1000 nested properties? How expensive would it be to automatically wire them all up to the UI when only 2 are needed? Things that just magically "work" are great, but they are usually expensive and complex, and hiding that complexity is not always a good idea. – Jason Jun 14 '22 at 23:09
  • that said, [Fody](https://github.com/Fody/PropertyChanged) might the "magical" alternative you are looking for – Jason Jun 14 '22 at 23:09
  • Fair point, but now that you mention it, I am going to have an object with 100 odd properties that do all need wiring up. :) – gfmoore Jun 15 '22 at 14:46
  • then definitely check out Fody – Jason Jun 15 '22 at 14:47