gfmoore
gfmoore

Reputation: 1236

.net maui Cannot get updated fields in an Observable collection to update in bound collection view

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

Upvotes: 8

Views: 11851

Answers (4)

usefulBee
usefulBee

Reputation: 9702

While you can observe item properties as suggested by other contributors, another solution is to declare a new Observable collection of the same type, process the original collection as you wish, add each processed item to the new collection, then assign it to the original collection. This should keep the UI updated with no problems. For example:

[ObservableProperty]
public ObservableCollection<MyItem> myItems = new();

[RelayCommand]
async Task ButtonClickCommand() {
  var SecondCollection = new ObservableCollection<MyItem>();

  foreach (var q in MyItems)
  {
      // Process item and add to second collection
  }
  
  // Assign the second collection to the original one
  MyItems = SecondCollection;
}

Tested on limited number of items only.

Upvotes: 0

RAB
RAB

Reputation: 357

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.

Upvotes: 6

gfmoore
gfmoore

Reputation: 1236

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

Upvotes: 2

Jason
Jason

Reputation: 89169

from the docs

public class MyItem : ObservableObject
{
    private string name;

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

Upvotes: 6

Related Questions