Finally really correct background erasing for wxMSW wxToolBar.

Do use TBSTYLE_FLAT and TBSTYLE_TRANSPARENT (the former actually implies the
latter) for MSW toolbar as it is the only way to avoid the flicker of toolbar
buttons. These styles were disabled before because of lack of understanding
about how they worked: with them, the toolbar supposes that its parent takes
care of erasing its background but wx didn't do this (in fact wxFrame did
accidentally erase toolbar background because of the use of Win32 client
rectangle, including tool/status bars, instead of wx client rectangle,
excluding them, in wxWindowMSW::DoEraseBackground(), but it didn't do it
correctly).

Now we allow hooking WM_ERASEBKGND events processing in a parent window by a
child one and use this to handle toolbar background erasing in toolbar itself.
We still prevent the native toolbar from drawing dummy separators and always
erase the area occupied by them ourselves and thus avoid the flicker entirely.

The only remaining flicker in the toolbar sample is that of embedded
wxStaticText control. It does appear with correctly transparent background and
bitmaps with alpha channel also (still) are drawn correctly in wxStaticBitmaps
embedded in the toolbar.

Finally, we still use solid background brush for toolbar but we can easily use
a themed background if really desired, there is just a single function to
change to do it (MSWGetToolbarBgBrush()).

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@62971 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
Vadim Zeitlin 2009-12-22 15:37:43 +00:00
parent 4b601a59b5
commit bec9bf3e20
4 changed files with 252 additions and 48 deletions

View File

@ -80,6 +80,11 @@ public:
// returns true if the platform should explicitly apply a theme border
virtual bool CanApplyThemeBorder() const { return false; }
#ifdef wxHAS_MSW_BACKGROUND_ERASE_HOOK
virtual bool MSWEraseBgHook(WXHDC hDC);
virtual WXHBRUSH MSWGetBgBrushForChild(WXHDC hDC, wxWindowMSW *child);
#endif // wxHAS_MSW_BACKGROUND_ERASE_HOOK
protected:
// common part of all ctors
void Init();
@ -115,9 +120,9 @@ protected:
// handlers for various events
bool HandleSize(WXWPARAM wParam, WXLPARAM lParam);
#ifndef __WXWINCE__
#ifdef wxHAS_MSW_BACKGROUND_ERASE_HOOK
bool HandlePaint(WXWPARAM wParam, WXLPARAM lParam);
#endif // __WXWINCE__
#endif // wxHAS_MSW_BACKGROUND_ERASE_HOOK
void HandleMouseMove(WXWPARAM wParam, WXLPARAM lParam);
// should be called whenever the toolbar size changes
@ -156,6 +161,16 @@ private:
// have
void UpdateStretchableSpacersSize();
#ifdef wxHAS_MSW_BACKGROUND_ERASE_HOOK
// do erase the toolbar background, always do it for the entire control as
// the caller sets the clipping region correctly to exclude parts which
// should not be erased
void MSWDoEraseBackground(WXHDC hDC);
// return the brush to use for erasing the toolbar background
WXHBRUSH MSWGetToolbarBgBrush();
#endif // wxHAS_MSW_BACKGROUND_ERASE_HOOK
DECLARE_EVENT_TABLE()
DECLARE_DYNAMIC_CLASS(wxToolBar)
wxDECLARE_NO_COPY_CLASS(wxToolBar);

View File

@ -447,6 +447,27 @@ public:
return true;
}
#if !defined(__WXWINCE__) && !defined(__WXUNIVERSAL__)
#define wxHAS_MSW_BACKGROUND_ERASE_HOOK
#endif
#ifdef wxHAS_MSW_BACKGROUND_ERASE_HOOK
// allows the child to hook into its parent WM_ERASEBKGND processing: call
// MSWSetEraseBgHook() with a non-NULL window to make parent call
// MSWEraseBgHook() on this window (don't forget to reset it to NULL
// afterwards)
//
// this hack is used by wxToolBar, see comments there
void MSWSetEraseBgHook(wxWindow *child);
// return true if WM_ERASEBKGND is currently hooked
bool MSWHasEraseBgHook() const;
// called when the window on which MSWSetEraseBgHook() had been called
// receives WM_ERASEBKGND
virtual bool MSWEraseBgHook(WXHDC WXUNUSED(hDC)) { return false; }
#endif // wxHAS_MSW_BACKGROUND_ERASE_HOOK
// common part of Show/HideWithEffect()
bool MSWShowWithEffect(bool show,
wxShowEffect effect,

View File

@ -36,6 +36,7 @@
#include "wx/intl.h"
#include "wx/settings.h"
#include "wx/bitmap.h"
#include "wx/region.h"
#include "wx/dcmemory.h"
#include "wx/control.h"
#include "wx/app.h" // for GetComCtl32Version
@ -335,17 +336,11 @@ bool wxToolBar::Create(wxWindow *parent,
wxSetCCUnicodeFormat(GetHwnd());
// workaround for flat toolbar on Windows XP classic style: we have to set
// the style after creating the control; doing it at creation time doesn't work
#if wxUSE_UXTHEME
if ( style & wxTB_FLAT )
{
LRESULT style = GetMSWToolbarStyle();
if ( !(style & TBSTYLE_FLAT) )
::SendMessage(GetHwnd(), TB_SETSTYLE, 0, style | TBSTYLE_FLAT);
}
#endif // wxUSE_UXTHEME
// we always erase our background on WM_PAINT so there is no need to do it
// in WM_ERASEBKGND too (by default this won't be done but if the toolbar
// has a non default background colour, then it would be used in both
// places resulting in flicker)
SetBackgroundStyle(wxBG_STYLE_PAINT);
return true;
}
@ -500,21 +495,11 @@ WXDWORD wxToolBar::MSWGetStyle(long style, WXDWORD *exstyle) const
if ( !(style & wxTB_NO_TOOLTIPS) )
msStyle |= TBSTYLE_TOOLTIPS;
if ( style & (wxTB_FLAT | wxTB_HORZ_LAYOUT) )
{
// static as it doesn't change during the program lifetime
static const int s_verComCtl = wxApp::GetComCtl32Version();
if ( style & wxTB_FLAT && wxApp::GetComCtl32Version() > 400 )
msStyle |= TBSTYLE_FLAT;
// comctl32.dll 4.00 doesn't support the flat toolbars and using this
// style with 6.00 (part of Windows XP) leads to the toolbar with
// incorrect background colour - and not using it still results in the
// correct (flat) toolbar, so don't use it there
if ( s_verComCtl > 400 && s_verComCtl < 600 )
msStyle |= TBSTYLE_FLAT | TBSTYLE_TRANSPARENT;
if ( s_verComCtl >= 470 && style & wxTB_HORZ_LAYOUT )
msStyle |= TBSTYLE_LIST;
}
if ( style & wxTB_HORZ_LAYOUT && wxApp::GetComCtl32Version() >= 470 )
msStyle |= TBSTYLE_LIST;
if ( style & wxTB_NODIVIDER )
msStyle |= CCS_NODIVIDER;
@ -531,6 +516,15 @@ WXDWORD wxToolBar::MSWGetStyle(long style, WXDWORD *exstyle) const
if ( style & wxTB_RIGHT )
msStyle |= CCS_RIGHT;
// always use TBSTYLE_TRANSPARENT because the background is not drawn
// correctly without it in all themes and, for whatever reason, the control
// also flickers horribly when it is resized if this style is not used
//
// note that this is implicitly enabled by the native toolbar itself when
// TBSTYLE_FLAT is used (i.e. it's impossible to use TBSTYLE_FLAT without
// TBSTYLE_TRANSPARENT) but turn it on explicitly in any case
msStyle |= TBSTYLE_TRANSPARENT;
return msStyle;
}
@ -1707,12 +1701,19 @@ bool wxToolBar::HandleSize(WXWPARAM WXUNUSED(wParam), WXLPARAM lParam)
return true;
}
#ifndef __WXWINCE__
#ifdef wxHAS_MSW_BACKGROUND_ERASE_HOOK
bool wxToolBar::HandlePaint(WXWPARAM WXUNUSED(wParam), WXLPARAM WXUNUSED(lParam))
bool wxToolBar::HandlePaint(WXWPARAM wParam, WXLPARAM lParam)
{
// exclude the area occupied by the controls and stretchable spaces from
// the update region to prevent the toolbar from drawing separators in it
// we must prevent the dummy separators corresponding to controls or
// stretchable spaces from being seen: we used to do it by painting over
// them but this, unsurprisingly, resulted in a lot of flicker so now we
// prevent the toolbar from painting them at all
// compute the region containing all dummy separators which we don't want
// to be seen
wxRegion rgnDummySeps;
const wxRect rectTotal = GetClientRect();
int toolIndex = 0;
for ( wxToolBarToolsList::compatibility_iterator node = m_tools.GetFirst();
node;
@ -1726,13 +1727,14 @@ bool wxToolBar::HandlePaint(WXWPARAM WXUNUSED(wParam), WXLPARAM WXUNUSED(lParam)
const size_t numSeps = tool->GetSeparatorsCount();
for ( size_t n = 0; n < numSeps; n++, toolIndex++ )
{
const RECT rcItem = wxGetTBItemRect(GetHwnd(), toolIndex);
// for some reason TB_GETITEMRECT returns a rectangle 1 pixel
// shorter than the full window size (at least under Windows 7)
// but we need to erase the full height below
RECT rcItem = wxGetTBItemRect(GetHwnd(), toolIndex);
rcItem.top = 0;
rcItem.bottom = rectTotal.height;
const wxRegion rgnItem(wxRectFromRECT(rcItem));
if ( !ValidateRgn(GetHwnd(), GetHrgnOf(rgnItem)) )
{
wxLogLastError(wxT("ValidateRgn()"));
}
rgnDummySeps.Union(wxRectFromRECT(rcItem));
}
}
else
@ -1742,10 +1744,114 @@ bool wxToolBar::HandlePaint(WXWPARAM WXUNUSED(wParam), WXLPARAM WXUNUSED(lParam)
}
}
// still let the native control draw everything else normally
return false;
if ( !rgnDummySeps.IsOk() )
{
// don't interfere with toolbar default painting at all if we don't
// need to -- and we don't if we have no dummy separators at all
return false;
}
// exclude the area occupied by the controls and stretchable spaces from
// the update region to prevent the toolbar from drawing separators in it
if ( !::ValidateRgn(GetHwnd(), GetHrgnOf(rgnDummySeps)) )
{
wxLogLastError(wxT("ValidateRgn()"));
}
// still let the native control draw everything else normally but set up a
// hook to be able to process the next WM_ERASEBKGND sent to our parent
// because toolbar will ask it to erase its background from its WM_PAINT
// handler (when using TBSTYLE_TRANSPARENT which we do always use)
//
// installing hook is not completely trivial as all kinds of strange
// situations are possible: sometimes we can be called recursively from
// inside the native toolbar WM_PAINT handler so the hook might already be
// installed and sometimes the native toolbar might not send WM_ERASEBKGND
// to the parent at all for whatever reason, so deal with all these cases
wxWindow * const parent = GetParent();
const bool hadHook = parent->MSWHasEraseBgHook();
if ( !hadHook )
GetParent()->MSWSetEraseBgHook(this);
MSWDefWindowProc(WM_PAINT, wParam, lParam);
if ( !hadHook )
GetParent()->MSWSetEraseBgHook(NULL);
// erase the dummy separators region ourselves now as nobody painted over
// them
WindowHDC hdc(GetHwnd());
::SelectClipRgn(hdc, GetHrgnOf(rgnDummySeps));
MSWDoEraseBackground(hdc);
return true;
}
#endif // __WXWINCE__
WXHBRUSH wxToolBar::MSWGetToolbarBgBrush()
{
// we conservatively use a solid brush here but we could also use a themed
// brush by using DrawThemeBackground() to create a bitmap brush (it'd need
// to be invalidated whenever the toolbar is resized and, also, correctly
// aligned using SetBrushOrgEx() before each use -- there is code for doing
// this in wxNotebook already so it'd need to be refactored into wxWindow)
//
// however inasmuch as there is a default background for the toolbar at all
// (and this is not a trivial question as different applications use very
// different colours), it seems to be a solid one and using REBAR
// background brush as we used to do before doesn't look good at all under
// Windows 7 (and probably Vista too), so for now we just keep it simple
wxColour const
colBg = m_hasBgCol ? GetBackgroundColour()
: wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE);
wxBrush * const
brush = wxTheBrushList->FindOrCreateBrush(colBg);
return brush ? brush->GetResourceHandle() : 0;
}
WXHBRUSH wxToolBar::MSWGetBgBrushForChild(WXHDC hDC, wxWindowMSW *child)
{
WXHBRUSH hbr = wxToolBarBase::MSWGetBgBrushForChild(hDC, child);
if ( hbr )
return hbr;
// the base class version only returns a brush for erasing children
// background if we have a non-default background colour but as the toolbar
// doesn't erase its own background by default, we need to always do it for
// (semi-)transparent children
if ( child->GetParent() == this && child->HasTransparentBackground() )
return MSWGetToolbarBgBrush();
return 0;
}
void wxToolBar::MSWDoEraseBackground(WXHDC hDC)
{
wxFillRect(GetHwnd(), (HDC)hDC, (HBRUSH)MSWGetToolbarBgBrush());
}
bool wxToolBar::MSWEraseBgHook(WXHDC hDC)
{
// toolbar WM_PAINT handler offsets the DC origin before sending
// WM_ERASEBKGND to the parent but as we handle it in the toolbar itself,
// we need to reset it back
HDC hdc = (HDC)hDC;
POINT ptOldOrg;
if ( !::SetWindowOrgEx(hdc, 0, 0, &ptOldOrg) )
{
wxLogLastError(wxT("SetWindowOrgEx(tbar-bg-hdc)"));
return false;
}
MSWDoEraseBackground(hDC);
::SetWindowOrgEx(hdc, ptOldOrg.x, ptOldOrg.y, NULL);
return true;
}
#endif // wxHAS_MSW_BACKGROUND_ERASE_HOOK
void wxToolBar::HandleMouseMove(WXWPARAM WXUNUSED(wParam), WXLPARAM lParam)
{
@ -1776,7 +1882,7 @@ WXLRESULT wxToolBar::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam
return 0;
break;
#ifndef __WXWINCE__
#ifdef wxHAS_MSW_BACKGROUND_ERASE_HOOK
case WM_PAINT:
// refreshing the controls in the toolbar inside a composite window
// results in an endless stream of WM_PAINT messages -- and seems
@ -1785,7 +1891,7 @@ WXLRESULT wxToolBar::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam
if ( !IsDoubleBuffered() && HandlePaint(wParam, lParam) )
return 0;
break;
#endif // __WXWINCE__
#endif // wxHAS_MSW_BACKGROUND_ERASE_HOOK
}
return wxControl::MSWWindowProc(nMsg, wParam, lParam);

View File

@ -173,13 +173,16 @@
extern wxMenu *wxCurrentPopupMenu;
#endif
namespace
{
// true if we had already created the std colour map, used by
// wxGetStdColourMap() and wxWindow::OnSysColourChanged() (FIXME-MT)
static bool gs_hasStdCmap = false;
bool gs_hasStdCmap = false;
// last mouse event information we need to filter out the duplicates
#if wxUSE_MOUSEEVENT_HACK
static struct MouseEventInfoDummy
struct MouseEventInfoDummy
{
// mouse position (in screen coordinates)
wxPoint pos;
@ -194,14 +197,29 @@ WX_DECLARE_HASH_MAP(int, wxWindow::MSWMessageHandler,
wxIntegerHash, wxIntegerEqual,
MSWMessageHandlers);
static MSWMessageHandlers gs_messageHandlers;
MSWMessageHandlers gs_messageHandlers;
// hash containing all our windows, it uses HWND keys and wxWindow* values
WX_DECLARE_HASH_MAP(HWND, wxWindow *,
wxPointerHash, wxPointerEqual,
WindowHandles);
static WindowHandles gs_windowHandles;
WindowHandles gs_windowHandles;
#ifdef wxHAS_MSW_BACKGROUND_ERASE_HOOK
// temporary override for WM_ERASEBKGND processing: we don't store this in
// wxWindow itself as we don't need it during most of the time so don't
// increase the size of all window objects unnecessarily
WX_DECLARE_HASH_MAP(wxWindow *, wxWindow *,
wxPointerHash, wxPointerEqual,
EraseBgHooks);
EraseBgHooks gs_eraseBgHooks;
#endif // wxHAS_MSW_BACKGROUND_ERASE_HOOK
} // anonymous namespace
// ---------------------------------------------------------------------------
// private functions
@ -3269,7 +3287,17 @@ WXLRESULT wxWindowMSW::MSWWindowProc(WXUINT message, WXWPARAM wParam, WXLPARAM l
break;
case WM_ERASEBKGND:
processed = HandleEraseBkgnd((WXHDC)wParam);
{
#ifdef wxHAS_MSW_BACKGROUND_ERASE_HOOK
// check if an override was configured for this window
EraseBgHooks::const_iterator it = gs_eraseBgHooks.find(this);
if ( it != gs_eraseBgHooks.end() )
processed = it->second->MSWEraseBgHook((WXHDC)wParam);
else
#endif // wxHAS_MSW_BACKGROUND_ERASE_HOOK
processed = HandleEraseBkgnd((WXHDC)wParam);
}
if ( processed )
{
// we processed the message, i.e. erased the background
@ -4867,13 +4895,47 @@ bool wxWindowMSW::HandleEraseBkgnd(WXHDC hdc)
return true;
}
#ifdef wxHAS_MSW_BACKGROUND_ERASE_HOOK
bool wxWindowMSW::MSWHasEraseBgHook() const
{
return gs_eraseBgHooks.find(this) != gs_eraseBgHooks.end();
}
void wxWindowMSW::MSWSetEraseBgHook(wxWindow *child)
{
if ( child )
{
if ( !gs_eraseBgHooks.insert(
EraseBgHooks::value_type(this, child)).second )
{
wxFAIL_MSG( wxT("Setting erase background hook twice?") );
}
}
else // reset the hook
{
if ( gs_eraseBgHooks.erase(this) != 1 )
{
wxFAIL_MSG( wxT("Resetting erase background which was not set?") );
}
}
}
#endif // wxHAS_MSW_BACKGROUND_ERASE_HOOK
bool wxWindowMSW::DoEraseBackground(WXHDC hDC)
{
HBRUSH hbr = (HBRUSH)MSWGetBgBrush(hDC);
if ( !hbr )
return false;
wxFillRect(GetHwnd(), (HDC)hDC, hbr);
// erase just the client area of the window, this is important for the
// frames to avoid drawing over the toolbar part of the window (you might
// think using WS_CLIPCHILDREN would prevent this from happening, but it
// clearly doesn't)
RECT rc;
wxCopyRectToRECT(GetClientRect(), rc);
::FillRect((HDC)hDC, &rc, hbr);
return true;
}