As you can see in my previous question, I am currently struggling with single and double click events – I wanted to perform an action after a click and a completely different action after a double click. The problem is that the low-level events only tell me things like this was a mouse down or this was a mouse up and the total click count is == 2. It’s hard to discriminate between a single and a double click this way, since when the mouse comes up after a single click, I have to wait a while to make sure there’s not a second click coming.
Now I am wondering how different GUI toolkits handle this, as there’s no magical way around. You can’t have separate high-level single-click and double-click events without having the single-click event delayed or something, can you?
(It looks like the usual way to solve this is to have the single and double click actions related, like selection on the first click and app launch on the second. That way it does not matter that the single-click action is triggered along with the double-click one. Am I right?)
You always need some sort of timer to distinguish between single and double click. Think about it: you cannot predict whether a second click will happen once the first occurred, you just have to wait and see.
On iOS, you can have gesture recognizers. And if you only register a single tap recognizer it fires right away, but as soon you also register a double tap recognizer you notice that a single tap doesn't fire right away but only after a noticeable delay. Made me change to a a single tap with two fingers in one app because the delay is really annoying.
You could work around the delay if your single click action is undoable or cancelable: do the action when click count == 1, and if click count == 2, revert the action of the single click. Might result in undesired/unexpected UI behavior, though (from a user perspective).
Related
I originally had code that set the focus to the first widget in a dialog, in the onInit method. But there were problems with it: if I pressed TAB, indeed focus moved to next control (wxTextCtrl), which got the blue 'focus' color, but the 'focus' color/highlight was not removed from previous focus widget. So now it looked like both first and second control had focus at the same time...
When cycling manually (by pressing TAB) full circle (till last control and then wrap around to the first), suddenly all worked well. That is, when moving focus from first control to next one, the first visually lost focus (blue color was removed) as it should. From now on, only one item had the focus color/highlight.
So instead of setting focus on the first control, I tried a different approach: I set the focus to the last control in the dialog, which is always the OK button. Next, I want to emulate programmatically that a TAB is pressed and received by the dialog. So I wrote this (inside Dialog::onInit):
m_buttonOK->SetFocus();
wxKeyEvent key;
key.SetEventObject(this);
key.SetEventType(wxEVT_CHAR);
key.m_keyCode=WXK_TAB;
ProcessWindowEvent(key);
Now the focus indeed moves away from the OK button, but it does not wrap around to the first control.
Only when I manually press TAB after the dialog opened, the first item gets focus.
Question: why does this wrapping around to set focus on first widget not work with the code shown above?
First of all, your initial problem is almost certainly related to not calling event.Skip() in one of your event handlers, see the note in wxFocusEvent documentation.
Second, you can't send wx events to the native windows, they don't know anything about it. In this particular case you can use wxWindow::Navigate() to do what you want, but generally speaking what you're doing simple can't, and won't, work reliably.
I'm writing an application using wxHaskell and I want to be able to detect the state of a button (whether or not it is pressed at any given time). I'm having a bit of trouble figuring out how to do this, however. First I thought that there might be a "button is pressed" attribute that I could use, but there didn't seem to be. Then I had the idea of maintaining an IORef which I update on button-up and button-down events. However, that would require that the Button object actually have button-up and button-down events, which is does not appear to. It is an instance of Commanding, but I assume that the command event is fired on button-up only, which isn't enough for that idea. Does anyone have any other suggestions?
Workaround
You can implement this yourself by detecting the low-level actions that trigger those events (eg. mouse button down, space bar down).
In WX you can use the following function and constructor:
mouse :: Reactive w => Event w (EventMouse -> IO ())
data EventMouse = ... | MouseLeftDown !Point !Modifiers
And, as you suggest, you could keep the state yourself in an IORef. My suspicion is that left button here means main button (right for left-handed users).
UI design principles
The second question, which you haven't asked by I'll answer, is whether this is good UI design.
The behaviour of a button (assuming interaction using a mouse) is that click events are reported when the user releases the mouse button in the button area after pressing the mouse button down in the same area. If the user moves away and releases, or presses 'Escape', there is no click.
Taking any action on a button being pressed (not clicked) would feel unnatural for users.
In practice, the only acceptable way to use this would be, imho, to take an action whose effects can only be witnessed after releasing and which is immediately undone if the click is cancelled (ie. mouse button released outside button area).
EDIT: Please, also, take into account that users with accessibility requirements may have OS settings enabled that affect how and when button clicks are reported (but not down/up mouse events).
There is no way to know if a wxButton is pressed or not because it is an abstraction of a push button which intentionally hides this implementation detail. If you need to know the button state, use a wxToggleButton instead.
is there a way to disable multiclicks? By multiclicks I mean 3-or more clicks. I would like to have only singleClick or doubleClick. The third click should be always registered as singleClick (clicks=1). event.mouse.clicks should always be <= 2.
Another problem is the interval between clicks. If I click multiple times on the same spot between 1-2 seconds, it is always registered as a multiClick (event.mouse.clicks keeps rising). Only if I move the cursor a bit, clicks would lover to 1. How can I change this behavior so the interval between click and doubleClick will be around 1/4 second?
Solution is to have a separate thread which reads the SDL information, then do a debounce algorithm to remove the unwanted clicks. I did this for a touch screen, which is too sensitive. After filtering, you should get what you want. Then the filtered events you put in a queue (std::deque) which can be used for the real user interface to get events.
The open source project https://sourceforge.net/p/sdl2ui/wiki/Home/ has a class CdialogEvent which is may just what you need.
I need to implement a behavior:
when element clicked - one thing happens
but when it's clicked and held for more than one second, something else happens (e.g element becomes draggable) and then the first event never fires
I think I know how to catch click&hold type of events, but how to distinguish between first and second?
Can you show me how to do that using this jsbin. I already made the "click, hold & drag" part, except that it is still firing the 'click' event after dragging the element and it shouldn't.
again: element clicked - one event, click and hold - element is draggable (even after mouse up) and when clicked again it's back to normal (undraggable) state.
I am not looking for a trivial solution, it has to be built using Rx.Observable or at least Bacon's streamEvent object
Thank you
I think you were pretty close with your solution, but probably it is not possible to elegantly achieve what you want while using the browser's built-in click event.
HERE is my attempt to tackle your problem.
The main idea is to define your own click streams like so:
var clicks = downs.flatMapLatest(function(){
return ups.takeUntil(Rx.Observable.timer(250));
});
var longDownsStart = downs.flatMapLatest(function(){
return Rx.Observable.timer(1000).takeUntil(ups);
});
In case of clicks we wait max 250 ms after a mouse down for a mouse-up; in case of the latter we generate the event only if there was no mouse-up within 1000 ms.
There might be some corner cases in which the code does not work as intended.
Here is my proposed solution (with Bacon.js).
In MFC a double-mouse click event triggers the following sequence of messages
WM_LBUTTONDOWN
WM_LBUTTONUP
WM_LBUTTONDBCLK
WM_LBUTTONUP
So responding to the WM_LBUTTONDBCLK message allows you to detect a double-click. But if I just want to detect a single-click how to I distinguish it?
But just looking at the WM_LBUTTONUP message isn't enough as it could be a single-click or it could be the first click of a double-click.
How can I successfully identify just a single-click?
(Please allow me to call these events Mouse Up and Mouse Down. My MFC is a little rusty. And there's this stuff called .NET who's been messing up my terminology lately ;-)
Short story: You don't simply want to know about Mouse Click. You need more.
Long story:
Although this is counter-intuitive, it appears that simply wanting a mouse-click is fairly uncommon. Most often, you'll want to perform some processing on Mouse Down and do some further processing on Mouse Up. The trick is that simply tracking Mouse Up messages is not enough: Mouse Down may not have happened in your window. Do you consider it a valid click then? Especially considering that the Mouse Down processing (such as selecting an item) did not occur.
Going further up the reasoning, you should not rely on receiving a Mouse Up after you processed Mouse Down: User may have moved the mouse and released the button somewhere else (think drag'n'drop), in which case, you don't receive the MouseUp event... unless you capture the mouse on MouseDown to make sure you get mouse event up to Mouse Up even if the mouse left your window.
All in all, you end up tracking Mouse Down, capture the mouse and when you receive Mouse Up, just check if you own the capture. If not, the mouse was either double-clicked (no 2nd mouse down) or Mouse Down happened somewhere else hence you most likely don't care about this Mouse Up.
In conclusion: There's no MouseClick message simply because you wouldn't go very far with it: You need to handle more messages and implement more mechanics anyway.
Oh! And if your dealing with an existing control which already handles all this items and selection stuff, such as a listview, chances are it provides with a similar custom notification such as Item Activate or Item Selection Changed.
I just tried this in Delphi, the behavior is the same: even when a double click is happening, a single click event is issued right after the first one of the two.
I solved it using a timer, which works like this:
deactivate timer on WM_LBUTTONDBLCLK (and set bDbl to true)
activate timer on WM_LBUTTONUP if bDbl==false
deactivate on WM_LBUTTONUP if bDbl==true (and reset bDbl)
I set the interval of the timer to the time returned by GetDoubleClickTime.
MSDN says:
The GetDoubleClickTime function
retrieves the current double-click
time for the mouse. A double-click is
a series of two clicks of the mouse
button, the second occurring within a
specified time after the first. The
double-click time is the maximum
number of milliseconds that may occur
between the first and second click of
a double-click.
If the timer happens to fire then you have the real click. In my case the double click interval is 500ms, so any "real click" will be delayed this long.
You could check WM_LBUTTONDOWN has not been called more than once before WM_LBUTTONUP. In practice Windows does this for you, in that if you get a WM_LBUTTONDBCLK you tend not to get a WM_LBUTTONUP.
You can use PreTranslateMessage() to count the messages as they appear. If you've received only the mouse messages corresponding to a single-click, and the system-configured time for double-clicking has expired, you can safely assume it's a single-click.
As far as I know there is no way to know that this is the case as it is happening, which makes sense -- until the time is expired, there's no way to know that a second click is or isn't coming.
that's a little tricky.
I would detect the WM_LBUTTONDOWN & WM_LBUTTONUP combo, store that event somewhere and set a timeout for a second or so. If there isn't a WM_LBUTTONDBCLK during that timeout then you have a single click.
This might imply you need to have another thread running but I think you could accomplish it with one thread.
I think the solution is to start a timer after the first click & then check the elapsed time after at the next immediate click, this will tell you if it is a single click or double click.
You typically look at #MLButtonUp and you would not have single click and double click behavior on the same mouse button.