Re: Re[2]: [wx-dev] Re: why do wxMac and wxGTK always erase window background?

16 views
Skip to first unread message

Kevin Ollivier

unread,
Nov 20, 2008, 1:28:56 PM11/20/08
to wx-...@lists.wxwidgets.org
Hi Vadim,

On Nov 20, 2008, at 8:36 AM, Vadim Zeitlin wrote:

> 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.

Regards,

Kevin

>
> Regards,
> VZ
> _______________________________________________
> wx-dev mailing list
> wx-...@lists.wxwidgets.org
> http://lists.wxwidgets.org/mailman/listinfo/wx-dev

Andrew Trevorrow

unread,
Nov 20, 2008, 7:04:56 PM11/20/08
to wx-...@lists.wxwidgets.org
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.

Andrew

Kevin Ollivier

unread,
Nov 19, 2008, 2:39:28 AM11/19/08
to wx-...@lists.wxwidgets.org

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:
>
> http://www.trevorrow.com/wx/minimal.cpp
>
> 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
>
> EVT_ERASE_BACKGROUND(MyFrame::OnErase)
> EVT_PAINT(MyFrame::OnPaint)
>
> 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.

Kevin

>
> 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).
>
> Andrew

Robin Dunn

unread,
Nov 20, 2008, 11:32:53 AM11/20/08
to wx-...@lists.wxwidgets.org
Andrew Trevorrow wrote:
> Vadim Zeitlin wrote:
>
>> 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!

Reply all
Reply to author
Forward
0 new messages