1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/msw/button.cpp
4 // Author: Julian Smart
8 // Copyright: (c) Julian Smart
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
12 // ============================================================================
14 // ============================================================================
16 // ----------------------------------------------------------------------------
18 // ----------------------------------------------------------------------------
20 // For compilers that support precompilation, includes "wx.h".
21 #include "wx/wxprec.h"
29 #include "wx/button.h"
35 #include "wx/bmpbuttn.h"
36 #include "wx/settings.h"
37 #include "wx/dcscreen.h"
38 #include "wx/dcclient.h"
39 #include "wx/toplevel.h"
40 #include "wx/msw/wrapcctl.h"
41 #include "wx/msw/private.h"
42 #include "wx/msw/missing.h"
45 #include "wx/imaglist.h"
46 #include "wx/stockitem.h"
47 #include "wx/msw/private/button.h"
48 #include "wx/msw/private/dc.h"
49 #include "wx/private/window.h"
52 #include "wx/generic/private/markuptext.h"
53 #endif // wxUSE_MARKUP
55 using namespace wxMSWImpl
;
58 #include "wx/msw/uxtheme.h"
60 // no need to include tmschema.h
62 #define BP_PUSHBUTTON 1
67 #define PBS_DISABLED 4
68 #define PBS_DEFAULTED 5
70 #define TMT_CONTENTMARGINS 3602
73 // provide the necessary declarations ourselves if they're missing from
75 #ifndef BCM_SETIMAGELIST
76 #define BCM_SETIMAGELIST 0x1602
77 #define BCM_SETTEXTMARGIN 0x1604
81 BUTTON_IMAGELIST_ALIGN_LEFT
,
82 BUTTON_IMAGELIST_ALIGN_RIGHT
,
83 BUTTON_IMAGELIST_ALIGN_TOP
,
84 BUTTON_IMAGELIST_ALIGN_BOTTOM
87 struct BUTTON_IMAGELIST
94 #endif // wxUSE_UXTHEME
96 #ifndef WM_THEMECHANGED
97 #define WM_THEMECHANGED 0x031A
101 #define ODS_NOACCEL 0x0100
104 #ifndef ODS_NOFOCUSRECT
105 #define ODS_NOFOCUSRECT 0x0200
108 #ifndef DT_HIDEPREFIX
109 #define DT_HIDEPREFIX 0x00100000
112 // set the value for BCM_SETSHIELD (for the UAC shield) if it's not defined in
114 #ifndef BCM_SETSHIELD
115 #define BCM_SETSHIELD 0x160c
118 // ----------------------------------------------------------------------------
120 // ----------------------------------------------------------------------------
122 // we use different data classes for owner drawn buttons and for themed XP ones
124 class wxButtonImageData
127 wxButtonImageData() { }
128 virtual ~wxButtonImageData() { }
130 virtual wxBitmap
GetBitmap(wxButton::State which
) const = 0;
131 virtual void SetBitmap(const wxBitmap
& bitmap
, wxButton::State which
) = 0;
133 virtual wxSize
GetBitmapMargins() const = 0;
134 virtual void SetBitmapMargins(wxCoord x
, wxCoord y
) = 0;
136 virtual wxDirection
GetBitmapPosition() const = 0;
137 virtual void SetBitmapPosition(wxDirection dir
) = 0;
140 wxDECLARE_NO_COPY_CLASS(wxButtonImageData
);
146 // the gap between button edge and the interior area used by Windows for the
148 const int OD_BUTTON_MARGIN
= 4;
150 class wxODButtonImageData
: public wxButtonImageData
153 wxODButtonImageData(wxButton
*btn
, const wxBitmap
& bitmap
)
155 SetBitmap(bitmap
, wxButton::State_Normal
);
159 // we use margins when we have both bitmap and text, but when we have
160 // only the bitmap it should take up the entire button area
161 if ( btn
->ShowsLabel() )
163 m_margin
.x
= btn
->GetCharWidth();
164 m_margin
.y
= btn
->GetCharHeight() / 2;
168 virtual wxBitmap
GetBitmap(wxButton::State which
) const
170 return m_bitmaps
[which
];
173 virtual void SetBitmap(const wxBitmap
& bitmap
, wxButton::State which
)
175 m_bitmaps
[which
] = bitmap
;
178 virtual wxSize
GetBitmapMargins() const
183 virtual void SetBitmapMargins(wxCoord x
, wxCoord y
)
185 m_margin
= wxSize(x
, y
);
188 virtual wxDirection
GetBitmapPosition() const
193 virtual void SetBitmapPosition(wxDirection dir
)
199 // just store the values passed to us to be able to retrieve them later
200 // from the drawing code
201 wxBitmap m_bitmaps
[wxButton::State_Max
];
205 wxDECLARE_NO_COPY_CLASS(wxODButtonImageData
);
210 // somehow the margin is one pixel greater than the value returned by
211 // GetThemeMargins() call
212 const int XP_BUTTON_EXTRA_MARGIN
= 1;
214 class wxXPButtonImageData
: public wxButtonImageData
217 // we must be constructed with the size of our images as we need to create
219 wxXPButtonImageData(wxButton
*btn
, const wxBitmap
& bitmap
)
220 : m_iml(bitmap
.GetWidth(), bitmap
.GetHeight(), true /* use mask */,
221 wxButton::State_Max
),
222 m_hwndBtn(GetHwndOf(btn
))
224 // initialize all bitmaps to normal state
225 for ( int n
= 0; n
< wxButton::State_Max
; n
++ )
230 m_data
.himl
= GetHimagelistOf(&m_iml
);
232 // no margins by default
234 m_data
.margin
.right
=
236 m_data
.margin
.bottom
= 0;
238 // use default alignment
239 m_data
.uAlign
= BUTTON_IMAGELIST_ALIGN_LEFT
;
244 virtual wxBitmap
GetBitmap(wxButton::State which
) const
246 return m_iml
.GetBitmap(which
);
249 virtual void SetBitmap(const wxBitmap
& bitmap
, wxButton::State which
)
251 m_iml
.Replace(which
, bitmap
);
256 virtual wxSize
GetBitmapMargins() const
258 return wxSize(m_data
.margin
.left
, m_data
.margin
.top
);
261 virtual void SetBitmapMargins(wxCoord x
, wxCoord y
)
263 RECT
& margin
= m_data
.margin
;
269 if ( !::SendMessage(m_hwndBtn
, BCM_SETTEXTMARGIN
, 0, (LPARAM
)&margin
) )
271 wxLogDebug("SendMessage(BCM_SETTEXTMARGIN) failed");
275 virtual wxDirection
GetBitmapPosition() const
277 switch ( m_data
.uAlign
)
280 wxFAIL_MSG( "invalid image alignment" );
283 case BUTTON_IMAGELIST_ALIGN_LEFT
:
286 case BUTTON_IMAGELIST_ALIGN_RIGHT
:
289 case BUTTON_IMAGELIST_ALIGN_TOP
:
292 case BUTTON_IMAGELIST_ALIGN_BOTTOM
:
297 virtual void SetBitmapPosition(wxDirection dir
)
303 wxFAIL_MSG( "invalid direction" );
307 alignNew
= BUTTON_IMAGELIST_ALIGN_LEFT
;
311 alignNew
= BUTTON_IMAGELIST_ALIGN_RIGHT
;
315 alignNew
= BUTTON_IMAGELIST_ALIGN_TOP
;
319 alignNew
= BUTTON_IMAGELIST_ALIGN_BOTTOM
;
323 if ( alignNew
!= m_data
.uAlign
)
325 m_data
.uAlign
= alignNew
;
331 void UpdateImageInfo()
333 if ( !::SendMessage(m_hwndBtn
, BCM_SETIMAGELIST
, 0, (LPARAM
)&m_data
) )
335 wxLogDebug("SendMessage(BCM_SETIMAGELIST) failed");
339 // we store image list separately to be able to use convenient wxImageList
340 // methods instead of working with raw HIMAGELIST
343 // store the rest of the data in BCM_SETIMAGELIST-friendly form
344 BUTTON_IMAGELIST m_data
;
346 // the button we're associated with
347 const HWND m_hwndBtn
;
350 wxDECLARE_NO_COPY_CLASS(wxXPButtonImageData
);
353 #endif // wxUSE_UXTHEME
355 } // anonymous namespace
357 // ----------------------------------------------------------------------------
359 // ----------------------------------------------------------------------------
361 // ============================================================================
363 // ============================================================================
365 // ----------------------------------------------------------------------------
366 // helper functions from wx/msw/private/button.h
367 // ----------------------------------------------------------------------------
369 void wxMSWButton::UpdateMultilineStyle(HWND hwnd
, const wxString
& label
)
371 // update BS_MULTILINE style depending on the new label (resetting it
372 // doesn't seem to do anything very useful but it shouldn't hurt and we do
373 // have to set it whenever the label becomes multi line as otherwise it
374 // wouldn't be shown correctly as we don't use BS_MULTILINE when creating
375 // the control unless it already has new lines in its label)
376 long styleOld
= ::GetWindowLong(hwnd
, GWL_STYLE
),
378 if ( label
.find(wxT('\n')) != wxString::npos
)
379 styleNew
= styleOld
| BS_MULTILINE
;
381 styleNew
= styleOld
& ~BS_MULTILINE
;
383 if ( styleNew
!= styleOld
)
384 ::SetWindowLong(hwnd
, GWL_STYLE
, styleNew
);
387 wxSize
wxMSWButton::GetFittingSize(wxWindow
*win
,
388 const wxSize
& sizeLabel
,
391 // FIXME: this is pure guesswork, need to retrieve the real button margins
392 wxSize sizeBtn
= sizeLabel
;
394 sizeBtn
.x
+= 3*win
->GetCharWidth();
395 sizeBtn
.y
+= win
->GetCharHeight()/2;
397 // account for the shield UAC icon if we have it
398 if ( flags
& Size_AuthNeeded
)
399 sizeBtn
.x
+= wxSystemSettings::GetMetric(wxSYS_SMALLICON_X
);
404 wxSize
wxMSWButton::ComputeBestFittingSize(wxControl
*btn
, int flags
)
409 dc
.GetMultiLineTextExtent(btn
->GetLabelText(), &sizeBtn
.x
, &sizeBtn
.y
);
411 return GetFittingSize(btn
, sizeBtn
, flags
);
414 wxSize
wxMSWButton::IncreaseToStdSizeAndCache(wxControl
*btn
, const wxSize
& size
)
416 wxSize
sizeBtn(size
);
418 // All buttons have at least the standard height and, unless the user
419 // explicitly wants them to be as small as possible and used wxBU_EXACTFIT
420 // style to indicate this, of at least the standard width too.
422 // Notice that we really want to make all buttons equally high, otherwise
423 // they look ugly and the existing code using wxBU_EXACTFIT only uses it to
424 // control width and not height.
426 // The 50x14 button size is documented in the "Recommended sizing and
427 // spacing" section of MSDN layout article.
429 // Note that we intentionally don't use GetDefaultSize() here, because
430 // it's inexact -- dialog units depend on this dialog's font.
431 const wxSize sizeDef
= btn
->ConvertDialogToPixels(wxSize(50, 14));
432 if ( !btn
->HasFlag(wxBU_EXACTFIT
) )
434 if ( sizeBtn
.x
< sizeDef
.x
)
435 sizeBtn
.x
= sizeDef
.x
;
437 if ( sizeBtn
.y
< sizeDef
.y
)
438 sizeBtn
.y
= sizeDef
.y
;
440 btn
->CacheBestSize(sizeBtn
);
445 // ----------------------------------------------------------------------------
446 // creation/destruction
447 // ----------------------------------------------------------------------------
449 bool wxButton::Create(wxWindow
*parent
,
455 const wxValidator
& validator
,
456 const wxString
& name
)
459 if (label
.empty() && wxIsStockID(id
))
461 // On Windows, some buttons aren't supposed to have mnemonics
462 label
= wxGetStockLabel
465 id
== wxID_OK
|| id
== wxID_CANCEL
|| id
== wxID_CLOSE
467 : wxSTOCK_WITH_MNEMONIC
471 if ( !CreateControl(parent
, id
, pos
, size
, style
, validator
, name
) )
475 WXDWORD msStyle
= MSWGetStyle(style
, &exstyle
);
477 // if the label contains several lines we must explicitly tell the button
478 // about it or it wouldn't draw it correctly ("\n"s would just appear as
481 // NB: we do it here and not in MSWGetStyle() because we need the label
482 // value and the label is not set yet when MSWGetStyle() is called
483 msStyle
|= wxMSWButton::GetMultilineStyle(label
);
485 return MSWCreateControl(wxT("BUTTON"), msStyle
, pos
, size
, label
, exstyle
);
488 wxButton::~wxButton()
490 wxTopLevelWindow
*tlw
= wxDynamicCast(wxGetTopLevelParent(this), wxTopLevelWindow
);
491 if ( tlw
&& tlw
->GetTmpDefaultItem() == this )
499 #endif // wxUSE_MARKUP
502 // ----------------------------------------------------------------------------
504 // ----------------------------------------------------------------------------
506 WXDWORD
wxButton::MSWGetStyle(long style
, WXDWORD
*exstyle
) const
508 // buttons never have an external border, they draw their own one
509 WXDWORD msStyle
= wxControl::MSWGetStyle
511 (style
& ~wxBORDER_MASK
) | wxBORDER_NONE
, exstyle
514 // we must use WS_CLIPSIBLINGS with the buttons or they would draw over
515 // each other in any resizeable dialog which has more than one button in
517 msStyle
|= WS_CLIPSIBLINGS
;
519 // don't use "else if" here: weird as it is, but you may combine wxBU_LEFT
520 // and wxBU_RIGHT to get BS_CENTER!
521 if ( style
& wxBU_LEFT
)
523 if ( style
& wxBU_RIGHT
)
525 if ( style
& wxBU_TOP
)
527 if ( style
& wxBU_BOTTOM
)
528 msStyle
|= BS_BOTTOM
;
531 if ( style
& wxNO_BORDER
)
533 #endif // __WXWINCE__
538 void wxButton::SetLabel(const wxString
& label
)
540 wxMSWButton::UpdateMultilineStyle(GetHwnd(), label
);
542 wxButtonBase::SetLabel(label
);
545 // If we have a plain text label, we shouldn't be using markup any longer.
551 // Unfortunately we don't really know whether we can reset the button
552 // to be non-owner-drawn or not: if we had made it owner-drawn just
553 // because of a call to SetLabelMarkup(), we could, but not if there
554 // were [also] calls to Set{Fore,Back}groundColour(). If it's really a
555 // problem to have button remain owner-drawn forever just because it
556 // had markup label once, we should record the reason for our current
557 // owner-drawnness and check it here.
559 #endif // wxUSE_MARKUP
562 // ----------------------------------------------------------------------------
563 // size management including autosizing
564 // ----------------------------------------------------------------------------
566 void wxButton::AdjustForBitmapSize(wxSize
&size
) const
568 wxCHECK_RET( m_imageData
, wxT("shouldn't be called if no image") );
570 // account for the bitmap size
571 const wxSize sizeBmp
= m_imageData
->GetBitmap(State_Normal
).GetSize();
572 const wxDirection dirBmp
= m_imageData
->GetBitmapPosition();
573 if ( dirBmp
== wxLEFT
|| dirBmp
== wxRIGHT
)
576 if ( sizeBmp
.y
> size
.y
)
579 else // bitmap on top/below the text
582 if ( sizeBmp
.x
> size
.x
)
586 // account for the user-specified margins
587 size
+= 2*m_imageData
->GetBitmapMargins();
589 // and also for the margins we always add internally (unless we have no
590 // border at all in which case the button has exactly the same size as
591 // bitmap and so no margins should be used)
592 if ( !HasFlag(wxBORDER_NONE
) )
597 if ( wxUxThemeEngine::GetIfActive() )
599 wxUxThemeHandle
theme(const_cast<wxButton
*>(this), L
"BUTTON");
602 wxUxThemeEngine::Get()->GetThemeMargins(theme
, NULL
,
609 // XP doesn't draw themed buttons correctly when the client
610 // area is smaller than 8x8 - enforce this minimum size for
612 size
.IncTo(wxSize(8, 8));
614 marginH
= margins
.cxLeftWidth
+ margins
.cxRightWidth
615 + 2*XP_BUTTON_EXTRA_MARGIN
;
616 marginV
= margins
.cyTopHeight
+ margins
.cyBottomHeight
617 + 2*XP_BUTTON_EXTRA_MARGIN
;
620 #endif // wxUSE_UXTHEME
623 marginV
= OD_BUTTON_MARGIN
;
626 size
.IncBy(marginH
, marginV
);
630 wxSize
wxButton::DoGetBestSize() const
632 wxButton
* const self
= const_cast<wxButton
*>(this);
636 // Account for the text part if we have it.
640 if ( GetAuthNeeded() )
641 flags
|= wxMSWButton::Size_AuthNeeded
;
647 size
= wxMSWButton::GetFittingSize(self
,
648 m_markupText
->Measure(dc
),
651 else // Normal plain text (but possibly multiline) label.
652 #endif // wxUSE_MARKUP
654 size
= wxMSWButton::ComputeBestFittingSize(self
, flags
);
659 AdjustForBitmapSize(size
);
661 return wxMSWButton::IncreaseToStdSizeAndCache(self
, size
);
665 wxSize
wxButtonBase::GetDefaultSize()
667 static wxSize s_sizeBtn
;
669 if ( s_sizeBtn
.x
== 0 )
672 dc
.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT
));
674 // The size of a standard button in the dialog units is 50x14,
675 // translate this to pixels.
677 // Windows' computes dialog units using average character width over
678 // upper- and lower-case ASCII alphabet and not using the average
679 // character width metadata stored in the font; see
680 // http://support.microsoft.com/default.aspx/kb/145994 for detailed
683 // NB: wxMulDivInt32() is used, because it correctly rounds the result
685 const wxSize base
= wxPrivate::GetAverageASCIILetterSize(dc
);
686 s_sizeBtn
.x
= wxMulDivInt32(50, base
.x
, 4);
687 s_sizeBtn
.y
= wxMulDivInt32(14, base
.y
, 8);
693 // ----------------------------------------------------------------------------
694 // default button handling
695 // ----------------------------------------------------------------------------
698 The comment below and all this code is probably due to not using WM_NEXTDLGCTL
699 message when changing focus (but just SetFocus() which is not enough), see
700 http://blogs.msdn.com/oldnewthing/archive/2004/08/02/205624.aspx for the
703 TODO: Do use WM_NEXTDLGCTL and get rid of all this code.
706 "Everything you ever wanted to know about the default buttons" or "Why do we
707 have to do all this?"
709 In MSW the default button should be activated when the user presses Enter
710 and the current control doesn't process Enter itself somehow. This is
711 handled by ::DefWindowProc() (or maybe ::DefDialogProc()) using DM_SETDEFID
712 Another aspect of "defaultness" is that the default button has different
713 appearance: this is due to BS_DEFPUSHBUTTON style which is completely
714 separate from DM_SETDEFID stuff (!). Also note that BS_DEFPUSHBUTTON should
715 be unset if our parent window is not active so it should be unset whenever
716 we lose activation and set back when we regain it.
718 Final complication is that when a button is active, it should be the default
719 one, i.e. pressing Enter on a button always activates it and not another
722 We handle this by maintaining a permanent and a temporary default items in
723 wxControlContainer (both may be NULL). When a button becomes the current
724 control (i.e. gets focus) it sets itself as the temporary default which
725 ensures that it has the right appearance and that Enter will be redirected
726 to it. When the button loses focus, it unsets the temporary default and so
727 the default item will be the permanent default -- that is the default button
728 if any had been set or none otherwise, which is just what we want.
730 NB: all this is quite complicated by now and the worst is that normally
731 it shouldn't be necessary at all as for the normal Windows programs
732 DefWindowProc() and IsDialogMessage() take care of all this
733 automatically -- however in wxWidgets programs this doesn't work for
734 nested hierarchies (i.e. a notebook inside a notebook) for unknown
735 reason and so we have to reproduce all this code ourselves. It would be
736 very nice if we could avoid doing it.
739 // set this button as the (permanently) default one in its panel
740 wxWindow
*wxButton::SetDefault()
742 // set this one as the default button both for wxWidgets ...
743 wxWindow
*winOldDefault
= wxButtonBase::SetDefault();
746 SetDefaultStyle(wxDynamicCast(winOldDefault
, wxButton
), false);
747 SetDefaultStyle(this, true);
749 return winOldDefault
;
752 // return the top level parent window if it's not being deleted yet, otherwise
754 static wxTopLevelWindow
*GetTLWParentIfNotBeingDeleted(wxWindow
*win
)
758 // IsTopLevel() will return false for a wxTLW being deleted, so we also
759 // need the parent test for this case
760 wxWindow
* const parent
= win
->GetParent();
761 if ( !parent
|| win
->IsTopLevel() )
763 if ( win
->IsBeingDeleted() )
772 wxASSERT_MSG( win
, wxT("button without top level parent?") );
774 wxTopLevelWindow
* const tlw
= wxDynamicCast(win
, wxTopLevelWindow
);
775 wxASSERT_MSG( tlw
, wxT("logic error in GetTLWParentIfNotBeingDeleted()") );
780 // set this button as being currently default
781 void wxButton::SetTmpDefault()
783 wxTopLevelWindow
* const tlw
= GetTLWParentIfNotBeingDeleted(GetParent());
787 wxWindow
*winOldDefault
= tlw
->GetDefaultItem();
788 tlw
->SetTmpDefaultItem(this);
790 SetDefaultStyle(wxDynamicCast(winOldDefault
, wxButton
), false);
791 SetDefaultStyle(this, true);
794 // unset this button as currently default, it may still stay permanent default
795 void wxButton::UnsetTmpDefault()
797 wxTopLevelWindow
* const tlw
= GetTLWParentIfNotBeingDeleted(GetParent());
801 tlw
->SetTmpDefaultItem(NULL
);
803 wxWindow
*winOldDefault
= tlw
->GetDefaultItem();
805 SetDefaultStyle(this, false);
806 SetDefaultStyle(wxDynamicCast(winOldDefault
, wxButton
), true);
811 wxButton::SetDefaultStyle(wxButton
*btn
, bool on
)
813 // we may be called with NULL pointer -- simpler to do the check here than
814 // in the caller which does wxDynamicCast()
818 // first, let DefDlgProc() know about the new default button
821 // we shouldn't set BS_DEFPUSHBUTTON for any button if we don't have
822 // focus at all any more
823 if ( !wxTheApp
->IsActive() )
826 wxWindow
* const tlw
= wxGetTopLevelParent(btn
);
827 wxCHECK_RET( tlw
, wxT("button without top level window?") );
829 ::SendMessage(GetHwndOf(tlw
), DM_SETDEFID
, btn
->GetId(), 0L);
831 // sending DM_SETDEFID also changes the button style to
832 // BS_DEFPUSHBUTTON so there is nothing more to do
835 // then also change the style as needed
836 long style
= ::GetWindowLong(GetHwndOf(btn
), GWL_STYLE
);
837 if ( !(style
& BS_DEFPUSHBUTTON
) == on
)
839 // don't do it with the owner drawn buttons because it will
840 // reset BS_OWNERDRAW style bit too (as BS_OWNERDRAW &
841 // BS_DEFPUSHBUTTON != 0)!
842 if ( (style
& BS_OWNERDRAW
) != BS_OWNERDRAW
)
844 ::SendMessage(GetHwndOf(btn
), BM_SETSTYLE
,
845 on
? style
| BS_DEFPUSHBUTTON
846 : style
& ~BS_DEFPUSHBUTTON
,
851 // redraw the button - it will notice itself that it's
852 // [not] the default one [any longer]
856 //else: already has correct style
859 // ----------------------------------------------------------------------------
861 // ----------------------------------------------------------------------------
863 bool wxButton::SendClickEvent()
865 wxCommandEvent
event(wxEVT_COMMAND_BUTTON_CLICKED
, GetId());
866 event
.SetEventObject(this);
868 return ProcessCommand(event
);
871 void wxButton::Command(wxCommandEvent
& event
)
873 ProcessCommand(event
);
876 // ----------------------------------------------------------------------------
877 // event/message handlers
878 // ----------------------------------------------------------------------------
880 bool wxButton::MSWCommand(WXUINT param
, WXWORD
WXUNUSED(id
))
882 bool processed
= false;
885 // NOTE: Apparently older versions (NT 4?) of the common controls send
886 // BN_DOUBLECLICKED but not a second BN_CLICKED for owner-drawn
887 // buttons, so in order to send two EVT_BUTTON events we should
888 // catch both types. Currently (Feb 2003) up-to-date versions of
889 // win98, win2k and winXP all send two BN_CLICKED messages for
890 // all button types, so we don't catch BN_DOUBLECLICKED anymore
891 // in order to not get 3 EVT_BUTTON events. If this is a problem
892 // then we need to figure out which version of the comctl32 changed
893 // this behaviour and test for it.
895 case 1: // message came from an accelerator
896 case BN_CLICKED
: // normal buttons send this
897 processed
= SendClickEvent();
904 WXLRESULT
wxButton::MSWWindowProc(WXUINT nMsg
, WXWPARAM wParam
, WXLPARAM lParam
)
906 // when we receive focus, we want to temporarily become the default button in
907 // our parent panel so that pressing "Enter" would activate us -- and when
908 // losing it we should restore the previous default button as well
909 if ( nMsg
== WM_SETFOCUS
)
913 // let the default processing take place too
915 else if ( nMsg
== WM_KILLFOCUS
)
919 else if ( nMsg
== WM_LBUTTONDBLCLK
)
921 // emulate a click event to force an owner-drawn button to change its
922 // appearance - without this, it won't do it
923 (void)wxControl::MSWWindowProc(WM_LBUTTONDOWN
, wParam
, lParam
);
925 // and continue with processing the message normally as well
928 else if ( nMsg
== WM_THEMECHANGED
)
930 // need to recalculate the best size here
931 // as the theme size might have changed
932 InvalidateBestSize();
934 #endif // wxUSE_UXTHEME
935 // must use m_mouseInWindow here instead of IsMouseInWindow()
936 // since we need to know the first time the mouse enters the window
937 // and IsMouseInWindow() would return true in this case
938 else if ( (nMsg
== WM_MOUSEMOVE
&& !m_mouseInWindow
) ||
939 nMsg
== WM_MOUSELEAVE
)
945 wxUxThemeEngine::GetIfActive() ||
946 #endif // wxUSE_UXTHEME
947 (m_imageData
&& m_imageData
->GetBitmap(State_Current
).IsOk())
955 // let the base class do all real processing
956 return wxControl::MSWWindowProc(nMsg
, wParam
, lParam
);
959 // ----------------------------------------------------------------------------
960 // authentication needed handling
961 // ----------------------------------------------------------------------------
963 bool wxButton::DoGetAuthNeeded() const
968 void wxButton::DoSetAuthNeeded(bool show
)
970 // show/hide UAC symbol on Windows Vista and later
971 if ( wxGetWinVersion() >= wxWinVersion_6
)
974 ::SendMessage(GetHwnd(), BCM_SETSHIELD
, 0, show
);
975 InvalidateBestSize();
979 // ----------------------------------------------------------------------------
981 // ----------------------------------------------------------------------------
983 wxBitmap
wxButton::DoGetBitmap(State which
) const
985 return m_imageData
? m_imageData
->GetBitmap(which
) : wxBitmap();
988 void wxButton::DoSetBitmap(const wxBitmap
& bitmap
, State which
)
990 // allocate the image data when the first bitmap is set
994 // using image list doesn't work correctly if we don't have any label
995 // (even if we use BUTTON_IMAGELIST_ALIGN_CENTER alignment and
996 // BS_BITMAP style), at least under Windows 2003 so use owner drawn
997 // strategy for bitmap-only buttons
998 if ( ShowsLabel() && wxUxThemeEngine::GetIfActive() )
1000 m_imageData
= new wxXPButtonImageData(this, bitmap
);
1003 #endif // wxUSE_UXTHEME
1005 m_imageData
= new wxODButtonImageData(this, bitmap
);
1011 m_imageData
->SetBitmap(bitmap
, which
);
1014 // it should be enough to only invalidate the best size when the normal
1015 // bitmap changes as all bitmaps assigned to the button should be of the
1017 if ( which
== State_Normal
)
1018 InvalidateBestSize();
1023 wxSize
wxButton::DoGetBitmapMargins() const
1025 return m_imageData
? m_imageData
->GetBitmapMargins() : wxSize(0, 0);
1028 void wxButton::DoSetBitmapMargins(wxCoord x
, wxCoord y
)
1030 wxCHECK_RET( m_imageData
, "SetBitmap() must be called first" );
1032 m_imageData
->SetBitmapMargins(x
, y
);
1033 InvalidateBestSize();
1036 void wxButton::DoSetBitmapPosition(wxDirection dir
)
1038 wxCHECK_RET( m_imageData
, "SetBitmap() must be called first" );
1040 m_imageData
->SetBitmapPosition(dir
);
1041 InvalidateBestSize();
1044 // ----------------------------------------------------------------------------
1046 // ----------------------------------------------------------------------------
1050 bool wxButton::DoSetLabelMarkup(const wxString
& markup
)
1052 if ( !wxButtonBase::DoSetLabelMarkup(markup
) )
1055 if ( !m_markupText
)
1057 m_markupText
= new wxMarkupText(markup
);
1062 // We are already owner-drawn so just update the text.
1063 m_markupText
->SetMarkup(markup
);
1071 #endif // wxUSE_MARKUP
1073 // ----------------------------------------------------------------------------
1074 // owner-drawn buttons support
1075 // ----------------------------------------------------------------------------
1081 // return the button state using both the ODS_XXX flags specified in state
1082 // parameter and the current button state
1083 wxButton::State
GetButtonState(wxButton
*btn
, UINT state
)
1085 if ( state
& ODS_DISABLED
)
1086 return wxButton::State_Disabled
;
1088 if ( state
& ODS_SELECTED
)
1089 return wxButton::State_Pressed
;
1091 if ( btn
->HasCapture() || btn
->IsMouseInWindow() )
1092 return wxButton::State_Current
;
1094 if ( state
& ODS_FOCUS
)
1095 return wxButton::State_Focused
;
1097 return wxButton::State_Normal
;
1100 void DrawButtonText(HDC hdc
,
1102 const wxString
& text
,
1105 // center text horizontally in any case
1108 if ( text
.find(wxT('\n')) != wxString::npos
)
1110 // draw multiline label
1112 // first we need to compute its bounding rect
1114 ::CopyRect(&rc
, pRect
);
1115 ::DrawText(hdc
, text
.wx_str(), text
.length(), &rc
,
1116 DT_CENTER
| DT_CALCRECT
);
1118 // now center this rect inside the entire button area
1119 const LONG w
= rc
.right
- rc
.left
;
1120 const LONG h
= rc
.bottom
- rc
.top
;
1121 rc
.left
= (pRect
->right
- pRect
->left
)/2 - w
/2;
1122 rc
.right
= rc
.left
+w
;
1123 rc
.top
= (pRect
->bottom
- pRect
->top
)/2 - h
/2;
1124 rc
.bottom
= rc
.top
+h
;
1126 ::DrawText(hdc
, text
.wx_str(), text
.length(), &rc
, flags
);
1128 else // single line label
1130 // centre text vertically too (notice that we must have DT_SINGLELINE
1131 // for DT_VCENTER to work)
1132 ::DrawText(hdc
, text
.wx_str(), text
.length(), pRect
,
1133 flags
| DT_SINGLELINE
| DT_VCENTER
);
1137 void DrawRect(HDC hdc
, const RECT
& r
)
1139 wxDrawLine(hdc
, r
.left
, r
.top
, r
.right
, r
.top
);
1140 wxDrawLine(hdc
, r
.right
, r
.top
, r
.right
, r
.bottom
);
1141 wxDrawLine(hdc
, r
.right
, r
.bottom
, r
.left
, r
.bottom
);
1142 wxDrawLine(hdc
, r
.left
, r
.bottom
, r
.left
, r
.top
);
1146 The button frame looks like this normally:
1149 WHHHHHHHHHHHHHHHHGB W = white (HILIGHT)
1150 WH GB H = light grey (LIGHT)
1151 WH GB G = dark grey (SHADOW)
1152 WH GB B = black (DKSHADOW)
1157 When the button is selected, the button becomes like this (the total button
1158 size doesn't change):
1169 When the button is pushed (while selected) it is like:
1180 void DrawButtonFrame(HDC hdc
, RECT
& rectBtn
,
1181 bool selected
, bool pushed
)
1184 CopyRect(&r
, &rectBtn
);
1186 AutoHPEN
hpenBlack(GetSysColor(COLOR_3DDKSHADOW
)),
1187 hpenGrey(GetSysColor(COLOR_3DSHADOW
)),
1188 hpenLightGr(GetSysColor(COLOR_3DLIGHT
)),
1189 hpenWhite(GetSysColor(COLOR_3DHILIGHT
));
1191 SelectInHDC
selectPen(hdc
, hpenBlack
);
1200 (void)SelectObject(hdc
, hpenGrey
);
1201 ::InflateRect(&r
, -1, -1);
1211 ::InflateRect(&r
, -1, -1);
1214 wxDrawLine(hdc
, r
.left
, r
.bottom
, r
.right
, r
.bottom
);
1215 wxDrawLine(hdc
, r
.right
, r
.bottom
, r
.right
, r
.top
- 1);
1217 (void)SelectObject(hdc
, hpenWhite
);
1218 wxDrawLine(hdc
, r
.left
, r
.bottom
- 1, r
.left
, r
.top
);
1219 wxDrawLine(hdc
, r
.left
, r
.top
, r
.right
, r
.top
);
1221 (void)SelectObject(hdc
, hpenLightGr
);
1222 wxDrawLine(hdc
, r
.left
+ 1, r
.bottom
- 2, r
.left
+ 1, r
.top
+ 1);
1223 wxDrawLine(hdc
, r
.left
+ 1, r
.top
+ 1, r
.right
- 1, r
.top
+ 1);
1225 (void)SelectObject(hdc
, hpenGrey
);
1226 wxDrawLine(hdc
, r
.left
+ 1, r
.bottom
- 1, r
.right
- 1, r
.bottom
- 1);
1227 wxDrawLine(hdc
, r
.right
- 1, r
.bottom
- 1, r
.right
- 1, r
.top
);
1230 InflateRect(&rectBtn
, -OD_BUTTON_MARGIN
, -OD_BUTTON_MARGIN
);
1234 void DrawXPBackground(wxButton
*button
, HDC hdc
, RECT
& rectBtn
, UINT state
)
1236 wxUxThemeHandle
theme(button
, L
"BUTTON");
1238 // this array is indexed by wxButton::State values and so must be kept in
1240 static const int uxStates
[] =
1242 PBS_NORMAL
, PBS_HOT
, PBS_PRESSED
, PBS_DISABLED
, PBS_DEFAULTED
1245 int iState
= uxStates
[GetButtonState(button
, state
)];
1247 wxUxThemeEngine
* const engine
= wxUxThemeEngine::Get();
1249 // draw parent background if needed
1250 if ( engine
->IsThemeBackgroundPartiallyTransparent
1257 engine
->DrawThemeParentBackground(GetHwndOf(button
), hdc
, &rectBtn
);
1261 engine
->DrawThemeBackground(theme
, hdc
, BP_PUSHBUTTON
, iState
,
1264 // calculate content area margins
1266 engine
->GetThemeMargins(theme
, hdc
, BP_PUSHBUTTON
, iState
,
1267 TMT_CONTENTMARGINS
, &rectBtn
, &margins
);
1268 ::InflateRect(&rectBtn
, -margins
.cxLeftWidth
, -margins
.cyTopHeight
);
1269 ::InflateRect(&rectBtn
, -XP_BUTTON_EXTRA_MARGIN
, -XP_BUTTON_EXTRA_MARGIN
);
1271 if ( button
->UseBgCol() )
1273 COLORREF colBg
= wxColourToRGB(button
->GetBackgroundColour());
1274 AutoHBRUSH
hbrushBackground(colBg
);
1276 // don't overwrite the focus rect
1278 ::CopyRect(&rectClient
, &rectBtn
);
1279 ::InflateRect(&rectClient
, -1, -1);
1280 FillRect(hdc
, &rectClient
, hbrushBackground
);
1283 #endif // wxUSE_UXTHEME
1285 } // anonymous namespace
1287 // ----------------------------------------------------------------------------
1288 // owner drawn buttons support
1289 // ----------------------------------------------------------------------------
1291 void wxButton::MakeOwnerDrawn()
1293 long style
= GetWindowLong(GetHwnd(), GWL_STYLE
);
1294 if ( (style
& BS_OWNERDRAW
) != BS_OWNERDRAW
)
1297 style
|= BS_OWNERDRAW
;
1298 SetWindowLong(GetHwnd(), GWL_STYLE
, style
);
1302 bool wxButton::SetBackgroundColour(const wxColour
&colour
)
1304 if ( !wxControl::SetBackgroundColour(colour
) )
1317 bool wxButton::SetForegroundColour(const wxColour
&colour
)
1319 if ( !wxControl::SetForegroundColour(colour
) )
1332 bool wxButton::MSWOnDraw(WXDRAWITEMSTRUCT
*wxdis
)
1334 LPDRAWITEMSTRUCT lpDIS
= (LPDRAWITEMSTRUCT
)wxdis
;
1335 HDC hdc
= lpDIS
->hDC
;
1337 UINT state
= lpDIS
->itemState
;
1338 bool pushed
= (SendMessage(GetHwnd(), BM_GETSTATE
, 0, 0) & BST_PUSHED
) != 0;
1341 CopyRect(&rectBtn
, &lpDIS
->rcItem
);
1343 // draw the button background
1344 if ( !HasFlag(wxBORDER_NONE
) )
1347 if ( wxUxThemeEngine::GetIfActive() )
1349 DrawXPBackground(this, hdc
, rectBtn
, state
);
1352 #endif // wxUSE_UXTHEME
1354 COLORREF colBg
= wxColourToRGB(GetBackgroundColour());
1356 // first, draw the background
1357 AutoHBRUSH
hbrushBackground(colBg
);
1358 FillRect(hdc
, &rectBtn
, hbrushBackground
);
1360 // draw the border for the current state
1361 bool selected
= (state
& ODS_SELECTED
) != 0;
1365 tlw
= wxDynamicCast(wxGetTopLevelParent(this), wxTopLevelWindow
);
1368 selected
= tlw
->GetDefaultItem() == this;
1372 DrawButtonFrame(hdc
, rectBtn
, selected
, pushed
);
1375 // draw the focus rectangle if we need it
1376 if ( (state
& ODS_FOCUS
) && !(state
& ODS_NOFOCUSRECT
) )
1378 DrawFocusRect(hdc
, &rectBtn
);
1381 if ( !wxUxThemeEngine::GetIfActive() )
1382 #endif // wxUSE_UXTHEME
1386 // the label is shifted by 1 pixel to create "pushed" effect
1387 OffsetRect(&rectBtn
, 1, 1);
1394 // draw the image, if any
1397 wxBitmap bmp
= m_imageData
->GetBitmap(GetButtonState(this, state
));
1399 bmp
= m_imageData
->GetBitmap(State_Normal
);
1401 const wxSize sizeBmp
= bmp
.GetSize();
1402 const wxSize margin
= m_imageData
->GetBitmapMargins();
1403 const wxSize
sizeBmpWithMargins(sizeBmp
+ 2*margin
);
1404 wxRect
rectButton(wxRectFromRECT(rectBtn
));
1406 // for simplicity, we start with centred rectangle and then move it to
1407 // the appropriate edge
1408 wxRect rectBitmap
= wxRect(sizeBmp
).CentreIn(rectButton
);
1410 // move bitmap only if we have a label, otherwise keep it centered
1413 switch ( m_imageData
->GetBitmapPosition() )
1416 wxFAIL_MSG( "invalid direction" );
1420 rectBitmap
.x
= rectButton
.x
+ margin
.x
;
1421 rectButton
.x
+= sizeBmpWithMargins
.x
;
1422 rectButton
.width
-= sizeBmpWithMargins
.x
;
1426 rectBitmap
.x
= rectButton
.GetRight() - sizeBmp
.x
- margin
.x
;
1427 rectButton
.width
-= sizeBmpWithMargins
.x
;
1431 rectBitmap
.y
= rectButton
.y
+ margin
.y
;
1432 rectButton
.y
+= sizeBmpWithMargins
.y
;
1433 rectButton
.height
-= sizeBmpWithMargins
.y
;
1437 rectBitmap
.y
= rectButton
.GetBottom() - sizeBmp
.y
- margin
.y
;
1438 rectButton
.height
-= sizeBmpWithMargins
.y
;
1443 wxDCTemp
dst((WXHDC
)hdc
);
1444 dst
.DrawBitmap(bmp
, rectBitmap
.GetPosition(), true);
1446 wxCopyRectToRECT(rectButton
, rectBtn
);
1450 // finally draw the label
1453 COLORREF colFg
= state
& ODS_DISABLED
1454 ? ::GetSysColor(COLOR_GRAYTEXT
)
1455 : wxColourToRGB(GetForegroundColour());
1457 wxTextColoursChanger
changeFg(hdc
, colFg
, CLR_INVALID
);
1458 wxBkModeChanger
changeBkMode(hdc
, wxBRUSHSTYLE_TRANSPARENT
);
1463 wxDCTemp
dc((WXHDC
)hdc
);
1464 dc
.SetTextForeground(wxColour(colFg
));
1465 dc
.SetFont(GetFont());
1467 m_markupText
->Render(dc
, wxRectFromRECT(rectBtn
),
1469 ? wxMarkupText::Render_Default
1470 : wxMarkupText::Render_ShowAccels
);
1472 else // Plain text label
1473 #endif // wxUSE_MARKUP
1475 // notice that DT_HIDEPREFIX doesn't work on old (pre-Windows 2000)
1476 // systems but by happy coincidence ODS_NOACCEL is not used under
1477 // them neither so DT_HIDEPREFIX should never be used there
1478 DrawButtonText(hdc
, &rectBtn
, GetLabel(),
1479 state
& ODS_NOACCEL
? DT_HIDEPREFIX
: 0);
1486 #endif // wxUSE_BUTTON