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
119 extern wxWindowMSW
*wxWindowBeingErased
; // From src/msw/window.cpp
120 #endif // wxUSE_UXTHEME
122 // ----------------------------------------------------------------------------
124 // ----------------------------------------------------------------------------
126 // we use different data classes for owner drawn buttons and for themed XP ones
128 class wxButtonImageData
131 wxButtonImageData() { }
132 virtual ~wxButtonImageData() { }
134 virtual wxBitmap
GetBitmap(wxButton::State which
) const = 0;
135 virtual void SetBitmap(const wxBitmap
& bitmap
, wxButton::State which
) = 0;
137 virtual wxSize
GetBitmapMargins() const = 0;
138 virtual void SetBitmapMargins(wxCoord x
, wxCoord y
) = 0;
140 virtual wxDirection
GetBitmapPosition() const = 0;
141 virtual void SetBitmapPosition(wxDirection dir
) = 0;
144 wxDECLARE_NO_COPY_CLASS(wxButtonImageData
);
150 // the gap between button edge and the interior area used by Windows for the
152 const int OD_BUTTON_MARGIN
= 4;
154 class wxODButtonImageData
: public wxButtonImageData
157 wxODButtonImageData(wxButton
*btn
, const wxBitmap
& bitmap
)
159 SetBitmap(bitmap
, wxButton::State_Normal
);
160 SetBitmap(bitmap
.ConvertToDisabled(), wxButton::State_Disabled
);
164 // we use margins when we have both bitmap and text, but when we have
165 // only the bitmap it should take up the entire button area
166 if ( btn
->ShowsLabel() )
168 m_margin
.x
= btn
->GetCharWidth();
169 m_margin
.y
= btn
->GetCharHeight() / 2;
173 virtual wxBitmap
GetBitmap(wxButton::State which
) const
175 return m_bitmaps
[which
];
178 virtual void SetBitmap(const wxBitmap
& bitmap
, wxButton::State which
)
180 m_bitmaps
[which
] = bitmap
;
183 virtual wxSize
GetBitmapMargins() const
188 virtual void SetBitmapMargins(wxCoord x
, wxCoord y
)
190 m_margin
= wxSize(x
, y
);
193 virtual wxDirection
GetBitmapPosition() const
198 virtual void SetBitmapPosition(wxDirection dir
)
204 // just store the values passed to us to be able to retrieve them later
205 // from the drawing code
206 wxBitmap m_bitmaps
[wxButton::State_Max
];
210 wxDECLARE_NO_COPY_CLASS(wxODButtonImageData
);
215 // somehow the margin is one pixel greater than the value returned by
216 // GetThemeMargins() call
217 const int XP_BUTTON_EXTRA_MARGIN
= 1;
219 class wxXPButtonImageData
: public wxButtonImageData
222 // we must be constructed with the size of our images as we need to create
224 wxXPButtonImageData(wxButton
*btn
, const wxBitmap
& bitmap
)
225 : m_iml(bitmap
.GetWidth(), bitmap
.GetHeight(), true /* use mask */,
226 wxButton::State_Max
),
227 m_hwndBtn(GetHwndOf(btn
))
229 // initialize all bitmaps except for the disabled one to normal state
230 for ( int n
= 0; n
< wxButton::State_Max
; n
++ )
232 m_iml
.Add(n
== wxButton::State_Disabled
? bitmap
.ConvertToDisabled()
236 m_data
.himl
= GetHimagelistOf(&m_iml
);
238 // no margins by default
240 m_data
.margin
.right
=
242 m_data
.margin
.bottom
= 0;
244 // use default alignment
245 m_data
.uAlign
= BUTTON_IMAGELIST_ALIGN_LEFT
;
250 virtual wxBitmap
GetBitmap(wxButton::State which
) const
252 return m_iml
.GetBitmap(which
);
255 virtual void SetBitmap(const wxBitmap
& bitmap
, wxButton::State which
)
257 m_iml
.Replace(which
, bitmap
);
262 virtual wxSize
GetBitmapMargins() const
264 return wxSize(m_data
.margin
.left
, m_data
.margin
.top
);
267 virtual void SetBitmapMargins(wxCoord x
, wxCoord y
)
269 RECT
& margin
= m_data
.margin
;
275 if ( !::SendMessage(m_hwndBtn
, BCM_SETTEXTMARGIN
, 0, (LPARAM
)&margin
) )
277 wxLogDebug("SendMessage(BCM_SETTEXTMARGIN) failed");
281 virtual wxDirection
GetBitmapPosition() const
283 switch ( m_data
.uAlign
)
286 wxFAIL_MSG( "invalid image alignment" );
289 case BUTTON_IMAGELIST_ALIGN_LEFT
:
292 case BUTTON_IMAGELIST_ALIGN_RIGHT
:
295 case BUTTON_IMAGELIST_ALIGN_TOP
:
298 case BUTTON_IMAGELIST_ALIGN_BOTTOM
:
303 virtual void SetBitmapPosition(wxDirection dir
)
309 wxFAIL_MSG( "invalid direction" );
313 alignNew
= BUTTON_IMAGELIST_ALIGN_LEFT
;
317 alignNew
= BUTTON_IMAGELIST_ALIGN_RIGHT
;
321 alignNew
= BUTTON_IMAGELIST_ALIGN_TOP
;
325 alignNew
= BUTTON_IMAGELIST_ALIGN_BOTTOM
;
329 if ( alignNew
!= m_data
.uAlign
)
331 m_data
.uAlign
= alignNew
;
337 void UpdateImageInfo()
339 if ( !::SendMessage(m_hwndBtn
, BCM_SETIMAGELIST
, 0, (LPARAM
)&m_data
) )
341 wxLogDebug("SendMessage(BCM_SETIMAGELIST) failed");
345 // we store image list separately to be able to use convenient wxImageList
346 // methods instead of working with raw HIMAGELIST
349 // store the rest of the data in BCM_SETIMAGELIST-friendly form
350 BUTTON_IMAGELIST m_data
;
352 // the button we're associated with
353 const HWND m_hwndBtn
;
356 wxDECLARE_NO_COPY_CLASS(wxXPButtonImageData
);
359 #endif // wxUSE_UXTHEME
361 } // anonymous namespace
363 // ----------------------------------------------------------------------------
365 // ----------------------------------------------------------------------------
367 // ============================================================================
369 // ============================================================================
371 // ----------------------------------------------------------------------------
372 // helper functions from wx/msw/private/button.h
373 // ----------------------------------------------------------------------------
375 void wxMSWButton::UpdateMultilineStyle(HWND hwnd
, const wxString
& label
)
377 // update BS_MULTILINE style depending on the new label (resetting it
378 // doesn't seem to do anything very useful but it shouldn't hurt and we do
379 // have to set it whenever the label becomes multi line as otherwise it
380 // wouldn't be shown correctly as we don't use BS_MULTILINE when creating
381 // the control unless it already has new lines in its label)
382 long styleOld
= ::GetWindowLong(hwnd
, GWL_STYLE
),
384 if ( label
.find(wxT('\n')) != wxString::npos
)
385 styleNew
= styleOld
| BS_MULTILINE
;
387 styleNew
= styleOld
& ~BS_MULTILINE
;
389 if ( styleNew
!= styleOld
)
390 ::SetWindowLong(hwnd
, GWL_STYLE
, styleNew
);
393 wxSize
wxMSWButton::GetFittingSize(wxWindow
*win
,
394 const wxSize
& sizeLabel
,
397 // FIXME: this is pure guesswork, need to retrieve the real button margins
398 wxSize sizeBtn
= sizeLabel
;
400 sizeBtn
.x
+= 3*win
->GetCharWidth();
401 sizeBtn
.y
+= win
->GetCharHeight()/2;
403 // account for the shield UAC icon if we have it
404 if ( flags
& Size_AuthNeeded
)
405 sizeBtn
.x
+= wxSystemSettings::GetMetric(wxSYS_SMALLICON_X
);
410 wxSize
wxMSWButton::ComputeBestFittingSize(wxControl
*btn
, int flags
)
415 dc
.GetMultiLineTextExtent(btn
->GetLabelText(), &sizeBtn
.x
, &sizeBtn
.y
);
417 return GetFittingSize(btn
, sizeBtn
, flags
);
420 wxSize
wxMSWButton::IncreaseToStdSizeAndCache(wxControl
*btn
, const wxSize
& size
)
422 wxSize
sizeBtn(size
);
424 // All buttons have at least the standard height and, unless the user
425 // explicitly wants them to be as small as possible and used wxBU_EXACTFIT
426 // style to indicate this, of at least the standard width too.
428 // Notice that we really want to make all buttons equally high, otherwise
429 // they look ugly and the existing code using wxBU_EXACTFIT only uses it to
430 // control width and not height.
432 // The 50x14 button size is documented in the "Recommended sizing and
433 // spacing" section of MSDN layout article.
435 // Note that we intentionally don't use GetDefaultSize() here, because
436 // it's inexact -- dialog units depend on this dialog's font.
437 const wxSize sizeDef
= btn
->ConvertDialogToPixels(wxSize(50, 14));
438 if ( !btn
->HasFlag(wxBU_EXACTFIT
) )
440 if ( sizeBtn
.x
< sizeDef
.x
)
441 sizeBtn
.x
= sizeDef
.x
;
443 if ( sizeBtn
.y
< sizeDef
.y
)
444 sizeBtn
.y
= sizeDef
.y
;
446 btn
->CacheBestSize(sizeBtn
);
451 // ----------------------------------------------------------------------------
452 // creation/destruction
453 // ----------------------------------------------------------------------------
455 bool wxButton::Create(wxWindow
*parent
,
461 const wxValidator
& validator
,
462 const wxString
& name
)
465 if (label
.empty() && wxIsStockID(id
))
467 // On Windows, some buttons aren't supposed to have mnemonics
468 label
= wxGetStockLabel
471 id
== wxID_OK
|| id
== wxID_CANCEL
|| id
== wxID_CLOSE
473 : wxSTOCK_WITH_MNEMONIC
477 if ( !CreateControl(parent
, id
, pos
, size
, style
, validator
, name
) )
481 WXDWORD msStyle
= MSWGetStyle(style
, &exstyle
);
483 // if the label contains several lines we must explicitly tell the button
484 // about it or it wouldn't draw it correctly ("\n"s would just appear as
487 // NB: we do it here and not in MSWGetStyle() because we need the label
488 // value and the label is not set yet when MSWGetStyle() is called
489 msStyle
|= wxMSWButton::GetMultilineStyle(label
);
491 return MSWCreateControl(wxT("BUTTON"), msStyle
, pos
, size
, label
, exstyle
);
494 wxButton::~wxButton()
496 wxTopLevelWindow
*tlw
= wxDynamicCast(wxGetTopLevelParent(this), wxTopLevelWindow
);
497 if ( tlw
&& tlw
->GetTmpDefaultItem() == this )
505 #endif // wxUSE_MARKUP
508 // ----------------------------------------------------------------------------
510 // ----------------------------------------------------------------------------
512 WXDWORD
wxButton::MSWGetStyle(long style
, WXDWORD
*exstyle
) const
514 // buttons never have an external border, they draw their own one
515 WXDWORD msStyle
= wxControl::MSWGetStyle
517 (style
& ~wxBORDER_MASK
) | wxBORDER_NONE
, exstyle
520 // we must use WS_CLIPSIBLINGS with the buttons or they would draw over
521 // each other in any resizable dialog which has more than one button in
523 msStyle
|= WS_CLIPSIBLINGS
;
525 // don't use "else if" here: weird as it is, but you may combine wxBU_LEFT
526 // and wxBU_RIGHT to get BS_CENTER!
527 if ( style
& wxBU_LEFT
)
529 if ( style
& wxBU_RIGHT
)
531 if ( style
& wxBU_TOP
)
533 if ( style
& wxBU_BOTTOM
)
534 msStyle
|= BS_BOTTOM
;
537 if ( style
& wxNO_BORDER
)
539 #endif // __WXWINCE__
544 void wxButton::SetLabel(const wxString
& label
)
546 wxMSWButton::UpdateMultilineStyle(GetHwnd(), label
);
548 wxButtonBase::SetLabel(label
);
551 // If we have a plain text label, we shouldn't be using markup any longer.
557 // Unfortunately we don't really know whether we can reset the button
558 // to be non-owner-drawn or not: if we had made it owner-drawn just
559 // because of a call to SetLabelMarkup(), we could, but not if there
560 // were [also] calls to Set{Fore,Back}groundColour(). If it's really a
561 // problem to have button remain owner-drawn forever just because it
562 // had markup label once, we should record the reason for our current
563 // owner-drawnness and check it here.
565 #endif // wxUSE_MARKUP
568 // ----------------------------------------------------------------------------
569 // size management including autosizing
570 // ----------------------------------------------------------------------------
572 void wxButton::AdjustForBitmapSize(wxSize
&size
) const
574 wxCHECK_RET( m_imageData
, wxT("shouldn't be called if no image") );
576 // account for the bitmap size
577 const wxSize sizeBmp
= m_imageData
->GetBitmap(State_Normal
).GetSize();
578 const wxDirection dirBmp
= m_imageData
->GetBitmapPosition();
579 if ( dirBmp
== wxLEFT
|| dirBmp
== wxRIGHT
)
582 if ( sizeBmp
.y
> size
.y
)
585 else // bitmap on top/below the text
588 if ( sizeBmp
.x
> size
.x
)
592 // account for the user-specified margins
593 size
+= 2*m_imageData
->GetBitmapMargins();
595 // and also for the margins we always add internally (unless we have no
596 // border at all in which case the button has exactly the same size as
597 // bitmap and so no margins should be used)
598 if ( !HasFlag(wxBORDER_NONE
) )
603 if ( wxUxThemeEngine::GetIfActive() )
605 wxUxThemeHandle
theme(const_cast<wxButton
*>(this), L
"BUTTON");
608 wxUxThemeEngine::Get()->GetThemeMargins(theme
, NULL
,
615 // XP doesn't draw themed buttons correctly when the client
616 // area is smaller than 8x8 - enforce this minimum size for
618 size
.IncTo(wxSize(8, 8));
620 marginH
= margins
.cxLeftWidth
+ margins
.cxRightWidth
621 + 2*XP_BUTTON_EXTRA_MARGIN
;
622 marginV
= margins
.cyTopHeight
+ margins
.cyBottomHeight
623 + 2*XP_BUTTON_EXTRA_MARGIN
;
626 #endif // wxUSE_UXTHEME
629 marginV
= OD_BUTTON_MARGIN
;
632 size
.IncBy(marginH
, marginV
);
636 wxSize
wxButton::DoGetBestSize() const
638 wxButton
* const self
= const_cast<wxButton
*>(this);
642 // Account for the text part if we have it.
646 if ( GetAuthNeeded() )
647 flags
|= wxMSWButton::Size_AuthNeeded
;
653 size
= wxMSWButton::GetFittingSize(self
,
654 m_markupText
->Measure(dc
),
657 else // Normal plain text (but possibly multiline) label.
658 #endif // wxUSE_MARKUP
660 size
= wxMSWButton::ComputeBestFittingSize(self
, flags
);
665 AdjustForBitmapSize(size
);
667 return wxMSWButton::IncreaseToStdSizeAndCache(self
, size
);
671 wxSize
wxButtonBase::GetDefaultSize()
673 static wxSize s_sizeBtn
;
675 if ( s_sizeBtn
.x
== 0 )
678 dc
.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT
));
680 // The size of a standard button in the dialog units is 50x14,
681 // translate this to pixels.
683 // Windows' computes dialog units using average character width over
684 // upper- and lower-case ASCII alphabet and not using the average
685 // character width metadata stored in the font; see
686 // http://support.microsoft.com/default.aspx/kb/145994 for detailed
689 // NB: wxMulDivInt32() is used, because it correctly rounds the result
691 const wxSize base
= wxPrivate::GetAverageASCIILetterSize(dc
);
692 s_sizeBtn
.x
= wxMulDivInt32(50, base
.x
, 4);
693 s_sizeBtn
.y
= wxMulDivInt32(14, base
.y
, 8);
699 // ----------------------------------------------------------------------------
700 // default button handling
701 // ----------------------------------------------------------------------------
704 The comment below and all this code is probably due to not using WM_NEXTDLGCTL
705 message when changing focus (but just SetFocus() which is not enough), see
706 http://blogs.msdn.com/oldnewthing/archive/2004/08/02/205624.aspx for the
709 TODO: Do use WM_NEXTDLGCTL and get rid of all this code.
712 "Everything you ever wanted to know about the default buttons" or "Why do we
713 have to do all this?"
715 In MSW the default button should be activated when the user presses Enter
716 and the current control doesn't process Enter itself somehow. This is
717 handled by ::DefWindowProc() (or maybe ::DefDialogProc()) using DM_SETDEFID
718 Another aspect of "defaultness" is that the default button has different
719 appearance: this is due to BS_DEFPUSHBUTTON style which is completely
720 separate from DM_SETDEFID stuff (!). Also note that BS_DEFPUSHBUTTON should
721 be unset if our parent window is not active so it should be unset whenever
722 we lose activation and set back when we regain it.
724 Final complication is that when a button is active, it should be the default
725 one, i.e. pressing Enter on a button always activates it and not another
728 We handle this by maintaining a permanent and a temporary default items in
729 wxControlContainer (both may be NULL). When a button becomes the current
730 control (i.e. gets focus) it sets itself as the temporary default which
731 ensures that it has the right appearance and that Enter will be redirected
732 to it. When the button loses focus, it unsets the temporary default and so
733 the default item will be the permanent default -- that is the default button
734 if any had been set or none otherwise, which is just what we want.
736 NB: all this is quite complicated by now and the worst is that normally
737 it shouldn't be necessary at all as for the normal Windows programs
738 DefWindowProc() and IsDialogMessage() take care of all this
739 automatically -- however in wxWidgets programs this doesn't work for
740 nested hierarchies (i.e. a notebook inside a notebook) for unknown
741 reason and so we have to reproduce all this code ourselves. It would be
742 very nice if we could avoid doing it.
745 // set this button as the (permanently) default one in its panel
746 wxWindow
*wxButton::SetDefault()
748 // set this one as the default button both for wxWidgets ...
749 wxWindow
*winOldDefault
= wxButtonBase::SetDefault();
752 SetDefaultStyle(wxDynamicCast(winOldDefault
, wxButton
), false);
753 SetDefaultStyle(this, true);
755 return winOldDefault
;
758 // return the top level parent window if it's not being deleted yet, otherwise
760 static wxTopLevelWindow
*GetTLWParentIfNotBeingDeleted(wxWindow
*win
)
764 // IsTopLevel() will return false for a wxTLW being deleted, so we also
765 // need the parent test for this case
766 wxWindow
* const parent
= win
->GetParent();
767 if ( !parent
|| win
->IsTopLevel() )
769 if ( win
->IsBeingDeleted() )
778 wxASSERT_MSG( win
, wxT("button without top level parent?") );
780 wxTopLevelWindow
* const tlw
= wxDynamicCast(win
, wxTopLevelWindow
);
781 wxASSERT_MSG( tlw
, wxT("logic error in GetTLWParentIfNotBeingDeleted()") );
786 // set this button as being currently default
787 void wxButton::SetTmpDefault()
789 wxTopLevelWindow
* const tlw
= GetTLWParentIfNotBeingDeleted(GetParent());
793 wxWindow
*winOldDefault
= tlw
->GetDefaultItem();
794 tlw
->SetTmpDefaultItem(this);
796 SetDefaultStyle(wxDynamicCast(winOldDefault
, wxButton
), false);
797 SetDefaultStyle(this, true);
800 // unset this button as currently default, it may still stay permanent default
801 void wxButton::UnsetTmpDefault()
803 wxTopLevelWindow
* const tlw
= GetTLWParentIfNotBeingDeleted(GetParent());
807 tlw
->SetTmpDefaultItem(NULL
);
809 wxWindow
*winOldDefault
= tlw
->GetDefaultItem();
811 SetDefaultStyle(this, false);
812 SetDefaultStyle(wxDynamicCast(winOldDefault
, wxButton
), true);
817 wxButton::SetDefaultStyle(wxButton
*btn
, bool on
)
819 // we may be called with NULL pointer -- simpler to do the check here than
820 // in the caller which does wxDynamicCast()
824 // first, let DefDlgProc() know about the new default button
827 // we shouldn't set BS_DEFPUSHBUTTON for any button if we don't have
828 // focus at all any more
829 if ( !wxTheApp
->IsActive() )
832 wxWindow
* const tlw
= wxGetTopLevelParent(btn
);
833 wxCHECK_RET( tlw
, wxT("button without top level window?") );
835 ::SendMessage(GetHwndOf(tlw
), DM_SETDEFID
, btn
->GetId(), 0L);
837 // sending DM_SETDEFID also changes the button style to
838 // BS_DEFPUSHBUTTON so there is nothing more to do
841 // then also change the style as needed
842 long style
= ::GetWindowLong(GetHwndOf(btn
), GWL_STYLE
);
843 if ( !(style
& BS_DEFPUSHBUTTON
) == on
)
845 // don't do it with the owner drawn buttons because it will
846 // reset BS_OWNERDRAW style bit too (as BS_OWNERDRAW &
847 // BS_DEFPUSHBUTTON != 0)!
848 if ( (style
& BS_OWNERDRAW
) != BS_OWNERDRAW
)
850 ::SendMessage(GetHwndOf(btn
), BM_SETSTYLE
,
851 on
? style
| BS_DEFPUSHBUTTON
852 : style
& ~BS_DEFPUSHBUTTON
,
857 // redraw the button - it will notice itself that it's
858 // [not] the default one [any longer]
862 //else: already has correct style
865 // ----------------------------------------------------------------------------
867 // ----------------------------------------------------------------------------
869 bool wxButton::SendClickEvent()
871 wxCommandEvent
event(wxEVT_COMMAND_BUTTON_CLICKED
, GetId());
872 event
.SetEventObject(this);
874 return ProcessCommand(event
);
877 void wxButton::Command(wxCommandEvent
& event
)
879 ProcessCommand(event
);
882 // ----------------------------------------------------------------------------
883 // event/message handlers
884 // ----------------------------------------------------------------------------
886 bool wxButton::MSWCommand(WXUINT param
, WXWORD
WXUNUSED(id
))
888 bool processed
= false;
891 // NOTE: Apparently older versions (NT 4?) of the common controls send
892 // BN_DOUBLECLICKED but not a second BN_CLICKED for owner-drawn
893 // buttons, so in order to send two EVT_BUTTON events we should
894 // catch both types. Currently (Feb 2003) up-to-date versions of
895 // win98, win2k and winXP all send two BN_CLICKED messages for
896 // all button types, so we don't catch BN_DOUBLECLICKED anymore
897 // in order to not get 3 EVT_BUTTON events. If this is a problem
898 // then we need to figure out which version of the comctl32 changed
899 // this behaviour and test for it.
901 case 1: // message came from an accelerator
902 case BN_CLICKED
: // normal buttons send this
903 processed
= SendClickEvent();
910 WXLRESULT
wxButton::MSWWindowProc(WXUINT nMsg
, WXWPARAM wParam
, WXLPARAM lParam
)
912 // when we receive focus, we want to temporarily become the default button in
913 // our parent panel so that pressing "Enter" would activate us -- and when
914 // losing it we should restore the previous default button as well
915 if ( nMsg
== WM_SETFOCUS
)
919 // let the default processing take place too
921 else if ( nMsg
== WM_KILLFOCUS
)
925 else if ( nMsg
== WM_LBUTTONDBLCLK
)
927 // emulate a click event to force an owner-drawn button to change its
928 // appearance - without this, it won't do it
929 (void)wxControl::MSWWindowProc(WM_LBUTTONDOWN
, wParam
, lParam
);
931 // and continue with processing the message normally as well
934 else if ( nMsg
== WM_THEMECHANGED
)
936 // need to recalculate the best size here
937 // as the theme size might have changed
938 InvalidateBestSize();
940 #endif // wxUSE_UXTHEME
941 // must use m_mouseInWindow here instead of IsMouseInWindow()
942 // since we need to know the first time the mouse enters the window
943 // and IsMouseInWindow() would return true in this case
944 else if ( (nMsg
== WM_MOUSEMOVE
&& !m_mouseInWindow
) ||
945 nMsg
== WM_MOUSELEAVE
)
951 wxUxThemeEngine::GetIfActive() ||
952 #endif // wxUSE_UXTHEME
953 (m_imageData
&& m_imageData
->GetBitmap(State_Current
).IsOk())
961 // let the base class do all real processing
962 return wxControl::MSWWindowProc(nMsg
, wParam
, lParam
);
965 // ----------------------------------------------------------------------------
966 // authentication needed handling
967 // ----------------------------------------------------------------------------
969 bool wxButton::DoGetAuthNeeded() const
974 void wxButton::DoSetAuthNeeded(bool show
)
976 // show/hide UAC symbol on Windows Vista and later
977 if ( wxGetWinVersion() >= wxWinVersion_6
)
980 ::SendMessage(GetHwnd(), BCM_SETSHIELD
, 0, show
);
981 InvalidateBestSize();
985 // ----------------------------------------------------------------------------
987 // ----------------------------------------------------------------------------
989 wxBitmap
wxButton::DoGetBitmap(State which
) const
991 return m_imageData
? m_imageData
->GetBitmap(which
) : wxBitmap();
994 void wxButton::DoSetBitmap(const wxBitmap
& bitmap
, State which
)
997 wxXPButtonImageData
*oldData
= NULL
;
998 #endif // wxUSE_UXTHEME
1000 // Check if we already had bitmaps of different size.
1002 bitmap
.GetSize() != m_imageData
->GetBitmap(State_Normal
).GetSize() )
1004 wxASSERT_MSG( which
== State_Normal
,
1005 "Must set normal bitmap with the new size first" );
1008 if ( ShowsLabel() && wxUxThemeEngine::GetIfActive() )
1010 // We can't change the size of the images stored in wxImageList
1011 // in wxXPButtonImageData::m_iml so force recreating it below but
1012 // keep the current data to copy its values into the new one.
1013 oldData
= static_cast<wxXPButtonImageData
*>(m_imageData
);
1016 #endif // wxUSE_UXTHEME
1017 //else: wxODButtonImageData doesn't require anything special
1020 // allocate the image data when the first bitmap is set
1024 // using image list doesn't work correctly if we don't have any label
1025 // (even if we use BUTTON_IMAGELIST_ALIGN_CENTER alignment and
1026 // BS_BITMAP style), at least under Windows 2003 so use owner drawn
1027 // strategy for bitmap-only buttons
1028 if ( ShowsLabel() && wxUxThemeEngine::GetIfActive() )
1030 m_imageData
= new wxXPButtonImageData(this, bitmap
);
1034 // Preserve the old values in case the user changed them.
1035 m_imageData
->SetBitmapPosition(oldData
->GetBitmapPosition());
1037 const wxSize oldMargins
= oldData
->GetBitmapMargins();
1038 m_imageData
->SetBitmapMargins(oldMargins
.x
, oldMargins
.y
);
1040 // No need to preserve the bitmaps though as they were of wrong
1047 #endif // wxUSE_UXTHEME
1049 m_imageData
= new wxODButtonImageData(this, bitmap
);
1055 m_imageData
->SetBitmap(bitmap
, which
);
1058 // it should be enough to only invalidate the best size when the normal
1059 // bitmap changes as all bitmaps assigned to the button should be of the
1061 if ( which
== State_Normal
)
1062 InvalidateBestSize();
1067 wxSize
wxButton::DoGetBitmapMargins() const
1069 return m_imageData
? m_imageData
->GetBitmapMargins() : wxSize(0, 0);
1072 void wxButton::DoSetBitmapMargins(wxCoord x
, wxCoord y
)
1074 wxCHECK_RET( m_imageData
, "SetBitmap() must be called first" );
1076 m_imageData
->SetBitmapMargins(x
, y
);
1077 InvalidateBestSize();
1080 void wxButton::DoSetBitmapPosition(wxDirection dir
)
1082 wxCHECK_RET( m_imageData
, "SetBitmap() must be called first" );
1084 m_imageData
->SetBitmapPosition(dir
);
1085 InvalidateBestSize();
1088 // ----------------------------------------------------------------------------
1090 // ----------------------------------------------------------------------------
1094 bool wxButton::DoSetLabelMarkup(const wxString
& markup
)
1096 if ( !wxButtonBase::DoSetLabelMarkup(markup
) )
1099 if ( !m_markupText
)
1101 m_markupText
= new wxMarkupText(markup
);
1106 // We are already owner-drawn so just update the text.
1107 m_markupText
->SetMarkup(markup
);
1115 #endif // wxUSE_MARKUP
1117 // ----------------------------------------------------------------------------
1118 // owner-drawn buttons support
1119 // ----------------------------------------------------------------------------
1125 // return the button state using both the ODS_XXX flags specified in state
1126 // parameter and the current button state
1127 wxButton::State
GetButtonState(wxButton
*btn
, UINT state
)
1129 if ( state
& ODS_DISABLED
)
1130 return wxButton::State_Disabled
;
1132 if ( state
& ODS_SELECTED
)
1133 return wxButton::State_Pressed
;
1135 if ( btn
->HasCapture() || btn
->IsMouseInWindow() )
1136 return wxButton::State_Current
;
1138 if ( state
& ODS_FOCUS
)
1139 return wxButton::State_Focused
;
1141 return wxButton::State_Normal
;
1144 void DrawButtonText(HDC hdc
,
1149 const wxString text
= btn
->GetLabel();
1151 if ( text
.find(wxT('\n')) != wxString::npos
)
1153 // draw multiline label
1155 // center text horizontally in any case
1158 // first we need to compute its bounding rect
1160 ::CopyRect(&rc
, pRect
);
1161 ::DrawText(hdc
, text
.wx_str(), text
.length(), &rc
,
1162 DT_CENTER
| DT_CALCRECT
);
1164 // now center this rect inside the entire button area
1165 const LONG w
= rc
.right
- rc
.left
;
1166 const LONG h
= rc
.bottom
- rc
.top
;
1167 rc
.left
= (pRect
->right
- pRect
->left
)/2 - w
/2;
1168 rc
.right
= rc
.left
+w
;
1169 rc
.top
= (pRect
->bottom
- pRect
->top
)/2 - h
/2;
1170 rc
.bottom
= rc
.top
+h
;
1172 ::DrawText(hdc
, text
.wx_str(), text
.length(), &rc
, flags
);
1174 else // single line label
1176 // translate wx button flags to alignment flags for DrawText()
1177 if ( btn
->HasFlag(wxBU_RIGHT
) )
1181 else if ( !btn
->HasFlag(wxBU_LEFT
) )
1185 //else: DT_LEFT is the default anyhow (and its value is 0 too)
1187 if ( btn
->HasFlag(wxBU_BOTTOM
) )
1191 else if ( !btn
->HasFlag(wxBU_TOP
) )
1193 flags
|= DT_VCENTER
;
1195 //else: as above, DT_TOP is the default
1197 // notice that we must have DT_SINGLELINE for vertical alignment flags
1199 ::DrawText(hdc
, text
.wx_str(), text
.length(), pRect
,
1200 flags
| DT_SINGLELINE
);
1204 void DrawRect(HDC hdc
, const RECT
& r
)
1206 wxDrawLine(hdc
, r
.left
, r
.top
, r
.right
, r
.top
);
1207 wxDrawLine(hdc
, r
.right
, r
.top
, r
.right
, r
.bottom
);
1208 wxDrawLine(hdc
, r
.right
, r
.bottom
, r
.left
, r
.bottom
);
1209 wxDrawLine(hdc
, r
.left
, r
.bottom
, r
.left
, r
.top
);
1213 The button frame looks like this normally:
1216 WHHHHHHHHHHHHHHHHGB W = white (HILIGHT)
1217 WH GB H = light grey (LIGHT)
1218 WH GB G = dark grey (SHADOW)
1219 WH GB B = black (DKSHADOW)
1224 When the button is selected, the button becomes like this (the total button
1225 size doesn't change):
1236 When the button is pushed (while selected) it is like:
1247 void DrawButtonFrame(HDC hdc
, RECT
& rectBtn
,
1248 bool selected
, bool pushed
)
1251 CopyRect(&r
, &rectBtn
);
1253 AutoHPEN
hpenBlack(GetSysColor(COLOR_3DDKSHADOW
)),
1254 hpenGrey(GetSysColor(COLOR_3DSHADOW
)),
1255 hpenLightGr(GetSysColor(COLOR_3DLIGHT
)),
1256 hpenWhite(GetSysColor(COLOR_3DHILIGHT
));
1258 SelectInHDC
selectPen(hdc
, hpenBlack
);
1267 (void)SelectObject(hdc
, hpenGrey
);
1268 ::InflateRect(&r
, -1, -1);
1278 ::InflateRect(&r
, -1, -1);
1281 wxDrawLine(hdc
, r
.left
, r
.bottom
, r
.right
, r
.bottom
);
1282 wxDrawLine(hdc
, r
.right
, r
.bottom
, r
.right
, r
.top
- 1);
1284 (void)SelectObject(hdc
, hpenWhite
);
1285 wxDrawLine(hdc
, r
.left
, r
.bottom
- 1, r
.left
, r
.top
);
1286 wxDrawLine(hdc
, r
.left
, r
.top
, r
.right
, r
.top
);
1288 (void)SelectObject(hdc
, hpenLightGr
);
1289 wxDrawLine(hdc
, r
.left
+ 1, r
.bottom
- 2, r
.left
+ 1, r
.top
+ 1);
1290 wxDrawLine(hdc
, r
.left
+ 1, r
.top
+ 1, r
.right
- 1, r
.top
+ 1);
1292 (void)SelectObject(hdc
, hpenGrey
);
1293 wxDrawLine(hdc
, r
.left
+ 1, r
.bottom
- 1, r
.right
- 1, r
.bottom
- 1);
1294 wxDrawLine(hdc
, r
.right
- 1, r
.bottom
- 1, r
.right
- 1, r
.top
);
1297 InflateRect(&rectBtn
, -OD_BUTTON_MARGIN
, -OD_BUTTON_MARGIN
);
1301 void DrawXPBackground(wxButton
*button
, HDC hdc
, RECT
& rectBtn
, UINT state
)
1303 wxUxThemeHandle
theme(button
, L
"BUTTON");
1305 // this array is indexed by wxButton::State values and so must be kept in
1307 static const int uxStates
[] =
1309 PBS_NORMAL
, PBS_HOT
, PBS_PRESSED
, PBS_DISABLED
, PBS_DEFAULTED
1312 int iState
= uxStates
[GetButtonState(button
, state
)];
1314 wxUxThemeEngine
* const engine
= wxUxThemeEngine::Get();
1316 // draw parent background if needed
1317 if ( engine
->IsThemeBackgroundPartiallyTransparent
1324 // Set this button as the one whose background is being erased: this
1325 // allows our WM_ERASEBKGND handler used by DrawThemeParentBackground()
1326 // to correctly align the background brush with this window instead of
1327 // the parent window to which WM_ERASEBKGND is sent. Notice that this
1328 // doesn't work with custom user-defined EVT_ERASE_BACKGROUND handlers
1329 // as they won't be aligned but unfortunately all the attempts to fix
1330 // it by shifting DC origin before calling DrawThemeParentBackground()
1331 // failed to work so we at least do this, even though this is far from
1332 // being the perfect solution.
1333 wxWindowBeingErased
= button
;
1335 engine
->DrawThemeParentBackground(GetHwndOf(button
), hdc
, &rectBtn
);
1337 wxWindowBeingErased
= NULL
;
1341 engine
->DrawThemeBackground(theme
, hdc
, BP_PUSHBUTTON
, iState
,
1344 // calculate content area margins
1346 engine
->GetThemeMargins(theme
, hdc
, BP_PUSHBUTTON
, iState
,
1347 TMT_CONTENTMARGINS
, &rectBtn
, &margins
);
1348 ::InflateRect(&rectBtn
, -margins
.cxLeftWidth
, -margins
.cyTopHeight
);
1349 ::InflateRect(&rectBtn
, -XP_BUTTON_EXTRA_MARGIN
, -XP_BUTTON_EXTRA_MARGIN
);
1351 if ( button
->UseBgCol() )
1353 COLORREF colBg
= wxColourToRGB(button
->GetBackgroundColour());
1354 AutoHBRUSH
hbrushBackground(colBg
);
1356 // don't overwrite the focus rect
1358 ::CopyRect(&rectClient
, &rectBtn
);
1359 ::InflateRect(&rectClient
, -1, -1);
1360 FillRect(hdc
, &rectClient
, hbrushBackground
);
1363 #endif // wxUSE_UXTHEME
1365 } // anonymous namespace
1367 // ----------------------------------------------------------------------------
1368 // owner drawn buttons support
1369 // ----------------------------------------------------------------------------
1371 void wxButton::MakeOwnerDrawn()
1373 long style
= GetWindowLong(GetHwnd(), GWL_STYLE
);
1374 if ( (style
& BS_OWNERDRAW
) != BS_OWNERDRAW
)
1377 style
|= BS_OWNERDRAW
;
1378 SetWindowLong(GetHwnd(), GWL_STYLE
, style
);
1382 bool wxButton::SetBackgroundColour(const wxColour
&colour
)
1384 if ( !wxControl::SetBackgroundColour(colour
) )
1397 bool wxButton::SetForegroundColour(const wxColour
&colour
)
1399 if ( !wxControl::SetForegroundColour(colour
) )
1412 bool wxButton::MSWOnDraw(WXDRAWITEMSTRUCT
*wxdis
)
1414 LPDRAWITEMSTRUCT lpDIS
= (LPDRAWITEMSTRUCT
)wxdis
;
1415 HDC hdc
= lpDIS
->hDC
;
1417 UINT state
= lpDIS
->itemState
;
1418 bool pushed
= (SendMessage(GetHwnd(), BM_GETSTATE
, 0, 0) & BST_PUSHED
) != 0;
1421 CopyRect(&rectBtn
, &lpDIS
->rcItem
);
1423 // draw the button background
1424 if ( !HasFlag(wxBORDER_NONE
) )
1427 if ( wxUxThemeEngine::GetIfActive() )
1429 DrawXPBackground(this, hdc
, rectBtn
, state
);
1432 #endif // wxUSE_UXTHEME
1434 COLORREF colBg
= wxColourToRGB(GetBackgroundColour());
1436 // first, draw the background
1437 AutoHBRUSH
hbrushBackground(colBg
);
1438 FillRect(hdc
, &rectBtn
, hbrushBackground
);
1440 // draw the border for the current state
1441 bool selected
= (state
& ODS_SELECTED
) != 0;
1445 tlw
= wxDynamicCast(wxGetTopLevelParent(this), wxTopLevelWindow
);
1448 selected
= tlw
->GetDefaultItem() == this;
1452 DrawButtonFrame(hdc
, rectBtn
, selected
, pushed
);
1455 // draw the focus rectangle if we need it
1456 if ( (state
& ODS_FOCUS
) && !(state
& ODS_NOFOCUSRECT
) )
1458 DrawFocusRect(hdc
, &rectBtn
);
1461 if ( !wxUxThemeEngine::GetIfActive() )
1462 #endif // wxUSE_UXTHEME
1466 // the label is shifted by 1 pixel to create "pushed" effect
1467 OffsetRect(&rectBtn
, 1, 1);
1474 // draw the image, if any
1477 wxBitmap bmp
= m_imageData
->GetBitmap(GetButtonState(this, state
));
1479 bmp
= m_imageData
->GetBitmap(State_Normal
);
1481 const wxSize sizeBmp
= bmp
.GetSize();
1482 const wxSize margin
= m_imageData
->GetBitmapMargins();
1483 const wxSize
sizeBmpWithMargins(sizeBmp
+ 2*margin
);
1484 wxRect
rectButton(wxRectFromRECT(rectBtn
));
1486 // for simplicity, we start with centred rectangle and then move it to
1487 // the appropriate edge
1488 wxRect rectBitmap
= wxRect(sizeBmp
).CentreIn(rectButton
);
1490 // move bitmap only if we have a label, otherwise keep it centered
1493 switch ( m_imageData
->GetBitmapPosition() )
1496 wxFAIL_MSG( "invalid direction" );
1500 rectBitmap
.x
= rectButton
.x
+ margin
.x
;
1501 rectButton
.x
+= sizeBmpWithMargins
.x
;
1502 rectButton
.width
-= sizeBmpWithMargins
.x
;
1506 rectBitmap
.x
= rectButton
.GetRight() - sizeBmp
.x
- margin
.x
;
1507 rectButton
.width
-= sizeBmpWithMargins
.x
;
1511 rectBitmap
.y
= rectButton
.y
+ margin
.y
;
1512 rectButton
.y
+= sizeBmpWithMargins
.y
;
1513 rectButton
.height
-= sizeBmpWithMargins
.y
;
1517 rectBitmap
.y
= rectButton
.GetBottom() - sizeBmp
.y
- margin
.y
;
1518 rectButton
.height
-= sizeBmpWithMargins
.y
;
1523 wxDCTemp
dst((WXHDC
)hdc
);
1524 dst
.DrawBitmap(bmp
, rectBitmap
.GetPosition(), true);
1526 wxCopyRectToRECT(rectButton
, rectBtn
);
1530 // finally draw the label
1533 COLORREF colFg
= state
& ODS_DISABLED
1534 ? ::GetSysColor(COLOR_GRAYTEXT
)
1535 : wxColourToRGB(GetForegroundColour());
1537 wxTextColoursChanger
changeFg(hdc
, colFg
, CLR_INVALID
);
1538 wxBkModeChanger
changeBkMode(hdc
, wxBRUSHSTYLE_TRANSPARENT
);
1543 wxDCTemp
dc((WXHDC
)hdc
);
1544 dc
.SetTextForeground(wxColour(colFg
));
1545 dc
.SetFont(GetFont());
1547 m_markupText
->Render(dc
, wxRectFromRECT(rectBtn
),
1549 ? wxMarkupText::Render_Default
1550 : wxMarkupText::Render_ShowAccels
);
1552 else // Plain text label
1553 #endif // wxUSE_MARKUP
1555 // notice that DT_HIDEPREFIX doesn't work on old (pre-Windows 2000)
1556 // systems but by happy coincidence ODS_NOACCEL is not used under
1557 // them neither so DT_HIDEPREFIX should never be used there
1558 DrawButtonText(hdc
, &rectBtn
, this,
1559 state
& ODS_NOACCEL
? DT_HIDEPREFIX
: 0);
1566 #endif // wxUSE_BUTTON