From a01ef3cc406cb5e146d99960dca46e8df515ab33 Mon Sep 17 00:00:00 2001 From: Jaakko Salli Date: Wed, 26 May 2010 14:54:27 +0000 Subject: [PATCH] Backported from wx2.9 improved wxComboCtrl/wxOwnerDrawnComboBox appearance under Vista/Win7. Should have been done years ago, really. git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/branches/WX_2_8_BRANCH@64396 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- docs/changes.txt | 5 + include/wx/combo.h | 9 +- src/common/combocmn.cpp | 11 +- src/msw/combo.cpp | 363 +++++++++++++++++++++++++++++----------- 4 files changed, 286 insertions(+), 102 deletions(-) diff --git a/docs/changes.txt b/docs/changes.txt index 0538ec3775..81a33bd64a 100644 --- a/docs/changes.txt +++ b/docs/changes.txt @@ -96,6 +96,11 @@ All (GUI): - wxRTC: fixed style selection resetting after editing a style. - wxRTC: can now edit line spacing in .1 increments from 1 to 2. +wxMSW: + +- Backported improved wxOwnerDrawnComboBox and wxComboCtrl appearance under + Windows Vista/7. + 2.8.11: ------- diff --git a/include/wx/combo.h b/include/wx/combo.h index 3068820ba6..d4840b1899 100644 --- a/include/wx/combo.h +++ b/include/wx/combo.h @@ -75,6 +75,12 @@ enum wxCC_POPUP_ON_MOUSE_UP = 0x0002, // All text is not automatically selected on click wxCC_NO_TEXT_AUTO_SELECT = 0x0004, + // Drop-button stays down as long as popup is displayed. + wxCC_BUTTON_STAYS_DOWN = 0x0008, + // Drop-button covers the entire control. + wxCC_FULL_BUTTON = 0x0010, + // Drop-button goes over the custom-border (used under WinVista). + wxCC_BUTTON_COVERS_BORDER = 0x0020, // Internal use: signals creation is complete wxCC_IFLAG_CREATED = 0x0100, @@ -426,7 +432,8 @@ protected: // flags for DrawButton() enum { - Draw_PaintBg = 1 + Draw_PaintBg = 1, + Draw_BitmapOnly = 2 }; // Draws dropbutton. Using wxRenderer or bitmaps, as appropriate. diff --git a/src/common/combocmn.cpp b/src/common/combocmn.cpp index 67d3100dca..6a1b299e2a 100644 --- a/src/common/combocmn.cpp +++ b/src/common/combocmn.cpp @@ -1326,7 +1326,7 @@ void wxComboCtrlBase::PrepareBackground( wxDC&, const wxRect&, int ) const } #endif -void wxComboCtrlBase::DrawButton( wxDC& dc, const wxRect& rect, int paintBg ) +void wxComboCtrlBase::DrawButton( wxDC& dc, const wxRect& rect, int flags ) { int drawState = m_btnState; @@ -1353,8 +1353,11 @@ void wxComboCtrlBase::DrawButton( wxDC& dc, const wxRect& rect, int paintBg ) if ( !m_bmpNormal.Ok() ) { + if ( flags & Draw_BitmapOnly ) + return; + // Need to clear button background even if m_btn is present - if ( paintBg ) + if ( flags & Draw_PaintBg ) { wxColour bgCol; @@ -1393,7 +1396,7 @@ void wxComboCtrlBase::DrawButton( wxDC& dc, const wxRect& rect, int paintBg ) { // If using blank button background, we need to clear its background // with button face colour instead of colour for rest of the control. - if ( paintBg ) + if ( flags & Draw_PaintBg ) { wxColour bgCol = GetParent()->GetBackgroundColour(); //wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE); //wxColour bgCol = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); @@ -1413,7 +1416,7 @@ void wxComboCtrlBase::DrawButton( wxDC& dc, const wxRect& rect, int paintBg ) { // Need to clear button background even if m_btn is present // (assume non-button background was cleared just before this call so brushes are good) - if ( paintBg ) + if ( flags & Draw_PaintBg ) dc.DrawRectangle(rect); } diff --git a/src/msw/combo.cpp b/src/msw/combo.cpp index 34609a14e4..221b2840fa 100644 --- a/src/msw/combo.cpp +++ b/src/msw/combo.cpp @@ -47,6 +47,7 @@ // parameters. #if 0 #include + #include #else //---------------------------------- #define EP_EDITTEXT 1 @@ -61,9 +62,59 @@ #define TMT_TEXTCOLOR 3803 #define TMT_BORDERCOLOR 3801 #define TMT_EDGEFILLCOLOR 3808 - //---------------------------------- + #define TMT_BGTYPE 4001 + + #define BT_IMAGEFILE 0 + #define BT_BORDERFILL 1 + + #define CP_DROPDOWNBUTTON 1 + #define CP_BACKGROUND 2 // This and above are Vista and later only + #define CP_TRANSPARENTBACKGROUND 3 + #define CP_BORDER 4 + #define CP_READONLY 5 + #define CP_DROPDOWNBUTTONRIGHT 6 + #define CP_DROPDOWNBUTTONLEFT 7 + #define CP_CUEBANNER 8 + + #define CBXS_NORMAL 1 + #define CBXS_HOT 2 + #define CBXS_PRESSED 3 + #define CBXS_DISABLED 4 + + #define CBXSR_NORMAL 1 + #define CBXSR_HOT 2 + #define CBXSR_PRESSED 3 + #define CBXSR_DISABLED 4 + + #define CBXSL_NORMAL 1 + #define CBXSL_HOT 2 + #define CBXSL_PRESSED 3 + #define CBXSL_DISABLED 4 + + #define CBTBS_NORMAL 1 + #define CBTBS_HOT 2 + #define CBTBS_DISABLED 3 + #define CBTBS_FOCUSED 4 + + #define CBB_NORMAL 1 + #define CBB_HOT 2 + #define CBB_FOCUSED 3 + #define CBB_DISABLED 4 + + #define CBRO_NORMAL 1 + #define CBRO_HOT 2 + #define CBRO_PRESSED 3 + #define CBRO_DISABLED 4 + + #define CBCB_NORMAL 1 + #define CBCB_HOT 2 + #define CBCB_PRESSED 3 + #define CBCB_DISABLED 4 + #endif +// In wx2.9, this is defined in msw\private.h +#define wxWinVersion_Vista 0x0600 #define NATIVE_TEXT_INDENT_XP 4 #define NATIVE_TEXT_INDENT_CLASSIC 2 @@ -290,11 +341,10 @@ wxComboCtrl::PrepareBackground( wxDC& dc, const wxRect& rect, int flags ) const #if wxUSE_UXTHEME wxUxThemeHandle hTheme(this, L"COMBOBOX"); #endif - //COLORREF cref; wxSize sz = GetClientSize(); bool isEnabled; - bool isFocused; // also selected + bool doDrawFocusRect; // also selected // For smaller size control (and for disabled background) use less spacing int focusSpacingX; @@ -304,7 +354,7 @@ wxComboCtrl::PrepareBackground( wxDC& dc, const wxRect& rect, int flags ) const { // Drawing control isEnabled = IsEnabled(); - isFocused = ShouldDrawFocus(); + doDrawFocusRect = ShouldDrawFocus(); #if wxUSE_UXTHEME // Windows-style: for smaller size control (and for disabled background) use less spacing @@ -334,7 +384,7 @@ wxComboCtrl::PrepareBackground( wxDC& dc, const wxRect& rect, int flags ) const { // Drawing a list item isEnabled = true; // they are never disabled - isFocused = flags & wxCONTROL_SELECTED ? true : false; + doDrawFocusRect = flags & wxCONTROL_SELECTED ? true : false; focusSpacingX = 0; focusSpacingY = 0; @@ -353,53 +403,70 @@ wxComboCtrl::PrepareBackground( wxDC& dc, const wxRect& rect, int flags ) const selRect.x += wcp + focusSpacingX; selRect.width -= wcp + (focusSpacingX*2); - //wxUxThemeEngine* theme = (wxUxThemeEngine*) NULL; + //wxUxThemeEngine* theme = NULL; //if ( hTheme ) // theme = wxUxThemeEngine::GetIfActive(); - wxColour bgCol; wxColour fgCol; - bool drawDottedEdge = false; + wxColour bgCol; + bool doDrawDottedEdge = false; + bool doDrawSelRect = true; - if ( isEnabled && isFocused && HasFlag(wxCB_READONLY) ) - drawDottedEdge = true; + // TODO: doDrawDottedEdge = true when focus has arrived to control via tab. + // (and other cases which are not that apparent). if ( isEnabled ) { - if ( isFocused ) - fgCol = wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT); - else if ( m_hasFgCol ) - // Honour the custom foreground colour - fgCol = GetForegroundColour(); + // If popup is hidden and this control is focused, + // then draw the focus-indicator (selbgcolor background etc.). + if ( doDrawFocusRect ) + { + // NB: We can't really use XP visual styles to get TMT_TEXTCOLOR since + // it is not properly defined for combo boxes. Instead, they expect + // you to use DrawThemeText. + // + // Here is, however, sample code how to get theme colours: + // + // COLORREF cref; + // theme->GetThemeColor(hTheme,EP_EDITTEXT,ETS_NORMAL,TMT_TEXTCOLOR,&cref); + // dc.SetTextForeground( wxRGBToColour(cref) ); + if ( (m_iFlags & wxCC_FULL_BUTTON) && !(flags & wxCONTROL_ISSUBMENU) ) + { + // Vista style read-only combo + fgCol = GetForegroundColour(); + bgCol = GetBackgroundColour(); + doDrawSelRect = false; + doDrawDottedEdge = true; + } + else + { + fgCol = wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT); + bgCol = wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT); + } + } else - fgCol = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); + { + fgCol = GetForegroundColour(); + bgCol = GetBackgroundColour(); + doDrawSelRect = false; + } } else { fgCol = wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT); - } - - if ( isEnabled ) - { - if ( isFocused ) - bgCol = wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT); - else if ( m_hasBgCol ) - // Honour the custom background colour - bgCol = GetBackgroundColour(); - else - bgCol = GetBackgroundColour(); - } - else - { bgCol = wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE); } dc.SetTextForeground(fgCol); dc.SetBrush(bgCol); - dc.SetPen(bgCol); - dc.DrawRectangle(selRect); - if ( drawDottedEdge ) - wxMSWDrawFocusRect(dc,selRect); + if ( doDrawSelRect ) + { + dc.SetPen(bgCol); + dc.DrawRectangle(selRect); + } + + if ( doDrawDottedEdge ) + wxMSWDrawFocusRect(dc, selRect); // Don't clip exactly to the selection rectangle so we can draw // to the non-selected area in front of it. @@ -412,94 +479,196 @@ void wxComboCtrl::OnPaintEvent( wxPaintEvent& WXUNUSED(event) ) { // TODO: Convert drawing in this function to Windows API Code + // TODO: Convert drawing in this function to Windows API Code + wxSize sz = GetClientSize(); wxAutoBufferedPaintDC dc(this); - const wxRect& rectb = m_btnArea; - wxRect rect = m_tcArea; - bool isEnabled = IsEnabled(); + const wxRect& rectButton = m_btnArea; + wxRect rectTextField = m_tcArea; wxColour bgCol = GetBackgroundColour(); - wxColour fgCol; #if wxUSE_UXTHEME + const bool isEnabled = IsEnabled(); + + HDC hDc = GetHdcOf(dc); + HWND hWnd = GetHwndOf(this); + wxUxThemeEngine* theme = NULL; wxUxThemeHandle hTheme(this, L"COMBOBOX"); -#endif - int etsState; + if ( hTheme ) + theme = wxUxThemeEngine::GetIfActive(); +#endif // wxUSE_UXTHEME + + wxRect borderRect(0,0,sz.x,sz.y); - // area around both controls - wxRect rect2(0,0,sz.x,sz.y); if ( m_iFlags & wxCC_IFLAG_BUTTON_OUTSIDE ) { - rect2 = m_tcArea; - rect2.Inflate(1); + borderRect = m_tcArea; + borderRect.Inflate(1); } + int drawButFlags = 0; + #if wxUSE_UXTHEME - // Use theme to draw border on XP if ( hTheme ) { - theme = wxUxThemeEngine::GetIfActive(); - COLORREF cref; + const bool useVistaComboBox = ::wxGetWinVersion() >= wxWinVersion_Vista; + + RECT rFull; + wxCopyRectToRECT(borderRect, rFull); + + RECT rButton; + wxCopyRectToRECT(rectButton, rButton); + + RECT rBorder; + wxCopyRectToRECT(borderRect, rBorder); + + wxUint32 isNonStdButton = (m_iFlags & wxCC_IFLAG_BUTTON_OUTSIDE); + + // + // Get some states for themed drawing + int butState; - // Select correct border colour if ( !isEnabled ) - etsState = ETS_DISABLED; - else - etsState = ETS_NORMAL; - - if ( m_widthCustomBorder ) { - theme->GetThemeColor(hTheme,EP_EDITTEXT,etsState,TMT_BORDERCOLOR,&cref); - - // Set border colour - dc.SetPen( wxRGBToColour(cref) ); - - dc.SetBrush( *wxTRANSPARENT_BRUSH ); - dc.DrawRectangle(rect2); + butState = CBXS_DISABLED; + } + // Vista will display the drop-button as depressed always + // when the popup window is visilbe + else if ( (m_btnState & wxCONTROL_PRESSED) || + (useVistaComboBox && !IsPopupWindowState(Hidden)) ) + { + butState = CBXS_PRESSED; + } + else if ( m_btnState & wxCONTROL_CURRENT ) + { + butState = CBXS_HOT; + } + else + { + butState = CBXS_NORMAL; } - theme->GetThemeColor(hTheme,EP_EDITTEXT,etsState,TMT_TEXTCOLOR,&cref); - fgCol = wxRGBToColour(cref); + int comboBoxPart = 0; // For XP, use the 'default' part + RECT* rUseForBg = &rBorder; + + bool drawFullButton = false; + int bgState = butState; + const bool isFocused = (FindFocus() == GetMainWindowOfCompositeControl()) ? true : false; + + if ( useVistaComboBox ) + { + // FIXME: Either SetBackgroundColour or GetBackgroundColour + // doesn't work under Vista, so here's a temporary + // workaround. + bgCol = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); + + // Draw the entire control as a single button? + if ( !isNonStdButton ) + { + if ( HasFlag(wxCB_READONLY) ) + drawFullButton = true; + } + + if ( drawFullButton ) + { + comboBoxPart = CP_READONLY; + rUseForBg = &rFull; + + // It should be safe enough to update this flag here. + m_iFlags |= wxCC_FULL_BUTTON; + } + else + { + comboBoxPart = CP_BORDER; + m_iFlags &= ~wxCC_FULL_BUTTON; + + if ( isFocused ) + bgState = CBB_FOCUSED; + else + bgState = CBB_NORMAL; + } + } + + // + // Draw parent's background, if necessary + RECT* rUseForTb = NULL; + + if ( theme->IsThemeBackgroundPartiallyTransparent( hTheme, comboBoxPart, bgState ) ) + rUseForTb = &rFull; + else if ( m_iFlags & wxCC_IFLAG_BUTTON_OUTSIDE ) + rUseForTb = &rButton; + + if ( rUseForTb ) + theme->DrawThemeParentBackground( hWnd, hDc, rUseForTb ); + + // + // Draw the control background (including the border) + if ( m_widthCustomBorder > 0 ) + { + theme->DrawThemeBackground( hTheme, hDc, comboBoxPart, bgState, rUseForBg, NULL ); + } + else + { + // No border. We can't use theme, since it cannot be relied on + // to deliver borderless drawing, even with DrawThemeBackgroundEx. + dc.SetBrush(bgCol); + dc.SetPen(bgCol); + dc.DrawRectangle(borderRect); + } + + // + // Draw the drop-button + if ( !isNonStdButton ) + { + drawButFlags = Draw_BitmapOnly; + + int butPart = CP_DROPDOWNBUTTON; + + if ( useVistaComboBox ) + { + if ( drawFullButton ) + { + // We need to alter the button style slightly before + // drawing the actual button (but it was good above + // when background etc was done). + if ( butState == CBXS_HOT || butState == CBXS_PRESSED ) + butState = CBXS_NORMAL; + } + + if ( m_btnSide == wxRIGHT ) + butPart = CP_DROPDOWNBUTTONRIGHT; + else + butPart = CP_DROPDOWNBUTTONLEFT; + + } + theme->DrawThemeBackground( hTheme, hDc, butPart, butState, &rButton, NULL ); + } + else if ( useVistaComboBox && + (m_iFlags & wxCC_IFLAG_BUTTON_OUTSIDE) ) + { + // We'll do this, because DrawThemeParentBackground + // doesn't seem to be reliable on Vista. + drawButFlags |= Draw_PaintBg; + } } else #endif { - // draw regular background - fgCol = GetForegroundColour(); + // Windows 2000 and earlier + drawButFlags = Draw_PaintBg; + + dc.SetBrush(bgCol); + dc.SetPen(bgCol); + dc.DrawRectangle(borderRect); } - rect2.Deflate(m_widthCustomBorder); + // Button rendering (may only do the bitmap on button, depending on the flags) + DrawButton( dc, rectButton, drawButFlags ); - dc.SetBrush(bgCol); - dc.SetPen(bgCol); - - // clear main background - dc.DrawRectangle(rect); - - // Button background with theme? - int drawButFlags = Draw_PaintBg; -#if wxUSE_UXTHEME - if ( hTheme && m_blankButtonBg ) - { - RECT r; - wxCopyRectToRECT(rectb, r); - - // Draw parent background if needed (since button looks like its out of - // the combo, this is preferred). - theme->DrawThemeParentBackground(GetHwndOf(this), - GetHdcOf(dc), - &r); - - drawButFlags = 0; - } -#endif - - // Standard button rendering - DrawButton(dc,rectb,drawButFlags); - - // paint required portion on the control + // Paint required portion of the custom image on the control if ( (!m_text || m_widthCustomPaint) ) { wxASSERT( m_widthCustomPaint >= 0 ); @@ -507,15 +676,15 @@ void wxComboCtrl::OnPaintEvent( wxPaintEvent& WXUNUSED(event) ) // this is intentionally here to allow drawed rectangle's // right edge to be hidden if ( m_text ) - rect.width = m_widthCustomPaint; + rectTextField.width = m_widthCustomPaint; dc.SetFont( GetFont() ); - dc.SetClippingRegion(rect); + dc.SetClippingRegion(rectTextField); if ( m_popupInterface ) - m_popupInterface->PaintComboControl(dc,rect); + m_popupInterface->PaintComboControl(dc,rectTextField); else - wxComboPopup::DefaultPaintComboControl(this,dc,rect); + wxComboPopup::DefaultPaintComboControl(this,dc,rectTextField); } }