From bec9bf3e20e07cfb3874069acce98aa2e898b8dc Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Tue, 22 Dec 2009 15:37:43 +0000 Subject: [PATCH] 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 --- include/wx/msw/toolbar.h | 19 +++- include/wx/msw/window.h | 21 +++++ src/msw/toolbar.cpp | 190 ++++++++++++++++++++++++++++++--------- src/msw/window.cpp | 74 +++++++++++++-- 4 files changed, 254 insertions(+), 50 deletions(-) diff --git a/include/wx/msw/toolbar.h b/include/wx/msw/toolbar.h index 2161bc49c4..bef24a2ee4 100644 --- a/include/wx/msw/toolbar.h +++ b/include/wx/msw/toolbar.h @@ -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); diff --git a/include/wx/msw/window.h b/include/wx/msw/window.h index a65fa4910a..0f2efd7d35 100644 --- a/include/wx/msw/window.h +++ b/include/wx/msw/window.h @@ -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, diff --git a/src/msw/toolbar.cpp b/src/msw/toolbar.cpp index 71aec08351..5dd2f917ff 100644 --- a/src/msw/toolbar.cpp +++ b/src/msw/toolbar.cpp @@ -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(); - - // 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_FLAT && wxApp::GetComCtl32Version() > 400 ) + msStyle |= TBSTYLE_FLAT; + + 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); - - const wxRegion rgnItem(wxRectFromRECT(rcItem)); - if ( !ValidateRgn(GetHwnd(), GetHrgnOf(rgnItem)) ) - { - wxLogLastError(wxT("ValidateRgn()")); - } + // 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; + + 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; +} + +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 // __WXWINCE__ + +#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); diff --git a/src/msw/window.cpp b/src/msw/window.cpp index ed1076ccfa..1aab7e12dd 100644 --- a/src/msw/window.cpp +++ b/src/msw/window.cpp @@ -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; } -- 2.45.2