For building the WPF application I used VS2022 (V17.4.4) and NuGet Fody.PropertyChanged Package (V4.1.0).
If a property is set directly, the PropertyChanged event will be raised for the property itself and all the [DependsOn] properties as well.
But if only the underlying field of the property is set and the PropertyChanged event is raised later manually by call of RaisePropertyChanged(nameof(Property)) the [DependsOn] properties will not raise any PropertyChanged event.
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace FodyPropertyChanged
{
[PropertyChanged.AddINotifyPropertyChangedInterface]
public class MainWindowViewModel : INotifyPropertyChanged
{
private DeviceStates _DeviceState;
private event PropertyChangedEventHandler _propertyChanged;
public event PropertyChangedEventHandler PropertyChanged
{
add { _propertyChanged += value; }
remove
{
if (_propertyChanged != null)
_propertyChanged -= value;
}
}
public DeviceStates DeviceState
{
get { return _DeviceState; }
set { _DeviceState = value; }
}
[PropertyChanged.DependsOn(nameof(DeviceState))]
public bool IsBusy
{
get { return DeviceState == DeviceStates.Working; }
}
public void SetDeviceStateTo(DeviceStates deviceState)
{
DeviceState = deviceState;
}
public void SetDeviceStateToAndRaisePropertyChangedAfterwards(DeviceStates deviceState)
{
_DeviceState = deviceState;
RaisePropertyChanged(nameof(DeviceState));
}
private void RaisePropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChangedEventHandler handler = _propertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
If I call SetDeviceStateTo(...) the PropertyChanged event will be raised for DeviceState and IsBusy (GUI will be updated, correctly). But if I call SetDeviceStateToAndRaisePropertyChangedAfterwards(...) the PropertyChanged event will be raised for DeviceState, only (IsBusy bindings will not be updated).
Any idea?
Like canton7 and BionicCode said:
[DependsOn] is only working for the setter not for events. So it is a known limitation and not a bug.
Related
I'm having a problem with subscribing to PropertyChangedEventHandler event of a property on a bound instance of my class.
Here is the setup:
XAML:
<CheckBox IsChecked="{Binding MyObservableClassInstance.BooleanProperty}"/>
DataContext class property:
public MyObservableClass MyObservableClassInstance
{
get { return _myClassInstance; }
set
{
_myClassInstance= value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("MyObservableClassInstance"));
}
}
the initialisation and subscription to PropertyChanged event (this subscribed method is never reached):
MyObservableClassInstance = new MyObservableClass();
MyObservableClassInstance.PropertyChanged += OnMyObservableClassPropertyChanged; // <--- This method is never hit
my observable class: (the BooleanProperty is working normally with the the XAML checkbox binding)
public class MyObservableClass : INotifyPropertyChanged
{
bool _mybool = false;
public event PropertyChangedEventHandler PropertyChanged;
public bool BooleanProperty
{
get { return _mybool; }
set
{
_mybool = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("BooleanProperty")); // <--- This is reached normally on checking/unchecking the checkbox
}
}
}
So why is my OnMyObservableClassPropertyChanged method never reached upon Invoking the PropertyChanged event?
You have to attach (and detach) the handler method to the PropertyChanged event whenever the MyObservableClass property value changes.
public MyObservableClass MyObservableClassInstance
{
get { return _myClassInstance; }
set
{
if (_myClassInstance != null)
{
_myClassInstance.PropertyChanged -= OnMyObservableClassPropertyChanged;
}
_myClassInstance = value;
if (_myClassInstance != null)
{
_myClassInstance.PropertyChanged += OnMyObservableClassPropertyChanged;
}
PropertyChanged?.Invoke(
this, new PropertyChangedEventArgs(nameof(MyObservableClassInstance)));
}
}
Here's what I have now:
class MyClass
{
public string status;
private void DoSomething()
{
// do something and make change to this.status;
}
}
class MyClass2
{
public List<MyClass> MyClassLst;
private void DetectChangeInList()
{
// if the status property of an item in this.MyClassLst changed, remove this item from list
}
}
I have a List<MyClass>, and each of the MyClass will do some work and change the property status. I want to detect if any of the MyClass has its status changed and remove this item from MyClassLst.
I read about something on event but not very clearly about how to make it work.
If you need to be notified about changes to individual properties of each MyClass instance, it's not something that can magically happen.
Your MyClass will have to be responsible for firing an event whenever something changes (usually the PropertyChanged event, i.e. the INotifyPropertyChanged interface), and the other class will have to attach a handler to each item in the list to get notified.
C#6 has a couple of syntactic improvements which simplify this a bit, but you still have lots of work to do for each property:
public class Model : INotifyPropertyChanged
{
// this is the event which gets fired
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
// you need to raise the event in each property's setter
private _someValue;
public string SomeValue
{
get { return _someValue; }
set { if (value != _someValue) { _someValue = value; OnPropertyChanged(); } }
}
private _anotherVal;
public string AnotherValue
{
get { return _anotherVal; }
set { if (value != _anotherVal) { _anotherVal = value; OnPropertyChanged(); } }
}
}
In your case, it would be:
public class MyClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
// Never use public fields!
// Status should be a property which gets and sets
// the actual private backing field (_status).
private _status;
public string Status
{
get { return _status; }
set { if (value != _status) { _status = value; OnPropertyChanged(); } }
}
}
You will also most likely want to change List<MyClass> into your own implementation of ICollection<T> which will attach and detach these handlers as you add or remove items. It is usually done by deriving from Collection<T> and overriding relevant methods. If you are not comfortable with that, a slightly simpler approach might be to make the list private and expose Add/Remove and similar methods where you will attach/detach to the PropertyChanged event.
I would like to cache some UI-specific properties only at the time an object is DataBound, if it gets bound.
Say I have
public interface IAmA<T> ()
{
T Value {get;set;}
}
public class MyString : IAmA<string>, INotifyPropertyChanged {
private string _value = String.Empty;
public virtual string Value
{
get
{
return this._value;
}
set
{
this._value = value;
OnPropertyChanged();
}
}
public virtual event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
}
Is it possible to implement an action or event to fire at the time the object is databound?
event Action OnDataBound { ... }
So I can detect, say, when
MyString myStrObj = new MyString("text");
myStrObj.OnDataBound += CacheUIElements();
Do.OtherStuff();
otherObj.DataSource = myStrObj; //causes CacheUIElements() to fire
You can very well do it in the property setter and call the Delegate.
In above case it will be for the OtherObj.DataSource. Because you want the Action to get triggered when you are setting the datasource. So it should be in the setter of whichever property where you want the action to trigger.
class OtherClass
{
DataSource
{
get{return _dataSource}
set
{
_dataSource = value;
OnPropertyChanged("DataSource");
OnDataBound();
}
}
}
If not, assuming OtherClass Implements OnPropertyChanged and triggers for DataSource like this, If its a framework element for sure it will implement NotifyPropertyChanged for DataSource because only then UI will refresh with the changes.
class OtherClass: INotifyPropertyChanged
{
DataSource
{
get{return _dataSource}
set
{
_dataSource = value;
OnPropertyChanged("DataSource");
}
}
}
Then you can subscribe to this event and check against the property name and trigger the OnDataBound(); e.g:
otherClassObj.PropertyChanged += OtherClassPropertyChanged()
private void OtherClassPropertyChanged(Obj sender, PropertyChangedEventargs e)
{
if(e.PropertyName = "DataSource")
{
OnDataBound();
}
}
My PropertyChanged event is getting set properly when I watch the variable, but somewhere in code it gets reset to null and I have no idea how to figure out where this is happening.
Here is come code:
public event PropertyChangedEventHandler PropertyChanged;
//this is in the NotifyTaskCompletion class, as used from the blog
// http://msdn.microsoft.com/en-us/magazine/dn605875.aspx
private async Task WatchTaskAsync(Task task)
{
try
{
await task; //After this task, PropertyChanged gets set to a non-null method("Void OnPropertyChanged()")
}
catch
{
}
//PropertyChanged, which was set to a value after the task was run, and still not null during IdentifyCompleted getting set, is now null here
var propertyChanged = PropertyChanged;
if (propertyChanged == null)
return;
//other code
}
//My Watch variable of PropertyChanged is still good after this setter is run.
public NotifyTaskCompletion<GraphicCollection> IdentifyCompleted
{
get { return _IdentifyCompleted; }
set
{
_IdentifyCompleted = value;
// _IdentifyCompleted.PropertyChanged+= new PropertyChangedEventHandler(this, new PropertyChangedEventArgs("IdentifyCompleted"));
// NotifyPropertyChanged();
}
}
My main issue is that I cannot use a {set;get;} on PropertyChanged to attempt to identify WHERE it is getting set to null. So my main question, unless anyone sees something that is obviously wrong, is how would I go about finding out where it is getting set to null? Thank you for any assistance.
EDIT
As per the last posters suggestion, I set my code as follows:
private PropertyChangedEventHandler _propertyChanged;
public event PropertyChangedEventHandler PropertyChanged
{
add { _propertyChanged += value; }
remove { _propertyChanged -= value; }
}
And here is the issue.
//this is in my View Model. The ViewModel CONTAINS NotifyTaskCompletion<GraphicCollection> IdentifyCompleted which in turn implements INotifyPropertyChanged and has its own PropertyChanged that is not getting set
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
//This line sets the PopertyChanged in the view model AND in the NotifyTaskCompletion class somehow, but I don't know how it is setting it properly in the NotifyTaskCompletion class in my other project where this code works, When I step through this line in my code, it doesn't trigger
//the add{} of the PropertyChanged in NotifyTaskCompletion, but it DOES in my other project...
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
So with all that said, I can now see what line SHOULD be working, but I have no idea WHY it isn't working. Any other ideas? Thanks for the help so far.
You can write your own event accessors:
private PropertyChangedEventHandler propertyChanged;
public event PropertyChangedEventHandler PropertyChanged {
add { propertyChanged += value; }
remove { propertyChanged -= value; }
}
You can then set breakpoints.
Note that this is not thread-safe.
I apologize for the newbie question, but I am struggling with this problem. I have the following TextBlock defined:
<TextBlock Text="{Binding Source={x:Static local:DeviceManager.Instance},
Path=Player.CurrentArtist}"></TextBlock>
The DeviceManager is a singleton that functions as a facade for other classes. For example, Player is a property of type IPlayer which represents an music-playing application. I would like the TextBlock to display the artist that is currently playing, which is periodically updated in the Player.CurrentArtist property.
Unfortunately, I cannot get the TextBlock to update when the CurrentArtist property updates. Both the DeviceManager and the IPlayer implement INotifyPropertyChanged, but when I step through the application, the DeviceManager does not have an event handler attached to it.
Does anyone have a suggestion for how to update the text block while preserving the singleton-facade?
Here is the code for the INotifyPropertyChanged members in both the DeviceManager and the IPlayer subclass:
public sealed class DeviceManager : INotifyPropertyChanged
{
// Singleton members omitted
public IPlayer Player
{
get { return player; }
set
{
this.player = value;
player.PropertyChanged += new PropertyChangedEventHandler(device_PropertyChanged);
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void device_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(sender, e);
}
}
#endregion
}
class MediaPlayer : IPlayer
{
private string artist;
private string title;
public event PropertyChangedEventHandler PropertyChanged;
public void Play(string artist, string title)
{
this.artist = artist;
this.title = title;
OnPropertyChanged("Player:Song");
}
private void OnPropertyChanged(string p)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(p));
}
}
public string CurrentTitle
{
get { return title; }
}
public string CurrentArtist
{
get { return artist; }
}
}
The problem is that WPF is never notified of the value of the CurrentArtist property changing. You can either implement a private setter for the CurrentArtist property, which will trigger the PropertyChanged event, or trigger a PropertyChanged event for the CurrentArtist property in MediaPlayer.Play().
WPF only responds to PropertyChanged if the name you pass in (i.e. right now "Player:Song") is the same as the property you're bound to - change the PropertyChanged to "CurrentArtist" and you'll see it update properly.
You are not raising the PropertyChanged event, what you need is:
public sealed class DeviceManager : INotifyPropertyChanged
{
// Singleton members omitted
public IPlayer Player
{
get { return player; }
set
{
this.player = value;
OnPropertyChanged(this, new PropertyChangedEventArgs("Player"));
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(sender, e);
}
}
#endregion
}
How does the UI know when you change the Player property? From that code it does not look like it raises PropertyChanged to me. Can you post a complete working sample of the problem? Otherwise we're forced to just guess.