I have a .json file with so-called commands:
"Commands":[{
"EventName": "MouseLeftButtonUp",
"MethodToExecute": "NextJson",
"Args": "Next.json"
},{
"EventName": "MouseRightButtonUp",
"MethodToExecute": "CloseApp"
}
I deserialize this json to this class:
public class Command
{
[JsonPropertyName("EventName")]
public string EventName { get; set; }
[JsonPropertyName("MethodToExecute")]
public string MethodToExecute { get; set; }
[JsonPropertyName("Args")]
public string Args { get; set; }
/*Methods*/
}
EventName is a name of UIElement class events.
MethodToExecute is a name of method to call, when event triggered.
Args are the args, passed to the MethodToExecute.
I don't want my users to be able to call any method in the application, so I don't use reflection to get MethodInfo, instead I create Dictionary: Dictionary<string, Delegate> MethodsDictionary. The key in this dictionary is the name of method (MethodToExecute from Command class), and the value is something like this:
MethodsDictionary.Add(nameof(CloseApp), new Action(CloseApp));
MethodsDictionary.Add(nameof(NextJson), new Action<string>(NextJson));
Without using reflection, I'd added the event handler like this:
button.MouseLeftButtonUp += (sender, args) => MethodsDictionary[command.MethodToExecute].DynamicInvoke(command.Args);
But I'd like to make a dynamic binding of events. Well, of course I can make it through ugly switch-case on command.Name property, but I still would like to try the solution with reflection.
The solutuion, as I see it, should looke something like:
foreach (var command in commands)
{
command.Bind(uielement, MethodsDictionary[command.MethodToExecute]);
}
//And command.Bind method is like:
public void Bind(UIElement uielement, Delegate methodToExecute)
{
//I know there's no such method like GetEventHandler, just an example
var handler = uielement.GetEventHandler(EventName);
handler += (sender, args) => methodToExecute.DynamicInvoke(Args);
}
I searched through several pretty similar questions:
Subscribe to an event with Reflection
https://social.msdn.microsoft.com/Forums/vstudio/en-US/d7f184f1-0964-412a-8659-6759a0e2db83/c-reflection-problem-subscribing-to-event?forum=netfxbcl
https://learn.microsoft.com/en-us/dotnet/framework/reflection-and-codedom/how-to-hook-up-a-delegate-using-reflection
AddEventHandler using reflection
Add Event Handler using Reflection ? / Get object of type?
But these doesn't help me to solve the problem the way I want to. I tried some of the solutions above, but they didn't work out for me, failing with different exceptions.
UPD.
I tried to implement handler binding through switch-case, as I mentioned above. It resulted in this method inside Command class:
public void Bind(UIElement element)
{
switch (this.Name)
{
case "MouseRightButtonUp":
{
element.MouseRightButtonUp += (sender, args) => MethodsDictionary[this.MethodToExecute].DynamicInvoke(this.Args);
break;
}
case "Click":
{
//UIElement doesn't have Click event
var button = element as ButtonBase;
button.Click += (sender, args) => MethodsDictionary[this.MethodToExecute].DynamicInvoke(this.Args);
break;
}
/*And so on for each event*/
default:
{
throw new NotSupportedException();
}
}
}
I don't like, how that part with adding new handlers is just a copy-paste of previous section, but I don't see another workaround in this situation. I'd like to use the reflection in this case, but I don't know if it's possible.
If you can't get reflection to work, you can use another Dictionary to store supported event subscriber methods:
Command.cs
public class Command
{
[JsonPropertyName("EventName")]
public string EventName { get; set; }
[JsonPropertyName("MethodToExecute")]
public string MethodToExecute { get; set; }
[JsonPropertyName("Args")]
public string Args { get; set; }
/*Methods*/
}
JsonEventMapper.cs
class JsonEventMapper
{
private Dictionary<string, Action<UIElement, EventHandler>>> SupportedEventSubscriberMap { get; }
private Dictionary<string, EventHandler> RegisteredEventHandlerMap { get; }
public JsonEventMapper()
{
this.SupportedEventSubscriberMap = new Dictionary<string, Action<UIElement, EventHandler>>()
{
{
nameof(UIElement.MouseRightButtonUp), (uiElement, handler) => uiElement.MouseRightButtonUp += handler.Invoke
},
{
nameof(UIElement.LostFocus), (uiElement, eventSubscriber) => uiElement.LostFocus += handler.Invoke
}
};
this.RegisteredEventHandlerMap = new Dictionary<string, EventHandler>()
{
{
nameof(UIElement.MouseLeftButtonUp), CloseApp
},
{
nameof(UIElement.LostFocus), NextJson
}
};
}
public void RegisterJsonCommands(IEnumerable<Command> commands, UIElement uiElement)
{
foreach (var command in commands)
{
BindCommandToEvent(uiElement, command);
}
}
private void BindCommandToEvent(UIElement uiElement, Command command)
{
if (this.SupportedEventSubscriberMap.TryGetValue(command.EventName, out Action<UIElement, EventHandler> eventSubscriber)
&& this.RegisteredEventHandlerMap.TryGetValue(command.EventName, out EventHandler eventHandler))
{
eventSubscriber.Invoke(uiElement, eventHandler);
}
}
private void CloseApp(object sender, EventArgs args)
{
// Handle event
}
private void NextJson(object sender, EventArgs args)
{
// Handle event
}
}
Usage
IEnumerable<Command> commands = DeserializeJsonToCommands();
var eventMapper = new JsonEventMapper();
eventMapper.RegisterJsonCommands(commands, button);
You surely want to adjust details to your specific scenario.
Don't forget to unsubscribe from the events (e.g., by defining another SupportedEventUnsubscriberMap).
I think what you're trying to do is something like that:
public class CommandModel
{
/// properties
public void Bind(UIElement element)
{
EventBinder.Bind(() => MainViewModel.RunCommand(MethodToExecute, Args), element, EventName);
}
}
public static class EventBinder
{
public static void Bind(Action action, object eventSource, string eventName)
{
var eventInfo = eventSource.GetType().GetEvent(eventName);
EventHandler eventHandler = (s, e) => action();
eventInfo.AddEventHandler(eventSource, ConvertDelegate(eventHandler, eventInfo.EventHandlerType));
}
private static Delegate ConvertDelegate(Delegate originalDelegate, Type targetDelegateType)
{
return Delegate.CreateDelegate(
targetDelegateType,
originalDelegate.Target,
originalDelegate.Method);
}
}
Related
I try to return an object in my event:
public class MyEvent : EventArgs
{
public Channels number = new Channels(); // Channels is a class where i declared only variables( i try to return all variables inside this class)
public MyEvent(Channels numero)
{
return numero;
}
}
This code doesn't work and i don't know how to return an object which contains my variables of Channels.
Change that to:
public class MyEvent : EventArgs
{
public Channels Number {get;}
public MyEvent(Channels numero)
{
Number = numero;
//return numero; You cannot use "return" in a CTOR!
}
}
Then you can use it in an EventHandler like this:
void MyEventHandler( object sender, MyEvent e )
{
// sender => object that raised the event
// e => an instance of `MyEvent`, having a property, we can read.
var channels = e.Number; // use the info
}
Of course you would have registered it, before it will be triggered:
someInstanceProvidingTheEvent.MyEventHappened += MyEventHandler;
Raising the event works something like this:
// assume we are in the class that offers the Event
public event EventHandler<MyEvent> MyEventHappened;
protected virtual void OnMyEventHappened( Channels chans )
{
// You may want to add some error fortification, here
MyEventHappened?.Invoke(this, new MyEvent(chans));
}
// raise it
public void SomeMethod(){
var theChannels = new Channels();
// yadda yadda
// now it happens!
OnMyEventHappened(theChannels);
}
public class MyEvent : EventArgs
{
public Channels _channels { get; set; }
public MyEvent(Channels numero)
{
_channels = numero;
}
}
public class Program
{
public Main()
{
Channels myChannels = new Channels();
MyEvent _myEvent = new MyEvent(myChannels);
var youWant = _myEvent._channels;
}
}
Let's suppose I have an observable collection and two clients that want to:
change it,
observe it and react on state change.
Now, if Client1 changes collection state (for example: adds new item), the collection will fire 'CollectionChanged' event. Since both clients are registered for this event notifications, Client1's handling method will be executed.
In order to avoid self-callback on Client1, I unsubscribe from an event, do my action and subscribe again. This is painful - I must remember about suspending Client1's subscription every time Client1 touches the collection and it just seems like a bad smell. Is there a better way (design pattern, external library) that would help me in callbacks management?
Although in my example I mentioned ObservableCollection and CollectionChanged event, I believe my question is more generic and comes down to: "how to exclude an entity that caused event trigger from event callback".
Thanks in advance!
Problem keeps reoccuring in my solution, bumping the question in a hope someone might help out.
I ran into your problem some times ago I didn't find a proper solution except for this one.
The idea is that when you change the collection you also pass an instance of the object changing it.
Then when the Collection fires the event, it also passes the reference.
So all observers may know which instance did the change, and check for equality.
Here is a basic example of this implementation:
class Program
{
private static MyCollection Collection;
private static MyCollectionModifier Modif1;
private static MyCollectionModifier Modif2;
static void Main(string[] args)
{
Collection = new MyCollection();
Modif1 = new MyCollectionModifier("Modifier 1", Collection);
Modif2 = new MyCollectionModifier("Modifier 2", Collection);
Modif1.AddItem("Test1");
Modif2.AddItem("Test2");
Console.ReadKey();
}
}
public class MyCollectionItemAddedEventArgs:EventArgs
{
public Object ChangeSource { get; set;}
public int newIndex {get;set;}
}
public delegate void MyCollectionItemAddedEventHandler(object sender, MyCollectionItemAddedEventArgs e);
public class MyCollection
{
private List<String> _myList;
public String this[int Index]
{
get { return _myList[Index]; }
}
public event MyCollectionItemAddedEventHandler ItemAdded;
public MyCollection()
{
_myList = new List<string>();
}
protected virtual void OnMyCollectionItemAdded(MyCollectionItemAddedEventArgs e)
{
if (ItemAdded != null)
ItemAdded(this, e);
}
public void AddItem(String Item, object ChangeSource = null)
{
_myList.Add(Item);
var e = new MyCollectionItemAddedEventArgs();
e.ChangeSource = ChangeSource;
e.newIndex = _myList.Count;
OnMyCollectionItemAdded(e);
}
}
public class MyCollectionModifier
{
private MyCollection _collection;
public string Name { get; set; }
public MyCollectionModifier(string Name, MyCollection Collection)
{
this.Name = Name;
_collection = Collection;
_collection.ItemAdded += Collection_ItemAdded;
}
public void AddItem(string Item)
{
_collection.AddItem(Item, this);
}
void Collection_ItemAdded(object sender, MyCollectionItemAddedEventArgs e)
{
if (e != null)
{
if (this.Equals(e.ChangeSource))
{
Console.WriteLine("{0} : I changed the collection", Name);
}
else
{
Console.WriteLine("{0} : Somebody else changed the collection", Name);
}
}
}
}
I've encountered this problem before as well.
Best solution I could come up with is to create extension methods that take the handler of the caller and then automate the unsubscribe/subscribe around the called method, that way you don't have to remember to do it each time and it does not end up cluttering your code either
public static void Add<T>(this ObservableCollection<T> self, T itemToAdd, NotifyCollectionChangedEventHandler handler)
{
self.CollectionChanged -= handler;
self.Add(itemToAdd);
self.CollectionChanged += handler;
}
It does take some effort to create the extensions initially but at least you won't forget to resubscribe. Only real extra code is then around invoking the method
public class ObserverClass
{
public ObserverClass()
{
ObservableIntegers.CollectionChanged += ObservableIntegersOnCollectionChanged;
//Add item to collection while preventing self-handling the callback
ObservableIntegers.Add(1, ObservableIntegersOnCollectionChanged);
}
private void ObservableIntegersOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
{
// Handle collection change
}
public ObservableCollection<int> ObservableIntegers { get; set; }
}
I'm trying to forward events from one class to objects contained within it (as described here: Forwarding events in C#). However, the events are of different type.
For example, I have a class Item which exposes a ValueChanged event handler of type EventHandler. The class ItemHaver exposes an EventHandler<StatusEventArgs>, which should fire whenever Item.ValueChanged does, but should also provide additional information. How do I properly implement add/remove to the ItemValueChanged event declaration?
In the below code, would the lambda function in the add method perform the correct action, and if so, what's the proper way to handle the remove?
class Item
{
public event EventHandler ValueChanged;
}
class ItemHaver
{
private int _status;
private Item _item;
public event EventHandler<StatusEventArgs> ItemValueChanged
{
add
{
_item.ValueChanged += value; // Wrong type
_item.ValueChanged +=
(obj, e) => value(obj, new StatusEventArgs(this._status));
}
remove
{
_item.ValueChanged -= // Does this even work?
(obj, e) => value(obj, new StatusEventArgs(this._status));
}
}
}
class StatusEventArgs : EventArgs
{
int Status { get; private set; }
StatusEventArgs(int status) { Status = status; }
}
I'd try using a dictionary in which I map the handlers.
class ItemHaver
{
private int _status;
private Item _item;
private Dictionary<EventHandler<StatusEventArgs>, EventHandler> handlersMap = new Dictionary<EventHandler<StatusEventArgs>, EventHandler>();
public event EventHandler<StatusEventArgs> ItemValueChanged
{
add
{
// _item.ValueChanged += value; // Wrong type
handlersMap.Add(value, (obj, e) => value(obj, new StatusEventArgs(this._status)));
_item.ValueChanged += handlersMap[value];
}
remove
{
_item.ValueChanged -= handlersMap[value];
}
}
}
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 try to run some code when collection is changed. I keep collection as property in Data class:
public static ObservableCollection<OfferedConfiguration> DeviceAdjustedConfigurations
{
get { return deviceAdjustedConfigurations; }
set { deviceAdjustedConfigurations = value; }
}
and register it in code like that:
Data.DeviceAdjustedConfigurations.CollectionChanged += new NotifyCollectionChangedEventHandler(DeviceAdjustedConfigurationsCollectionChanged);
But after registration CollectionChanged is null and the appropriate code in delegated method is not run. In this place DeviceAdjustedConiguration already contains some data. What am I doing wrong?
You should avoid having a set property accessor for collection types, one reason being the one you experienced here with events. Another problem is if someone caches the collection and adds items to it later.
var old = obj.DeviceAdjustedConfigurations;
obj.DeviceAdjustedConfigurations = new ObservableCollection<OfferedConfiguration>();
old.Add(new OfferedConfiguration()); // what should happen here?
instead, remove the set-accessor and use the existing collection directly.
obj.DeviceAdjustedConfigurations.Add(new OfferedConfiguration());
If you really need to set the collection, you need to handle this with for instance a property change event from the class that owns the DeviceAdjustedConfigurations.
public class Item
{
public static ObservableCollection<OfferedConfiguration> DeviceAdjustedConfigurations
{
get { return deviceAdjustedConfigurations; }
set
{
if (deviceAdjustedConfigurations != value)
{
onDeviceConfigurationsChanging(deviceAdjustedConfigurations, value);
deviceAdjustedConfigurations = value;
}
}
}
public static event EventHandler<ConfigurationChangedEventArgs> DeviceConfigurationsChanging;
private static void onDeviceConfigurationsChanging(
ObservableCollection<OfferedConfiguration> oldList,
ObservableCollection<OfferedConfiguration> newList)
{
var handler = DeviceConfigurationsChanging;
if (handler != null)
{
handler(null, new ConfigurationChangedEventArgs(oldList, newList));
}
}
}
public class ConfigurationChangedEventArgs : EventArgs
{
public ConfigurationChangedEventArgs(
ObservableCollection<OfferedConfiguration> oldList,
ObservableCollection<OfferedConfiguration> newList)
{
OldList = oldList;
NewList = newList;
}
public ObservableCollection<OfferedConfiguration> OldList { get; private set; }
public ObservableCollection<OfferedConfiguration> NewList { get; private set; }
}
public class Consumer
{
public void foo()
{
Item.DeviceConfigurationsChanging += updateEvents;
}
private void updateEvents(object sender, ConfigurationChangedEventArgs args)
{
args.OldList.CollectionChanged -= onCollectionChanged;
args.NewList.CollectionChanged += onCollectionChanged;
}
private void onCollectionChanged(object sender, NotifyCollectionChangedEventArgs args) { }
}