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
);
163 // we use margins when we have both bitmap and text, but when we have
164 // only the bitmap it should take up the entire button area
165 if ( btn
->ShowsLabel() )
167 m_margin
.x
= btn
->GetCharWidth();
168 m_margin
.y
= btn
->GetCharHeight() / 2;
172 virtual wxBitmap
GetBitmap(wxButton::State which
) const
174 return m_bitmaps
[which
];
177 virtual void SetBitmap(const wxBitmap
& bitmap
, wxButton::State which
)
179 m_bitmaps
[which
] = bitmap
;
182 virtual wxSize
GetBitmapMargins() const
187 virtual void SetBitmapMargins(wxCoord x
, wxCoord y
)
189 m_margin
= wxSize(x
, y
);
192 virtual wxDirection
GetBitmapPosition() const
197 virtual void SetBitmapPosition(wxDirection dir
)
203 // just store the values passed to us to be able to retrieve them later
204 // from the drawing code
205 wxBitmap m_bitmaps
[wxButton::State_Max
];
209 wxDECLARE_NO_COPY_CLASS(wxODButtonImageData
);
214 // somehow the margin is one pixel greater than the value returned by
215 // GetThemeMargins() call
216 const int XP_BUTTON_EXTRA_MARGIN
= 1;
218 class wxXPButtonImageData
: public wxButtonImageData
221 // we must be constructed with the size of our images as we need to create
223 wxXPButtonImageData(wxButton
*btn
, const wxBitmap
& bitmap
)
224 : m_iml(bitmap
.GetWidth(), bitmap
.GetHeight(), true /* use mask */,
225 wxButton::State_Max
),
226 m_hwndBtn(GetHwndOf(btn
))
228 // initialize all bitmaps to normal state
229 for ( int n
= 0; n
< wxButton::State_Max
; n
++ )
234 m_data
.himl
= GetHimagelistOf(&m_iml
);
236 // no margins by default
238 m_data
.margin
.right
=
240 m_data
.margin
.bottom
= 0;
242 // use default alignment
243 m_data
.uAlign
= BUTTON_IMAGELIST_ALIGN_LEFT
;
248 virtual wxBitmap
GetBitmap(wxButton::State which
) const
250 return m_iml
.GetBitmap(which
);
253 virtual void SetBitmap(const wxBitmap
& bitmap
, wxButton::State which
)
255 m_iml
.Replace(which
, bitmap
);
260 virtual wxSize
GetBitmapMargins() const
262 return wxSize(m_data
.margin
.left
, m_data
.margin
.top
);
265 virtual void SetBitmapMargins(wxCoord x
, wxCoord y
)
267 RECT
& margin
= m_data
.margin
;
273 if ( !::SendMessage(m_hwndBtn
, BCM_SETTEXTMARGIN
, 0, (LPARAM
)&margin
) )
275 wxLogDebug("SendMessage(BCM_SETTEXTMARGIN) failed");
279 virtual wxDirection
GetBitmapPosition() const
281 switch ( m_data
.uAlign
)
284 wxFAIL_MSG( "invalid image alignment" );
287 case BUTTON_IMAGELIST_ALIGN_LEFT
:
290 case BUTTON_IMAGELIST_ALIGN_RIGHT
:
293 case BUTTON_IMAGELIST_ALIGN_TOP
:
296 case BUTTON_IMAGELIST_ALIGN_BOTTOM
:
301 virtual void SetBitmapPosition(wxDirection dir
)
307 wxFAIL_MSG( "invalid direction" );
311 alignNew
= BUTTON_IMAGELIST_ALIGN_LEFT
;
315 alignNew
= BUTTON_IMAGELIST_ALIGN_RIGHT
;
319 alignNew
= BUTTON_IMAGELIST_ALIGN_TOP
;
323 alignNew
= BUTTON_IMAGELIST_ALIGN_BOTTOM
;
327 if ( alignNew
!= m_data
.uAlign
)
329 m_data
.uAlign
= alignNew
;
335 void UpdateImageInfo()
337 if ( !::SendMessage(m_hwndBtn
, BCM_SETIMAGELIST
, 0, (LPARAM
)&m_data
) )
339 wxLogDebug("SendMessage(BCM_SETIMAGELIST) failed");
343 // we store image list separately to be able to use convenient wxImageList
344 // methods instead of working with raw HIMAGELIST
347 // store the rest of the data in BCM_SETIMAGELIST-friendly form
348 BUTTON_IMAGELIST m_data
;
350 // the button we're associated with
351 const HWND m_hwndBtn
;
354 wxDECLARE_NO_COPY_CLASS(wxXPButtonImageData
);
357 #endif // wxUSE_UXTHEME
359 } // anonymous namespace
361 // ----------------------------------------------------------------------------
363 // ----------------------------------------------------------------------------
365 // ============================================================================
367 // ============================================================================
369 // ----------------------------------------------------------------------------
370 // helper functions from wx/msw/private/button.h
371 // ----------------------------------------------------------------------------
373 void wxMSWButton::UpdateMultilineStyle(HWND hwnd
, const wxString
& label
)
375 // update BS_MULTILINE style depending on the new label (resetting it
376 // doesn't seem to do anything very useful but it shouldn't hurt and we do
377 // have to set it whenever the label becomes multi line as otherwise it
378 // wouldn't be shown correctly as we don't use BS_MULTILINE when creating
379 // the control unless it already has new lines in its label)
380 long styleOld
= ::GetWindowLong(hwnd
, GWL_STYLE
),
382 if ( label
.find(wxT('\n')) != wxString::npos
)
383 styleNew
= styleOld
| BS_MULTILINE
;
385 styleNew
= styleOld
& ~BS_MULTILINE
;
387 if ( styleNew
!= styleOld
)
388 ::SetWindowLong(hwnd
, GWL_STYLE
, styleNew
);
391 wxSize
wxMSWButton::GetFittingSize(wxWindow
*win
,
392 const wxSize
& sizeLabel
,
395 // FIXME: this is pure guesswork, need to retrieve the real button margins
396 wxSize sizeBtn
= sizeLabel
;
398 sizeBtn
.x
+= 3*win
->GetCharWidth();
399 sizeBtn
.y
+= win
->GetCharHeight()/2;
401 // account for the shield UAC icon if we have it
402 if ( flags
& Size_AuthNeeded
)
403 sizeBtn
.x
+= wxSystemSettings::GetMetric(wxSYS_SMALLICON_X
);
408 wxSize
wxMSWButton::ComputeBestFittingSize(wxControl
*btn
, int flags
)
413 dc
.GetMultiLineTextExtent(btn
->GetLabelText(), &sizeBtn
.x
, &sizeBtn
.y
);
415 return GetFittingSize(btn
, sizeBtn
, flags
);
418 wxSize
wxMSWButton::IncreaseToStdSizeAndCache(wxControl
*btn
, const wxSize
& size
)
420 wxSize
sizeBtn(size
);
422 // All buttons have at least the standard height and, unless the user
423 // explicitly wants them to be as small as possible and used wxBU_EXACTFIT
424 // style to indicate this, of at least the standard width too.
426 // Notice that we really want to make all buttons equally high, otherwise
427 // they look ugly and the existing code using wxBU_EXACTFIT only uses it to
428 // control width and not height.
430 // The 50x14 button size is documented in the "Recommended sizing and
431 // spacing" section of MSDN layout article.
433 // Note that we intentionally don't use GetDefaultSize() here, because
434 // it's inexact -- dialog units depend on this dialog's font.
435 const wxSize sizeDef
= btn
->ConvertDialogToPixels(wxSize(50, 14));
436 if ( !btn
->HasFlag(wxBU_EXACTFIT
) )
438 if ( sizeBtn
.x
< sizeDef
.x
)
439 sizeBtn
.x
= sizeDef
.x
;
441 if ( sizeBtn
.y
< sizeDef
.y
)
442 sizeBtn
.y
= sizeDef
.y
;
444 btn
->CacheBestSize(sizeBtn
);
449 // ----------------------------------------------------------------------------
450 // creation/destruction
451 // ----------------------------------------------------------------------------
453 bool wxButton::Create(wxWindow
*parent
,
459 const wxValidator
& validator
,
460 const wxString
& name
)
463 if (label
.empty() && wxIsStockID(id
))
465 // On Windows, some buttons aren't supposed to have mnemonics
466 label
= wxGetStockLabel
469 id
== wxID_OK
|| id
== wxID_CANCEL
|| id
== wxID_CLOSE
471 : wxSTOCK_WITH_MNEMONIC
475 if ( !CreateControl(parent
, id
, pos
, size
, style
, validator
, name
) )
479 WXDWORD msStyle
= MSWGetStyle(style
, &exstyle
);
481 // if the label contains several lines we must explicitly tell the button
482 // about it or it wouldn't draw it correctly ("\n"s would just appear as
485 // NB: we do it here and not in MSWGetStyle() because we need the label
486 // value and the label is not set yet when MSWGetStyle() is called
487 msStyle
|= wxMSWButton::GetMultilineStyle(label
);
489 return MSWCreateControl(wxT("BUTTON"), msStyle
, pos
, size
, label
, exstyle
);
492 wxButton::~wxButton()
494 wxTopLevelWindow
*tlw
= wxDynamicCast(wxGetTopLevelParent(this), wxTopLevelWindow
);
495 if ( tlw
&& tlw
->GetTmpDefaultItem() == this )
503 #endif // wxUSE_MARKUP
506 // ----------------------------------------------------------------------------
508 // ----------------------------------------------------------------------------
510 WXDWORD
wxButton::MSWGetStyle(long style
, WXDWORD
*exstyle
) const
512 // buttons never have an external border, they draw their own one
513 WXDWORD msStyle
= wxControl::MSWGetStyle
515 (style
& ~wxBORDER_MASK
) | wxBORDER_NONE
, exstyle
518 // we must use WS_CLIPSIBLINGS with the buttons or they would draw over
519 // each other in any resizeable dialog which has more than one button in
521 msStyle
|= WS_CLIPSIBLINGS
;
523 // don't use "else if" here: weird as it is, but you may combine wxBU_LEFT
524 // and wxBU_RIGHT to get BS_CENTER!
525 if ( style
& wxBU_LEFT
)
527 if ( style
& wxBU_RIGHT
)
529 if ( style
& wxBU_TOP
)
531 if ( style
& wxBU_BOTTOM
)
532 msStyle
|= BS_BOTTOM
;
535 if ( style
& wxNO_BORDER
)
537 #endif // __WXWINCE__
542 void wxButton::SetLabel(const wxString
& label
)
544 wxMSWButton::UpdateMultilineStyle(GetHwnd(), label
);
546 wxButtonBase::SetLabel(label
);
549 // If we have a plain text label, we shouldn't be using markup any longer.
555 // Unfortunately we don't really know whether we can reset the button
556 // to be non-owner-drawn or not: if we had made it owner-drawn just
557 // because of a call to SetLabelMarkup(), we could, but not if there
558 // were [also] calls to Set{Fore,Back}groundColour(). If it's really a
559 // problem to have button remain owner-drawn forever just because it
560 // had markup label once, we should record the reason for our current
561 // owner-drawnness and check it here.
563 #endif // wxUSE_MARKUP
566 // ----------------------------------------------------------------------------
567 // size management including autosizing
568 // ----------------------------------------------------------------------------
570 void wxButton::AdjustForBitmapSize(wxSize
&size
) const
572 wxCHECK_RET( m_imageData
, wxT("shouldn't be called if no image") );
574 // account for the bitmap size
575 const wxSize sizeBmp
= m_imageData
->GetBitmap(State_Normal
).GetSize();
576 const wxDirection dirBmp
= m_imageData
->GetBitmapPosition();
577 if ( dirBmp
== wxLEFT
|| dirBmp
== wxRIGHT
)
580 if ( sizeBmp
.y
> size
.y
)
583 else // bitmap on top/below the text
586 if ( sizeBmp
.x
> size
.x
)
590 // account for the user-specified margins
591 size
+= 2*m_imageData
->GetBitmapMargins();
593 // and also for the margins we always add internally (unless we have no
594 // border at all in which case the button has exactly the same size as
595 // bitmap and so no margins should be used)
596 if ( !HasFlag(wxBORDER_NONE
) )
601 if ( wxUxThemeEngine::GetIfActive() )
603 wxUxThemeHandle
theme(const_cast<wxButton
*>(this), L
"BUTTON");
606 wxUxThemeEngine::Get()->GetThemeMargins(theme
, NULL
,
613 // XP doesn't draw themed buttons correctly when the client
614 // area is smaller than 8x8 - enforce this minimum size for
616 size
.IncTo(wxSize(8, 8));
618 marginH
= margins
.cxLeftWidth
+ margins
.cxRightWidth
619 + 2*XP_BUTTON_EXTRA_MARGIN
;
620 marginV
= margins
.cyTopHeight
+ margins
.cyBottomHeight
621 + 2*XP_BUTTON_EXTRA_MARGIN
;
624 #endif // wxUSE_UXTHEME
627 marginV
= OD_BUTTON_MARGIN
;
630 size
.IncBy(marginH
, marginV
);
634 wxSize
wxButton::DoGetBestSize() const
636 wxButton
* const self
= const_cast<wxButton
*>(this);
640 // Account for the text part if we have it.
644 if ( GetAuthNeeded() )
645 flags
|= wxMSWButton::Size_AuthNeeded
;
651 size
= wxMSWButton::GetFittingSize(self
,
652 m_markupText
->Measure(dc
),
655 else // Normal plain text (but possibly multiline) label.
656 #endif // wxUSE_MARKUP
658 size
= wxMSWButton::ComputeBestFittingSize(self
, flags
);
663 AdjustForBitmapSize(size
);
665 return wxMSWButton::IncreaseToStdSizeAndCache(self
, size
);
669 wxSize
wxButtonBase::GetDefaultSize()
671 static wxSize s_sizeBtn
;
673 if ( s_sizeBtn
.x
== 0 )
676 dc
.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT
));
678 // The size of a standard button in the dialog units is 50x14,
679 // translate this to pixels.
681 // Windows' computes dialog units using average character width over
682 // upper- and lower-case ASCII alphabet and not using the average
683 // character width metadata stored in the font; see
684 // http://support.microsoft.com/default.aspx/kb/145994 for detailed
687 // NB: wxMulDivInt32() is used, because it correctly rounds the result
689 const wxSize base
= wxPrivate::GetAverageASCIILetterSize(dc
);
690 s_sizeBtn
.x
= wxMulDivInt32(50, base
.x
, 4);
691 s_sizeBtn
.y
= wxMulDivInt32(14, base
.y
, 8);
697 // ----------------------------------------------------------------------------
698 // default button handling
699 // ----------------------------------------------------------------------------
702 The comment below and all this code is probably due to not using WM_NEXTDLGCTL
703 message when changing focus (but just SetFocus() which is not enough), see
704 http://blogs.msdn.com/oldnewthing/archive/2004/08/02/205624.aspx for the
707 TODO: Do use WM_NEXTDLGCTL and get rid of all this code.
710 "Everything you ever wanted to know about the default buttons" or "Why do we
711 have to do all this?"
713 In MSW the default button should be activated when the user presses Enter
714 and the current control doesn't process Enter itself somehow. This is
715 handled by ::DefWindowProc() (or maybe ::DefDialogProc()) using DM_SETDEFID
716 Another aspect of "defaultness" is that the default button has different
717 appearance: this is due to BS_DEFPUSHBUTTON style which is completely
718 separate from DM_SETDEFID stuff (!). Also note that BS_DEFPUSHBUTTON should
719 be unset if our parent window is not active so it should be unset whenever
720 we lose activation and set back when we regain it.
722 Final complication is that when a button is active, it should be the default
723 one, i.e. pressing Enter on a button always activates it and not another
726 We handle this by maintaining a permanent and a temporary default items in
727 wxControlContainer (both may be NULL). When a button becomes the current
728 control (i.e. gets focus) it sets itself as the temporary default which
729 ensures that it has the right appearance and that Enter will be redirected
730 to it. When the button loses focus, it unsets the temporary default and so
731 the default item will be the permanent default -- that is the default button
732 if any had been set or none otherwise, which is just what we want.
734 NB: all this is quite complicated by now and the worst is that normally
735 it shouldn't be necessary at all as for the normal Windows programs
736 DefWindowProc() and IsDialogMessage() take care of all this
737 automatically -- however in wxWidgets programs this doesn't work for
738 nested hierarchies (i.e. a notebook inside a notebook) for unknown
739 reason and so we have to reproduce all this code ourselves. It would be
740 very nice if we could avoid doing it.
743 // set this button as the (permanently) default one in its panel
744 wxWindow
*wxButton::SetDefault()
746 // set this one as the default button both for wxWidgets ...
747 wxWindow
*winOldDefault
= wxButtonBase::SetDefault();
750 SetDefaultStyle(wxDynamicCast(winOldDefault
, wxButton
), false);
751 SetDefaultStyle(this, true);
753 return winOldDefault
;
756 // return the top level parent window if it's not being deleted yet, otherwise
758 static wxTopLevelWindow
*GetTLWParentIfNotBeingDeleted(wxWindow
*win
)
762 // IsTopLevel() will return false for a wxTLW being deleted, so we also
763 // need the parent test for this case
764 wxWindow
* const parent
= win
->GetParent();
765 if ( !parent
|| win
->IsTopLevel() )
767 if ( win
->IsBeingDeleted() )
776 wxASSERT_MSG( win
, wxT("button without top level parent?") );
778 wxTopLevelWindow
* const tlw
= wxDynamicCast(win
, wxTopLevelWindow
);
779 wxASSERT_MSG( tlw
, wxT("logic error in GetTLWParentIfNotBeingDeleted()") );
784 // set this button as being currently default
785 void wxButton::SetTmpDefault()
787 wxTopLevelWindow
* const tlw
= GetTLWParentIfNotBeingDeleted(GetParent());
791 wxWindow
*winOldDefault
= tlw
->GetDefaultItem();
792 tlw
->SetTmpDefaultItem(this);
794 SetDefaultStyle(wxDynamicCast(winOldDefault
, wxButton
), false);
795 SetDefaultStyle(this, true);
798 // unset this button as currently default, it may still stay permanent default
799 void wxButton::UnsetTmpDefault()
801 wxTopLevelWindow
* const tlw
= GetTLWParentIfNotBeingDeleted(GetParent());
805 tlw
->SetTmpDefaultItem(NULL
);
807 wxWindow
*winOldDefault
= tlw
->GetDefaultItem();
809 SetDefaultStyle(this, false);
810 SetDefaultStyle(wxDynamicCast(winOldDefault
, wxButton
), true);
815 wxButton::SetDefaultStyle(wxButton
*btn
, bool on
)
817 // we may be called with NULL pointer -- simpler to do the check here than
818 // in the caller which does wxDynamicCast()
822 // first, let DefDlgProc() know about the new default button
825 // we shouldn't set BS_DEFPUSHBUTTON for any button if we don't have
826 // focus at all any more
827 if ( !wxTheApp
->IsActive() )
830 wxWindow
* const tlw
= wxGetTopLevelParent(btn
);
831 wxCHECK_RET( tlw
, wxT("button without top level window?") );
833 ::SendMessage(GetHwndOf(tlw
), DM_SETDEFID
, btn
->GetId(), 0L);
835 // sending DM_SETDEFID also changes the button style to
836 // BS_DEFPUSHBUTTON so there is nothing more to do
839 // then also change the style as needed
840 long style
= ::GetWindowLong(GetHwndOf(btn
), GWL_STYLE
);
841 if ( !(style
& BS_DEFPUSHBUTTON
) == on
)
843 // don't do it with the owner drawn buttons because it will
844 // reset BS_OWNERDRAW style bit too (as BS_OWNERDRAW &
845 // BS_DEFPUSHBUTTON != 0)!
846 if ( (style
& BS_OWNERDRAW
) != BS_OWNERDRAW
)
848 ::SendMessage(GetHwndOf(btn
), BM_SETSTYLE
,
849 on
? style
| BS_DEFPUSHBUTTON
850 : style
& ~BS_DEFPUSHBUTTON
,
855 // redraw the button - it will notice itself that it's
856 // [not] the default one [any longer]
860 //else: already has correct style
863 // ----------------------------------------------------------------------------
865 // ----------------------------------------------------------------------------
867 bool wxButton::SendClickEvent()
869 wxCommandEvent
event(wxEVT_COMMAND_BUTTON_CLICKED
, GetId());
870 event
.SetEventObject(this);
872 return ProcessCommand(event
);
875 void wxButton::Command(wxCommandEvent
& event
)
877 ProcessCommand(event
);
880 // ----------------------------------------------------------------------------
881 // event/message handlers
882 // ----------------------------------------------------------------------------
884 bool wxButton::MSWCommand(WXUINT param
, WXWORD
WXUNUSED(id
))
886 bool processed
= false;
889 // NOTE: Apparently older versions (NT 4?) of the common controls send
890 // BN_DOUBLECLICKED but not a second BN_CLICKED for owner-drawn
891 // buttons, so in order to send two EVT_BUTTON events we should
892 // catch both types. Currently (Feb 2003) up-to-date versions of
893 // win98, win2k and winXP all send two BN_CLICKED messages for
894 // all button types, so we don't catch BN_DOUBLECLICKED anymore
895 // in order to not get 3 EVT_BUTTON events. If this is a problem
896 // then we need to figure out which version of the comctl32 changed
897 // this behaviour and test for it.
899 case 1: // message came from an accelerator
900 case BN_CLICKED
: // normal buttons send this
901 processed
= SendClickEvent();
908 WXLRESULT
wxButton::MSWWindowProc(WXUINT nMsg
, WXWPARAM wParam
, WXLPARAM lParam
)
910 // when we receive focus, we want to temporarily become the default button in
911 // our parent panel so that pressing "Enter" would activate us -- and when
912 // losing it we should restore the previous default button as well
913 if ( nMsg
== WM_SETFOCUS
)
917 // let the default processing take place too
919 else if ( nMsg
== WM_KILLFOCUS
)
923 else if ( nMsg
== WM_LBUTTONDBLCLK
)
925 // emulate a click event to force an owner-drawn button to change its
926 // appearance - without this, it won't do it
927 (void)wxControl::MSWWindowProc(WM_LBUTTONDOWN
, wParam
, lParam
);
929 // and continue with processing the message normally as well
932 else if ( nMsg
== WM_THEMECHANGED
)
934 // need to recalculate the best size here
935 // as the theme size might have changed
936 InvalidateBestSize();
938 #endif // wxUSE_UXTHEME
939 // must use m_mouseInWindow here instead of IsMouseInWindow()
940 // since we need to know the first time the mouse enters the window
941 // and IsMouseInWindow() would return true in this case
942 else if ( (nMsg
== WM_MOUSEMOVE
&& !m_mouseInWindow
) ||
943 nMsg
== WM_MOUSELEAVE
)
949 wxUxThemeEngine::GetIfActive() ||
950 #endif // wxUSE_UXTHEME
951 (m_imageData
&& m_imageData
->GetBitmap(State_Current
).IsOk())
959 // let the base class do all real processing
960 return wxControl::MSWWindowProc(nMsg
, wParam
, lParam
);
963 // ----------------------------------------------------------------------------
964 // authentication needed handling
965 // ----------------------------------------------------------------------------
967 bool wxButton::DoGetAuthNeeded() const
972 void wxButton::DoSetAuthNeeded(bool show
)
974 // show/hide UAC symbol on Windows Vista and later
975 if ( wxGetWinVersion() >= wxWinVersion_6
)
978 ::SendMessage(GetHwnd(), BCM_SETSHIELD
, 0, show
);
979 InvalidateBestSize();
983 // ----------------------------------------------------------------------------
985 // ----------------------------------------------------------------------------
987 wxBitmap
wxButton::DoGetBitmap(State which
) const
989 return m_imageData
? m_imageData
->GetBitmap(which
) : wxBitmap();
992 void wxButton::DoSetBitmap(const wxBitmap
& bitmap
, State which
)
995 wxXPButtonImageData
*oldData
= NULL
;
996 #endif // wxUSE_UXTHEME
998 // Check if we already had bitmaps of different size.
1000 bitmap
.GetSize() != m_imageData
->GetBitmap(State_Normal
).GetSize() )
1002 wxASSERT_MSG( which
== State_Normal
,
1003 "Must set normal bitmap with the new size first" );
1006 if ( ShowsLabel() && wxUxThemeEngine::GetIfActive() )
1008 // We can't change the size of the images stored in wxImageList
1009 // in wxXPButtonImageData::m_iml so force recreating it below but
1010 // keep the current data to copy its values into the new one.
1011 oldData
= static_cast<wxXPButtonImageData
*>(m_imageData
);
1014 #endif // wxUSE_UXTHEME
1015 //else: wxODButtonImageData doesn't require anything special
1018 // allocate the image data when the first bitmap is set
1022 // using image list doesn't work correctly if we don't have any label
1023 // (even if we use BUTTON_IMAGELIST_ALIGN_CENTER alignment and
1024 // BS_BITMAP style), at least under Windows 2003 so use owner drawn
1025 // strategy for bitmap-only buttons
1026 if ( ShowsLabel() && wxUxThemeEngine::GetIfActive() )
1028 m_imageData
= new wxXPButtonImageData(this, bitmap
);
1032 // Preserve the old values in case the user changed them.
1033 m_imageData
->SetBitmapPosition(oldData
->GetBitmapPosition());
1035 const wxSize oldMargins
= oldData
->GetBitmapMargins();
1036 m_imageData
->SetBitmapMargins(oldMargins
.x
, oldMargins
.y
);
1038 // No need to preserve the bitmaps though as they were of wrong
1045 #endif // wxUSE_UXTHEME
1047 m_imageData
= new wxODButtonImageData(this, bitmap
);
1053 m_imageData
->SetBitmap(bitmap
, which
);
1056 // it should be enough to only invalidate the best size when the normal
1057 // bitmap changes as all bitmaps assigned to the button should be of the
1059 if ( which
== State_Normal
)
1060 InvalidateBestSize();
1065 wxSize
wxButton::DoGetBitmapMargins() const
1067 return m_imageData
? m_imageData
->GetBitmapMargins() : wxSize(0, 0);
1070 void wxButton::DoSetBitmapMargins(wxCoord x
, wxCoord y
)
1072 wxCHECK_RET( m_imageData
, "SetBitmap() must be called first" );
1074 m_imageData
->SetBitmapMargins(x
, y
);
1075 InvalidateBestSize();
1078 void wxButton::DoSetBitmapPosition(wxDirection dir
)
1080 wxCHECK_RET( m_imageData
, "SetBitmap() must be called first" );
1082 m_imageData
->SetBitmapPosition(dir
);
1083 InvalidateBestSize();
1086 // ----------------------------------------------------------------------------
1088 // ----------------------------------------------------------------------------
1092 bool wxButton::DoSetLabelMarkup(const wxString
& markup
)
1094 if ( !wxButtonBase::DoSetLabelMarkup(markup
) )
1097 if ( !m_markupText
)
1099 m_markupText
= new wxMarkupText(markup
);
1104 // We are already owner-drawn so just update the text.
1105 m_markupText
->SetMarkup(markup
);
1113 #endif // wxUSE_MARKUP
1115 // ----------------------------------------------------------------------------
1116 // owner-drawn buttons support
1117 // ----------------------------------------------------------------------------
1123 // return the button state using both the ODS_XXX flags specified in state
1124 // parameter and the current button state
1125 wxButton::State
GetButtonState(wxButton
*btn
, UINT state
)
1127 if ( state
& ODS_DISABLED
)
1128 return wxButton::State_Disabled
;
1130 if ( state
& ODS_SELECTED
)
1131 return wxButton::State_Pressed
;
1133 if ( btn
->HasCapture() || btn
->IsMouseInWindow() )
1134 return wxButton::State_Current
;
1136 if ( state
& ODS_FOCUS
)
1137 return wxButton::State_Focused
;
1139 return wxButton::State_Normal
;
1142 void DrawButtonText(HDC hdc
,
1147 const wxString text
= btn
->GetLabel();
1149 if ( text
.find(wxT('\n')) != wxString::npos
)
1151 // draw multiline label
1153 // center text horizontally in any case
1156 // first we need to compute its bounding rect
1158 ::CopyRect(&rc
, pRect
);
1159 ::DrawText(hdc
, text
.wx_str(), text
.length(), &rc
,
1160 DT_CENTER
| DT_CALCRECT
);
1162 // now center this rect inside the entire button area
1163 const LONG w
= rc
.right
- rc
.left
;
1164 const LONG h
= rc
.bottom
- rc
.top
;
1165 rc
.left
= (pRect
->right
- pRect
->left
)/2 - w
/2;
1166 rc
.right
= rc
.left
+w
;
1167 rc
.top
= (pRect
->bottom
- pRect
->top
)/2 - h
/2;
1168 rc
.bottom
= rc
.top
+h
;
1170 ::DrawText(hdc
, text
.wx_str(), text
.length(), &rc
, flags
);
1172 else // single line label
1174 // translate wx button flags to alignment flags for DrawText()
1175 if ( btn
->HasFlag(wxBU_RIGHT
) )
1179 else if ( !btn
->HasFlag(wxBU_LEFT
) )
1183 //else: DT_LEFT is the default anyhow (and its value is 0 too)
1185 if ( btn
->HasFlag(wxBU_BOTTOM
) )
1189 else if ( !btn
->HasFlag(wxBU_TOP
) )
1191 flags
|= DT_VCENTER
;
1193 //else: as above, DT_TOP is the default
1195 // notice that we must have DT_SINGLELINE for vertical alignment flags
1197 ::DrawText(hdc
, text
.wx_str(), text
.length(), pRect
,
1198 flags
| DT_SINGLELINE
);
1202 void DrawRect(HDC hdc
, const RECT
& r
)
1204 wxDrawLine(hdc
, r
.left
, r
.top
, r
.right
, r
.top
);
1205 wxDrawLine(hdc
, r
.right
, r
.top
, r
.right
, r
.bottom
);
1206 wxDrawLine(hdc
, r
.right
, r
.bottom
, r
.left
, r
.bottom
);
1207 wxDrawLine(hdc
, r
.left
, r
.bottom
, r
.left
, r
.top
);
1211 The button frame looks like this normally:
1214 WHHHHHHHHHHHHHHHHGB W = white (HILIGHT)
1215 WH GB H = light grey (LIGHT)
1216 WH GB G = dark grey (SHADOW)
1217 WH GB B = black (DKSHADOW)
1222 When the button is selected, the button becomes like this (the total button
1223 size doesn't change):
1234 When the button is pushed (while selected) it is like:
1245 void DrawButtonFrame(HDC hdc
, RECT
& rectBtn
,
1246 bool selected
, bool pushed
)
1249 CopyRect(&r
, &rectBtn
);
1251 AutoHPEN
hpenBlack(GetSysColor(COLOR_3DDKSHADOW
)),
1252 hpenGrey(GetSysColor(COLOR_3DSHADOW
)),
1253 hpenLightGr(GetSysColor(COLOR_3DLIGHT
)),
1254 hpenWhite(GetSysColor(COLOR_3DHILIGHT
));
1256 SelectInHDC
selectPen(hdc
, hpenBlack
);
1265 (void)SelectObject(hdc
, hpenGrey
);
1266 ::InflateRect(&r
, -1, -1);
1276 ::InflateRect(&r
, -1, -1);
1279 wxDrawLine(hdc
, r
.left
, r
.bottom
, r
.right
, r
.bottom
);
1280 wxDrawLine(hdc
, r
.right
, r
.bottom
, r
.right
, r
.top
- 1);
1282 (void)SelectObject(hdc
, hpenWhite
);
1283 wxDrawLine(hdc
, r
.left
, r
.bottom
- 1, r
.left
, r
.top
);
1284 wxDrawLine(hdc
, r
.left
, r
.top
, r
.right
, r
.top
);
1286 (void)SelectObject(hdc
, hpenLightGr
);
1287 wxDrawLine(hdc
, r
.left
+ 1, r
.bottom
- 2, r
.left
+ 1, r
.top
+ 1);
1288 wxDrawLine(hdc
, r
.left
+ 1, r
.top
+ 1, r
.right
- 1, r
.top
+ 1);
1290 (void)SelectObject(hdc
, hpenGrey
);
1291 wxDrawLine(hdc
, r
.left
+ 1, r
.bottom
- 1, r
.right
- 1, r
.bottom
- 1);
1292 wxDrawLine(hdc
, r
.right
- 1, r
.bottom
- 1, r
.right
- 1, r
.top
);
1295 InflateRect(&rectBtn
, -OD_BUTTON_MARGIN
, -OD_BUTTON_MARGIN
);
1299 void DrawXPBackground(wxButton
*button
, HDC hdc
, RECT
& rectBtn
, UINT state
)
1301 wxUxThemeHandle
theme(button
, L
"BUTTON");
1303 // this array is indexed by wxButton::State values and so must be kept in
1305 static const int uxStates
[] =
1307 PBS_NORMAL
, PBS_HOT
, PBS_PRESSED
, PBS_DISABLED
, PBS_DEFAULTED
1310 int iState
= uxStates
[GetButtonState(button
, state
)];
1312 wxUxThemeEngine
* const engine
= wxUxThemeEngine::Get();
1314 // draw parent background if needed
1315 if ( engine
->IsThemeBackgroundPartiallyTransparent
1322 // Set this button as the one whose background is being erased: this
1323 // allows our WM_ERASEBKGND handler used by DrawThemeParentBackground()
1324 // to correctly align the background brush with this window instead of
1325 // the parent window to which WM_ERASEBKGND is sent. Notice that this
1326 // doesn't work with custom user-defined EVT_ERASE_BACKGROUND handlers
1327 // as they won't be aligned but unfortunately all the attempts to fix
1328 // it by shifting DC origin before calling DrawThemeParentBackground()
1329 // failed to work so we at least do this, even though this is far from
1330 // being the perfect solution.
1331 wxWindowBeingErased
= button
;
1333 engine
->DrawThemeParentBackground(GetHwndOf(button
), hdc
, &rectBtn
);
1335 wxWindowBeingErased
= NULL
;
1339 engine
->DrawThemeBackground(theme
, hdc
, BP_PUSHBUTTON
, iState
,
1342 // calculate content area margins
1344 engine
->GetThemeMargins(theme
, hdc
, BP_PUSHBUTTON
, iState
,
1345 TMT_CONTENTMARGINS
, &rectBtn
, &margins
);
1346 ::InflateRect(&rectBtn
, -margins
.cxLeftWidth
, -margins
.cyTopHeight
);
1347 ::InflateRect(&rectBtn
, -XP_BUTTON_EXTRA_MARGIN
, -XP_BUTTON_EXTRA_MARGIN
);
1349 if ( button
->UseBgCol() )
1351 COLORREF colBg
= wxColourToRGB(button
->GetBackgroundColour());
1352 AutoHBRUSH
hbrushBackground(colBg
);
1354 // don't overwrite the focus rect
1356 ::CopyRect(&rectClient
, &rectBtn
);
1357 ::InflateRect(&rectClient
, -1, -1);
1358 FillRect(hdc
, &rectClient
, hbrushBackground
);
1361 #endif // wxUSE_UXTHEME
1363 } // anonymous namespace
1365 // ----------------------------------------------------------------------------
1366 // owner drawn buttons support
1367 // ----------------------------------------------------------------------------
1369 void wxButton::MakeOwnerDrawn()
1371 long style
= GetWindowLong(GetHwnd(), GWL_STYLE
);
1372 if ( (style
& BS_OWNERDRAW
) != BS_OWNERDRAW
)
1375 style
|= BS_OWNERDRAW
;
1376 SetWindowLong(GetHwnd(), GWL_STYLE
, style
);
1380 bool wxButton::SetBackgroundColour(const wxColour
&colour
)
1382 if ( !wxControl::SetBackgroundColour(colour
) )
1395 bool wxButton::SetForegroundColour(const wxColour
&colour
)
1397 if ( !wxControl::SetForegroundColour(colour
) )
1410 bool wxButton::MSWOnDraw(WXDRAWITEMSTRUCT
*wxdis
)
1412 LPDRAWITEMSTRUCT lpDIS
= (LPDRAWITEMSTRUCT
)wxdis
;
1413 HDC hdc
= lpDIS
->hDC
;
1415 UINT state
= lpDIS
->itemState
;
1416 bool pushed
= (SendMessage(GetHwnd(), BM_GETSTATE
, 0, 0) & BST_PUSHED
) != 0;
1419 CopyRect(&rectBtn
, &lpDIS
->rcItem
);
1421 // draw the button background
1422 if ( !HasFlag(wxBORDER_NONE
) )
1425 if ( wxUxThemeEngine::GetIfActive() )
1427 DrawXPBackground(this, hdc
, rectBtn
, state
);
1430 #endif // wxUSE_UXTHEME
1432 COLORREF colBg
= wxColourToRGB(GetBackgroundColour());
1434 // first, draw the background
1435 AutoHBRUSH
hbrushBackground(colBg
);
1436 FillRect(hdc
, &rectBtn
, hbrushBackground
);
1438 // draw the border for the current state
1439 bool selected
= (state
& ODS_SELECTED
) != 0;
1443 tlw
= wxDynamicCast(wxGetTopLevelParent(this), wxTopLevelWindow
);
1446 selected
= tlw
->GetDefaultItem() == this;
1450 DrawButtonFrame(hdc
, rectBtn
, selected
, pushed
);
1453 // draw the focus rectangle if we need it
1454 if ( (state
& ODS_FOCUS
) && !(state
& ODS_NOFOCUSRECT
) )
1456 DrawFocusRect(hdc
, &rectBtn
);
1459 if ( !wxUxThemeEngine::GetIfActive() )
1460 #endif // wxUSE_UXTHEME
1464 // the label is shifted by 1 pixel to create "pushed" effect
1465 OffsetRect(&rectBtn
, 1, 1);
1472 // draw the image, if any
1475 wxBitmap bmp
= m_imageData
->GetBitmap(GetButtonState(this, state
));
1477 bmp
= m_imageData
->GetBitmap(State_Normal
);
1479 const wxSize sizeBmp
= bmp
.GetSize();
1480 const wxSize margin
= m_imageData
->GetBitmapMargins();
1481 const wxSize
sizeBmpWithMargins(sizeBmp
+ 2*margin
);
1482 wxRect
rectButton(wxRectFromRECT(rectBtn
));
1484 // for simplicity, we start with centred rectangle and then move it to
1485 // the appropriate edge
1486 wxRect rectBitmap
= wxRect(sizeBmp
).CentreIn(rectButton
);
1488 // move bitmap only if we have a label, otherwise keep it centered
1491 switch ( m_imageData
->GetBitmapPosition() )
1494 wxFAIL_MSG( "invalid direction" );
1498 rectBitmap
.x
= rectButton
.x
+ margin
.x
;
1499 rectButton
.x
+= sizeBmpWithMargins
.x
;
1500 rectButton
.width
-= sizeBmpWithMargins
.x
;
1504 rectBitmap
.x
= rectButton
.GetRight() - sizeBmp
.x
- margin
.x
;
1505 rectButton
.width
-= sizeBmpWithMargins
.x
;
1509 rectBitmap
.y
= rectButton
.y
+ margin
.y
;
1510 rectButton
.y
+= sizeBmpWithMargins
.y
;
1511 rectButton
.height
-= sizeBmpWithMargins
.y
;
1515 rectBitmap
.y
= rectButton
.GetBottom() - sizeBmp
.y
- margin
.y
;
1516 rectButton
.height
-= sizeBmpWithMargins
.y
;
1521 wxDCTemp
dst((WXHDC
)hdc
);
1522 dst
.DrawBitmap(bmp
, rectBitmap
.GetPosition(), true);
1524 wxCopyRectToRECT(rectButton
, rectBtn
);
1528 // finally draw the label
1531 COLORREF colFg
= state
& ODS_DISABLED
1532 ? ::GetSysColor(COLOR_GRAYTEXT
)
1533 : wxColourToRGB(GetForegroundColour());
1535 wxTextColoursChanger
changeFg(hdc
, colFg
, CLR_INVALID
);
1536 wxBkModeChanger
changeBkMode(hdc
, wxBRUSHSTYLE_TRANSPARENT
);
1541 wxDCTemp
dc((WXHDC
)hdc
);
1542 dc
.SetTextForeground(wxColour(colFg
));
1543 dc
.SetFont(GetFont());
1545 m_markupText
->Render(dc
, wxRectFromRECT(rectBtn
),
1547 ? wxMarkupText::Render_Default
1548 : wxMarkupText::Render_ShowAccels
);
1550 else // Plain text label
1551 #endif // wxUSE_MARKUP
1553 // notice that DT_HIDEPREFIX doesn't work on old (pre-Windows 2000)
1554 // systems but by happy coincidence ODS_NOACCEL is not used under
1555 // them neither so DT_HIDEPREFIX should never be used there
1556 DrawButtonText(hdc
, &rectBtn
, this,
1557 state
& ODS_NOACCEL
? DT_HIDEPREFIX
: 0);
1564 #endif // wxUSE_BUTTON