I am wondering in which cases does a widget receive its paint event, and how does it vary with the OS.
Qt documentation for paintEvent says only
A paint event is a request to repaint all or part of a widget. It can happen for one of the following reasons:
repaint() or update() was invoked,
the widget was obscured and has now been uncovered, or
many other reasons.
So far, I've put some traces in the paintEvent,
void Widget::paintEvent(QPaintEvent *e)
{
static int count = 0;
qDebug("paintEvent, %d", count++);
}
and this is what I've found out (on Windows 7 at least):
The paintEvent is called when the widget loses/gains focus. The paint event is not called when another widget passes over our widget. I don't know if that's because of Windows 7 compositing. The paintEvent is also called when a minimized window is restored. The paintEvent is called when resizing.
So is the behaviour dependent on the OS?
Yes, in the sense that you describe, it's dependent upon the operating system.
The Desktop Window Manager (DWM), found in Windows Vista and 7, the doohickey that is responsible for desktop composition, the Aero glass effect, and all kinds of other eye candy, works a bit differently than the model used in previous versions of Windows. As you suspect, it caches the bitmaps for your windows, even when they are not visible because they're obscured by another window. That means it doesn't need you to repaint them (and thus it doesn't raise a paint event) because it can just blit them from the cached bitmap. Not only is this a potential optimization over having each application redraw itself, it also allows the DWM to implement things like Aero Flip, for which it uses its cached bitmap.
The exception to this is as it has always been for, say, the CS_SAVEBITS class style. If the bitmap that the DWM has cached has become invalidated (e.g., because your window image has changed), it will discard it and ask you to redraw the window.
Test this theory by turning off DWM composition (switching to the "Windows Classic" theme), and then obscuring your window to see if you receive a paint event. You should, just like you did in all previous versions of Windows.
But the larger point is that you should not rely on receiving paint events in any particular order. The only thing you should assume about paint events is that you'll receive one when the operating system needs you to repaint your window. Otherwise, it won't bother you. I'm sure that's why the documentation is content with being vague on this point, beyond possible technical constraints.
This is why logic should not go inside of the paint event handler. The only thing that method should be responsible for is repainting the window by its current state. That state needs to saved elsewhere. This rule is also commutative: you should not do any painting outside of the paint event handler.
Of course, you can always force a paint event to be raised by invalidating your window (I'm sure Qt has an invalidate or refresh method for this, check the documentation), but that still doesn't mean it's a good pattern to place application logic in the method that handles this event.
Related
On Windows 7 to be specific, while I don't think it matters.
We have all seen this issue in countless desktop applications, especially games that tend not to use OS-supplied controls: when the screen changes programmatically under a motionless mouse cursor (as opposed to user moving the cursor to a new widget), they go out of sync. Either the cursor does not change or the widget is not painted as it should be with the cursor inside it - obviously the widget's mouse enter event is not triggered. If you shake the mouse a bit without even leaving the widget, the thing fixes itself.
Sadly, Qt 5.7 shares this widespread problem. The first solution to come to mind is to move the mouse programmatically to (0, 0) and back by Windows means. However, it's not cross-platform(ish). Any better ideas?
I don't know if your question is still actual
You can override method underMouse() with following code:
bool MyWidget::underMouse()
{
return rect().contains(mapFromGlobal(QCursor::pos()));
}
Events moveEvent or resizeEvent in the rest panels will be definitely triggered when splitter resizes panel. All you need is check if widget is under mouse and then invoke enterEvent() manually
I found two solutions.
The first one is a simple hack: Call widget->hide() and then immediately widget->show(). This will reevaluate and update the widget's visual state depending on whether it is under mouse cursor or not, even if the cursor has not moved. But I would not recommend this solution because it might have some unwanted side effects. Though I have not encountered any yet.
The second solution is better because it does not look like a hack and it probably does not have any side effects:
widget->setAttribute(Qt::WA_UnderCursor, qApp->widgetAt(QCursor::pos()) == widget);
widget->update();
The code is assumed to be called from widget's parent. But you can adjust it and calle it from any other place. Note: it is better to use QApplication::widgetAt() than widget->rect().contains(), which is suggested in another answers, because in the latter case we would get false positives for widgets which are overlayed by other widgets.
What is actually complicated, is to find the place in code from where you should call this. Because there can be many sources of the widget's motion - moving withing its parent, moving of parent(s), resizing, resizing of parent(s), scrolling etc. This is probably the reason why this would be too complicated to implement this to standard Qt widget library. It would be probably a performance killer in some scenarios. (my guess)
Just to show my usage: I am moving a whole container widget which contains many child widgets. Subsequently one of the child widgets may get under the cursor after the container is moved. So I call:
containerWidget->move(dx, dy); // this moves the container
for (QWidget *child : containerWidget->findChildren<QWidget*>())
{
child->setAttribute(Qt::WA_UnderMouse, qApp->widgetAt(QCursor::pos()) == child);
child->update();
}
I have some problems. Hope anyone can help me.
I have a Qwidget1 and Qwidget2. Qwidget1 have a widget that promote to Qwidget2. Both Qwidget1 and Qwidget2 have paintEvent. I have writed "qDebug()<< "Update"; " in paint event of Qwidget1. When I run project, I see a word "Update" has been printed a lot of times. So why Qwidget1 execute paint event a lot of times. How can I fix it, just execute paint event when show Qwidget1 at the first time and when I call update.
This is expected behavior. Your code works like it should. From Qt documentation:
A paint event is a request to repaint all or part of a widget. It can
happen for one of the following reasons:
repaint() or update() was invoked,
the widget was obscured and has now been uncovered,
or many other reasons.
There can be any number of situations when a window or its part becomes invalidated and has to be repainted. Such situations include, but are not limited to:
window size change (including minimizing / maximizing / restoring the window);
mouse pointer passing over a widget - it may or may not trigger repaint;
other window moving over the window in question.
When it happens, Windows will send the WM_PAINT message to the application. You could check whether or not the number of WM_PAINT messages received matches the number of paintEvent calls, but I doubt Qt adds any significant overhead.
I inherited an MFC app, and it has a window that has several owner-draw widgets that respond to OnPaint and do various drawing.
I noticed that in order to force the controls to redraw in response to various user actions, there was the following code:
CRect rect;
m_myControl.GetWindowRect(&rect);
ScreenToClient(&rect);
InvalidateRect(&rect, FALSE);
I thought this could be simplified like so:
m_myControl.Invalidate(FALSE);
But, in practice, when I do it this way, the control paints sometimes but not others. Specifically, when I'm interacting with controls in the window, sometimes myControl ends up just painting as solid gray. I changed the code back to the more-complicated InvalidateRect style and it's working great again.
Why would there be a difference here?
When you invalidate a window, you don't invalidate the window underneath it. If the parent window is responsible for drawing the control it won't get triggered because you didn't tell it that it needed updating. The original code does the right thing in that case.
I am writing Qt application which plays a fade-in animation whenever the mouse is moved to a certain area in the screen, and a fade out animation whenever the mouse is moved out of that same area.
I've already found a similar question here in stack overflow, however, it does not quite answer my question. (similar question here)
If I install an event filter to the application, will I be able to see ALL the events in the application even if it's outside my widget window?
If not, I am aware of an alternative involving QWidget::grabMouse() inside a reimplementation of leaveEvent(). But if I do so, will I be able to interact with anything OUTSIDE my application?
edit: though i am using the Qt library, my application is only for deployment to Windows.
I'm fairly the certain the answer is no, because events outside of your widgets are handled by the OSs window manager (and propagated onto whatever application is in that space).
However you can get the mouse position anywhere on the screen by calling QCursor::pos(), you could poll at regular intervals to find out where the mouse is.
You could try creating a completely transparent window that stays on top of the area where you want to receive mouse events, with the Qt::WindowStaysOnTopHint, Qt::FramelessWindowHint and Qt::ToolTip flags (the last one might prevent the window from receiving the focus), the Qt::WA_TranslucentBackground attribute and the mouse tracking enabled.
If you are on Windows, you can create a global hook to receive every mouse message (right before it's sent to the window under the mouse pointer). Unfortunately I don't know whether this functionality exists in other OSs.
I have an application with three MDI windows, all of them showing OpenGL content. On XP, everything works fine. But on Vista/Win7 the mdi child windows don't refresh properly.
After startup, all windows show their content properly. But when I change the focus from one mdi window to the next, those two windows are cleared (i.e., they only show white, no content). I have no idea why the windows get cleared, they don't receive any WM_* message when that happens, and of course don't receive a WM_PAINT message either.
When resizing those windows, I correctly get the WM_PAINT message (after WM_SIZE) and redraw the content, but then the window gets cleared too, which results in a strange flicker while resizing. After resizing stopped, the window stays cleared (white) until I manually force a refresh.
This happens independently of Aero enabled or disabled.
Any idea why this happens?
I'm surprised it works on XP. In my (limited) experience dabbling with OpenGL, WM_PAINT is not always the best place to redraw OpenGL scenes. Most likely the content is getting wiped out at the driver level. You can check for this by seeing what happens when one of your MDI windows happens to span two monitors connected to two different video cards.
Try the following:
Reinitialize your OpenGL contexts after WM_SIZE occurs.
Draw on-demand instead of in WM_PAINT. In your handler for WM_PAINT, do nothing. Use a timer or some other mechanism to periodically trigger updates of your displays.
Flicker is usually caused by interference via WM_ERASEBKGND. If you haven't already, intercept WM_ERASEBKGND and do nothing in the regions where you are displaying OpenGL content.
Use the CS_OWNDC window style on any windows hosting OpenGL content so that the HDC doesn't change per-message/per-call during the lifetimes of your MDI windows.
Other rarer causes of interference that might apply (since you are using MDI windows)
WM_NCPAINT and other related Non-Client drawing messages- you can workaround these by moving your OpenGL content to a child window with no border, inside the MDI windows.
Incorrect/Incompatible default features for OpenGL on your video card that explicitly require overlays or implicitly use them (frequent cause of issues in overlapping contexts). Unfortunately, diagnosing this out of my realm of knowledge, but some testing may shed some additional light here.