How could I refactor the method
private void ListenToPropertyChangedEvent(INotifyPropertyChanged source,
string propertyName)
{
source.PropertyChanged += (o, e) =>
{
if (e.PropertyName == propertyName)
MyMagicMethod();
};
}
if I wished to avoid using the anonymous method here?
Implement the closure that is implicitly created by the lambda explicitly:
private void ListenToPropertyChangedEvent(INotifyPropertyChanged source,
string propertyName)
{
var listener = new MyPropertyChangedListener(propertyName);
source.PropertyChanged += listener.Handle;
}
class MyPropertyChangedListener
{
private readonly string propertyName;
public MyPropertyChangedListener(string propertyName)
{
this.propertyName = propertyName;
}
public void Handle(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == this.propertyName)
{
// do something
}
}
}
You can handle this by having a single event handler for all instances that uses a dictionary of instances that you are following:
private Dictionary<INotifyPropertyChanged, List<string>> sourceMap =
new Dictionary<INotifyPropertyChanged, List<string>>();
private void ListenToPropertyChangedEvent(INotifyPropertyChanged source,
string propertyName)
{
if (sourceMap.ContainsKey(source))
sourceMap[source].Add(propertyName);
else
{
source.PropertyChanged += source_PropertyChanged;
sourceMap[source] = new List<string> { propertyName };
}
}
void source_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
var source = sender as INotifyPropertyChanged;
var list = sourceMap[source];
if (list.Contains(e.PropertyName))
MyMagicMethod();
}
This version doesn't have any error checking or removal but it demonstrates the technique. It is particularly valuable if you listen for multiple properties from the same source. This is because it only adds a single handler to the PropertyChanged event per instance.
I'm not sure exactly what youre trying to achieve or why you dont want to use anonymous methods, but you could do something more generic:
private PropertyChangedEventHandler GetHandler
(Func<PropertyChangedEventArgs, bool> test, Action toInvoke)
{
return new PropertyChangedEventHandler(
(o, e) =>
{
if (test(e))
toInvoke();
});
}
Then you can use it like so:
source.PropertyChanged += GetHandler
(
p => p.PropertyName == propertyName, MyMagicMethod
);
That way your if test and the target method group can be swapped about easily. Your event handler is also strongly typed rather than anonymous.
Related
Introduction
In my current WPF project I quite regularly had to convert DataTables into Lists of model classes, like this:
public void CreateExmapleModeList()
{
ExampleModels = new List<ExampleModel>();
foreach (DataRow row in tbl)
{
ExampleModel example = new ExampleModel
{
Name = row["Name"].ToString(),
Tag = row["Tag"].ToString(),
Value = double.Parse(row["Value"].ToString()),
// [...]
};
ExampleModels.Add(example);
example.PropertyChanged += ExampleModel_PropertyChanged;
}
}
assinging dozens of properties for dozens of lists is quite annoying, so I googled a little and found this quite handy answer on StackOverflow to assign properties dynamically, which cut down creating new lists to this:
ExampleModels = ListConverter.ConvertToList<ExampleModel>(tbl);
Problem
Now after refactoring my code I didn't know how to subscribe my custom PropertyChanged-Event to the PropertyChanged-Event of my model, so I simply iterated through the whole list again:
foreach (ExampleModel exmp in ExampleModels)
{
example.PropertyChanged += ExampleModel_PropertyChanged;
}
PropertyChanged:
public void ExampleModel_Propertychanged(object sender, PropertyChangedEventArgs e)
{
//do something
}
Question
I would much rather subscribe to the PropertyChanged-Event when creating the list rather then redundantly iterate a second time through the whole list.
Since there are quite a few Models, which have custom PropertyChanged-Events I need to subscribe them dynamically.
Is there a way simular to the RelayCommand for example:
public RelayCommand(Action<T> execute, Predicate<T> canExecute)
{
m_execute = execute ?? throw new ArgumentNullException("execute");
m_canExecute = canExecute;
}
to tell my ContVertToList-Method which event it has to subscirbe
like:
ExampleModels = ListConverter.ConvertToList<ExampleModel>(tbl, ExampleModel_Propertychanged(object sender, PropertyChangedEventArgs e));
and in ConvertToList something like this:
public static List<T> ConvertToList<T>(DataTable dt, CustomPropertyChanged<S, E>) where T : TemplateModel
// [...]
objT.PropertyChanged = CustomPropertyChanged;
return onjT;
}).ToList();
You could pass a PropertyChangedEventHandler to your method and hook it up using the += syntax:
public static List<T> ConvertToList<T>(DataTable dt, PropertyChangedEventHandler eventHandler) where T : INotifyPropertyChanged
{
//...
objT.PropertyChanged += eventHandler;
}
Usage:
var list = ConvertToList<YourType>(dataTable, ExampleModel_Propertychanged);
...
private void ExampleModel_Propertychanged(object sender, PropertyChangedEventArgs e)
{
//do something
}
I will explain what I am trying to do first.
I have a quite a few of DataGrids and each DataGrid use different classes for there data type and instead of subscribing an Event handler for each one, I was hoping to make a generic event handler and get the type from from the sender object.
I am using EntityFramework Database First
One example of one of the classes:
public partial class StaffData : INotifyPropertyChanged
{
public long ID { get; set; }
public string StaffNameFirst { get; set; }
public string StaffNameSecond { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
My ViewModel:
VeiwModelBase holds the INotifyPropertyChanged data.
public class MasterViewModel : ViewModelBase
{
public static ObservableCollection<StaffData> MasterDataBinding
{
get { return _mMasterData; }
private set
{
if (value == _mMasterData)
return;
_mMasterData = value;
OnPropertyChanged();
}
}
public MasterViewModel()
{
_mMasterData.CollectionChanged += master_CollectionChanged;
}
public void master_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
//asign PropertyChanged event here
}
private void master_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
Type foo = sender.GetType().GetGenericArguments()[0];
var newRowData = sender as foo;
SaveData(newRowData);
}
private static void SaveData(object newRowData)
{
Type foo = newRowData.GetType().GetGenericArguments()[0];
var originalData = dataBaseEntities.foo.FirstOrDefault(p => p.ID == newRowData.ID);
entities.Entry(originalData).CurrentValues.SetValues(newRowData);
dataBaseEntities.SaveChanges();
}
}
These are the two methods above which I can't seem to figure this out, I have tried countless ways using Getype with not much success (I left my last attempt in hopefully to illustrate what I am trying to do). I have commented out how I am normally going about this:
private void master_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
Type foo = sender.GetType().GetGenericArguments()[0];
var newRowData = sender as foo;
//var newRowData = sender as StaffData
SaveData(newRowData);
}
//private static void SaveData(StaffData newRowData)
private static void SaveData(object newRowData)
{
Type foo = newRowData.GetType().GetGenericArguments()[0];
var originalData = dataBaseEntities.foo.FirstOrDefault(p => p.ID == newRowData.ID);
//var originalData = dataBaseEntities.StaffData.FirstOrDefault(p => p.ID == newRowData.ID);
entities.Entry(originalData).CurrentValues.SetValues(newRowData);
entities.SaveChanges();
}
When trying to use the variable as a type I get this error,
Error CS0118 'foo' is a variable but is used like a
type
Is there a way to get the type when you don't know which datagrid will implement the PropertyChanged event and use it so as you can make a generic event handler for all the Datagrid controls?
Or am I going about this the wrong way?
Not sure if I really understand your question, but you could check the type of the sender argument at runtime and call an appropriate method like this:
private void master_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (sender is StaffData)
{
DoSomething((StaffData)sender);
}
else if (sender is SomeOtherData)
{
DoSomething((SomeOtherData)sender);
}
...
}
private void DoSomething(StaffData data)
{
...
}
private void DoSomething(SomeOtherData data)
{
...
}
However, I'd prefer to have different PropertyChanged handler methods for different sender types.
You cant get the type inside the propertyChanged event handler but you can get the property name from PropertyChangedEventArgs.
Something like:
private void OnPropertyChanged(object sender, PropertyChangedEventArgs args)
{
if (args.PropertyName == "SomePropertyName")
{
//... do your stuf
}
}
I have the following method:
void ViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "InitializeFailureMessage":
if (Vm.InitializeFailureMessage != null)
ShowInitializeFailure(Vm.InitializeFailureMessage);
break;
}
}
Just now, the method had a bug: the property used to be called InitializeFailureErrorMessage, and when it was renamed, no one updated the string in this handler.
Is there a better, less error-prone way to subscribe to the PropertyChanged event? When firing the event we can now use [CallerMemberName]. Is there a similar trick when implementing the handler?
Quick idea using extension method, expression and delegates:
public static class Extension
{
public static void RegisterNotify<T>(this T obj, Expression<Func<T, object>> propExpr, Action action) where T : INotifyPropertyChanged
{
string name = GetPropertyName(propExpr);
obj.PropertyChanged += (s, e) => { if (e.PropertyName == name) action() };
}
}
And it is called like:
Notifier obj = new Notifier(); // implements INotifyPropertyChanged
obj.RegisterNotify(x => x.Property, () => { /* do something when Property changes */ });
obj.RegisterNotify(x => x.Property2, () => { /* do something else when Property2 changes */ });
Use this utility method to get the property name using Expressions.
Consider this is your class which fires the event, introduce a static readonly string field which tells the string representation of property. Then use that static field to check which was the property changed.
class MyClass
{
public static readonly string InitializeFailureMessageProperty = GetPropertyName(() => x.InitializeFailureMessageProperty);//x can be a dummy instance.
}
void ViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == MyClass.InitializeFailureMessageProperty)
{
if (Vm.InitializeFailureMessage != null)
ShowInitializeFailure(Vm.InitializeFailureMessage);
}
}
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);
}
}
}
basically:
public delegate void RecvCommandHandler (ChatApplication sender, byte[] content);
event RecvCommandHandler[] commands = new RecvCommandHandler[255];
I want to activate a different method/function for each command number, but I am really uncertain of the syntax. How am I supposed to do it?
I think I'll go with just an array of delegates for this one, but the question is still interesting.
You could create an array of a class with operator overloading to simulate the behavior you are interested in...
public delegate void EventDelegate(EventData kEvent);
public class EventElement
{
protected event EventDelegate eventdelegate;
public void Dispatch(EventData kEvent)
{
if (eventdelegate != null)
{
eventdelegate(kEvent);
}
}
public static EventElement operator +(EventElement kElement, EventDelegate kDelegate)
{
kElement.eventdelegate += kDelegate;
return kElement;
}
public static EventElement operator -(EventElement kElement, EventDelegate kDelegate)
{
kElement.eventdelegate -= kDelegate;
return kElement;
}
}
public EventElement[] commands = new EventElement[255];
commands[100] += OnWhatever;
commands[100].Dispatch(new EventData());
commands[100] -= OnWhatever;
There's really no concept of an array of events - it's like talking about an array of properties. Events are really just methods which let you subscribe and unsubscribe handlers. If you need to be able to do this by index, I suggest you just have a pair of methods. (AddCommandHandler(int, RecvCommandHandler) and RemoveCommandHandler(int, RecvCommandHandler)). That won't support the normal event handling syntactic sugar, of course, but I don't see that there's a lot of alternative.
The other option is to specify and index in the delegate prototype and have one event handler that "delegates" to the others, e.g.:
public delegate void RecvCommandHandler (int id, ChatApplication sender, byte[] content);
// ...
private RecvCommandHandler[] internalhandlers;
public void MyCommandHandler(int id, ChatApplication sender, byte[] content)
{
internalHandlers[id](id, sender, content);
}
I was just looking for the same answer, however my class is also event sender for WPF, so it should look as much as normal C#/WPF event sender class. So I simply added this:
To sender:
enum with properties name -- this is lame workaround for lack of nameof
one additional method to record requests
To receiver:
request event for given enum
The code, sender:
public enum Properties
{
NetworkFileName,
DatasetFileName,
LearningWatch
}
private string network_filename;
public string NetworkFileName
{
get { return network_filename; }
private set
{
if (network_filename != value)
{
network_filename = value;
OnPropertyChanged(Properties.NetworkFileName.ToString());
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
public void OnChange(Properties prop, Action<object, PropertyChangedEventArgs> action)
{
PropertyChanged += new PropertyChangedEventHandler((obj, args) => { if (args.PropertyName == prop.ToString()) action(obj, args); });
}
And to the receiver:
private void OnNetworkLoaded(object sender, PropertyChangedEventArgs e)
{
SetTitle();
}
...
ExpManager.OnChange(ExperimentManager.Properties.DatasetFileName, OnDatasetLoaded);
It is still ugly, but at least:
I don't have to deal with "ifs" in receiver
I can easily create multiple event handlers
it is compatible with WPF
no magic strings (I hate those)
Disadvantage:
obsfuscation ruins this (but I have special class for that case, this project is just for me, so no problem here)