Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updating properties and enabling commands that depend on observable model properties #906

Open
djordje200179 opened this issue Jul 23, 2024 · 0 comments
Labels
feature request 📬 A request for new changes to improve functionality

Comments

@djordje200179
Copy link

djordje200179 commented Jul 23, 2024

Overview

As we know, RelayCommand and getter-only properties need to be notified when their dependencies change. That is usually done with NotifyCanExecuteChangedForAttribute and NotifyPropertyChangedForAttribute.

But what needs to be done in cases when we access nested object fields, and not primitive types? I know that those classes also need to implement INotifyPropertyChanged. But that is not enough here, because that event isn't propagated to cause reevaluation of CanExecute method.

Example

There is an object of Bill.Item class that implements INotifyPropertyChanged (I have tried to use both ObservableObjectand Fody.PropertyChange, but there is no difference).

[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(SaveCommand))]
[NotifyPropertyChangedFor(nameof(NotEnoughDrinks))]
private Bill.Item billItem;

Its properties are updated through UI with XAML two-way bindings:

<Entry Placeholder="Title" Text="{Binding BillItem.Title}" />
...
<HorizontalStackLayout>
	<Entry Placeholder="Quantity" Text="{Binding BillItem.Quantity}" IsReadOnly="True" />
	<Stepper Value="{Binding BillItem.Quantity}" Minimum="1" Increment="1" />
</HorizontalStackLayout>

SaveCommand and NotEnoughDrinks property depend on fields inside BillItem.

public bool NotEnoughDrinks => ChosenDrink?.Quantity < BillItem.Quantity;
[RelayCommand(CanExecute = nameof(IsSaveable))]
private void Save() {
	OnClose?.Invoke(BillItem);
}

private bool IsSaveable() {
	if (string.IsNullOrWhiteSpace(BillItem.Title)) return false;

	return true;
}

But dependent property won't be recalculated when user types in Entry fields, same thing with enabling command.
I tried to find an attribute that will help for this case, but I don't think there is a solution in this library for the given case.

API breakdown

First possibility

One way I can think of to deal with this problem would be to add a parameter to the existing ObservableProperty attribute, so that changing the field of nested ObservableObject behaves the same as changing the reference itself (which is the only thing that is tracked at the moment).

[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
public sealed class ObservablePropertyAttribute : Attribute
{
    ...
    public bool TrackPropertyChanges { get; init; } = false;
}

Second possibility

Second thing that comes to my mind is to somehow extend NotifyCanExecuteChangedForAttribute and NotifyPropertyChangedForAttribute.

[AttributeUsage(AttributeTargets.Field, AllowMultiple = true, Inherited = false)]
public sealed class NotifyPropertyChangedForAttribute : Attribute
{
    ...
    bool string[] TrackPropertyChanges { get; init; }
}

[AttributeUsage(AttributeTargets.Field, AllowMultiple = true, Inherited = false)]
public sealed class NotifyCanExecuteChangedForAttribute : Attribute
{
    ...
    bool string[] TrackPropertyChanges { get; init; }
}

Usage example

[ObservableProperty(TrackPropertyChanges = true)]
[NotifyCanExecuteChangedFor(nameof(SaveCommand))]
[NotifyPropertyChangedFor(nameof(NotEnoughDrinks))]
private Bill.Item billItem;

Breaking change?

No

Alternatives

I have found a workaround, by subscribing to PropertyChanged event and manually invoking OnPropertyChanged and NotifyCanExecuteChanged. Unfortunately, that solution produces COMException and TargetInvocationException from time to time when invoked outside main UI thread. And except from that issue, I assume that this solution is not idiomatic enough because it makes code more complex.

partial void OnBillItemChanged(Bill.Item? oldValue, Bill.Item newValue) {
	void OnBillItemPropertyChanged(object? sender, PropertyChangedEventArgs args) {
		try  {
			OnPropertyChanged(nameof(NotEnoughDrinks));
			SaveCommand.NotifyCanExecuteChanged();
		} 
        	catch (Exception) {}
	}

	if (oldValue is not null)
		oldValue.PropertyChanged -= OnBillItemPropertyChanged;
	if (newValue is not null)
		newValue.PropertyChanged += OnBillItemPropertyChanged;
}

Additional context

No response

Help us help you

Yes, but only if others can assist

@djordje200179 djordje200179 added the feature request 📬 A request for new changes to improve functionality label Jul 23, 2024
@djordje200179 djordje200179 changed the title Updating properties and enabling commands that depend on observable model fields Updating properties and enabling commands that depend on observable model properties Jul 23, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature request 📬 A request for new changes to improve functionality
Projects
None yet
Development

No branches or pull requests

1 participant