> On Thu, 20 Nov 2008 08:32:53 -0800 Robin Dunn <ro...@alldunn.com> > wrote:
> RD> Maybe another way to look at it would help people understand > what Andrew > RD> is asking about. If you turn it around and look at it from a > different > RD> perspective you can say that on Windows when Refresh(false) is > called it > RD> causes the current content of the window to be preserved and that > RD> whatever is drawn in the next EVT_PAINT handler will be drawn on > top of > RD> that current content. On Mac and GTK the current content is not > RD> preserved, and Andrew is wondering how to make it behave that way.
> I finally understood this after the last Andrew's post but thanks for > repeating it clearly. And while I was wrong in my original > assessment of > the problem, I still think the double buffering is to blame here > because it > seems that it isn't done correctly for some reason, i.e. the stuff > already > drawn on the window gets erased instead of being preserved. I have > no idea > why does this happen though.
There isn't anything wrong with the double-buffering - it's simply that on Mac and likely GTK there is no way to retain the drawing done during previous paints except to NOT invalidate the area you don't want repainted. (Which I'm almost certain will give correct results on Win, too, BTW.) Once you invalidate an area by calling Refresh on it, it is redrawn from scratch on Mac/GTK regardless of what eraseBackground says.
The reason things are designed this way is probably due to compositing - when any pixel drawn on screen could be a composition of 2 or more windows, a real mess could be caused if you draw on an area that wasn't invalidated first. For example, the area of the screen you're drawing could have a semi-transparent window floating above it, which you would happily be drawing over with your (opaque) drawing calls, and since that screen area wasn't invalidated, the window manager doesn't realize it needs to tell the semi-transparent window to redraw itself and composite the results of both drawings into a final result. So, rather than risk all sorts of weird messes like this, Apple just decided to not make this sort of thing possible, and IMHO I'm glad they did. :-)
In short, on Mac at least, you HAVE to let the window manager know that you want to repaint any area of the screen before you draw there, so that it can check if it needs to tell other windows above or below to repaint as well and then merge the results.
The Windows WM was written in simpler times and avoids this problem by not completely supporting compositing (with Win32 at least, AFAIK, you have to do special hacks to get true compositing), but on Mac/GTK programmers can't take the easy way out. ;-) (Of course, on the flip side, they can do lots of cool stuff without hacks too.)
> RD> As a side note, I don't think anybody has mentioned yet that the > RD> eraseBackground parameter to wxWindow::Refresh is totally > ignored on Mac > RD> and GTK.
> Yes, Andrew did write this. But this doesn't make it less of a > problem...
AFAICT, there's not much we can do about it, this is just how the OS works. However, as long as you use Refresh efficiently, the same code should work fine on Win, Mac and Linux. It's just that the usefulness of EVT_ERASE_BACKGROUND is limited to Windows, and unfortunately people who use it will just run into troubles when they port to GTK/ Mac because of the reasons mentioned above.
To be honest, I wonder if we ought not either put some serious warnings about this or even outright deprecate it. It's only really of use on Windows, and while I know some people stick to Windows, unexpectedly having to refactor your whole drawing model when you decide to port to any other platform would I think be an unpleasant surprise, to say the least. And once you get used to calling Refresh with the area you want repainted, it's really not that much more code. It's more just moving code from your paint method into a CalculateDirtyRegion type of function.
Kevin Ollivier wrote: > There isn't anything wrong with the double-buffering ...
Agreed -- double-buffering has nothing to do with this issue.
> - it's simply > that on Mac and likely GTK there is no way to retain the drawing done > during previous paints except to NOT invalidate the area you don't > want repainted. (Which I'm almost certain will give correct results on > Win, too, BTW.) Once you invalidate an area by calling Refresh on it, > it is redrawn from scratch on Mac/GTK regardless of what > eraseBackground says.
This is simply not correct, as my changes to the minimal app show. By replacing the frame's default erase and paint event handlers with handlers that draw nothing, you *can* retain the drawing done by previous paints. Run the app and select the About item and you'll see 100 random overlapping squares appear in a child window as a result of this code in MyFrame::OnAbout():
for (int i=0; i<100; i++) { mywin->Refresh(false); // invalidate ALL of child window mywin->Update(); // draw ONE square at random location }
If your paragraph above was correct then we'd only see one square at the end of this loop, but we see 100. So it *is* possible to retain drawing done by previous paints, but wxMac makes it more difficult than it should be.
> AFAICT, there's not much we can do about it, this is just how the OS > works. ...
It's not the Mac OS that is painting the background, it's a bit of wxMac code. Theoretically, it should be possible to set an internal flag if Refresh(false) is called and then use that flag in the next Update() call to avoid painting the background.
I've no idea how difficult that would be to implement -- my tests show there are some wxMac controls (eg. wxStatusBar) that assume the background has already been painted, but it should be possible to add a dc.Clear() call in the paint event handler for those controls.
On Nov 18, 2008, at 7:14 PM, Andrew Trevorrow wrote:
> Kevin Ollivier wrote:
>>> That's not what my tests show -- wxMac and wxGTK *do* erase the >>> background. ...
>> No, that's incorrect. They do not erase the background, instead, they >> repaint the entire control, which is exactly what you tell them to do >> when you call Refresh() rather than using RefreshRect().
> I'm not calling Refresh(), I'm calling Refresh(false). According to > the wx docs, passing false for the eraseBackground parameter should > prevent the background being erased, but wxMac/wxGTK seem to ignore > that parameter. That's either a bug, or else the docs should mention > that fact so other people don't waste their time.
Looking closer at it, on Mac, there is a bug in certain cases (i.e. when you don't have an empty EVT_ERASE_BACKGROUND handler and don't use a transparent background style), but it's not what's causing your problem. I'll agree though that the docs could probably be more clear about the fact that wxMac and wxGTK synthesize the EVT_ERASE_BACKGROUND event and that Refresh(false) doesn't always work as expected.
> I've done some testing using a modified version of the minimal sample > code and I've found a way to prevent the background being erased > (only tested with wxMac but I suspect it will also work with wxGTK) > so please download this and try it out:
> This version creates a child window near the top left corner of > the main frame. If you select About (or hit F1) then it draws > 100 green squares at random locations within the child window, > without the background ever being erased. To achieve this I had > to create OnErase and OnPaint handlers for the *main frame* > (these handlers essentially do nothing).
> Next, disable the handlers by commenting out these 2 lines
> and rebuild the app. Now you'll see only ONE green square appear > each time the OnChildPaint handler is called. This is because > the default handlers for the main frame are erasing the *entire* > window background (ie. painting it with horizontal gray stripes), > including the child window's background.
I think what you're missing is that on Mac at least, controls are transparent by default. So, if you invalidate your entire control's area (i.e. call Refresh) and then don't paint part of your control, that doesn't mean that no painting occurs in that area. Instead, because the control is transparent, the areas that the control doesn't paint itself will be painted by the parent control (in this example, the wxFrame). This is why to completely stop "background" painting, you need to add an empty EVT_PAINT handler to your wxFrame - in essence, you need to completely turn off all parent control drawing. However, this is not a solution, just a workaround that could cause weird graphical glitches as you noticed.
Bottom line - don't invalidate any areas of the control where you don't want any painting to occur. This is a simple and straightforward way of avoiding unnecessary painting. That is what RefreshRect and GetUpdateRegion are there for.
> I hope this makes the problem clear. Unfortunately, even though > creating OnErase and OnPaint handlers for the main frame avoids > unwanted erasing, it seems that some wxMac drawing code relies > on this happening. For example, the minimal app's status bar > doesn't get drawn correctly when the frame is resized, and when > I tried this solution in our app everything was drawn correctly > except for the sash in a split window.
>> ... In fact, reading some of your other >> replies, I think the real difference you're seeing is that MSW is not >> double-buffered while Mac is. (I think GTK is too, though I don't >> know >> for sure.)
> GTK windows are indeed double-buffered, but that has nothing to do > with the background erasing problem (in fact our Windows app uses > double-buffering to minimize flicker).
>> AFAICS the problem is that under Mac all windows are double buffered and >> this can't be changed. So any refresh of the area automatically results in >> fully repainting it because double buffering makes the separation between >> painting the background and the rest nonsensical. I don't see what can be >> done about it to be honest.
> Double-buffered windows are not the problem. > I think there is some confusion about what I mean by "erasing the > background". The real background of an OS X window is white -- ie. if > you create a window and don't draw *anything* in it then its background > will be white. This is made clear by the modified minimal app I created > (see http://www.trevorrow.com/wx/minimal.cpp).
> To get a background of gray horizontal stripes you actually have to draw > something (using a HIThemeSetFill call or whatever). This is the drawing > I'm talking about when I say wxMac is erasing the background, and it's > this drawing that I think should NOT be done if Refresh(false) is called.
Maybe another way to look at it would help people understand what Andrew is asking about. If you turn it around and look at it from a different perspective you can say that on Windows when Refresh(false) is called it causes the current content of the window to be preserved and that whatever is drawn in the next EVT_PAINT handler will be drawn on top of that current content. On Mac and GTK the current content is not preserved, and Andrew is wondering how to make it behave that way.
As a side note, I don't think anybody has mentioned yet that the eraseBackground parameter to wxWindow::Refresh is totally ignored on Mac and GTK. It there anything that it could be used for to give us the ability to preserve the current window content?
-- Robin Dunn Software Craftsman http://wxPython.org Java give you jitters? Relax with wxPython!