-
Notifications
You must be signed in to change notification settings - Fork 377
Description
Overview
I frequently have a property that depends on the sub-property of another property in my viewmodels. The most common case for me is the IsRunning property of AsyncRelayCommand, but I've run into many other examples.
I'd like some sort of way to indicate that another property or command has changed when a sub-property has changed. Ideally this could be extended to [RelayCommand] declarations as well so that when the automatically generated command starts or stops, other properties are notified.
API breakdown
Preferably we could add a boolean property called NotifyOnSubPropertyChanged to NotifyPropertyChangedForAttribute and NotifyCanExecuteChangedForAttribute that opts into this behavior when set to true.
Usage example
I would use it like this:
public partial class TestViewModel : ObservableObject
{
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(FormattedProp1), NotifyOnSubPropertyChanged = true)]
[NotifyCanExecuteChangedFor(nameof(Command1Command), NotifyOnSubPropertyChanged = true)]
public partial SubViewModel SubViewModel { get; private set; } = new();
public string FormattedProp1 => "_" + SubViewModel.Prop1 + "_";
[RelayCommand(CanExecute = nameof(CanDoThing1))]
[property: NotifyCanExecuteChangedFor(nameof(DoThing2Command), NotifyOnSubPropertyChanged = true)]
private Task DoThing1() => Task.CompletedTask;
private bool CanDoThing1() => SubViewModel.Prop1 is not null;
[RelayCommand(CanExecute = nameof(CanDoThing2))]
private void DoThing2() { }
private bool CanDoThing2() => !DoThing1Command.IsRunning;
}
public partial class SubViewModel : ObservableObject
{
[ObservableProperty]
public partial string Prop1 { get; set; }
}I think the generated code would look something like this for the SubViewModel property:
/// <inheritdoc/>
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.4.0.0")]
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public partial global::WpfTests.SubViewModel SubViewModel
{
get => field;
private set
{
if (!global::System.Collections.Generic.EqualityComparer<global::WpfTests.SubViewModel>.Default.Equals(field, value))
{
OnSubViewModelChanging(value);
OnSubViewModelChanging(default, value);
OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.SubViewModel);
OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.FormattedProp1);
(field as INotifyPropertyChanged)?.PropertyChanged -= OnSubPropertyChanged;
field = value;
(field as INotifyPropertyChanged)?.PropertyChanged += OnSubPropertyChanged;
OnSubViewModelChanged(value);
OnSubViewModelChanged(default, value);
OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.SubViewModel);
OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.FormattedProp1);
DoThing1Command.NotifyCanExecuteChanged();
}
void OnSubPropertyChanged(object? sender, PropertyChangedEventArgs args)
{
OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.FormattedProp1);
DoThing1Command.NotifyCanExecuteChanged();
}
}Note that the handler for detecting sub-property changes is removed and added in the parent property setter, and the handler itself is a local function of the setter that is populated based on the property names provided.
I'm not sure how it would integrate into the RelayCommandAttribute, but it would be really nice to have for that for asynchronous commands.
Breaking change?
No
Alternatives
Right now all of this has to be configured in the constructor or initializer, and greatly hinders some of the benefits of the source generators.
An alternative might be to have a new attribute, but I think this is a little cleaner.
Additional context
No response
Help us help you
Yes, but only if others can assist