Subscribe weakly to events obtained by reflection - c#

I'm making a MessageBox control in WPF using the MVVM pattern. This MessageBox will be used in multiple applications with different appearances, so ideally I want to keep code out of code-behind.
I'm trying to make the MessageBox appear when an event is raised, specified in the declaration of the MessageBox.
For example, this would be specified in the XAML of the window in which the MessageBox should appear.
<dialog:MessageBox
ShowOnEvent="EventRaised"
EventContext="{Binding}"
Message="I am a message box"
IconType="Warning"
ButtonsType="YesNo" />
The way this currently works: in the MessageBox ViewModel I'm using reflection to get the EventInfo for the event, then subscribing directly:
if (eventContext != null && showOnEvent != string.Empty)
{
EventInfo eventInfo = eventContext.GetType ().GetEvent (showOnEvent);
if (eventInfo != null)
{
eventInfo.AddEventHandler (eventContext, eventHandler);
}
else
{
Debug.WriteLine (string.Format ("Dialog: Couldn't find event {0} on {1}, check event name.", showOnEvent, eventContext));
}
}
This shows the MessageBox when the event is raised, as expected.
However, the event handler means that the MessageBox ViewModel is not GC'd when the main window's View is disposed. This means that if another view for the main window is created, another MessageBox is created, so if the event is raised, both MessageBoxs will show.
I tried getting around this by using a WeakEventManager, but the Weak Event Patterns documentation specify that an implementation of WeakEventManager should only handle one event - which means that I can't make a ShowOnEventEventManager with the event name as a string property and subscribe using that.
Does anyone have any ideas for the best way to go about this?

Having a weak event won't solve your problem because you won't be unsubscribed until the GC decides to run (unless you're explicitly calling GC.Collect()). As Will suggests in the comments, you can try to unsubscribe at the appropriate time or what might be even easier is just have your MessageBox check if it IsLoaded before showing itself.
I wouldn't worry about code-behind in your MessageBox unless you know of some reason why it would hurt its reusability. It's fine to have MessageBox code reference its view directly as long as the consumers of the MessageBox have a MVVM-friendly API.

The PRISM EventAggregator implements eventing using weak references by default. You need to be able to alter the code where events are published to implement this in your app.
There are proper code examples at the linked page as well as the obligatory flow diagrams. The event aggregator is fairly simple to use: you Publish with a strongly typed payload and Subscribe in as many places as you need. (And it's free to download)

Related

MvvmCross: Best way to raise event in ViewModel?

I currently have a View (Android Fragment) and a coresponding ViewModel. I now want to raise an event in the ViewModel which the View can subscribe to.
What's the best way to archive this? I heard a regular C# event (delegate) can lead to memory leaks? Is this the reason for the WeakSubscribe function? How to bind it to an event?
To prevent memory leaks in your views, every event you subscribed to needs to be unsubscribed from, either way using WeakSubscribe or subscribing to events as usual.
A common scenario would be subscribing on:
Android OnResume()
iOS ViewWillAppear()
then dispose the subscription on:
Android OnPause()
iOS ViewWillDisappear()
WeakSubscribe
If you want to "listen" for ViewModel property changes, WeakSubscribe comes in handy:
private IDisposable _selectedItemToken;
_selectedItemToken = ViewModel.WeakSubscribe(() =>
ViewModel.SelectedItem, (sender, eventArgs) => {
// do something
});
Just notice that WeakSubscribe() returns an MvxWeakEventSubscription that is also IDisposable. You need to save a reference to that subscription in your view and dispose it when thew view is no longer needed.
There are two reasons to keep that reference:
You can dispose it later
If you don´t keep it, your lambda event handler may not always work
Later on...
_selectedItemToken?.Dispose();
Normal event subscription
If you just need to subscribe to another kind of event (not property changes) in your ViewModel, you don´t actually need WeakSubscribe. You can just add an event listener to the ViewModel, as you would do with any object.
ViewModel.AnEvent += YourDelegate;
Later on...
ViewModel.AnEvent -= YourDelegate;
Don´t forget the last step. That will prevent memory leaks. As I said, Android OnPause() and iOS ViewWillDisappear() are good places to do it.
That way your ViewModel won´t be stuck in memory when the view is disposed thus your view can be garbage collected correctly.
You can create a leak if you subscribe to an event in a temporary object and don't unsubscribe before releasing the temporary object. Little chance for that in your case as the view model most likely will be created only once.
Since you are using mvvm, an alternative to events are Messengers which you can find implemented in popular mvvm-frameworks like MvvmLight or MvvmCross. This gives you truly decoupled events as you only need to know the format of the message and don't need to know anything about the sender (which in your case is the ViewModel). With messenger you only subscribe to a message-type and the sender can be anywhere in the app.

Is there anyway to stop the flow of events in .NET?

that is a question I have been asking myself for a while.
Giving a certain flow of events, can I when handling one of them, stop the next ones to be raised?
For example, when collapsing a node which child was selected in a treeview (winform), the events are raised like that:
BeforeCollapse
BeforeSelect
AfterSelect
AfterCollapse
I could stop them by using a class member, but I was wondering whether there was a built-in function or just another way (a more elegant way) to achieve this, by acting directly on the events queue.
Any idea?
Not easily, no. The order of the events firing is controlled by the TreeView control class, and there is no built-in way to prevent events from firing. But you have a couple of options:
Create your own TreeView class that inherits from the base class,
then add a bool property to prevent the events from processing.
Then you can override BeforeCollapse, etc. to check the bool
before calling base.BeforeCollapse.
Just create a bool flag, and check the flag in each of the events.
No there is no way to do that for that type of event (you are asking for TreeView).
Like for example could be managed KeyEventArgs.Handled via built-in mechanism.
You can use some instance (boolean ?) value to manage the flow,
or you can, unsubscribe from the event that you don't want more recieve, but after subscribe to it again. Sounds rough solution, but sometimes turns out reasonable one.
even if the event are raised nothing will happen if you don't bind an event handler to them. In this case you can just remove the handler using the code below:
object.Event -= new EventHandlerType(your_Method)
Otherwise you should create your own custom control
according to OnBeforeCollapse you get an TreeViewCancelEventArgs which has an Cancel property. Setting this to true should stop the flow, but will also not collapse it.
Same goes for OnBeforeSelect.
The only times you can easily "cancel" an event is if the event handler has the CancelEventHandler delegate type. Even then it doesn't really cancel it as much as set a flag for the remaining events that makes it skip performing all the events subscribed to it.
If you did have a CancelEventHandler type (which these don't) you'd simply set Cancel to true on the event object itself in the handler.
Plenty of other answers give you suggestions for what you should o. I'd just go with your idea: set a 'event cancelled' flag in your control class, and check it. When the last event in the series gets called, reset it.

Routing an event through multiple classes in C#

A common scenerio we are running into with our current application is where we need to route and event through several classes.
Here is a sample class heirarchy.
ActionManager
MainWindow
PresentationManager
MenuManager
Menu
MenuButton
The Menu subscribes to the click event of a MenuButton. It then creates a CustomAction object and raises an event that is subscribed to MenuManager. In the MenuManager event handler it in turn raises an event that is subscribed to by the PresentationManager, and so on.
Here is a sample of what is implemented for the PresentationManager:
void MenuManager_ActionGenerated(object sender, CustomActionEventArgs e)
{
if (ActionGenerated != null)
ActionGenerated(sender, e);
}
I was hoping that there would be a way that I could raise the event at the Menu level and receive it at the ActionManager level.
Is it bad practise what I am currently doing?
You can also look into Event Aggregator. A good example can be found at codeproject: Event Aggregator with Specialized Listeners
If what you've listed as your class hierarchy is actually your visual tree, it sounds like what you are describing is Routed events.
http://msdn.microsoft.com/en-us/library/ms742806.aspx
Personally, I get scared by having a lot of events. If you are not careful with unsubscription, they can extend the lifetime of your objects. Also, they may cause tight-coupling, reducing testability. In some cases using a Commanding pattern is a better approach.
I would try this CSharpMessenger Extended.
You can write your own SubscriptionManager.
By simplifying can be a Dicationary<string, List<Action<...>>>.
The key is the event-name, value is the List of Actions to run wen that even was raised.
So all yuor components subscribe to some specified event by adding its Action<..> to the list of specified event.
And when the even raised (always via SubscriptionManager) all Action<..>s from the list will be executed.
Just a basic idea. To make this production ready you need to code a bit more.
Good luck.

Getting KeyCode in GotFocus event?

C# WinApps: Is there any way that I can check if something like CTRL-V is pressed but not in the KeyDown,PreviewKeyDown,KeyPress,etc ... events? those are being eaten by some other parts in my App and it is so hard to find them so I thought Ok for this contorl lets check the pressed keys in its GotFocus event! Is it possible?
Not sure what you mean by the events being "eaten". Events can call multiple handlers. So even if the event is already being subscribed to by one handler, you can subscribe to it with another handler and it should work just fine.
Another option would be to subclass the control you are using and use the subclass instead. Then you can override the On{event} methods and do anything you want with those (be sure to call the base method as well to ensure the behavior of the original class is still in place).
HTH

C#: Create an event that is fired whenever another event that has listeners is fired, dynamically via reflection maybe?

Here's what I am working with:
Part of my project is a windows form app. I want to basically capture every event that fires and has listeners. So some button's click event, some checkbox's check event, everything on a basic "Control" event list. So that event still fires, but also fires my event.
Is there a generic "some event fired" event under the hood I can tap into, or is there a way using reflection to enumerate through all objects in my form, parse out all the events, parse which have listeners, and then subscribe all of them to a generic event elsewhere in addition to where they are already going?
Anyone know how to do this?
You fundamentally can't do this: an event is a black box with just "subscribe" and "unsubscribe" functionality. So while you can use reflection to find out all the events, you can't reliably detect which have been subscribed to. For field-like events you could fetch the backing field and check whether or not it's null, but even that's not reliable - to avoid null checks, the author may have written something like this:
public event EventHandler SomeEvent = delegate {};
For other events, you'd have to work out what subscribing to the event actually does - for example, it might use EventHandlerList.
Basically, you should rethink your design so you don't need to do this.
Doesn't the fact that a subscribed event got fired indicate it has subscriber(s)? So then all you would need is a list of subscribable events, which you can validate against during an intercepted call.
You can intercept a call using any AOP framework. For instance, by using Unity Interception, you can do something like this:
public IMethodReturn Invoke(IMethodInvocation input,
GetNextHandlerDelegate getNext)
{
// 1. assuming that you are keeping a list of method names
// that are being subscribed to.
// 2. assuming that if the event is fired, then it must have
// been subscribed to...
if (MyReflectedListOfSubscribedEvents.Contains(input.MethodBase.ToString())
{
HandleItSomeHow();
}
// process the call...
return getNext().Invoke(input, getNext);
}

Categories