In WPF, I want to be able to use the mouse events with reactive extensions to create an observable for a UIElement that works like a Click event. There's plenty of examples of using this to create drag/drop behavior, but I can't find anything for just a simple click.
I'm anticipating it'll involve observables on MouseLeftButtonDown, MouseLeftButtonUp, MouseLeave, and MouseEnter. But I'm unsure what combination of Merge, SelectMany, TakeUntil, or TakeWhile I need to use. In trying to wrap it all up in an extension, here is what I have so far:
public static IDisposable GetClick(this UIElement item, Action clickAction)
{
var obs1 = Observable.FromEventPattern<MouseButtonEventHandler, MouseButtonEventArgs>(
h => (s, e) => h(s, e),
h => item.MouseLeftButtonDown += h,
h => item.MouseLeftButtonDown -= h);
var obs2 = Observable.FromEventPattern<MouseButtonEventHandler, MouseButtonEventArgs>(
h => (s, e) => h(s, e),
h => item.MouseLeftButtonUp += h,
h => item.MouseLeftButtonUp -= h);
var obs3 = Observable.FromEventPattern<MouseEventHandler, MouseEventArgs>(
h => (s, e) => h(s, e),
h => item.MouseLeave += h,
h => item.MouseLeave -= h);
var obs4 = Observable.FromEventPattern<MouseEventHandler, MouseEventArgs>(
h => (s, e) => h(s, e),
h => item.MouseEnter += h,
h => item.MouseEnter -= h);
var finalObs = ???
return finalObs.Subscribe(x => clickAction.Invoke());
}
The following seems to work, but I suspect that it's possible to do it in a neater way.
var click = mouseEnter
.SelectMany(_ => mouseDown.TakeUntil(mouseLeave))
.SelectMany(_ => mouseUp.TakeUntil(mouseLeave).Take(1));
I've renamed finalObs to click, obs1 to mouseDown, obs2 to mouseUp...
EDIT: Added Take(1) to fix the flaw pointed out by Enigmativity
EDIT(2):
Here is another solution that I like more.
You'll need to add a .Select(_ => "U") to the definition of mouseUp, .Select(_ => "D") to mouseDown...
var click = Observable.Merge(mouseDown, mouseUp, mouseLeave, mouseEnter)
.Scan((s, c) => c == "L" ? "" : s + c) // Create a string of the events, reset on mouseLeave
.Where(s => s.Length >= 2 && s.Substring(s.Length - 2) == "DU");
After thinking about it, it's impossible to get exactly correct behavior in the case where the user mouse downs over the item, then moves outside of the item, then moves back and mouse ups. This is because you don't get mouse ups when not over the item, so you can't be sure they didn't mouse up, then mouse down while outside.
The correct way is to use this.CaptureMouse and this.ReleaseMouseCapture which solves some of the problems in the accepted answer to do with detecting the mouse leaving and returning. A full (untested) solution using ReactiveUI to bind the events is.
// Create a factory for capturing the mouse and and releasing it as an
// IDisposable compatible with Observable.Using
Func<IDisposable> captureDisposable = () => {
this.CaptureMouse();
return Disposable.Create(()=>this.ReleaseMouseCapture());
};
// Capture the mouse and then release it on mouse up
var up = Observable.Using
( captureDisposable
, capture => this.Events().PreviewMouseUp.Take(1)
);
// Create the click event
var click = this.Events().PreviewMouseDown.Select(e=>up).Switch();
Related
I have WPF program which listens what income data from Pushbullet through websocket (onMessage event)
The process of data is done in a background worker (in DoWork event) in order to not freeze the UI.
But might happen the data are more than one and seems that results are broken.
WS.onMessage += (s, ev) => {
var wk = new BackgroundWorker();
wk.DoWork += (se, evt) => {
Process1();
Dispatcher.Invoke(()=>{ print to ui });
}
wk.RunWorkerAsync();
}
How to process item one by one when onMessage is triggered? I mean I want to process an item and the process another after first is done even onMessage is already triggered few times, depends how many data comes ...
I'd suggest looking at Microsoft's Reactive Framework (NuGet "System.Reactive"). Then you can do this:
IDisposable subscription =
Observable
.FromEventPattern<EventHandler, EventArgs>(
h => WS.onMessage += h, h => WS.onMessage -= h)
.SelectMany(ep => Observable.Start(() => Process1()))
.ObserveOnDispatcher()
.Subscribe(x =>
{
/* Print to UI */
});
It'll process on a background thread and then marshall back to the dispatcher. It'll produce zero or more values in the .Subscribe method and will only produce one value at a time.
If you want to close down the observable just call .Dispose() on the subscription.
You might need to adjust the EventHandler & EventArgs types to suit your source event.
I'm struggling with converting the following event to an IObservable:
public delegate void _dispSolutionEvents_OpenedEventHandler();
event _dispSolutionEvents_OpenedEventHandler Opened;
The event comes from a library so I can't change it.
The overload of IObservable.FromEvent that should do it has the following signature:
public static IObservable<Unit> FromEvent
( Action<Action> addHandler
, Action<Action> removeHandler
)
So I tried converting the event like this:
var opened = Observable.FromEvent
( h => _SolutionEvents.Opened += h
, h => _SolutionEvents.Opened -= h
);
But the compiler doesn't like _SolutionEvents.Opened += h and _SolutionEvents.Opened += h because
Cannot implicitly convert type 'System.Action' to 'EnvDTE._dispSolutionEvents_OpenedEventHandler'.
I don't think that I can just say_SolutionEvents.Opened += new _dispSolutionEvents_OpenedEventHandler(h) because then removal won't work because I have a different instance, right?
There is another overload of Observable.FromEvent with the following signature:
public static IObservable<TEventArgs> FromEvent<TDelegate, TEventArgs>
( Func<Action<TEventArgs>, TDelegate> conversion
, Action<TDelegate> addHandler
, Action<TDelegate> removeHandler
)
This one allows to convert the action to an event handler, but it seems to only work with TEventArgs.
Is Rx missing an appropriate overload or am I missing something?
This turns out that it is very easy to use the FromEvent pattern.
Just do this:
var opened = Observable.FromEvent<_dispSolutionEvents_OpenedEventHandler, Unit>(
h => () => h(Unit.Default),
h => _SolutionEvents.Opened += h,
h => _SolutionEvents.Opened -= h);
I've tested the observable with this code:
void Main()
{
var _SolutionEvents = new Foo();
var opened = Observable.FromEvent<_dispSolutionEvents_OpenedEventHandler, Unit>(h => () => h(Unit.Default), h => _SolutionEvents.Opened += h, h => _SolutionEvents.Opened -= h);
opened.Subscribe(x => Console.WriteLine("Opened"));
_SolutionEvents.OnOpened();
}
public delegate void _dispSolutionEvents_OpenedEventHandler();
public class Foo
{
public event _dispSolutionEvents_OpenedEventHandler Opened;
public void OnOpened()
{
this.Opened?.Invoke();
}
}
It produces the following expected output:
Opened
It's worth noting that there is no IObservable interface, but only an IObservable<T> so you must return something. The trick here is to convert delegate void _dispSolutionEvents_OpenedEventHandler() into an IObservable<Unit> to make it work and that's what the h => () => h(Unit.Default) does.
You are running into a a type issue here. The _dispSolutionEvents_OpenedEventHandler type is not Action. It looks like the Action type, but it is not the Action type.
IMO this event does not conform to the .NET standards for events. Generally the delegate would match the pattern of taking a sender object parameter and an EventArg subclass of for the second parameter.
ie.
public delegate void _dispSolutionEvents_OpenedEventHandler(object sender, EventArgs e);
If you try to just attach an Action to the event you will find that fails too.
Action onOpened = ()=>Console.WriteLine("Opened");
_SolutionEvents.Opened += onOpened; //CS0029 Cannot implicitly convert type 'System.Action' to '_dispSolutionEvents_OpenedEventHandler'
The compiler is smart enough to do some type inference if you do this;
_SolutionEvents.Opened+= () => Console.WriteLine("Opened");
but when you are using Rx, you are already typed into the Action type, so are effectively back at the previous issue above.
If the library owner was nice, the event would follow the normal sender/eventArgs pattern. Failing that, they would at least specify the delegate as just an Action instead of their own customer parameterless, void method. :-/
So, as the event you have doesn't meet the standard .NET patterns, you will need to provide Rx some more hand-holding (blame your library provider not Rx).
You could fight the FromEvent/FromEventPattern methods, but as your library is not in the spirit of an Event, I would suggest just going with the simple use of Observable.Create which at least keeps the code obvious what is happening and should allow the next user to better understand it.
Observable.Create<Unit>(obs =>
{
_dispSolutionEvents_OpenedEventHandler handler = () => obs.OnNext(Unit.Default);
_SolutionEvents.Opened += handler;
return System.Reactive.Disposables.Disposable.Create(() => _SolutionEvents.Opened -= handler);
});
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 write an Add-In for Outlook, and one of the events I'm using is ItemsEvents_Event.ItemChange - and its handler's signature takes an Object as a parameter (the item that changed):
items.ItemChange += CalendarItems_ItemChange;
private void CalendarItems_ItemChange(object anItem) {...}
How would I use Observable.FromEvent or Observable.FromEventPattern to create an observable sequence from this event "stream" instead of attaching/detaching the event as usual?
You need to use the FromEvent conversion overload to tell Rx how it should interpret your event:
IObservable<TEventArgs> FromEvent<TDelegate, TEventArgs>(Func<Action<TEventArgs>, TDelegate> conversion,
Action<TDelegate> addHandler,
Action<TDelegate> removeHandler);
In your case it would look like:
var source = Observable.FromEvent<ItemsEvents_ItemEventChangeHandler, object>(
emit => new ItemsEvents_ItemEventChangeHandler((obj) => emit(obj)),
h => items.ItemChange += h,
h => items.ItemChange -= h);
Similar answer
How do I add (compose) more actions, e.g, updateIndicators, instead of the single action so that information flows without side effects?
quote =>
{
this.changeQuote(quote.S, quote.B, quote.A);
} // Add action here, e.g., UpdateIndicators()
var qu = Observable.FromEvent<ApiQuoteHandler, QuoteUpdate>(
emit => (_, s, b, a) => emit(new QuoteUpdate(s, b, a)),
handler => apiClient.QuoteUpdated += handler,
handler => apiClient.QuoteUpdated -= handler)
.Where(quote => (SymbolStrs.Contains(quote.S)))
.SubscribeOn(Scheduler.Default)
.Subscribe
(
quote =>
{
this.changeQuote(quote.S, quote.B, quote.A);
// I could put updateIndicators in here, but it doesn't feel Rx composable like?
}
);
public void changeQuote(string symbol, double bid, double ask)
{
}
public void updateIndicators(string symbol, double bid, double ask)
{
}
// more actions here
Well first obviously both of your actions will be nothing but sideeffects.
So the just either call subscribe 2 times:
var quoteUpdate =
Observable.FromEvent<ApiQuoteHandler, QuoteUpdate>(
emit => (_, s, b, a) => emit(new QuoteUpdate(s, b, a)),
handler => apiClient.QuoteUpdated += handler,
handler => apiClient.QuoteUpdated -= handler)
.Where(quote => (SymbolStrs.Contains(quote.S)));
var subscription1 =
quoteUpdate
.SubscribeOn(Scheduler.Default)
.Subscribe (quote => this.changeQuote(quote.S, quote.B, quote.A));
var subscription2 =
quoteUpdate
.SubscribeOn(Scheduler.Default)
.Subscribe (quote => this.updateIndicators(quote.S, quote.B, quote.A));
or subscribe to one Action that will just call one after the other (as you already guessed - don't see what's wrong with it):
public void DoBoth(string symbol, double bid, double ask)
{
changeQuote(symbol,bid,ask);
updateIndicators(symbol,bid,ask);
}
// ...
var subscription =
quoteUpdate
.SubscribeOn(Scheduler.Default)
.Subscribe (quote => this.DoBoth(quote.S, quote.B, quote.A));
remark:
right now you are only using Where and SubscribeOn from RX but you have quite a few lines of overhead. If you don't want to do more I would suggest just handling the event itself with a simple if instead of the .Where (of course dispatching to the UI Thread if you really have to) - it's way easier and you don't need the external dependency to RX then