Get notified when static property changes in wpf - c#

Here microsoft described that in wpf 4.5 we can use INotifypropertyChanged for static properties as well. So I tried to do that.
Here is the code:
public static event PropertyChangedEventHandler StaticPropertyChanged;
protected static void OnStaticPropertyChanged(string PropertyName)
{
PropertyChangedEventHandler handler = StaticPropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(PropertyName));
}
}
But I dont know what to use instead of this keyword in the above code?
Here is my code:
public static event PropertyChangedEventHandler StaticPropertyChanged;
protected static void OnStaticPropertyChanged(string PropertyName)
{
PropertyChangedEventHandler handler = StaticPropertyChanged;
if (handler != null)
{
handler(typeof(MainWindowViewModel), new PropertyChangedEventArgs(PropertyName));
}
}
private static Haemogram _cHaemogram;
public static Haemogram cHaemogram
{
get
{
return _cHaemogram;
}
set
{
_cHaemogram = value;
OnStaticPropertyChanged("cHaemogram");
}
}

Unless anything uses the sender parameter, it won't matter. Logically it would make sense to use the type:
handler(typeof(TypeDeclaringEvent), new PropertyChangedEventArgs(PropertyName));
EDIT: Note that in the document you referred to, it states:
The static event can use either of the following signatures.
public static event EventHandler MyPropertyChanged;
public static event EventHandler<PropertyChangedEventArgs> StaticPropertyChanged;
Your event doesn't comply with these, which could be an issue.

Think that you added this to your viewmodel :
yourClass.StaticPropertyChanged+= yourClassStaticPropertyChanged;
...
void yourClassStaticPropertyChanged(object sender, DependencyPropertyChangedEventArgs e)
{
}
The "this" keyword refers the "object sender" parameter.
If you use "this" in your code while creating handler, it refers "sender" in yourClassStaticPropertyChanged function. If you send null, the sender parameter will be null.
--Edit--
If you want to get changes to the textbox add this code to your viewmodel :
private string _updatedText;
public string UpdatedText
{
get
{
return _updatedText;
}
set
{
_updatedText= value;
OnStaticPropertyChanged("UpdatedText")
}
}
And set UpdatedText in the event :
void yourClassStaticPropertyChanged(object sender, DependencyPropertyChangedEventArgs e)
{
UpdatedText=e.NewValue;
}
then bind the UpdatedText to your textbox like this :
<TextBlock Text="{Binding UpdatedText}"/>

Related

INotifyPropertyChanged does't work when field of property change internally

I try to binding textblock usercontrol with property of my class, but it only works at initial stage, I have implement IPropertyChnaged in my class.
In my class, _Feedbackpos (field of property) would change in background, I don't know how to solve this problem.
my class
public class TestControl : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyname)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyname));
}
}
private double _Feedbackpos;
public double Feedbackpos
{
get
{
return _Feedbackpos;
}
set
{
_Feedbackpos = value;
NotifyPropertyChanged("Feedbackpos");
}
}
//it's a callback function, it would excute when detect feedback position of controller change
private void ReadFeedbackpos()
{
_Feedbackpos = Controller.Read();
}
}
application windows
TestControl TestDll = new TestControl();
Binding BindingTxtBlk = new Binding(){Source= TestDll, Path = new Property("Feedbackpos")};
FeedbackPosTxtBlk.Setbinding(Textblock.TextProperty,BindingTxtBlk);
Change the function ReadFeedbackpos() to
private void ReadFeedbackpos()
{
Feedbackpos = Controller.Read();
}
Otherwise NotifyPropertyChanged("Feedbackpos"); will never get called.

Why is my event handler null?

I've got quite simple problem but gives me a headache and I'm wasting too much time. I've ran out of ideas anyway:)
For some reason I can't pass the value of my property variable to my event handler. Here is what I've got , an for me everything is fine but it won't work:(
Any idea why it's not passing the actual value of the variable? Thanks in advance:)
Just as the name suggests, propertyName should contain the property's name, not value. Also see PropertyChangedEventHandler and PropertyChangedEventArgs on MSDN for more details.
As to why your event handler null, I suspect you haven't subscribed to it. You should have something like the following somewhere in your program:
obj.PropertyChanged += new PropertyChangedEventHandler(obj_PropertyChanged);
private void obj_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
....
}
then when obj.Moves changes obj_PropertyChanged will be called.
I understand your confusion, so let me give a little more explanations. Suppose you have two classes A and B, and you want B to be notified when a property of A is changed. Then you can make A implement INotifyPropertyChanged, and make B subscribe to the PropertyChanged event of A, like the following:
public class A: INotifyPropertyChanged
{
private int moves;
public int Moves
{
get { return moves; }
set
{
if (value != moves) {
moves = value;
OnPropertyChanged("Moves");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public class B
{
private A a = new A();
public B()
{
a.PropertyChanged += new PropertyChangedEventHandler(a_PropertyChanged);
}
private void a_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
....
}
}
With the above, B.a_PropertyChanged will be called whenever A.Moves is changed. More precisely, if b is an instance of B, then b.a_PropertyChanged will be called whenever b.a.Moves is changed. Also please note how my implementation of the setter of Moves differs from yours.

How to bind TextBox.Lines to BindingList<string> in WinForms in C#?

I have
private BindingList<string> log;
And I have multi line logTextBox on my form.
How Can I bind "log" list to that textbox?
I don't need 2 way bind. One way bind from log to texbox would be enough.
You can't directly bind from BindingList<string> to TextBox since Lines property in TextBox is of type string[] not BindingList<string>.
You need a string[] property, and a property change notification to the same.
Here is a example of how you do that.
public class LinesDataSource : INotifyPropertyChanged
{
private BindingList<string> lines = new BindingList<string>();
public LinesDataSource()
{
lines.ListChanged += (sender, e) => OnPropertyChanged("LinesArray");
}
public BindingList<string> Lines
{
get { return lines; }
}
public string[] LinesArray
{
get
{
return lines.ToArray();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Then in your form/user control
private LinesDataSource dataSource = new LinesDataSource();
private void Setup()
{
textBox.DataBindings.Add("Lines", dataSource, "LinesArray");
Populate();
}
private void Populate()
{
dataSource.Lines.Add("whatever");
}

Creating an INotifyPropertyChanged proxy to dispatch calls to UI thread

I would like to create a dynamic proxy for binding WinForms controls to objects changed by a different (non-GUI) thread. Such a proxy would intercept the PropertyChanged event and dispatch it using the proper SynchronizationContext.
That way I could use a helper class to do the job, without having to implement the synchronization manually every time (if (control.InvokeRequired) etc.).
Is there a way to do that using LinFu, Castle or a similar library?
[Edit]
Data source is not necessarily a list. It can be any business object, e.g.:
interface IConnection : INotifyPropertyChanged
{
ConnectionStatus Status { get; }
}
I could create a wrapper which could do the job, and it would look something like this:
public class ConnectionWrapper : IConnection
{
private readonly SynchronizationContext _ctx;
private readonly IConnection _actual;
public ConnectionWrapper(IConnection actual)
{
_ctx = SynchronizationContext.Current;
_actual= actual;
_actual.PropertyChanged +=
new PropertyChangedEventHandler(actual_PropertyChanged);
}
// we have to do 2 things:
// 1. wrap each property manually
// 2. handle the source event and fire it on the GUI thread
private void PropertyChanged(object sender, PropertyChangedEvArgs e)
{
// we will send the same event args to the GUI thread
_ctx.Send(delegate { this.PropertyChanged(sender, e); }, null);
}
public ConnectionStatus Status
{ get { return _instance.Status; } }
public event PropertyChangedEventHandler PropertyChanged;
}
(there may be some errors in this code, I am making it up)
What I would like to do is to have a dynamic proxy (Reflection.Emit) one liner for this, e.g.
IConnection syncConnection
= new SyncPropertyChangedProxy<IConnection>(actualConnection);
and I wanted to know if something like this was possible using existing dynamic proxy implementations.
A more general question would be: How to intercept an event when creating a dynamic proxy? Intercepting (overriding) properties is explained well in all implementations.
[Edit2]
The reason (I think) I need a proxy is that the stack trace looks like this:
at PropertyManager.OnCurrentChanged(System.EventArgs e)
at BindToObject.PropValueChanged(object sender, EventArgs e)
at PropertyDescriptor.OnValueChanged(object component, EventArgs e)
at ReflectPropertyDescriptor.OnValueChanged(object component, EventArgs e)
at ReflectPropertyDescriptor.OnINotifyPropertyChanged(object component,
PropertyChangedEventArgs e)
at MyObject.OnPropertyChanged(string propertyName)
You can see that BindToObject.PropValueChanged does not pass the sender instance to the PropertyManager, and Reflector shows that sender object is not referenced anywhere. In other words, when the PropertyChanged event is triggered, component will use reflection to access the property of the original (bound) data source.
If I wrapped my object in a class containing only the event (as Sam proposed), such wrapper class would not contain any properties which could be accessed through Reflection.
Here's a class that will wrap a INotifyPropertyChanged, forward the PropertyChanged event through SynchronizationContext.Current, and forward the property.
This solution should work, but with some time it could be improved to use a lambda expression instead of a property name. That would allow getting rid the reflection, provide typed access to the property. The complication with this is you need to also get the expression tree from the lambda to pull out the property name so you can use it in the OnSourcePropertyChanged method. I saw a post about pulling a property name from a lambda expression tree but I couldn't find it just now.
To use this class, you'd want to change your binding like this:
Bindings.Add("TargetProperty", new SyncBindingWrapper<PropertyType>(source, "SourceProperty"), "Value");
And here's SyncBindingWrapper:
using System.ComponentModel;
using System.Reflection;
using System.Threading;
public class SyncBindingWrapper<T> : INotifyPropertyChanged
{
private readonly INotifyPropertyChanged _source;
private readonly PropertyInfo _property;
public event PropertyChangedEventHandler PropertyChanged;
public T Value
{
get
{
return (T)_property.GetValue(_source, null);
}
}
public SyncBindingWrapper(INotifyPropertyChanged source, string propertyName)
{
_source = source;
_property = source.GetType().GetProperty(propertyName);
source.PropertyChanged += OnSourcePropertyChanged;
}
private void OnSourcePropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName != _property.Name)
{
return;
}
PropertyChangedEventHandler propertyChanged = PropertyChanged;
if (propertyChanged == null)
{
return;
}
SynchronizationContext.Current.Send(state => propertyChanged(this, e), null);
}
}
I have come across the same problems and Samuel's solution didn't work for me, so I placed the synchronization context initialization in the constructor, and the "Value" property name should be passed instead of the original property. This worked for me:
public class SyncBindingWrapper: INotifyPropertyChanged
{
private readonly INotifyPropertyChanged _source;
private readonly PropertyInfo _property;
public event PropertyChangedEventHandler PropertyChanged;
private readonly SynchronizationContext _context;
public object Value
{
get
{
return _property.GetValue(_source, null);
}
}
public SyncBindingWrapper(INotifyPropertyChanged source, string propertyName)
{
_context = SynchronizationContext.Current;
_source = source;
_property = source.GetType().GetProperty(propertyName);
source.PropertyChanged += OnSourcePropertyChanged;
}
private void OnSourcePropertyChanged(object sender, PropertyChangedEventArgs e)
{
var propertyChanged = PropertyChanged;
if (propertyChanged != null && e.PropertyName == _property.Name)
{
_context.Send(state => propertyChanged(this, new PropertyChangedEventArgs("Value")), null);
}
}
}
Usage:
_textBox1.DataBindings.Add("Text", new SyncBindingWrapper(someObject, "SomeProperty"), "Value");
Without relying on the SynchrnoisationConext you can rely on ISynchronizeInvoke
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
foreach (EventHandler h in handler.GetInvocationList())
{
var synch = h.Target as ISynchronizeInvoke;
if (synch != null && synch.InvokeRequired)
synch.Invoke(h, new object[] { this, e });
else
h(this, e);
}
}
}

TextBlock data-bound to Singleton not updating in WPF

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.

Categories