Avalonia.Controls.TreeDataGrid icon indicating copy to clipboard operation
Avalonia.Controls.TreeDataGrid copied to clipboard

TreeDataGrid does not update when model properties change

Open SuperJMN opened this issue 2 years ago • 12 comments

Describe the bug Given an underlying model that implements INotifyPropertyChanged, the TreeDataGrid doesn't update when the model is updated.

To Reproduce

  • For convenience, I've created a repo ready to test the issue:

https://github.com/SuperJMN-Tutorials/TreeDataGrid-Out-Of-Sync

If you want to do it yourself:

  1. Create a new MVVM application.
  2. Add the TreeDataGrid package.
  3. Add this code to MainViewModel.
public class MainWindowViewModel : ViewModelBase
{
    public MainWindowViewModel()
    {
        var collection = new ObservableCollection<Model>();
        var model = new Model("Hello!");
        collection.Add(model);
        Source = new FlatTreeDataGridSource<Model>(collection)
        {
            Columns =
            {
                new TextColumn<Model, string>("Text", x => x.Text),
            }
        };

        Observable.Timer(TimeSpan.FromSeconds(2))
            .ObserveOn(RxApp.MainThreadScheduler)
            .Subscribe(_ => model.Text = "How are you?");
    }

    public FlatTreeDataGridSource<Model> Source { get; }
}

public class Model : ViewModelBase
{
    private string text;

    public Model(string text)
    {
        Text = text;
    }

    public string Text
    {
        get => text;
        set => this.RaiseAndSetIfChanged(ref text, value);
    }
}
  1. Add this code to MainWindow.axaml:
 <TreeDataGrid Source="{Binding Source}" />
  1. Run the project.

Expected behavior After 2 seconds, the data in the DataGrid should display "How are you?". However, it shows the initial text "Hello".

NOTES Interestingly enough, the TreeDataGrid starts to behave correctly when you click the column header. It seems that sorting a column makes it update.

Desktop (please complete the following information):

  • OS: Windows 11

Additional context Avalonia 0.10.18.

SuperJMN avatar Sep 08 '22 08:09 SuperJMN

@grokys @Takoooooo

maxkatz6 avatar Sep 08 '22 08:09 maxkatz6

For anyone looking for a workaround - some reflection magic on private methods does allow to refresh the cells - namely calling CollectionChanged on the HierarchicalRows-Instance. You'll have to adjust the type though to match your HierarchicalTreeDataGridSource. In my case it was enough to call it every time the selection changes.

var rows = (HierarchicalRows<Entry>)EntriesTree.GetType()
    .GetMethod("GetOrCreateRows", BindingFlags.NonPublic | BindingFlags.Instance)
    .Invoke(EntriesTree, null);
var evt = (NotifyCollectionChangedEventHandler)rows.GetType().GetField("CollectionChanged",
    BindingFlags.NonPublic | BindingFlags.Instance)
    .GetValue(rows);

if (evt != null) {
    foreach (var handler in evt.GetInvocationList()) {
        handler.Method.Invoke(handler.Target,
            new object[]
                { rows, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset) });
    }
}

CShark avatar Dec 16 '22 10:12 CShark

Any update on this one? This makes the grid unusable when relying on INotifyPropertyChanged for the data.

boomer41 avatar Jun 17 '23 21:06 boomer41

@SuperJMN

So, I went on and went to debug the missing IPropertyChanged subscription. Turns out, in v10, the TreeDataGridCell does indeed not subscribe.

However, this issue is already fixed in the latest master branch for v11 by commit 58394adb346e3472aae114053002d8b0639ae755 "Implement cell editing for text cells."

When adding the following code to the sample project on v11 to the Country model's constructor, we can observe the desired effect. I also made the model a ReactiveObject for convenience.

The columns refresh as expected. Now just to wait until v11 :)

            Task.Run(async () =>
            {
                while (true)
                {
                    await Task.Delay(TimeSpan.FromSeconds(1));

                    Dispatcher.UIThread.Post(() =>
                    {
                        Name += "1";
                        this.RaisePropertyChanged(nameof(Name));
                    });
                }
            });

boomer41 avatar Jun 18 '23 07:06 boomer41

~~Okay, there STILL is a bug in the latest v11... The changed value only gets rendered every second property changed event. :(~~

EDIT: Was my fault. Works as intended.

boomer41 avatar Jun 18 '23 08:06 boomer41

This problem still happens for me with a TextColumn; my workaround was to use a TemplatedColumn...

alexandrehtrb avatar Nov 10 '23 20:11 alexandrehtrb

This problem still happens for me with a TextColumn; my workaround was to use a TemplatedColumn...

Can you share the code for the workaround? I've tried with TemplateColumn and it still doesn't work. For me, this is a big showstopper.

SuperJMN avatar May 23 '24 20:05 SuperJMN

Can you share the code for the workaround?

I don't have the code anymore, but it was something like this, I remember that I had troubles too because both property setters and getters functions need to be defined when creating the columns, like:

new TextColumn<Person, string>(
"First Name",
x => x.FirstName, // getter
s => x.FirstName = s) // setter 

In the end, I switched to DataGrid instead of TreeDataGrid, since my data wasn't hierarchical.

alexandrehtrb avatar May 24 '24 13:05 alexandrehtrb

I have the same issue, can you please, explain me how can update the render of the TreeDataGrid when an item of the observable collection gest changed?

TreeDataGrid When I click on ModFirst the value SessionCount of the first row is added by 1 When I click on ModRandom the value SessionCount of a random row is added by 1

in my case, the values shown in the columns update only when I change the order of a column

dade6 avatar May 24 '24 14:05 dade6

This is solved by creating a FlatTreeDataGridSource of view models instead of models. So that when a property of a single element is changed the MVVM framework can updated the UI.

Sorry for the novice question.

dade6 avatar Jun 03 '24 13:06 dade6

This is solved by creating a FlatTreeDataGridSource of view models instead of models. So that when a property of a single element is changed the MVVM framework can updated the UI.

Sorry for the novice question.

This is solved by creating a FlatTreeDataGridSource of view models instead of models. So that when a property of a single element is changed the MVVM framework can updated the UI.

Sorry for the novice question.

This is solved by creating a FlatTreeDataGridSource of view models instead of models. So that when a property of a single element is changed the MVVM framework can updated the UI.

Sorry for the novice question.

@dade6 Having similar issues to what you had. Would you be willing to share your code that fixed this for you?

AlbeyAl avatar Jun 18 '24 16:06 AlbeyAl

I have the same problem while using CheckBoxColumn. I've switched to TemplateColumn as workaround.

With itemSrc.Columns
    .Add(New TextColumn(Of VideoSlice, String)("テキスト", Function(x) x.Text, width:=GridLength.Star))
    ' CheckBoxColumn が INotifyPropertyChanged の処理を正しく行っていないため、TemplateColumn に変更しました。 
    .Add(New TemplateColumn(Of VideoSlice)("保留", "CheckBoxCell",
              width:=New GridLength(48, GridUnitType.Pixel)))
End With
<TreeDataGrid.Resources>
  <DataTemplate x:Key="CheckBoxCell" DataType="local:VideoSlice">
    <CheckBox IsChecked="{Binding IsSelected}"/>
  </DataTemplate>
</TreeDataGrid.Resources>

Nukepayload2 avatar Jul 10 '24 14:07 Nukepayload2