Search code examples
c#avaloniauiavalonia

Updating an ObservableCollection does not have an effect


I try to update an ObservableCollection in Avalonia from another Thread. The following does not seem to have any effect on the UI:

public ObservableCollection<FolderInfo> Folders { get; set; }
public void RefreshSingleItem(FolderInfo folderInfo)
{
   Dispatcher.UIThread.Post(() =>
   {
      var folder = Folders.FirstOrDefault(f => f.FolderName == folderInfo.FolderName);
      folder.Random = rnd.Next();
   });   
}

This does not either:

public void RefreshSingleItem(FolderInfo folderInfo)
{
   Dispatcher.UIThread.Post(() =>
   {
      var folder = Folders.FirstOrDefault(f => f.FolderName == folderInfo.FolderName);
      Folders.Remove(folder);
      folderInfo.Random = rnd.Next();
      Folders.Add(folderInfo);
   });   
}

But this does work:

public void RefreshSingleItem(FolderInfo folderInfo)
{
   Dispatcher.UIThread.Post(() =>
   {
      folderInfo.Random = rnd.Next();
      Folders.Add(folderInfo);
   });   
}

So if I add an item to the observablecollection the UI is getting updated, when I update an existing item it does not.

("Random" is just a simple property):

public class FolderInfo {
   public int Random {get;set;}
}

The DataGrid displaying the contents looks like this:

 <DataGrid Margin="5 0 5 5" CanUserResizeColumns="True" ItemsSource="{Binding Folders}" AutoGenerateColumns="False" BorderBrush="Aqua">
   <DataGrid.Columns>
      <DataGridTextColumn Binding="{ Binding Random }"></DataGridTextColumn>
      <-- more columns here -->                  
   </DataGrid.Columns>
</DataGrid>

How do I force the UI update?


Solution

  • So if I add an item to the observablecollection the UI is getting updated, when I update an existing item it does not.

    This exactly how ObservableCollection deal, it just cares about adding or removing items from the collection, and it will NOT notify UI if an existing item has been updated.

    FolderInfo should notify the UI by itself when he updated ... this could be done just by implement INotifyPropertyChanged Interface (NOTE: UI will automatically subscribe to PropertyChanged event if the binding model implement INotifyPropertyChanged interfaces)

    public class FolderInfo : INotifyPropertyChanged
    {
        private int _random;
    
        public int Random
        {
            get => _random;
            set
            {
                if(value != this._random)
                {
                    _random = value;
                    NotifyPropertyChanged();
                }
    
            }
        }
         
        public event PropertyChangedEventHandler? PropertyChanged;
        private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    

    avalonia templates is coming already with some MVVM package which facilitate this process.

    You can achieve the same result with a fewer code lines If you use the CommunityToolkit.Mvvm.

    public partial class FolderInfo : ObservableObject
    {
        [ObservableProperty]
        private int _random;    
    }
    

    Your class is partial due, there will be some code generated and add to your class from CommunityToolkit.Mvvm so you can get a property called Random at the end.

    Or if Using ReactiveUI as following

    public class FolderInfo : ReactiveObject
    { 
        private int _random;
    
        public int Random
        {
            get => _random;
            set => this.RaiseAndSetIfChanged(ref _random, value);
        }
    }