Better Way to Trigger Onpropertychanged

Better way to trigger OnPropertyChanged

You could have a look at PostSharp. They even have a sample at Data Binding. The code taken from there:

/// <summary>
/// Aspect that, when apply on a class, fully implements the interface
/// <see cref="INotifyPropertyChanged"/> into that class, and overrides all properties to
/// that they raise the event <see cref="INotifyPropertyChanged.PropertyChanged"/>.
/// </summary>
[Serializable]
[IntroduceInterface( typeof(INotifyPropertyChanged),
OverrideAction = InterfaceOverrideAction.Ignore )]
[MulticastAttributeUsage( MulticastTargets.Class,
Inheritance = MulticastInheritance.Strict )]
public sealed class NotifyPropertyChangedAttribute : InstanceLevelAspect,
INotifyPropertyChanged
{

/// <summary>
/// Field bound at runtime to a delegate of the method <c>OnPropertyChanged</c>.
/// </summary>
[ImportMember( "OnPropertyChanged", IsRequired = false)]
public Action<string> OnPropertyChangedMethod;

/// <summary>
/// Method introduced in the target type (unless it is already present);
/// raises the <see cref="PropertyChanged"/> event.
/// </summary>
/// <param name="propertyName">Name of the property.</param>
[IntroduceMember( Visibility = Visibility.Family, IsVirtual = true,
OverrideAction = MemberOverrideAction.Ignore )]
public void OnPropertyChanged( string propertyName )
{
if ( this.PropertyChanged != null )
{
this.PropertyChanged( this.Instance,
new PropertyChangedEventArgs( propertyName ) );
}
}

/// <summary>
/// Event introduced in the target type (unless it is already present);
/// raised whenever a property has changed.
/// </summary>
[IntroduceMember( OverrideAction = MemberOverrideAction.Ignore )]
public event PropertyChangedEventHandler PropertyChanged;

/// <summary>
/// Method intercepting any call to a property setter.
/// </summary>
/// <param name="args">Aspect arguments.</param>
[OnLocationSetValueAdvice,
MulticastPointcut( Targets = MulticastTargets.Property,
Attributes = MulticastAttributes.Instance)]
public void OnPropertySet( LocationInterceptionArgs args )
{
// Don't go further if the new value is equal to the old one.
// (Possibly use object.Equals here).
if ( args.Value == args.GetCurrentValue() ) return;

// Actually sets the value.
args.ProceedSetValue();

// Invoke method OnPropertyChanged (our, the base one, or the overridden one).
this.OnPropertyChangedMethod.Invoke( args.Location.Name );

}
}

Usage is then as simple as this:

[NotifyPropertyChanged]
public class Shape
{
public double X { get; set; }
public double Y { get; set; }
}

Examples taken from PostSharp site and inserted for completing the answer

Is there a way to trigger some kind of `OnPropertyChanged` event for a computed property that uses a property of a child entity in a collection?

Do you have control of what goes inside your collections? If you do, you can create a Parent property on your Invoice object and when it is added to the collection, set the parent to your Customer. Then when PaidInFull gets set, run your Customer.OnPropertyChanged("TotalOpenInvoices") or call a method on the Customer object to recalculate your invoices.

Enforcing parent-child relationship in C# and .Net

Shorter code to trigger property changed events

The quoted code is not thread safe as written. See Pattern for implementing INotifyPropertyChanged? why the code below is better, and the link to Eric Lippert's blog in the accepted reply why the story doesn't end there.

    PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs("TimeStamp"));

For answers to the actual question, see Implementing INotifyPropertyChanged - does a better way exist? including this C# 6.0 shortcut.

    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("TimeStamp"));

How to Trigger OnPropertyChanged in ViewModel on change of Property inside ObservableCollectionObject?

A pretty simple way to do it is to just hook into the property changed events of the list items and raise your own property changed whenever the property you are watching for changes.

After your observable collection is created

Objects.CollectionChanged += Objects_CollectionChanged;

Then

void Objects_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case System.Collections.Specialized.NotifyCollectionChangedAction.Add:
foreach (var item in e.NewItems.Cast<System.ComponentModel.INotifyPropertyChanged>())
{
item.PropertyChanged += item_PropertyChanged;
}
break;
case System.Collections.Specialized.NotifyCollectionChangedAction.Move:
break;
case System.Collections.Specialized.NotifyCollectionChangedAction.Remove:
foreach (var item in e.OldItems.Cast<System.ComponentModel.INotifyPropertyChanged>())
{
item.PropertyChanged -= item_PropertyChanged;
}
break;
case System.Collections.Specialized.NotifyCollectionChangedAction.Replace:
break;
case System.Collections.Specialized.NotifyCollectionChangedAction.Reset:
break;
default:
break;
}
}

void item_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if(e.PropertyName == "Percentage")
{
RaisePropertyChanged("TotalCost");
}
}

You will also need to handle the reset (clear) and replace cases to be safe.

Also if your observable collection has any items in it before attaching the collection changed handler then you will have to loop through and hook into them as well.

WPF: How to trigger PropertyChanged on INotifyPropertyChanged items in my view model from my control?

So... I found the solution. You will probably not like it, but if it's ugly, but it works, why not use it?

#region ItemsSource notifier

private void NotifySourceItemChanged(object item, string propertyName)
=> ItemsSourceOnPropertyChanged(item, new PropertyChangedEventArgs(propertyName));

private void ItemsSourceOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
=> ItemsSourceOnPropertyChangedMethod?.Invoke(ItemsSource, new object?[] { sender, e });

private MethodInfo? GetItemsSourceOnPropertyChanged() {
if (ItemsSource is null) return null;
foreach (var method in ItemsSource.GetType().GetMethods(BindingFlags.NonPublic | BindingFlags.Instance)) {
var parameters = method.GetParameters();
if (parameters.Length == 2 &&
parameters[0].ParameterType == typeof(object) &&
parameters[1].ParameterType == typeof(PropertyChangedEventArgs)) return method;
}
return null;
}

private MethodInfo? ItemsSourceOnPropertyChangedMethod;

#endregion

We assume that our control contains the ItemsSource property.
Also, when ItemsSource property is changed we must call GetItemsSourceOnPropertyChanged(). It's done once on the binding.

Then, it will work if the object bound to the ItemSource implements INotifyPropertyChanged interface. I think ObservableCollection does.

Then, the collection must also contain OnPropertyChanged() method accepting an object and PropertyChangedEventArgs.

Again, ObservableCollection does.

The method is non-public, but well. We can call it.
If the ItemsSource is null, or it doesn't implement INotifyPropertyChanged or it doesn't have non public method triggering the PropertyChanged event - nothing will happen.

So - if we provide a compatible collection - it will work.

I just finished my control. It works. My view model observes it's item collection, if one is checked or unchecked, the collection is updated and the event is triggered, so my view model can do whatever it needs with it.

Goal achieved, because now I can forget about the view implementation. The view model provide checkable items, and when their state is changed - it can react to that with zero knowledge about the view.

Also the view knows nothing about the view model. It only knows the collection type (and the item type to be precise).

So the binding is as pure as it gets.

Yes, I know, I could probably just make another bindable property in my control to provide the changes done in the view to the view model binding. It could be done without any hacks and easier. But I'm not sure. The consuming code would not be smaller, more readable or simpler. Nah, this is neat.

BTW, don't copy the code from the question, the full working version is on GitHub: https://github.com/HTD/Woof.Windows/blob/master/Woof.Windows.Controls/Checks.cs

Trigger OnPropertyChanged in PostSharp's INotifyPropertyChanged aspect manually

I think you have misunderstood the following:

Raises the PropertyChanged event. If this method is already present in the target code, the NotifyPropertyChangedAttribute aspect will use it to raise the PropertyChanged event. Otherwise, the aspect will introduce the method into the target class.

If the method is already present in the class, NotifyPropertyChangedAttribute will use it. That means that the method needs to do the actual notification, because aspect is using it instead of it's own implementation. Hence, you need to raise PropertyChanged event. That implies that you need to implement INotifyPropertyChanged interface yourself like this:

[NotifyPropertyChanged]
public class TestClass : INotifyPropertyChanged
{
public int Property1 { get; set; }
public int Property2 { get; set; }

public event PropertyChangedEventHandler PropertyChanged;

public void DoSomething()
{
this.Property1 = 42;
this.OnPropertyChanged( "Property2" );
}

protected void OnPropertyChanged( string propertyName )
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if ( handler != null )
handler( this, new PropertyChangedEventArgs(propertyName) );
}
}

class Program
{
static void Main(string[] args)
{
TestClass test = new TestClass();
INotifyPropertyChanged inpc = Post.Cast<TestClass, INotifyPropertyChanged>(test);

inpc.PropertyChanged += ( s, ea ) =>
{
Console.WriteLine("Notification received for {0}", ea.PropertyName);
};

test.DoSomething();
}
}

This way, it is going to work as you want, i.e. the output would be:

Notification received for Property2
Notification received for Property1

Note that the order is like this because Property1 change is raised automatically (by the aspect) when the DoSomething method is exiting.



Related Topics



Leave a reply



Submit