As written everywhere (e.g. here, here, here, here...), reporting an ObservableCollection
item property change to a view requires the item to implement INotifyPropertyChanged
.
Using CommunityToolkit.Mvvm this can be done using an attribute:
public class MyViewModel : ObservableObject
{
public ObservableCollection<MyItem> MyCollection { get; set; }
//...
}
[INotifyPropertyChanged] // yay! No boilerplate code needed
public partial class MyItem
{
public string MyProperty { get; set; }
}
If somewhere inside MyViewModel
there is a change to the MyProperty
of an item of MyCollection
, the view will be updated.
What if an interface comes into play?
public class MyViewModel : ObservableObject
{
public ObservableCollection<IMyInterface> MyCollection { get; set; }
//...
}
[INotifyPropertyChanged]
public partial class MyItem : IMyInterface // MyProperty is in IMyInterface too
{
public string MyProperty { get; set; }
}
The view seems not to be updated anymore. I tried:
- Inheriting
INotifyPropertyChanged
inIMyInterface
(that requires explicit implementation of thePropertyChanged
event andOnPropertyMethod
method inMyItem
, which I don't want as otherwise I would have not used CommunityToolkit.Mvvm) - Adding
[INotifyPropertyChanged]
toMyViewModel
(expecting nothing but there was an answer somewhere telling that)
Is there an obvious, no-boilerplate solution that I'm missing here?
The view is updated if I do something like suggested here
var item = MyCollection[0];
item.MyProperty = "new value";
MyCollection[0] = item;
but I hope there's a better way.
CodePudding user response:
I'm not sure this will solve your issue but I can see you're using the attribute incorrectly.
Please see https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/generators/inotifypropertychanged
"These attributes are only meant to be used in cases where the target types cannot just inherit from the equivalent types (eg. from ObservableObject). If that is possible, inheriting is the recommended approach, as it will reduce the binary size by avoiding creating duplicated code into the final assembly."
You're not inheriting from IMyInterface, you're implementing it.
Your viewmodel should inherit ObservableObject
Instead of
[INotifyPropertyChanged]
public partial class MyItem : IMyInterface
You should have:
public partial class MyItem : ObservableObject, IMyInterface
Properties look like:
[ObservableProperty]
private List<FlatTransactionVM> transactions = new List<FlatTransactionVM>();
That generates a public Transactions property.
If you only have properties, you don't need that to be a partial class. That's necessary for relaycommand generation.
EDIT
This works for me:
MainWindow
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<ListBox ItemsSource="{Binding MyCollection}"
DisplayMemberPath="MyProperty"/>
</Grid>
MainWindowViewModel
public partial class MainWindowViewModel : ObservableObject
{
[ObservableProperty]
private ObservableCollection<IMyInterface> myCollection = new ObservableCollection<IMyInterface>();
public MainWindowViewModel()
{
MyCollection = new ObservableCollection<IMyInterface>(
new List<MyItem>{
new MyItem{ MyProperty = "A" },
new MyItem { MyProperty = "B" },
new MyItem { MyProperty = "C" }
});
}
}
MyItem
public partial class MyItem : ObservableObject, IMyInterface
{
[ObservableProperty]
private string myProperty;
}
Interface
public interface IMyInterface
{
string MyProperty { get; set; }
}
Quick and dirty code in the view. This is purely to see what happens when one of the MyItems.MyProperty is set.
private async void Window_ContentRendered(object sender, EventArgs e)
{
await Task.Delay(5000);
var mw = this.DataContext as MainWindowViewModel;
mw.MyCollection[1].MyProperty = "XXXXX";
}
After a short wait, I see the second item change to XXXXX as expected.