1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/msw/anybutton.cpp
3 // Purpose: wxAnyButton
4 // Author: Julian Smart
5 // Created: 1998-01-04 (extracted from button.cpp)
7 // Copyright: (c) Julian Smart
8 // Licence: wxWindows licence
9 /////////////////////////////////////////////////////////////////////////////
11 // ============================================================================
13 // ============================================================================
15 // ----------------------------------------------------------------------------
17 // ----------------------------------------------------------------------------
19 // For compilers that support precompilation, includes "wx.h".
20 #include "wx/wxprec.h"
26 #ifdef wxHAS_ANY_BUTTON
28 #include "wx/anybutton.h"
34 #include "wx/bmpbuttn.h"
35 #include "wx/settings.h"
36 #include "wx/dcscreen.h"
37 #include "wx/dcclient.h"
38 #include "wx/toplevel.h"
39 #include "wx/msw/wrapcctl.h"
40 #include "wx/msw/private.h"
41 #include "wx/msw/missing.h"
44 #include "wx/imaglist.h"
45 #include "wx/stockitem.h"
46 #include "wx/msw/private/button.h"
47 #include "wx/msw/private/dc.h"
48 #include "wx/private/window.h"
51 #include "wx/generic/private/markuptext.h"
52 #endif // wxUSE_MARKUP
54 using namespace wxMSWImpl
;
57 #include "wx/msw/uxtheme.h"
59 // no need to include tmschema.h
61 #define BP_PUSHBUTTON 1
66 #define PBS_DISABLED 4
67 #define PBS_DEFAULTED 5
69 #define TMT_CONTENTMARGINS 3602
72 // provide the necessary declarations ourselves if they're missing from
74 #ifndef BCM_SETIMAGELIST
75 #define BCM_SETIMAGELIST 0x1602
76 #define BCM_SETTEXTMARGIN 0x1604
80 BUTTON_IMAGELIST_ALIGN_LEFT
,
81 BUTTON_IMAGELIST_ALIGN_RIGHT
,
82 BUTTON_IMAGELIST_ALIGN_TOP
,
83 BUTTON_IMAGELIST_ALIGN_BOTTOM
86 struct BUTTON_IMAGELIST
93 #endif // wxUSE_UXTHEME
95 #ifndef WM_THEMECHANGED
96 #define WM_THEMECHANGED 0x031A
100 #define ODS_NOACCEL 0x0100
103 #ifndef ODS_NOFOCUSRECT
104 #define ODS_NOFOCUSRECT 0x0200
107 #ifndef DT_HIDEPREFIX
108 #define DT_HIDEPREFIX 0x00100000
112 extern wxWindowMSW
*wxWindowBeingErased
; // From src/msw/window.cpp
113 #endif // wxUSE_UXTHEME
115 // ----------------------------------------------------------------------------
117 // ----------------------------------------------------------------------------
119 // we use different data classes for owner drawn buttons and for themed XP ones
121 class wxButtonImageData
124 wxButtonImageData() { }
125 virtual ~wxButtonImageData() { }
127 virtual wxBitmap
GetBitmap(wxAnyButton::State which
) const = 0;
128 virtual void SetBitmap(const wxBitmap
& bitmap
, wxAnyButton::State which
) = 0;
130 virtual wxSize
GetBitmapMargins() const = 0;
131 virtual void SetBitmapMargins(wxCoord x
, wxCoord y
) = 0;
133 virtual wxDirection
GetBitmapPosition() const = 0;
134 virtual void SetBitmapPosition(wxDirection dir
) = 0;
137 wxDECLARE_NO_COPY_CLASS(wxButtonImageData
);
143 // the gap between button edge and the interior area used by Windows for the
145 const int OD_BUTTON_MARGIN
= 4;
147 class wxODButtonImageData
: public wxButtonImageData
150 wxODButtonImageData(wxAnyButton
*btn
, const wxBitmap
& bitmap
)
152 SetBitmap(bitmap
, wxAnyButton::State_Normal
);
154 SetBitmap(bitmap
.ConvertToDisabled(), wxAnyButton::State_Disabled
);
158 // we use margins when we have both bitmap and text, but when we have
159 // only the bitmap it should take up the entire button area
160 if ( btn
->ShowsLabel() )
162 m_margin
.x
= btn
->GetCharWidth();
163 m_margin
.y
= btn
->GetCharHeight() / 2;
167 virtual wxBitmap
GetBitmap(wxAnyButton::State which
) const
169 return m_bitmaps
[which
];
172 virtual void SetBitmap(const wxBitmap
& bitmap
, wxAnyButton::State which
)
174 m_bitmaps
[which
] = bitmap
;
177 virtual wxSize
GetBitmapMargins() const
182 virtual void SetBitmapMargins(wxCoord x
, wxCoord y
)
184 m_margin
= wxSize(x
, y
);
187 virtual wxDirection
GetBitmapPosition() const
192 virtual void SetBitmapPosition(wxDirection dir
)
198 // just store the values passed to us to be able to retrieve them later
199 // from the drawing code
200 wxBitmap m_bitmaps
[wxAnyButton::State_Max
];
204 wxDECLARE_NO_COPY_CLASS(wxODButtonImageData
);
209 // somehow the margin is one pixel greater than the value returned by
210 // GetThemeMargins() call
211 const int XP_BUTTON_EXTRA_MARGIN
= 1;
213 class wxXPButtonImageData
: public wxButtonImageData
216 // we must be constructed with the size of our images as we need to create
218 wxXPButtonImageData(wxAnyButton
*btn
, const wxBitmap
& bitmap
)
219 : m_iml(bitmap
.GetWidth(), bitmap
.GetHeight(), true /* use mask */,
220 wxAnyButton::State_Max
+ 1 /* see "pulse" comment below */),
221 m_hwndBtn(GetHwndOf(btn
))
223 // initialize all bitmaps except for the disabled one to normal state
224 for ( int n
= 0; n
< wxAnyButton::State_Max
; n
++ )
227 m_iml
.Add(n
== wxAnyButton::State_Disabled
? bitmap
.ConvertToDisabled()
234 // In addition to the states supported by wxWidgets such as normal,
235 // hot, pressed, disabled and focused, we need to add bitmap for
236 // another state when running under Windows 7 -- the so called "stylus
237 // hot" state corresponding to PBS_STYLUSHOT constant. While it's
238 // documented in MSDN as being only used with tablets, it is a lie as
239 // a focused button actually alternates between the image list elements
240 // with PBS_DEFAULTED and PBS_STYLUSHOT indices and, in particular,
241 // just disappears during half of the time if the latter is not set so
242 // we absolutely must set it.
244 // This also explains why we need to allocate an extra slot in the
245 // image list ctor above, the slot State_Max is used for this one.
248 m_data
.himl
= GetHimagelistOf(&m_iml
);
250 // no margins by default
252 m_data
.margin
.right
=
254 m_data
.margin
.bottom
= 0;
256 // use default alignment
257 m_data
.uAlign
= BUTTON_IMAGELIST_ALIGN_LEFT
;
262 virtual wxBitmap
GetBitmap(wxAnyButton::State which
) const
264 return m_iml
.GetBitmap(which
);
267 virtual void SetBitmap(const wxBitmap
& bitmap
, wxAnyButton::State which
)
269 m_iml
.Replace(which
, bitmap
);
271 // As we want the focused button to always show its bitmap, we need to
272 // update the "stylus hot" one to match it to avoid any pulsing.
273 if ( which
== wxAnyButton::State_Focused
)
274 m_iml
.Replace(wxAnyButton::State_Max
, bitmap
);
279 virtual wxSize
GetBitmapMargins() const
281 return wxSize(m_data
.margin
.left
, m_data
.margin
.top
);
284 virtual void SetBitmapMargins(wxCoord x
, wxCoord y
)
286 RECT
& margin
= m_data
.margin
;
292 if ( !::SendMessage(m_hwndBtn
, BCM_SETTEXTMARGIN
, 0, (LPARAM
)&margin
) )
294 wxLogDebug("SendMessage(BCM_SETTEXTMARGIN) failed");
298 virtual wxDirection
GetBitmapPosition() const
300 switch ( m_data
.uAlign
)
303 wxFAIL_MSG( "invalid image alignment" );
306 case BUTTON_IMAGELIST_ALIGN_LEFT
:
309 case BUTTON_IMAGELIST_ALIGN_RIGHT
:
312 case BUTTON_IMAGELIST_ALIGN_TOP
:
315 case BUTTON_IMAGELIST_ALIGN_BOTTOM
:
320 virtual void SetBitmapPosition(wxDirection dir
)
326 wxFAIL_MSG( "invalid direction" );
330 alignNew
= BUTTON_IMAGELIST_ALIGN_LEFT
;
334 alignNew
= BUTTON_IMAGELIST_ALIGN_RIGHT
;
338 alignNew
= BUTTON_IMAGELIST_ALIGN_TOP
;
342 alignNew
= BUTTON_IMAGELIST_ALIGN_BOTTOM
;
346 if ( alignNew
!= m_data
.uAlign
)
348 m_data
.uAlign
= alignNew
;
354 void UpdateImageInfo()
356 if ( !::SendMessage(m_hwndBtn
, BCM_SETIMAGELIST
, 0, (LPARAM
)&m_data
) )
358 wxLogDebug("SendMessage(BCM_SETIMAGELIST) failed");
362 // we store image list separately to be able to use convenient wxImageList
363 // methods instead of working with raw HIMAGELIST
366 // store the rest of the data in BCM_SETIMAGELIST-friendly form
367 BUTTON_IMAGELIST m_data
;
369 // the button we're associated with
370 const HWND m_hwndBtn
;
373 wxDECLARE_NO_COPY_CLASS(wxXPButtonImageData
);
376 #endif // wxUSE_UXTHEME
378 } // anonymous namespace
380 // ----------------------------------------------------------------------------
382 // ----------------------------------------------------------------------------
384 // ============================================================================
386 // ============================================================================
388 // ----------------------------------------------------------------------------
389 // helper functions from wx/msw/private/button.h
390 // ----------------------------------------------------------------------------
392 void wxMSWButton::UpdateMultilineStyle(HWND hwnd
, const wxString
& label
)
394 // update BS_MULTILINE style depending on the new label (resetting it
395 // doesn't seem to do anything very useful but it shouldn't hurt and we do
396 // have to set it whenever the label becomes multi line as otherwise it
397 // wouldn't be shown correctly as we don't use BS_MULTILINE when creating
398 // the control unless it already has new lines in its label)
399 long styleOld
= ::GetWindowLong(hwnd
, GWL_STYLE
),
401 if ( label
.find(wxT('\n')) != wxString::npos
)
402 styleNew
= styleOld
| BS_MULTILINE
;
404 styleNew
= styleOld
& ~BS_MULTILINE
;
406 if ( styleNew
!= styleOld
)
407 ::SetWindowLong(hwnd
, GWL_STYLE
, styleNew
);
410 wxSize
wxMSWButton::GetFittingSize(wxWindow
*win
,
411 const wxSize
& sizeLabel
,
414 wxSize sizeBtn
= sizeLabel
;
416 // FIXME: The numbers here are pure guesswork, no idea how should the
417 // button margins be really calculated.
418 if ( flags
& Size_ExactFit
)
420 // We still need some margin or the text would be overwritten, just
421 // make it as small as possible.
422 sizeBtn
.x
+= (3*win
->GetCharWidth());
426 sizeBtn
.x
+= 3*win
->GetCharWidth();
427 sizeBtn
.y
+= win
->GetCharHeight()/2;
430 // account for the shield UAC icon if we have it
431 if ( flags
& Size_AuthNeeded
)
432 sizeBtn
.x
+= wxSystemSettings::GetMetric(wxSYS_SMALLICON_X
);
437 wxSize
wxMSWButton::ComputeBestFittingSize(wxControl
*btn
, int flags
)
442 dc
.GetMultiLineTextExtent(btn
->GetLabelText(), &sizeBtn
.x
, &sizeBtn
.y
);
444 return GetFittingSize(btn
, sizeBtn
, flags
);
447 wxSize
wxMSWButton::IncreaseToStdSizeAndCache(wxControl
*btn
, const wxSize
& size
)
449 wxSize
sizeBtn(size
);
451 // The 50x14 button size is documented in the "Recommended sizing and
452 // spacing" section of MSDN layout article.
454 // Note that we intentionally don't use GetDefaultSize() here, because
455 // it's inexact -- dialog units depend on this dialog's font.
456 const wxSize sizeDef
= btn
->ConvertDialogToPixels(wxSize(50, 14));
458 // All buttons should have at least the standard size, unless the user
459 // explicitly wants them to be as small as possible and used wxBU_EXACTFIT
460 // style to indicate this.
461 const bool incToStdSize
= !btn
->HasFlag(wxBU_EXACTFIT
);
464 if ( sizeBtn
.x
< sizeDef
.x
)
465 sizeBtn
.x
= sizeDef
.x
;
468 // Notice that we really want to make all buttons with text label equally
469 // high, otherwise they look ugly and the existing code using wxBU_EXACTFIT
470 // only uses it to control width and not height.
471 if ( incToStdSize
|| !btn
->GetLabel().empty() )
473 if ( sizeBtn
.y
< sizeDef
.y
)
474 sizeBtn
.y
= sizeDef
.y
;
477 btn
->CacheBestSize(sizeBtn
);
482 // ----------------------------------------------------------------------------
483 // creation/destruction
484 // ----------------------------------------------------------------------------
486 wxAnyButton::~wxAnyButton()
491 #endif // wxUSE_MARKUP
494 void wxAnyButton::SetLabel(const wxString
& label
)
496 wxMSWButton::UpdateMultilineStyle(GetHwnd(), label
);
498 wxAnyButtonBase::SetLabel(label
);
501 // If we have a plain text label, we shouldn't be using markup any longer.
507 // Unfortunately we don't really know whether we can reset the button
508 // to be non-owner-drawn or not: if we had made it owner-drawn just
509 // because of a call to SetLabelMarkup(), we could, but not if there
510 // were [also] calls to Set{Fore,Back}groundColour(). If it's really a
511 // problem to have button remain owner-drawn forever just because it
512 // had markup label once, we should record the reason for our current
513 // owner-drawnness and check it here.
515 #endif // wxUSE_MARKUP
518 // ----------------------------------------------------------------------------
519 // size management including autosizing
520 // ----------------------------------------------------------------------------
522 void wxAnyButton::AdjustForBitmapSize(wxSize
&size
) const
524 wxCHECK_RET( m_imageData
, wxT("shouldn't be called if no image") );
526 // account for the bitmap size
527 const wxSize sizeBmp
= m_imageData
->GetBitmap(State_Normal
).GetSize();
528 const wxDirection dirBmp
= m_imageData
->GetBitmapPosition();
529 if ( dirBmp
== wxLEFT
|| dirBmp
== wxRIGHT
)
532 if ( sizeBmp
.y
> size
.y
)
535 else // bitmap on top/below the text
538 if ( sizeBmp
.x
> size
.x
)
542 // account for the user-specified margins
543 size
+= 2*m_imageData
->GetBitmapMargins();
545 // and also for the margins we always add internally (unless we have no
546 // border at all in which case the button has exactly the same size as
547 // bitmap and so no margins should be used)
548 if ( !HasFlag(wxBORDER_NONE
) )
553 if ( wxUxThemeEngine::GetIfActive() )
555 wxUxThemeHandle
theme(const_cast<wxAnyButton
*>(this), L
"BUTTON");
558 wxUxThemeEngine::Get()->GetThemeMargins(theme
, NULL
,
565 // XP doesn't draw themed buttons correctly when the client
566 // area is smaller than 8x8 - enforce this minimum size for
568 size
.IncTo(wxSize(8, 8));
570 marginH
= margins
.cxLeftWidth
+ margins
.cxRightWidth
571 + 2*XP_BUTTON_EXTRA_MARGIN
;
572 marginV
= margins
.cyTopHeight
+ margins
.cyBottomHeight
573 + 2*XP_BUTTON_EXTRA_MARGIN
;
576 #endif // wxUSE_UXTHEME
579 marginV
= OD_BUTTON_MARGIN
;
582 size
.IncBy(marginH
, marginV
);
586 wxSize
wxAnyButton::DoGetBestSize() const
588 wxAnyButton
* const self
= const_cast<wxAnyButton
*>(this);
592 // Account for the text part if we have it.
596 if ( HasFlag(wxBU_EXACTFIT
) )
597 flags
|= wxMSWButton::Size_ExactFit
;
598 if ( DoGetAuthNeeded() )
599 flags
|= wxMSWButton::Size_AuthNeeded
;
605 size
= wxMSWButton::GetFittingSize(self
,
606 m_markupText
->Measure(dc
),
609 else // Normal plain text (but possibly multiline) label.
610 #endif // wxUSE_MARKUP
612 size
= wxMSWButton::ComputeBestFittingSize(self
, flags
);
617 AdjustForBitmapSize(size
);
619 return wxMSWButton::IncreaseToStdSizeAndCache(self
, size
);
622 // ----------------------------------------------------------------------------
623 // event/message handlers
624 // ----------------------------------------------------------------------------
626 WXLRESULT
wxAnyButton::MSWWindowProc(WXUINT nMsg
, WXWPARAM wParam
, WXLPARAM lParam
)
628 if ( nMsg
== WM_LBUTTONDBLCLK
)
630 // emulate a click event to force an owner-drawn button to change its
631 // appearance - without this, it won't do it
632 (void)wxControl::MSWWindowProc(WM_LBUTTONDOWN
, wParam
, lParam
);
634 // and continue with processing the message normally as well
637 else if ( nMsg
== WM_THEMECHANGED
)
639 // need to recalculate the best size here
640 // as the theme size might have changed
641 InvalidateBestSize();
643 #endif // wxUSE_UXTHEME
644 // must use m_mouseInWindow here instead of IsMouseInWindow()
645 // since we need to know the first time the mouse enters the window
646 // and IsMouseInWindow() would return true in this case
647 else if ( (nMsg
== WM_MOUSEMOVE
&& !m_mouseInWindow
) ||
648 nMsg
== WM_MOUSELEAVE
)
654 wxUxThemeEngine::GetIfActive() ||
655 #endif // wxUSE_UXTHEME
656 (m_imageData
&& m_imageData
->GetBitmap(State_Current
).IsOk())
664 // let the base class do all real processing
665 return wxControl::MSWWindowProc(nMsg
, wParam
, lParam
);
668 // ----------------------------------------------------------------------------
670 // ----------------------------------------------------------------------------
672 wxBitmap
wxAnyButton::DoGetBitmap(State which
) const
674 return m_imageData
? m_imageData
->GetBitmap(which
) : wxBitmap();
677 void wxAnyButton::DoSetBitmap(const wxBitmap
& bitmap
, State which
)
680 wxXPButtonImageData
*oldData
= NULL
;
681 #endif // wxUSE_UXTHEME
683 // Check if we already had bitmaps of different size.
685 bitmap
.GetSize() != m_imageData
->GetBitmap(State_Normal
).GetSize() )
687 wxASSERT_MSG( (which
== State_Normal
) || bitmap
.IsNull(),
688 "Must set normal bitmap with the new size first" );
691 if ( ShowsLabel() && wxUxThemeEngine::GetIfActive() )
693 // We can't change the size of the images stored in wxImageList
694 // in wxXPButtonImageData::m_iml so force recreating it below but
695 // keep the current data to copy its values into the new one.
696 oldData
= static_cast<wxXPButtonImageData
*>(m_imageData
);
699 #endif // wxUSE_UXTHEME
700 //else: wxODButtonImageData doesn't require anything special
703 // allocate the image data when the first bitmap is set
707 // using image list doesn't work correctly if we don't have any label
708 // (even if we use BUTTON_IMAGELIST_ALIGN_CENTER alignment and
709 // BS_BITMAP style), at least under Windows 2003 so use owner drawn
710 // strategy for bitmap-only buttons
711 if ( ShowsLabel() && wxUxThemeEngine::GetIfActive() )
713 m_imageData
= new wxXPButtonImageData(this, bitmap
);
717 // Preserve the old values in case the user changed them.
718 m_imageData
->SetBitmapPosition(oldData
->GetBitmapPosition());
720 const wxSize oldMargins
= oldData
->GetBitmapMargins();
721 m_imageData
->SetBitmapMargins(oldMargins
.x
, oldMargins
.y
);
723 // No need to preserve the bitmaps though as they were of wrong
730 #endif // wxUSE_UXTHEME
732 m_imageData
= new wxODButtonImageData(this, bitmap
);
738 m_imageData
->SetBitmap(bitmap
, which
);
741 // it should be enough to only invalidate the best size when the normal
742 // bitmap changes as all bitmaps assigned to the button should be of the
744 if ( which
== State_Normal
)
745 InvalidateBestSize();
750 wxSize
wxAnyButton::DoGetBitmapMargins() const
752 return m_imageData
? m_imageData
->GetBitmapMargins() : wxSize(0, 0);
755 void wxAnyButton::DoSetBitmapMargins(wxCoord x
, wxCoord y
)
757 wxCHECK_RET( m_imageData
, "SetBitmap() must be called first" );
759 m_imageData
->SetBitmapMargins(x
, y
);
760 InvalidateBestSize();
763 void wxAnyButton::DoSetBitmapPosition(wxDirection dir
)
765 wxCHECK_RET( m_imageData
, "SetBitmap() must be called first" );
767 m_imageData
->SetBitmapPosition(dir
);
768 InvalidateBestSize();
771 // ----------------------------------------------------------------------------
773 // ----------------------------------------------------------------------------
777 bool wxAnyButton::DoSetLabelMarkup(const wxString
& markup
)
779 if ( !wxAnyButtonBase::DoSetLabelMarkup(markup
) )
784 m_markupText
= new wxMarkupText(markup
);
789 // We are already owner-drawn so just update the text.
790 m_markupText
->SetMarkup(markup
);
798 #endif // wxUSE_MARKUP
800 // ----------------------------------------------------------------------------
801 // owner-drawn buttons support
802 // ----------------------------------------------------------------------------
808 // return the button state using both the ODS_XXX flags specified in state
809 // parameter and the current button state
810 wxAnyButton::State
GetButtonState(wxAnyButton
*btn
, UINT state
)
812 if ( state
& ODS_DISABLED
)
813 return wxAnyButton::State_Disabled
;
815 if ( state
& ODS_SELECTED
)
816 return wxAnyButton::State_Pressed
;
818 if ( btn
->HasCapture() || btn
->IsMouseInWindow() )
819 return wxAnyButton::State_Current
;
821 if ( state
& ODS_FOCUS
)
822 return wxAnyButton::State_Focused
;
824 return btn
->GetNormalState();
827 void DrawButtonText(HDC hdc
,
832 const wxString text
= btn
->GetLabel();
834 if ( text
.find(wxT('\n')) != wxString::npos
)
836 // draw multiline label
838 // center text horizontally in any case
841 // first we need to compute its bounding rect
843 ::CopyRect(&rc
, pRect
);
844 ::DrawText(hdc
, text
.t_str(), text
.length(), &rc
,
845 DT_CENTER
| DT_CALCRECT
);
847 // now center this rect inside the entire button area
848 const LONG w
= rc
.right
- rc
.left
;
849 const LONG h
= rc
.bottom
- rc
.top
;
850 rc
.left
= pRect
->left
+ (pRect
->right
- pRect
->left
)/2 - w
/2;
851 rc
.right
= rc
.left
+w
;
852 rc
.top
= pRect
->top
+ (pRect
->bottom
- pRect
->top
)/2 - h
/2;
853 rc
.bottom
= rc
.top
+h
;
855 ::DrawText(hdc
, text
.t_str(), text
.length(), &rc
, flags
);
857 else // single line label
859 // translate wx button flags to alignment flags for DrawText()
860 if ( btn
->HasFlag(wxBU_RIGHT
) )
864 else if ( !btn
->HasFlag(wxBU_LEFT
) )
868 //else: DT_LEFT is the default anyhow (and its value is 0 too)
870 if ( btn
->HasFlag(wxBU_BOTTOM
) )
874 else if ( !btn
->HasFlag(wxBU_TOP
) )
878 //else: as above, DT_TOP is the default
880 // notice that we must have DT_SINGLELINE for vertical alignment flags
882 ::DrawText(hdc
, text
.t_str(), text
.length(), pRect
,
883 flags
| DT_SINGLELINE
);
887 void DrawRect(HDC hdc
, const RECT
& r
)
889 wxDrawLine(hdc
, r
.left
, r
.top
, r
.right
, r
.top
);
890 wxDrawLine(hdc
, r
.right
, r
.top
, r
.right
, r
.bottom
);
891 wxDrawLine(hdc
, r
.right
, r
.bottom
, r
.left
, r
.bottom
);
892 wxDrawLine(hdc
, r
.left
, r
.bottom
, r
.left
, r
.top
);
896 The button frame looks like this normally:
899 WHHHHHHHHHHHHHHHHGB W = white (HILIGHT)
900 WH GB H = light grey (LIGHT)
901 WH GB G = dark grey (SHADOW)
902 WH GB B = black (DKSHADOW)
907 When the button is selected, the button becomes like this (the total button
908 size doesn't change):
919 When the button is pushed (while selected) it is like:
930 void DrawButtonFrame(HDC hdc
, RECT
& rectBtn
,
931 bool selected
, bool pushed
)
934 CopyRect(&r
, &rectBtn
);
936 AutoHPEN
hpenBlack(GetSysColor(COLOR_3DDKSHADOW
)),
937 hpenGrey(GetSysColor(COLOR_3DSHADOW
)),
938 hpenLightGr(GetSysColor(COLOR_3DLIGHT
)),
939 hpenWhite(GetSysColor(COLOR_3DHILIGHT
));
941 SelectInHDC
selectPen(hdc
, hpenBlack
);
950 (void)SelectObject(hdc
, hpenGrey
);
951 ::InflateRect(&r
, -1, -1);
961 ::InflateRect(&r
, -1, -1);
964 wxDrawLine(hdc
, r
.left
, r
.bottom
, r
.right
, r
.bottom
);
965 wxDrawLine(hdc
, r
.right
, r
.bottom
, r
.right
, r
.top
- 1);
967 (void)SelectObject(hdc
, hpenWhite
);
968 wxDrawLine(hdc
, r
.left
, r
.bottom
- 1, r
.left
, r
.top
);
969 wxDrawLine(hdc
, r
.left
, r
.top
, r
.right
, r
.top
);
971 (void)SelectObject(hdc
, hpenLightGr
);
972 wxDrawLine(hdc
, r
.left
+ 1, r
.bottom
- 2, r
.left
+ 1, r
.top
+ 1);
973 wxDrawLine(hdc
, r
.left
+ 1, r
.top
+ 1, r
.right
- 1, r
.top
+ 1);
975 (void)SelectObject(hdc
, hpenGrey
);
976 wxDrawLine(hdc
, r
.left
+ 1, r
.bottom
- 1, r
.right
- 1, r
.bottom
- 1);
977 wxDrawLine(hdc
, r
.right
- 1, r
.bottom
- 1, r
.right
- 1, r
.top
);
980 InflateRect(&rectBtn
, -OD_BUTTON_MARGIN
, -OD_BUTTON_MARGIN
);
984 void DrawXPBackground(wxAnyButton
*button
, HDC hdc
, RECT
& rectBtn
, UINT state
)
986 wxUxThemeHandle
theme(button
, L
"BUTTON");
988 // this array is indexed by wxAnyButton::State values and so must be kept in
990 static const int uxStates
[] =
992 PBS_NORMAL
, PBS_HOT
, PBS_PRESSED
, PBS_DISABLED
, PBS_DEFAULTED
995 int iState
= uxStates
[GetButtonState(button
, state
)];
997 wxUxThemeEngine
* const engine
= wxUxThemeEngine::Get();
999 // draw parent background if needed
1000 if ( engine
->IsThemeBackgroundPartiallyTransparent
1007 // Set this button as the one whose background is being erased: this
1008 // allows our WM_ERASEBKGND handler used by DrawThemeParentBackground()
1009 // to correctly align the background brush with this window instead of
1010 // the parent window to which WM_ERASEBKGND is sent. Notice that this
1011 // doesn't work with custom user-defined EVT_ERASE_BACKGROUND handlers
1012 // as they won't be aligned but unfortunately all the attempts to fix
1013 // it by shifting DC origin before calling DrawThemeParentBackground()
1014 // failed to work so we at least do this, even though this is far from
1015 // being the perfect solution.
1016 wxWindowBeingErased
= button
;
1018 engine
->DrawThemeParentBackground(GetHwndOf(button
), hdc
, &rectBtn
);
1020 wxWindowBeingErased
= NULL
;
1024 engine
->DrawThemeBackground(theme
, hdc
, BP_PUSHBUTTON
, iState
,
1027 // calculate content area margins
1029 engine
->GetThemeMargins(theme
, hdc
, BP_PUSHBUTTON
, iState
,
1030 TMT_CONTENTMARGINS
, &rectBtn
, &margins
);
1031 ::InflateRect(&rectBtn
, -margins
.cxLeftWidth
, -margins
.cyTopHeight
);
1032 ::InflateRect(&rectBtn
, -XP_BUTTON_EXTRA_MARGIN
, -XP_BUTTON_EXTRA_MARGIN
);
1034 if ( button
->UseBgCol() )
1036 COLORREF colBg
= wxColourToRGB(button
->GetBackgroundColour());
1037 AutoHBRUSH
hbrushBackground(colBg
);
1039 // don't overwrite the focus rect
1041 ::CopyRect(&rectClient
, &rectBtn
);
1042 ::InflateRect(&rectClient
, -1, -1);
1043 FillRect(hdc
, &rectClient
, hbrushBackground
);
1046 #endif // wxUSE_UXTHEME
1048 } // anonymous namespace
1050 // ----------------------------------------------------------------------------
1051 // owner drawn buttons support
1052 // ----------------------------------------------------------------------------
1054 void wxAnyButton::MakeOwnerDrawn()
1056 if ( !IsOwnerDrawn() )
1059 // note that BS_OWNERDRAW is not independent from other style bits
1060 long style
= GetWindowLong(GetHwnd(), GWL_STYLE
);
1061 style
&= ~(BS_3STATE
| BS_AUTO3STATE
| BS_AUTOCHECKBOX
| BS_AUTORADIOBUTTON
| BS_CHECKBOX
| BS_DEFPUSHBUTTON
| BS_GROUPBOX
| BS_PUSHBUTTON
| BS_RADIOBUTTON
| BS_PUSHLIKE
);
1062 style
|= BS_OWNERDRAW
;
1063 SetWindowLong(GetHwnd(), GWL_STYLE
, style
);
1067 bool wxAnyButton::IsOwnerDrawn() const
1069 long style
= GetWindowLong(GetHwnd(), GWL_STYLE
);
1070 return ( (style
& BS_OWNERDRAW
) == BS_OWNERDRAW
);
1073 bool wxAnyButton::SetBackgroundColour(const wxColour
&colour
)
1075 if ( !wxControl::SetBackgroundColour(colour
) )
1088 bool wxAnyButton::SetForegroundColour(const wxColour
&colour
)
1090 if ( !wxControl::SetForegroundColour(colour
) )
1103 bool wxAnyButton::MSWOnDraw(WXDRAWITEMSTRUCT
*wxdis
)
1105 LPDRAWITEMSTRUCT lpDIS
= (LPDRAWITEMSTRUCT
)wxdis
;
1106 HDC hdc
= lpDIS
->hDC
;
1108 UINT state
= lpDIS
->itemState
;
1109 switch ( GetButtonState(this, state
) )
1111 case State_Disabled
:
1112 state
|= ODS_DISABLED
;
1115 state
|= ODS_SELECTED
;
1124 bool pushed
= (SendMessage(GetHwnd(), BM_GETSTATE
, 0, 0) & BST_PUSHED
) != 0;
1127 CopyRect(&rectBtn
, &lpDIS
->rcItem
);
1129 // draw the button background
1130 if ( !HasFlag(wxBORDER_NONE
) )
1133 if ( wxUxThemeEngine::GetIfActive() )
1135 DrawXPBackground(this, hdc
, rectBtn
, state
);
1138 #endif // wxUSE_UXTHEME
1140 COLORREF colBg
= wxColourToRGB(GetBackgroundColour());
1142 // first, draw the background
1143 AutoHBRUSH
hbrushBackground(colBg
);
1144 FillRect(hdc
, &rectBtn
, hbrushBackground
);
1146 // draw the border for the current state
1147 bool selected
= (state
& ODS_SELECTED
) != 0;
1151 tlw
= wxDynamicCast(wxGetTopLevelParent(this), wxTopLevelWindow
);
1154 selected
= tlw
->GetDefaultItem() == this;
1158 DrawButtonFrame(hdc
, rectBtn
, selected
, pushed
);
1161 // draw the focus rectangle if we need it
1162 if ( (state
& ODS_FOCUS
) && !(state
& ODS_NOFOCUSRECT
) )
1164 DrawFocusRect(hdc
, &rectBtn
);
1167 if ( !wxUxThemeEngine::GetIfActive() )
1168 #endif // wxUSE_UXTHEME
1172 // the label is shifted by 1 pixel to create "pushed" effect
1173 OffsetRect(&rectBtn
, 1, 1);
1180 // draw the image, if any
1183 wxBitmap bmp
= m_imageData
->GetBitmap(GetButtonState(this, state
));
1185 bmp
= m_imageData
->GetBitmap(State_Normal
);
1187 const wxSize sizeBmp
= bmp
.GetSize();
1188 const wxSize margin
= m_imageData
->GetBitmapMargins();
1189 const wxSize
sizeBmpWithMargins(sizeBmp
+ 2*margin
);
1190 wxRect
rectButton(wxRectFromRECT(rectBtn
));
1192 // for simplicity, we start with centred rectangle and then move it to
1193 // the appropriate edge
1194 wxRect rectBitmap
= wxRect(sizeBmp
).CentreIn(rectButton
);
1196 // move bitmap only if we have a label, otherwise keep it centered
1199 switch ( m_imageData
->GetBitmapPosition() )
1202 wxFAIL_MSG( "invalid direction" );
1206 rectBitmap
.x
= rectButton
.x
+ margin
.x
;
1207 rectButton
.x
+= sizeBmpWithMargins
.x
;
1208 rectButton
.width
-= sizeBmpWithMargins
.x
;
1212 rectBitmap
.x
= rectButton
.GetRight() - sizeBmp
.x
- margin
.x
;
1213 rectButton
.width
-= sizeBmpWithMargins
.x
;
1217 rectBitmap
.y
= rectButton
.y
+ margin
.y
;
1218 rectButton
.y
+= sizeBmpWithMargins
.y
;
1219 rectButton
.height
-= sizeBmpWithMargins
.y
;
1223 rectBitmap
.y
= rectButton
.GetBottom() - sizeBmp
.y
- margin
.y
;
1224 rectButton
.height
-= sizeBmpWithMargins
.y
;
1229 wxDCTemp
dst((WXHDC
)hdc
);
1230 dst
.DrawBitmap(bmp
, rectBitmap
.GetPosition(), true);
1232 wxCopyRectToRECT(rectButton
, rectBtn
);
1236 // finally draw the label
1239 COLORREF colFg
= state
& ODS_DISABLED
1240 ? ::GetSysColor(COLOR_GRAYTEXT
)
1241 : wxColourToRGB(GetForegroundColour());
1243 wxTextColoursChanger
changeFg(hdc
, colFg
, CLR_INVALID
);
1244 wxBkModeChanger
changeBkMode(hdc
, wxBRUSHSTYLE_TRANSPARENT
);
1249 wxDCTemp
dc((WXHDC
)hdc
);
1250 dc
.SetTextForeground(wxColour(colFg
));
1251 dc
.SetFont(GetFont());
1253 m_markupText
->Render(dc
, wxRectFromRECT(rectBtn
),
1255 ? wxMarkupText::Render_Default
1256 : wxMarkupText::Render_ShowAccels
);
1258 else // Plain text label
1259 #endif // wxUSE_MARKUP
1261 // notice that DT_HIDEPREFIX doesn't work on old (pre-Windows 2000)
1262 // systems but by happy coincidence ODS_NOACCEL is not used under
1263 // them neither so DT_HIDEPREFIX should never be used there
1264 DrawButtonText(hdc
, &rectBtn
, this,
1265 state
& ODS_NOACCEL
? DT_HIDEPREFIX
: 0);
1272 #endif // wxHAS_ANY_BUTTON