In my WPF application, I have an event handler that gets called on the MouseEnter event of my UI element:
myUiElement.MouseEnter += myEventHandler
I would like to throttle myEventHandler so it doesn't get called more than once every second. How can I do this? Is Rx the best approach just for this? I'm using .NET 4.0 if it makes a difference.
Also, I need to make sure that the MouseLeave event always gets called before the next MouseEnter event; do I need to manage this on my own? Or is the framework already designed so that MouseLeave events will always be called before the next MouseEnter event? What if I have asynchronous code in these event handlers?
Using Rx, you want to use the Sample method or Throttle.
Something like this should work (untested):
Observable
.FromEventPattern<TextChangedEventArgs>(myUiElement, "MouseEnter")
.Sample(TimeSpan.FromSeconds(1))
.Subscribe(x => ... Do Stuff Here ...);
The difference between Sample and Throttle is that Sample will take a value every 1 second no matter when the last value was taken, whereas Throttle will take a value and then wait another 1 second before taking another.
It probably depends on what you are shooting for...
You could use reactive extensions, but you could accomplish this just as easily with a timer.
Set a flag along with a Timer. When the timer tick event fires, set the flag to false, disable the timer, and run the code for your event. Then, in your control event handlers, have the handler code skipped if the flag is set.
bool flag;
DispatcherTimer timer;
public constructor()
{
timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromSeconds(1);
timer.Tick += (s,e) => {
flag = false;
timer.Stop()
DoThrottledEvent();
}
}
void mouse_enter(object sender, MouseEventArgs args)
{
if(!flag)
{
flag = true;
timer.Start();
}
}
void DoThrottledEvent()
{
//code for event here
}
Reactive extensions introduces an extra dependency, but they are a bit of fun. If you are interested, go for it!
Another approach would be to use a private field to keep track of the "time" when the last mouse event occurred, and only continue processing if that time was more than one second ago.
DateTime _lastMouseEventTime = DateTime.UtcNow;
void OnMouseEnter(object sender, MouseEventArgs e)
{
DateTime now = DateTime.UtcNow;
if (now.Subtract(_lastMouseEventTime).TotalSeconds >= 1)
{
// do stuff...
}
_lastMouseEventTime = now;
}
This ensures that "stuff" gets done at least one second apart, which is what I think you were asking for.
Related
TL;DR: Can I make a handler that is attached to many events fire only once, even when I know all of the events will be fired?
I'm trying to listen to a collection of objects, each of which will complete a specific task and then notify the owner that it is completed. To do this I've simply looped over the collection and added an event handler to each object
foreach(var obj in collection)
{
obj.Event += GroupHandler;
}
and that works fine. However since I know the tasks will complete around the same time, and since it really doesn't matter if I queue a bit early I'd like to have a way to ensure that once GroupHandler is raised, it won't be raised again for the current execution set so I could do something like:
private void GroupHandler(object sender, EventArgs e)
{
foreach(var obj in collection)
{
obj.QueueNext();
}
}
Ideally I'd end up with something like
private void GroupHandler(object sender, EventArgs e)
{
if(GroupHandler.HasRun) return;
foreach(var obj in collection)
{
obj.QueueNext();
}
}
I know that I could use some global bool (or more likely an int to track cycles) but I don't like that as a solution. I'm looking for something a bit nicer than that.
As Gusman said in comments, you can have just one of the objects fire off the event. That way you know it executes only once per event set.
Alternatively, you could set up a Timer, a counter and a flag. Set the flag on the first event execution and then start the timer. As long as the flag is set, ignore the rest of the events. Set your counter to the number of objects you're tracking and decrement the counter as each one fires off its event. When either the timer expires or the counter reaches zero, reset the flag, reset the counter and cancel the timer. This could rapidly get complicated and may have a bunch of corner cases, so if you don't care too much about timing, then the first method is much better. Simplify as much as possible.
I ended up working with what Gusman mentioned in the comments an removed the event handlers. The real key though was finding out where to reattach them. So I ended up passing the handler function in the QueueNext call.
Code looks like this
private void GroupHandler(object sender, EventArgs e)
{
foreach(var obj in collection)
{
obj.Event -= GroupHandler;
}
foreach(var obj in collection)
{
obj.QueueNext(GroupHandler);
}
}
and QueueNext looks like
public void QueueNext(EventHandler nextHandler)
{
this.Event += nextHandler
QueueNext();
}
this way the event is only called once, and will be called for each set of execution.
I needed a small function that will wait for the left mous button to be released, and will not be based on the MouseUp event.
In many cases when we need this, we simply write an event handler for the MouseUp event.
It's simple, and it works.
There are however cases, where using the MouseUp event will not be useful,
such as when we are already in another (different) event handler,
and the left mouse button might be pressed when this event handler is called, and we need to wait for it to be released.
(the goal is to have a single flow of code, and not have to split it between several places which might already be occupied with another code)
I implemented it this way:
public void WaitForMouseUp()
{
while( (Control.MouseButtons&MouseButtons.Left)!=0 )
Application.DoEvents();
}
It works,
you can use it for example when you are in the event handler for the Control.Enter event,
and if the control was entered via the mouse, then this function will block until the mouse button is released.
I only worry about one thing:
I am using Application.DoEvents() there, and I wonder if there another way instead of using Application.DoEvents().
(Application.DoEvents(); has disadvantages of possible reentrancy, and so, so for this reason I try to minimize using it, whenever possible)
Anyone has an idea with what I can substitute the Application.DoEvents() part?
Here's an awesome way to do what you're asking. Use Microsoft's Reactive Extensions to make a single line of code do everything you want.
The reactive extensions provide a whole lot of operators that can be applied to events.
So first some basic observables that directly relate to normal control events:
var mouseEnters =
Observable
.FromEventPattern(
h => button1.MouseEnter += h,
h => button1.MouseEnter -= h);
var mouseLeaves =
Observable
.FromEventPattern(
h => button1.MouseLeave += h,
h => button1.MouseLeave -= h);
var mouseUps =
Observable
.FromEventPattern<MouseEventHandler, MouseEventArgs>(
h => button1.MouseUp += h,
h => button1.MouseUp -= h);
Now we need a query that will fire only once when the mouse up occurs, but only if the mouse has entered the button1, but only before it leaves.
var query =
mouseEnters
.Select(me => mouseUps.Take(1).TakeUntil(mouseLeaves))
.Switch();
Now to subscribe to the event to be able to handle it:
var subscription =
query
.Subscribe(ep =>
{
/*
this code runs for the first mouse up only
after each mouse enter on `button1`
unless the mouse leaves `button1`
*/
});
It now because very simple to unsubscribe as the type of subscription is IDisposable. So you simply call subscription.Dispose();.
Just NuGet "Rx-WinForms" to get the bits for your project.
In fact what #Kai Brummund is suggesting is a variation of my answer to Force loop to wait for an event. Adjusting the code from there for MouseUp is simple as
public static class Utils
{
public static Task WhenMouseUp(this Control control)
{
var tcs = new TaskCompletionSource<object>();
MouseEventHandler onMouseUp = null;
onMouseUp = (sender, e) =>
{
control.MouseUp -= onMouseUp;
tcs.TrySetResult(null);
};
control.MouseUp += onMouseUp;
return tcs.Task;
}
}
and the usage is
Control c = ...;
await c.WhenMouseUp();
The same technique can be used for any event.
If You wan't to write a flow within a single method, you can make an awaitable using a TaskCompletionSource.
Your flow:
await MouseUp();
...
private Task MouseUp() {
_tcs = new TaskCompletionSource();
return _tcs.Task;
}
public ... OnMouseUpEvent() {
_tcs?.SetResult(true);
}
Sorry for Pseudo code, will update this once I get something other than a mobile.
OT: Commenters: Think outside of the Box!
I needed a small function that will wait for the mouse's left button to be released.
No you don't. WinForms GUI programming is event driven, asynchronous. You should use the MouseUp event to detect the mouse button's release. This does mean that you need to implement your logic using state based asynchronous techniques, rather than the synchronous model that you crave.
I'm trying to get more familiar with eventhanlders, but my current even only updates once, I want it to update until I close the application.
This is my code:
private static event EventHandler Updater;
Updater += Program_updater;
Updater.Invoke(null, EventArgs.Empty);
Application.Run();
private static void Program_updater(object sender, EventArgs e)
{
KeyUtils.Update();
Framework.Update();
}
But like I said, it will only update once, I want it to update until I close my application. I know I can just do a While(true) but I rather not.
I think you want a Timer here:
Timer aTimer = new System.Timers.Timer(2000);
// Hook up the Elapsed event for the timer.
aTimer.Elapsed += Program_updater;
// Have the timer fire repeated events (true is the default)
aTimer.AutoReset = true;
// Start the timer
aTimer.Enabled = true;
Specify callback:
private void Program_updater(Object source, System.Timers.ElapsedEventArgs e)
{
KeyUtils.Update();
Framework.Update();
}
Now every 2 seconds (or specify any other interval) callback OnTimedEvent will be called.
It is absolutely normal that your event is fired only once because the application starts only once.
What you acctualy need is to set up a timer and do some work on its tick.
Please have a look on example in answer for that question Simple example of the use of System. Timers. Timer in C#
Well it only updates once since you only invoke it once (I don't really get the context where your code runs since you both declare a static variable and invokes it on the same scope which is impossible).
If you want something to occur periodically you should use Timer, or in some cases AutoResetEvent/ManualResetEvent.
EventHandlers should be used only when you work as event driven which mean you want your handler to invoke When something happens
Here an example for [System.Timers.Timer][2] with your handler:
//Invoke every 5 seconds.
Timer timer = new Timer(5000);
//Add your handler to the timer invocation list.
timer.Elapsed += Program_updater;
//Start the timer.
timer.Start();
Also you need Program_update's signature to look like that:
private void Program_updater(object source, ElapsedEventArgs e)
I have a class that contains a queue and a form that contains a listbox.
When the user opens the form it gets all of the accumulated objects from the queue and shows them in the listbox. While the form is open as soon as the queue gets a new item it notifies the form of the new item via a custom event.
After closing the form the data will accumulate again.
My problem is the following: As soon the form is subscribed to the notification event it should dump all of the queue to the form and keep dumping it as long as someone is subscribed to the event. It should not wait until another item is added to the queue.
One solution would be to use a timer to check if there are any subscriptions to the event and then dump it. It is not much but i would be wasting resources with the timer.
It would seem to be better if the form's subscription to the event could itself trigger the event. The app is very modular and modules communicate through events to a eventNexus and then the nexus notifies everyone who needs to know.
Since an event is an object as well, it should be possible to accomplish something like this but I did not manage to find any links.
You can customize the adding and removing of event handlers in the code of the event itself by using the add and remove statements:
private EventHandler onMyEvent;
public event MyEventHandler MyEvent
{
add
{
// run when event handler is added ( += )
onMyEvent = (MyEventHandler)Delegate.Combine(onMyEvent, value);
// Add additional, custom logic here...
}
remove
{
// run when event handler is removed ( -= )
onMyEvent = (MyEventHandler)Delegate.Remove(onMyEvent, value);
}
}
Here you can add your own code to trigger actions upon adding or removing your event handler.
For timer your can try with this code
private static System.Timers.Timer aTimer;
aTimer = new System.Timers.Timer(10000);
// Hook up the Elapsed event for the timer.
aTimer.Elapsed += new ElapsedEventHandler(OnTimedEvent);
// Set the Interval to 2 seconds (2000 milliseconds).
aTimer.Interval = 2000;
aTimer.Enabled = true;
Your function
private static void OnTimedEvent(object source, ElapsedEventArgs e)
{
//Your treatment
}
The following code hides a form for 10 seconds. Nothing too crazy.
Each time the button is pressed, it creates a new timer object that doesn't stop and just keeps going. My intuition tells me that if you end up pressing this button many times, you'll have a bunch of timers that are running when only one is necessary (or is my assumption incorrect?). Also, if I do need to stop and dispose this timer, would I just send it as an argument in RevealForm or have the timer be a class level variable and just stop/reset it each time?
private void ButtonHide_Click(object sender, EventArgs e) {
this.Visible = false;
System.Timers.Timer t = new System.Timers.Timer();
t.Elapsed += new ElapsedEventHandler(RevealForm);
t.Interval = 10000;
t.AutoReset = false;
t.Start();
}
private void RevealForm(object source, ElapsedEventArgs e) {
InvokeReveal();
}
private void InvokeReveal() {
if (InvokeRequired) {
Invoke(new Action(InvokeReveal));
}
else {
this.Visible = true;
}
}
Thanks much!
Create the timer in the class then call t.start() on each click.
No need to destroy/cleanup/etc. Just recycle the one you have.
Your assumption is correct - testing would have asserted such for you.
You could either:
A) Disable the timer after each execution (per-interval) and enable on click, or,
B) Stop and destroy the timer and create a new one with each click.
Either option will require a little refactoring of your existing code.
As for the second part of the question - how you stop the timer is preferential. in such a small application (if this is its function in entirety) then simply stopping the timer within the event handler (or related method) would just do the trick, though in order to access the Timer instance you would declare it at a higher level in scope (i.e not bound within the scope of the click event handler).
Generally, the first thing you do is stop the timer in your event handler.
If you just want one timer then make it a form level variable, start it in your ButtonHide_Click, then at the top of your RevealForm method, stop the timer.