I am trying to set up an add-tab button in a Gtk::Notebook (gtkmm). I am doing this by drawing a pixmap to a calculated position -- that works just fine. However, when trying to receive events for it, I cannot pick up a single left click. Single middle and single right give both press and release events, and double left gives just a press event, but single left doesn't register anything. How can I properly receive events?
Did you draw your image in a GtkEventBox?
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 using WM_MOUSEMOVE to get changes in mouse position. When simulating "knobs" for example it's desired to let the user go up/down with mouse without any limits. In this cases I hide cursor and use SetCursorPos to change its position every time user moves with it and detect just the difference from the original position.
Unfortunately it doesn't seem to work - if I set the mouse position, it sometimes works, but sometimes is one or more pixels away, which is just wrong. And even bigger trouble is that after the call another WM_MOUSEMOVE seems to be delivered, which unfortunately does the same thing as it wants to move the cursor back to the original position again. So it ends up in an infinite cycle or settings mouse position and receiving messages until the user releases the mouse button.
What's the correct approach or what's the problem?
The raw input system can do this - it lets you register for raw mouse input that isn't clipped or confined to the screen boundaries.
Broadly speaking, you register for raw input using RegisterRawInputDevices(). Your window will then receive WM_INPUT messages, which you process using the GetRawInputData() function.
See Using Raw Input for an example.
I hide cursor and use SetCursorPos to change its position every time user moves with it and detect just the difference from the original position.
This is just plain wrong. Instead, use SetCapture() to capture the mouse. All movements will be reported as WM_MOUSEMOVE messages with coordinates that are relative to the specified window, even if the mouse is outside of that window, until you release the capture.
Asking the user to move the mouse continuously, even after the cursor hit the screen limit is a very bad idea in terms of User Interface, IMHO.
Some games have another approach: when the mouse hit the "limit", the game enter a special mode: things appears to function exactly as if the mouse was moving, even if the user don't move it. When the user wants to exit that mode, he just has to move the mouse of the limit.
Doing so requires a timer, armed when the mouse hit some limit, executing code periodically as if the mouse was moving. The timer is stopped when a real mouse movement makes it leaves the limit.
Ok folks, so I found a solution simple enough:
The main problem is that SetCursorPos may not set the coordinates accurately, I guess it's because of some high resolution processing, nevertheless it's probably a bug. Anyway if SetCursorPos doesn't set the coordinates correctly (but +-1 in x and/or y) it also sends WM_MOUSEMOVE to the target window. As a result the window performs the exact same operation as before and this goes on and on.
So the solution is to remove all WM_MOUSEMOVE messages right after SetCursorPos:
MSG msg;
while (::PeekMessage(&msg, NULL, WM_MOUSEMOVE, WM_MOUSEMOVE, PM_REMOVE)) { };
Then retrieving the current mouse cursor pos using ::GetCursorPos .
It's ugly but seems to fix the problem. It basically seems that in some position of the mouse, the system always adds or subtracts 1 in either coordinate, so this way you let system do the weird stuff and use the new coordinates without trying to persuade system that your coordinates are the correct ones :).
How can I attach a "clicked" event to a label? I tried GtkEventBox but had no luck with it.
Connect to the button-press-event signal on the EventBox.
Gtk# differentiates between Widgets and 'Containers'. Most widgets placed on a Gtk# form will NOT receive mouse click events. In order to receive a mouse event you need to place the widget inside a specific container - like EventBox:
Add an EventBox containter to your form. You can place it behind other Widgets or since it is not visible, unless you specifically select it to be (or change its background color).
Put your label widget inside this EventBox. Notice that the label will get the shape and size of the EventBox.
Add to this EventBox the signal 'ButtonPressEvent' out of the "Common Widget Signals".
If you need to identify the button that was clicked while handling this event, use the uint value in: args.Event.Button typically '1' will be the left mouse button, '2' the center button and '3' the right button ('2' might be also when both left and right buttons are clicked).
It seems you don't need EventBox at all: Since the problem is that labels don't have any associated X11 window, just give it one with set_has_window(True)! The following works for me:
self.label = Gtk.Label()
self.label.set_has_window(True)
self.label.set_events(Gdk.EventMask.BUTTON_PRESS_MASK)
self.label.connect("button-press-event", self.click_label)
The documentation says it should only be used for implementing widgets - but hey, it works. Who cares about "should"?
2019-04-17 Update:
ptomato here is right, GtkLabel is one of the exceptions that indeed requires an eventbox, so you should connect to the button-press-event signal of the eventbox. For other widgets, the set/add events APIs in my original answer should be still relevant.
Original (wrong) answer:
Connect to the button-press-event signal, but directly on the GtkLabel. I'd say you don't need an eventbox here, as GtkLabel already inherits this signal from GtkWidget. To enable the GtkLabel to receive those events, you need first to call gtk_widget_set_events or gtk_widget_add_events, and add the sensitivity to the GDK_BUTTON_PRESS_MASK event.
Let me explain what we are doing:
We have designed a frame of ir sensors/receivers that can be put around a screen, and basically it converts the screen into a touch-less screen. We are able to move the mouse cursor around when the user moves his finger inside the frame and we can also generate a right click, simply by seeing that if the user is holding his/her finger over the same coords for x amount of time then generate a right click.
The problem is double click. With a regular mouse a user simply double clicks the button and done. Any thoughts on how this can be achieved?
Thank you.
You would need to figure out if the finger is within the screen at all; once you've got a mechanism that tells you that - you can check for this sequence:
Time 0: (finger off screen)
Time 1: (finger touches P1)
Time 2: (finger off screen)
Time 3: (finger touches P2)
where P1 and P2 are close (up to a preset tolerance level) and Time 3 and Time 1 are close (up to a preset double-click sensitivity level).
A solution would be to use different timers for Right click and double click. When you place the finger over a clickable element, a circular progress bar appears at the position of the finger, when the progress reachs the end (the circle is complete) and the user moves the finger away a right click is detected. If the user doesn't move away the finger, a different progress bar appears, if that progress bar reachs 100%, then you have a double click.
In my opinion, a much better solution would be to NOT need a double click, or even a right click, check the XBox Kinect menu navigation interface for a good example.
Hope it helps.
Regards.
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.