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"
42 #include "wx/imaglist.h"
43 #include "wx/stockitem.h"
44 #include "wx/msw/private.h"
45 #include "wx/msw/private/button.h"
46 #include "wx/msw/private/dc.h"
47 #include "wx/private/window.h"
49 using namespace wxMSWImpl
;
52 #include "wx/msw/uxtheme.h"
54 // no need to include tmschema.h
56 #define BP_PUSHBUTTON 1
61 #define PBS_DISABLED 4
62 #define PBS_DEFAULTED 5
64 #define TMT_CONTENTMARGINS 3602
67 // provide the necessary declarations ourselves if they're missing from
69 #ifndef BCM_SETIMAGELIST
70 #define BCM_SETIMAGELIST 0x1602
71 #define BCM_SETTEXTMARGIN 0x1604
75 BUTTON_IMAGELIST_ALIGN_LEFT
,
76 BUTTON_IMAGELIST_ALIGN_RIGHT
,
77 BUTTON_IMAGELIST_ALIGN_TOP
,
78 BUTTON_IMAGELIST_ALIGN_BOTTOM
81 struct BUTTON_IMAGELIST
88 #endif // wxUSE_UXTHEME
90 #ifndef WM_THEMECHANGED
91 #define WM_THEMECHANGED 0x031A
95 #define ODS_NOACCEL 0x0100
98 #ifndef ODS_NOFOCUSRECT
99 #define ODS_NOFOCUSRECT 0x0200
102 #ifndef DT_HIDEPREFIX
103 #define DT_HIDEPREFIX 0x00100000
106 // set the value for BCM_SETSHIELD (for the UAC shield) if it's not defined in
108 #ifndef BCM_SETSHIELD
109 #define BCM_SETSHIELD 0x160c
112 // ----------------------------------------------------------------------------
114 // ----------------------------------------------------------------------------
116 // we use different data classes for owner drawn buttons and for themed XP ones
118 class wxButtonImageData
121 wxButtonImageData() { }
122 virtual ~wxButtonImageData() { }
124 virtual wxBitmap
GetBitmap(wxButton::State which
) const = 0;
125 virtual void SetBitmap(const wxBitmap
& bitmap
, wxButton::State which
) = 0;
127 virtual wxSize
GetBitmapMargins() const = 0;
128 virtual void SetBitmapMargins(wxCoord x
, wxCoord y
) = 0;
130 virtual wxDirection
GetBitmapPosition() const = 0;
131 virtual void SetBitmapPosition(wxDirection dir
) = 0;
134 wxDECLARE_NO_COPY_CLASS(wxButtonImageData
);
140 // the gap between button edge and the interior area used by Windows for the
142 const int OD_BUTTON_MARGIN
= 4;
144 class wxODButtonImageData
: public wxButtonImageData
147 wxODButtonImageData(wxButton
*btn
, const wxBitmap
& bitmap
)
149 SetBitmap(bitmap
, wxButton::State_Normal
);
153 // we use margins when we have both bitmap and text, but when we have
154 // only the bitmap it should take up the entire button area
155 if ( btn
->ShowsLabel() )
157 m_margin
.x
= btn
->GetCharWidth();
158 m_margin
.y
= btn
->GetCharHeight() / 2;
162 virtual wxBitmap
GetBitmap(wxButton::State which
) const
164 return m_bitmaps
[which
];
167 virtual void SetBitmap(const wxBitmap
& bitmap
, wxButton::State which
)
169 m_bitmaps
[which
] = bitmap
;
172 virtual wxSize
GetBitmapMargins() const
177 virtual void SetBitmapMargins(wxCoord x
, wxCoord y
)
179 m_margin
= wxSize(x
, y
);
182 virtual wxDirection
GetBitmapPosition() const
187 virtual void SetBitmapPosition(wxDirection dir
)
193 // just store the values passed to us to be able to retrieve them later
194 // from the drawing code
195 wxBitmap m_bitmaps
[wxButton::State_Max
];
199 wxDECLARE_NO_COPY_CLASS(wxODButtonImageData
);
204 // somehow the margin is one pixel greater than the value returned by
205 // GetThemeMargins() call
206 const int XP_BUTTON_EXTRA_MARGIN
= 1;
208 class wxXPButtonImageData
: public wxButtonImageData
211 // we must be constructed with the size of our images as we need to create
213 wxXPButtonImageData(wxButton
*btn
, const wxBitmap
& bitmap
)
214 : m_iml(bitmap
.GetWidth(), bitmap
.GetHeight(), true /* use mask */,
215 wxButton::State_Max
),
216 m_hwndBtn(GetHwndOf(btn
))
218 // initialize all bitmaps to normal state
219 for ( int n
= 0; n
< wxButton::State_Max
; n
++ )
224 m_data
.himl
= GetHimagelistOf(&m_iml
);
226 // use default margins
228 m_data
.margin
.right
= btn
->GetCharWidth();
230 m_data
.margin
.bottom
= btn
->GetCharHeight() / 2;
232 // and default alignment
233 m_data
.uAlign
= BUTTON_IMAGELIST_ALIGN_LEFT
;
238 virtual wxBitmap
GetBitmap(wxButton::State which
) const
240 return m_iml
.GetBitmap(which
);
243 virtual void SetBitmap(const wxBitmap
& bitmap
, wxButton::State which
)
245 m_iml
.Replace(which
, bitmap
);
250 virtual wxSize
GetBitmapMargins() const
252 return wxSize(m_data
.margin
.left
, m_data
.margin
.top
);
255 virtual void SetBitmapMargins(wxCoord x
, wxCoord y
)
257 RECT
& margin
= m_data
.margin
;
263 if ( !::SendMessage(m_hwndBtn
, BCM_SETTEXTMARGIN
, 0, (LPARAM
)&margin
) )
265 wxLogDebug("SendMessage(BCM_SETTEXTMARGIN) failed");
269 virtual wxDirection
GetBitmapPosition() const
271 switch ( m_data
.uAlign
)
274 wxFAIL_MSG( "invalid image alignment" );
277 case BUTTON_IMAGELIST_ALIGN_LEFT
:
280 case BUTTON_IMAGELIST_ALIGN_RIGHT
:
283 case BUTTON_IMAGELIST_ALIGN_TOP
:
286 case BUTTON_IMAGELIST_ALIGN_BOTTOM
:
291 virtual void SetBitmapPosition(wxDirection dir
)
297 wxFAIL_MSG( "invalid direction" );
301 alignNew
= BUTTON_IMAGELIST_ALIGN_LEFT
;
305 alignNew
= BUTTON_IMAGELIST_ALIGN_RIGHT
;
309 alignNew
= BUTTON_IMAGELIST_ALIGN_TOP
;
313 alignNew
= BUTTON_IMAGELIST_ALIGN_BOTTOM
;
317 if ( alignNew
!= m_data
.uAlign
)
319 m_data
.uAlign
= alignNew
;
325 void UpdateImageInfo()
327 if ( !::SendMessage(m_hwndBtn
, BCM_SETIMAGELIST
, 0, (LPARAM
)&m_data
) )
329 wxLogDebug("SendMessage(BCM_SETIMAGELIST) failed");
333 // we store image list separately to be able to use convenient wxImageList
334 // methods instead of working with raw HIMAGELIST
337 // store the rest of the data in BCM_SETIMAGELIST-friendly form
338 BUTTON_IMAGELIST m_data
;
340 // the button we're associated with
341 const HWND m_hwndBtn
;
344 wxDECLARE_NO_COPY_CLASS(wxXPButtonImageData
);
347 #endif // wxUSE_UXTHEME
349 } // anonymous namespace
351 // ----------------------------------------------------------------------------
353 // ----------------------------------------------------------------------------
355 #if wxUSE_EXTENDED_RTTI
357 WX_DEFINE_FLAGS( wxButtonStyle
)
359 wxBEGIN_FLAGS( wxButtonStyle
)
360 // new style border flags, we put them first to
361 // use them for streaming out
362 wxFLAGS_MEMBER(wxBORDER_SIMPLE
)
363 wxFLAGS_MEMBER(wxBORDER_SUNKEN
)
364 wxFLAGS_MEMBER(wxBORDER_DOUBLE
)
365 wxFLAGS_MEMBER(wxBORDER_RAISED
)
366 wxFLAGS_MEMBER(wxBORDER_STATIC
)
367 wxFLAGS_MEMBER(wxBORDER_NONE
)
369 // old style border flags
370 wxFLAGS_MEMBER(wxSIMPLE_BORDER
)
371 wxFLAGS_MEMBER(wxSUNKEN_BORDER
)
372 wxFLAGS_MEMBER(wxDOUBLE_BORDER
)
373 wxFLAGS_MEMBER(wxRAISED_BORDER
)
374 wxFLAGS_MEMBER(wxSTATIC_BORDER
)
375 wxFLAGS_MEMBER(wxBORDER
)
377 // standard window styles
378 wxFLAGS_MEMBER(wxTAB_TRAVERSAL
)
379 wxFLAGS_MEMBER(wxCLIP_CHILDREN
)
380 wxFLAGS_MEMBER(wxTRANSPARENT_WINDOW
)
381 wxFLAGS_MEMBER(wxWANTS_CHARS
)
382 wxFLAGS_MEMBER(wxFULL_REPAINT_ON_RESIZE
)
383 wxFLAGS_MEMBER(wxALWAYS_SHOW_SB
)
384 wxFLAGS_MEMBER(wxVSCROLL
)
385 wxFLAGS_MEMBER(wxHSCROLL
)
387 wxFLAGS_MEMBER(wxBU_LEFT
)
388 wxFLAGS_MEMBER(wxBU_RIGHT
)
389 wxFLAGS_MEMBER(wxBU_TOP
)
390 wxFLAGS_MEMBER(wxBU_BOTTOM
)
391 wxFLAGS_MEMBER(wxBU_EXACTFIT
)
392 wxEND_FLAGS( wxButtonStyle
)
394 IMPLEMENT_DYNAMIC_CLASS_XTI(wxButton
, wxControl
,"wx/button.h")
396 wxBEGIN_PROPERTIES_TABLE(wxButton
)
397 wxEVENT_PROPERTY( Click
, wxEVT_COMMAND_BUTTON_CLICKED
, wxCommandEvent
)
399 wxPROPERTY( Font
, wxFont
, SetFont
, GetFont
, EMPTY_MACROVALUE
, 0 /*flags*/ , wxT("Helpstring") , wxT("group"))
400 wxPROPERTY( Label
, wxString
, SetLabel
, GetLabel
, wxString(), 0 /*flags*/ , wxT("Helpstring") , wxT("group") )
402 wxPROPERTY_FLAGS( WindowStyle
, wxButtonStyle
, long , SetWindowStyleFlag
, GetWindowStyleFlag
, EMPTY_MACROVALUE
, 0 /*flags*/ , wxT("Helpstring") , wxT("group")) // style
404 wxEND_PROPERTIES_TABLE()
406 wxBEGIN_HANDLERS_TABLE(wxButton
)
407 wxEND_HANDLERS_TABLE()
409 wxCONSTRUCTOR_6( wxButton
, wxWindow
* , Parent
, wxWindowID
, Id
, wxString
, Label
, wxPoint
, Position
, wxSize
, Size
, long , WindowStyle
)
413 IMPLEMENT_DYNAMIC_CLASS(wxButton
, wxControl
)
416 // ============================================================================
418 // ============================================================================
420 // ----------------------------------------------------------------------------
421 // helper functions from wx/msw/private/button.h
422 // ----------------------------------------------------------------------------
424 void wxMSWButton::UpdateMultilineStyle(HWND hwnd
, const wxString
& label
)
426 // update BS_MULTILINE style depending on the new label (resetting it
427 // doesn't seem to do anything very useful but it shouldn't hurt and we do
428 // have to set it whenever the label becomes multi line as otherwise it
429 // wouldn't be shown correctly as we don't use BS_MULTILINE when creating
430 // the control unless it already has new lines in its label)
431 long styleOld
= ::GetWindowLong(hwnd
, GWL_STYLE
),
433 if ( label
.find(wxT('\n')) != wxString::npos
)
434 styleNew
= styleOld
| BS_MULTILINE
;
436 styleNew
= styleOld
& ~BS_MULTILINE
;
438 if ( styleNew
!= styleOld
)
439 ::SetWindowLong(hwnd
, GWL_STYLE
, styleNew
);
442 wxSize
wxMSWButton::GetFittingSize(wxWindow
*win
,
443 const wxSize
& sizeLabel
,
446 // FIXME: this is pure guesswork, need to retrieve the real button margins
447 wxSize sizeBtn
= sizeLabel
;
449 sizeBtn
.x
+= 3*win
->GetCharWidth();
450 sizeBtn
.y
= 11*EDIT_HEIGHT_FROM_CHAR_HEIGHT(sizeLabel
.y
)/10;
452 // account for the shield UAC icon if we have it
453 if ( flags
& Size_AuthNeeded
)
454 sizeBtn
.x
+= wxSystemSettings::GetMetric(wxSYS_SMALLICON_X
);
459 wxSize
wxMSWButton::ComputeBestSize(wxControl
*btn
, int flags
)
464 dc
.GetMultiLineTextExtent(btn
->GetLabelText(), &sizeBtn
.x
, &sizeBtn
.y
);
466 sizeBtn
= GetFittingSize(btn
, sizeBtn
, flags
);
468 // all buttons have at least the standard size unless the user explicitly
469 // wants them to be of smaller size and used wxBU_EXACTFIT style when
470 // creating the button
471 if ( !btn
->HasFlag(wxBU_EXACTFIT
) )
473 // The size of a standard button in the dialog units is 50x14, use it.
474 // Note that we intentionally don't use GetDefaultSize() here, because
475 // it's inexact -- dialog units depend on this dialog's font.
476 wxSize sizeDef
= btn
->ConvertDialogToPixels(wxSize(50, 14));
477 if ( sizeBtn
.x
< sizeDef
.x
)
478 sizeBtn
.x
= sizeDef
.x
;
479 if ( sizeBtn
.y
< sizeDef
.y
)
480 sizeBtn
.y
= sizeDef
.y
;
483 btn
->CacheBestSize(sizeBtn
);
488 // ----------------------------------------------------------------------------
489 // creation/destruction
490 // ----------------------------------------------------------------------------
492 bool wxButton::Create(wxWindow
*parent
,
498 const wxValidator
& validator
,
499 const wxString
& name
)
501 m_authNeeded
= false;
504 if (label
.empty() && wxIsStockID(id
))
506 // On Windows, some buttons aren't supposed to have mnemonics
507 label
= wxGetStockLabel
510 id
== wxID_OK
|| id
== wxID_CANCEL
|| id
== wxID_CLOSE
512 : wxSTOCK_WITH_MNEMONIC
516 if ( !CreateControl(parent
, id
, pos
, size
, style
, validator
, name
) )
520 WXDWORD msStyle
= MSWGetStyle(style
, &exstyle
);
522 // if the label contains several lines we must explicitly tell the button
523 // about it or it wouldn't draw it correctly ("\n"s would just appear as
526 // NB: we do it here and not in MSWGetStyle() because we need the label
527 // value and the label is not set yet when MSWGetStyle() is called
528 msStyle
|= wxMSWButton::GetMultilineStyle(label
);
530 return MSWCreateControl(wxT("BUTTON"), msStyle
, pos
, size
, label
, exstyle
);
533 wxButton::~wxButton()
535 wxTopLevelWindow
*tlw
= wxDynamicCast(wxGetTopLevelParent(this), wxTopLevelWindow
);
536 if ( tlw
&& tlw
->GetTmpDefaultItem() == this )
544 // ----------------------------------------------------------------------------
546 // ----------------------------------------------------------------------------
548 WXDWORD
wxButton::MSWGetStyle(long style
, WXDWORD
*exstyle
) const
550 // buttons never have an external border, they draw their own one
551 WXDWORD msStyle
= wxControl::MSWGetStyle
553 (style
& ~wxBORDER_MASK
) | wxBORDER_NONE
, exstyle
556 // we must use WS_CLIPSIBLINGS with the buttons or they would draw over
557 // each other in any resizeable dialog which has more than one button in
559 msStyle
|= WS_CLIPSIBLINGS
;
561 // don't use "else if" here: weird as it is, but you may combine wxBU_LEFT
562 // and wxBU_RIGHT to get BS_CENTER!
563 if ( style
& wxBU_LEFT
)
565 if ( style
& wxBU_RIGHT
)
567 if ( style
& wxBU_TOP
)
569 if ( style
& wxBU_BOTTOM
)
570 msStyle
|= BS_BOTTOM
;
573 if ( style
& wxNO_BORDER
)
575 #endif // __WXWINCE__
580 void wxButton::SetLabel(const wxString
& label
)
582 wxMSWButton::UpdateMultilineStyle(GetHwnd(), label
);
584 wxButtonBase::SetLabel(label
);
587 // ----------------------------------------------------------------------------
588 // size management including autosizing
589 // ----------------------------------------------------------------------------
591 void wxButton::AdjustForBitmapSize(wxSize
&size
) const
596 // account for the bitmap size
597 const wxSize sizeBmp
= m_imageData
->GetBitmap(State_Normal
).GetSize();
598 const wxDirection dirBmp
= m_imageData
->GetBitmapPosition();
599 if ( dirBmp
== wxLEFT
|| dirBmp
== wxRIGHT
)
602 if ( sizeBmp
.y
> size
.y
)
605 else // bitmap on top/below the text
608 if ( sizeBmp
.x
> size
.x
)
612 // account for the user-specified margins
613 size
+= 2*m_imageData
->GetBitmapMargins();
615 // and also for the margins we always add internally (unless we have no
616 // border at all in which case the button has exactly the same size as
617 // bitmap and so no margins should be used)
618 if ( !HasFlag(wxBORDER_NONE
) )
623 if ( wxUxThemeEngine::GetIfActive() )
625 wxUxThemeHandle
theme(const_cast<wxButton
*>(this), L
"BUTTON");
628 wxUxThemeEngine::Get()->GetThemeMargins(theme
, NULL
,
635 // XP doesn't draw themed buttons correctly when the client
636 // area is smaller than 8x8 - enforce this minimum size for
638 size
.IncTo(wxSize(8, 8));
640 marginH
= margins
.cxLeftWidth
+ margins
.cxRightWidth
641 + 2*XP_BUTTON_EXTRA_MARGIN
;
642 marginV
= margins
.cyTopHeight
+ margins
.cyBottomHeight
643 + 2*XP_BUTTON_EXTRA_MARGIN
;
646 #endif // wxUSE_UXTHEME
649 marginV
= OD_BUTTON_MARGIN
;
652 size
.IncBy(marginH
, marginV
);
656 wxSize
wxButton::DoGetBestSize() const
660 // account for the text part if we have it or if we don't have any image at
661 // all (buttons initially created with empty label should still have a non
663 if ( ShowsLabel() || !m_imageData
)
666 if ( GetAuthNeeded() )
667 flags
|= wxMSWButton::Size_AuthNeeded
;
669 size
= wxMSWButton::ComputeBestSize(const_cast<wxButton
*>(this), flags
);
674 AdjustForBitmapSize(size
);
683 wxSize
wxButtonBase::GetDefaultSize()
685 static wxSize s_sizeBtn
;
687 if ( s_sizeBtn
.x
== 0 )
690 dc
.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT
));
692 // The size of a standard button in the dialog units is 50x14,
693 // translate this to pixels.
695 // Windows' computes dialog units using average character width over
696 // upper- and lower-case ASCII alphabet and not using the average
697 // character width metadata stored in the font; see
698 // http://support.microsoft.com/default.aspx/kb/145994 for detailed
701 // NB: wxMulDivInt32() is used, because it correctly rounds the result
703 const wxSize base
= wxPrivate::GetAverageASCIILetterSize(dc
);
704 s_sizeBtn
.x
= wxMulDivInt32(50, base
.x
, 4);
705 s_sizeBtn
.y
= wxMulDivInt32(14, base
.y
, 8);
711 // ----------------------------------------------------------------------------
712 // default button handling
713 // ----------------------------------------------------------------------------
716 The comment below and all this code is probably due to not using WM_NEXTDLGCTL
717 message when changing focus (but just SetFocus() which is not enough), see
718 http://blogs.msdn.com/oldnewthing/archive/2004/08/02/205624.aspx for the
721 TODO: Do use WM_NEXTDLGCTL and get rid of all this code.
724 "Everything you ever wanted to know about the default buttons" or "Why do we
725 have to do all this?"
727 In MSW the default button should be activated when the user presses Enter
728 and the current control doesn't process Enter itself somehow. This is
729 handled by ::DefWindowProc() (or maybe ::DefDialogProc()) using DM_SETDEFID
730 Another aspect of "defaultness" is that the default button has different
731 appearance: this is due to BS_DEFPUSHBUTTON style which is completely
732 separate from DM_SETDEFID stuff (!). Also note that BS_DEFPUSHBUTTON should
733 be unset if our parent window is not active so it should be unset whenever
734 we lose activation and set back when we regain it.
736 Final complication is that when a button is active, it should be the default
737 one, i.e. pressing Enter on a button always activates it and not another
740 We handle this by maintaining a permanent and a temporary default items in
741 wxControlContainer (both may be NULL). When a button becomes the current
742 control (i.e. gets focus) it sets itself as the temporary default which
743 ensures that it has the right appearance and that Enter will be redirected
744 to it. When the button loses focus, it unsets the temporary default and so
745 the default item will be the permanent default -- that is the default button
746 if any had been set or none otherwise, which is just what we want.
748 NB: all this is quite complicated by now and the worst is that normally
749 it shouldn't be necessary at all as for the normal Windows programs
750 DefWindowProc() and IsDialogMessage() take care of all this
751 automatically -- however in wxWidgets programs this doesn't work for
752 nested hierarchies (i.e. a notebook inside a notebook) for unknown
753 reason and so we have to reproduce all this code ourselves. It would be
754 very nice if we could avoid doing it.
757 // set this button as the (permanently) default one in its panel
758 wxWindow
*wxButton::SetDefault()
760 // set this one as the default button both for wxWidgets ...
761 wxWindow
*winOldDefault
= wxButtonBase::SetDefault();
764 SetDefaultStyle(wxDynamicCast(winOldDefault
, wxButton
), false);
765 SetDefaultStyle(this, true);
767 return winOldDefault
;
770 // return the top level parent window if it's not being deleted yet, otherwise
772 static wxTopLevelWindow
*GetTLWParentIfNotBeingDeleted(wxWindow
*win
)
776 // IsTopLevel() will return false for a wxTLW being deleted, so we also
777 // need the parent test for this case
778 wxWindow
* const parent
= win
->GetParent();
779 if ( !parent
|| win
->IsTopLevel() )
781 if ( win
->IsBeingDeleted() )
790 wxASSERT_MSG( win
, wxT("button without top level parent?") );
792 wxTopLevelWindow
* const tlw
= wxDynamicCast(win
, wxTopLevelWindow
);
793 wxASSERT_MSG( tlw
, wxT("logic error in GetTLWParentIfNotBeingDeleted()") );
798 // set this button as being currently default
799 void wxButton::SetTmpDefault()
801 wxTopLevelWindow
* const tlw
= GetTLWParentIfNotBeingDeleted(GetParent());
805 wxWindow
*winOldDefault
= tlw
->GetDefaultItem();
806 tlw
->SetTmpDefaultItem(this);
808 SetDefaultStyle(wxDynamicCast(winOldDefault
, wxButton
), false);
809 SetDefaultStyle(this, true);
812 // unset this button as currently default, it may still stay permanent default
813 void wxButton::UnsetTmpDefault()
815 wxTopLevelWindow
* const tlw
= GetTLWParentIfNotBeingDeleted(GetParent());
819 tlw
->SetTmpDefaultItem(NULL
);
821 wxWindow
*winOldDefault
= tlw
->GetDefaultItem();
823 SetDefaultStyle(this, false);
824 SetDefaultStyle(wxDynamicCast(winOldDefault
, wxButton
), true);
829 wxButton::SetDefaultStyle(wxButton
*btn
, bool on
)
831 // we may be called with NULL pointer -- simpler to do the check here than
832 // in the caller which does wxDynamicCast()
836 // first, let DefDlgProc() know about the new default button
839 // we shouldn't set BS_DEFPUSHBUTTON for any button if we don't have
840 // focus at all any more
841 if ( !wxTheApp
->IsActive() )
844 wxWindow
* const tlw
= wxGetTopLevelParent(btn
);
845 wxCHECK_RET( tlw
, wxT("button without top level window?") );
847 ::SendMessage(GetHwndOf(tlw
), DM_SETDEFID
, btn
->GetId(), 0L);
849 // sending DM_SETDEFID also changes the button style to
850 // BS_DEFPUSHBUTTON so there is nothing more to do
853 // then also change the style as needed
854 long style
= ::GetWindowLong(GetHwndOf(btn
), GWL_STYLE
);
855 if ( !(style
& BS_DEFPUSHBUTTON
) == on
)
857 // don't do it with the owner drawn buttons because it will
858 // reset BS_OWNERDRAW style bit too (as BS_OWNERDRAW &
859 // BS_DEFPUSHBUTTON != 0)!
860 if ( (style
& BS_OWNERDRAW
) != BS_OWNERDRAW
)
862 ::SendMessage(GetHwndOf(btn
), BM_SETSTYLE
,
863 on
? style
| BS_DEFPUSHBUTTON
864 : style
& ~BS_DEFPUSHBUTTON
,
869 // redraw the button - it will notice itself that it's
870 // [not] the default one [any longer]
874 //else: already has correct style
877 // ----------------------------------------------------------------------------
879 // ----------------------------------------------------------------------------
881 bool wxButton::SendClickEvent()
883 wxCommandEvent
event(wxEVT_COMMAND_BUTTON_CLICKED
, GetId());
884 event
.SetEventObject(this);
886 return ProcessCommand(event
);
889 void wxButton::Command(wxCommandEvent
& event
)
891 ProcessCommand(event
);
894 // ----------------------------------------------------------------------------
895 // event/message handlers
896 // ----------------------------------------------------------------------------
898 bool wxButton::MSWCommand(WXUINT param
, WXWORD
WXUNUSED(id
))
900 bool processed
= false;
903 // NOTE: Apparently older versions (NT 4?) of the common controls send
904 // BN_DOUBLECLICKED but not a second BN_CLICKED for owner-drawn
905 // buttons, so in order to send two EVT_BUTTON events we should
906 // catch both types. Currently (Feb 2003) up-to-date versions of
907 // win98, win2k and winXP all send two BN_CLICKED messages for
908 // all button types, so we don't catch BN_DOUBLECLICKED anymore
909 // in order to not get 3 EVT_BUTTON events. If this is a problem
910 // then we need to figure out which version of the comctl32 changed
911 // this behaviour and test for it.
913 case 1: // message came from an accelerator
914 case BN_CLICKED
: // normal buttons send this
915 processed
= SendClickEvent();
922 WXLRESULT
wxButton::MSWWindowProc(WXUINT nMsg
, WXWPARAM wParam
, WXLPARAM lParam
)
924 // when we receive focus, we want to temporarily become the default button in
925 // our parent panel so that pressing "Enter" would activate us -- and when
926 // losing it we should restore the previous default button as well
927 if ( nMsg
== WM_SETFOCUS
)
931 // let the default processing take place too
933 else if ( nMsg
== WM_KILLFOCUS
)
937 else if ( nMsg
== WM_LBUTTONDBLCLK
)
939 // emulate a click event to force an owner-drawn button to change its
940 // appearance - without this, it won't do it
941 (void)wxControl::MSWWindowProc(WM_LBUTTONDOWN
, wParam
, lParam
);
943 // and continue with processing the message normally as well
946 else if ( nMsg
== WM_THEMECHANGED
)
948 // need to recalculate the best size here
949 // as the theme size might have changed
950 InvalidateBestSize();
952 #endif // wxUSE_UXTHEME
953 // must use m_mouseInWindow here instead of IsMouseInWindow()
954 // since we need to know the first time the mouse enters the window
955 // and IsMouseInWindow() would return true in this case
956 else if ( (nMsg
== WM_MOUSEMOVE
&& !m_mouseInWindow
) ||
957 nMsg
== WM_MOUSELEAVE
)
963 wxUxThemeEngine::GetIfActive() ||
964 #endif // wxUSE_UXTHEME
965 (m_imageData
&& m_imageData
->GetBitmap(State_Current
).IsOk())
973 // let the base class do all real processing
974 return wxControl::MSWWindowProc(nMsg
, wParam
, lParam
);
977 // ----------------------------------------------------------------------------
978 // authentication needed handling
979 // ----------------------------------------------------------------------------
981 bool wxButton::DoGetAuthNeeded() const
986 void wxButton::DoSetAuthNeeded(bool show
)
988 // show/hide UAC symbol on Windows Vista and later
989 if ( wxGetWinVersion() >= wxWinVersion_6
)
992 ::SendMessage(GetHwnd(), BCM_SETSHIELD
, 0, show
);
993 InvalidateBestSize();
997 // ----------------------------------------------------------------------------
999 // ----------------------------------------------------------------------------
1001 wxBitmap
wxButton::DoGetBitmap(State which
) const
1003 return m_imageData
? m_imageData
->GetBitmap(which
) : wxBitmap();
1006 void wxButton::DoSetBitmap(const wxBitmap
& bitmap
, State which
)
1008 // allocate the image data when the first bitmap is set
1012 // using image list doesn't work correctly if we don't have any label
1013 // (even if we use BUTTON_IMAGELIST_ALIGN_CENTER alignment and
1014 // BS_BITMAP style), at least under Windows 2003 so use owner drawn
1015 // strategy for bitmap-only buttons
1016 if ( ShowsLabel() && wxUxThemeEngine::GetIfActive() )
1018 m_imageData
= new wxXPButtonImageData(this, bitmap
);
1021 #endif // wxUSE_UXTHEME
1023 m_imageData
= new wxODButtonImageData(this, bitmap
);
1029 m_imageData
->SetBitmap(bitmap
, which
);
1032 // it should be enough to only invalidate the best size when the normal
1033 // bitmap changes as all bitmaps assigned to the button should be of the
1035 if ( which
== State_Normal
)
1036 InvalidateBestSize();
1041 wxSize
wxButton::DoGetBitmapMargins() const
1043 return m_imageData
? m_imageData
->GetBitmapMargins() : wxSize(0, 0);
1046 void wxButton::DoSetBitmapMargins(wxCoord x
, wxCoord y
)
1048 wxCHECK_RET( m_imageData
, "SetBitmap() must be called first" );
1050 m_imageData
->SetBitmapMargins(x
, y
);
1051 InvalidateBestSize();
1054 void wxButton::DoSetBitmapPosition(wxDirection dir
)
1056 wxCHECK_RET( m_imageData
, "SetBitmap() must be called first" );
1058 m_imageData
->SetBitmapPosition(dir
);
1059 InvalidateBestSize();
1062 // ----------------------------------------------------------------------------
1063 // owner-drawn buttons support
1064 // ----------------------------------------------------------------------------
1070 // return the button state using both the ODS_XXX flags specified in state
1071 // parameter and the current button state
1072 wxButton::State
GetButtonState(wxButton
*btn
, UINT state
)
1074 if ( state
& ODS_DISABLED
)
1075 return wxButton::State_Disabled
;
1077 if ( state
& ODS_SELECTED
)
1078 return wxButton::State_Pressed
;
1080 if ( btn
->HasCapture() || btn
->IsMouseInWindow() )
1081 return wxButton::State_Current
;
1083 if ( state
& ODS_FOCUS
)
1084 return wxButton::State_Focused
;
1086 return wxButton::State_Normal
;
1089 void DrawButtonText(HDC hdc
,
1091 const wxString
& text
,
1095 wxTextColoursChanger
changeFg(hdc
, col
, CLR_INVALID
);
1096 wxBkModeChanger
changeBkMode(hdc
, wxBRUSHSTYLE_TRANSPARENT
);
1098 // center text horizontally in any case
1101 if ( text
.find(wxT('\n')) != wxString::npos
)
1103 // draw multiline label
1105 // first we need to compute its bounding rect
1107 ::CopyRect(&rc
, pRect
);
1108 ::DrawText(hdc
, text
.wx_str(), text
.length(), &rc
,
1109 DT_CENTER
| DT_CALCRECT
);
1111 // now center this rect inside the entire button area
1112 const LONG w
= rc
.right
- rc
.left
;
1113 const LONG h
= rc
.bottom
- rc
.top
;
1114 rc
.left
= (pRect
->right
- pRect
->left
)/2 - w
/2;
1115 rc
.right
= rc
.left
+w
;
1116 rc
.top
= (pRect
->bottom
- pRect
->top
)/2 - h
/2;
1117 rc
.bottom
= rc
.top
+h
;
1119 ::DrawText(hdc
, text
.wx_str(), text
.length(), &rc
, flags
);
1121 else // single line label
1123 // centre text vertically too (notice that we must have DT_SINGLELINE
1124 // for DT_VCENTER to work)
1125 ::DrawText(hdc
, text
.wx_str(), text
.length(), pRect
,
1126 flags
| DT_SINGLELINE
| DT_VCENTER
);
1130 void DrawRect(HDC hdc
, const RECT
& r
)
1132 wxDrawLine(hdc
, r
.left
, r
.top
, r
.right
, r
.top
);
1133 wxDrawLine(hdc
, r
.right
, r
.top
, r
.right
, r
.bottom
);
1134 wxDrawLine(hdc
, r
.right
, r
.bottom
, r
.left
, r
.bottom
);
1135 wxDrawLine(hdc
, r
.left
, r
.bottom
, r
.left
, r
.top
);
1139 The button frame looks like this normally:
1142 WHHHHHHHHHHHHHHHHGB W = white (HILIGHT)
1143 WH GB H = light grey (LIGHT)
1144 WH GB G = dark grey (SHADOW)
1145 WH GB B = black (DKSHADOW)
1150 When the button is selected, the button becomes like this (the total button
1151 size doesn't change):
1162 When the button is pushed (while selected) it is like:
1173 void DrawButtonFrame(HDC hdc
, RECT
& rectBtn
,
1174 bool selected
, bool pushed
)
1177 CopyRect(&r
, &rectBtn
);
1179 AutoHPEN
hpenBlack(GetSysColor(COLOR_3DDKSHADOW
)),
1180 hpenGrey(GetSysColor(COLOR_3DSHADOW
)),
1181 hpenLightGr(GetSysColor(COLOR_3DLIGHT
)),
1182 hpenWhite(GetSysColor(COLOR_3DHILIGHT
));
1184 SelectInHDC
selectPen(hdc
, hpenBlack
);
1193 (void)SelectObject(hdc
, hpenGrey
);
1194 ::InflateRect(&r
, -1, -1);
1204 ::InflateRect(&r
, -1, -1);
1207 wxDrawLine(hdc
, r
.left
, r
.bottom
, r
.right
, r
.bottom
);
1208 wxDrawLine(hdc
, r
.right
, r
.bottom
, r
.right
, r
.top
- 1);
1210 (void)SelectObject(hdc
, hpenWhite
);
1211 wxDrawLine(hdc
, r
.left
, r
.bottom
- 1, r
.left
, r
.top
);
1212 wxDrawLine(hdc
, r
.left
, r
.top
, r
.right
, r
.top
);
1214 (void)SelectObject(hdc
, hpenLightGr
);
1215 wxDrawLine(hdc
, r
.left
+ 1, r
.bottom
- 2, r
.left
+ 1, r
.top
+ 1);
1216 wxDrawLine(hdc
, r
.left
+ 1, r
.top
+ 1, r
.right
- 1, r
.top
+ 1);
1218 (void)SelectObject(hdc
, hpenGrey
);
1219 wxDrawLine(hdc
, r
.left
+ 1, r
.bottom
- 1, r
.right
- 1, r
.bottom
- 1);
1220 wxDrawLine(hdc
, r
.right
- 1, r
.bottom
- 1, r
.right
- 1, r
.top
);
1223 InflateRect(&rectBtn
, -OD_BUTTON_MARGIN
, -OD_BUTTON_MARGIN
);
1227 void DrawXPBackground(wxButton
*button
, HDC hdc
, RECT
& rectBtn
, UINT state
)
1229 wxUxThemeHandle
theme(button
, L
"BUTTON");
1231 // this array is indexed by wxButton::State values and so must be kept in
1233 static const int uxStates
[] =
1235 PBS_NORMAL
, PBS_HOT
, PBS_PRESSED
, PBS_DISABLED
, PBS_DEFAULTED
1238 int iState
= uxStates
[GetButtonState(button
, state
)];
1240 wxUxThemeEngine
* const engine
= wxUxThemeEngine::Get();
1242 // draw parent background if needed
1243 if ( engine
->IsThemeBackgroundPartiallyTransparent
1250 engine
->DrawThemeParentBackground(GetHwndOf(button
), hdc
, &rectBtn
);
1254 engine
->DrawThemeBackground(theme
, hdc
, BP_PUSHBUTTON
, iState
,
1257 // calculate content area margins
1259 engine
->GetThemeMargins(theme
, hdc
, BP_PUSHBUTTON
, iState
,
1260 TMT_CONTENTMARGINS
, &rectBtn
, &margins
);
1261 ::InflateRect(&rectBtn
, -margins
.cxLeftWidth
, -margins
.cyTopHeight
);
1262 ::InflateRect(&rectBtn
, -XP_BUTTON_EXTRA_MARGIN
, -XP_BUTTON_EXTRA_MARGIN
);
1264 if ( button
->UseBgCol() )
1266 COLORREF colBg
= wxColourToRGB(button
->GetBackgroundColour());
1267 AutoHBRUSH
hbrushBackground(colBg
);
1269 // don't overwrite the focus rect
1271 ::CopyRect(&rectClient
, &rectBtn
);
1272 ::InflateRect(&rectClient
, -1, -1);
1273 FillRect(hdc
, &rectClient
, hbrushBackground
);
1276 #endif // wxUSE_UXTHEME
1278 } // anonymous namespace
1280 // ----------------------------------------------------------------------------
1281 // owner drawn buttons support
1282 // ----------------------------------------------------------------------------
1284 void wxButton::MakeOwnerDrawn()
1286 long style
= GetWindowLong(GetHwnd(), GWL_STYLE
);
1287 if ( (style
& BS_OWNERDRAW
) != BS_OWNERDRAW
)
1290 style
|= BS_OWNERDRAW
;
1291 SetWindowLong(GetHwnd(), GWL_STYLE
, style
);
1295 bool wxButton::SetBackgroundColour(const wxColour
&colour
)
1297 if ( !wxControl::SetBackgroundColour(colour
) )
1310 bool wxButton::SetForegroundColour(const wxColour
&colour
)
1312 if ( !wxControl::SetForegroundColour(colour
) )
1325 bool wxButton::MSWOnDraw(WXDRAWITEMSTRUCT
*wxdis
)
1327 LPDRAWITEMSTRUCT lpDIS
= (LPDRAWITEMSTRUCT
)wxdis
;
1328 HDC hdc
= lpDIS
->hDC
;
1330 UINT state
= lpDIS
->itemState
;
1331 bool pushed
= (SendMessage(GetHwnd(), BM_GETSTATE
, 0, 0) & BST_PUSHED
) != 0;
1334 CopyRect(&rectBtn
, &lpDIS
->rcItem
);
1336 // draw the button background
1337 if ( !HasFlag(wxBORDER_NONE
) )
1340 if ( wxUxThemeEngine::GetIfActive() )
1342 DrawXPBackground(this, hdc
, rectBtn
, state
);
1345 #endif // wxUSE_UXTHEME
1347 COLORREF colBg
= wxColourToRGB(GetBackgroundColour());
1349 // first, draw the background
1350 AutoHBRUSH
hbrushBackground(colBg
);
1351 FillRect(hdc
, &rectBtn
, hbrushBackground
);
1353 // draw the border for the current state
1354 bool selected
= (state
& ODS_SELECTED
) != 0;
1358 tlw
= wxDynamicCast(wxGetTopLevelParent(this), wxTopLevelWindow
);
1361 selected
= tlw
->GetDefaultItem() == this;
1365 DrawButtonFrame(hdc
, rectBtn
, selected
, pushed
);
1368 // draw the focus rectangle if we need it
1369 if ( (state
& ODS_FOCUS
) && !(state
& ODS_NOFOCUSRECT
) )
1371 DrawFocusRect(hdc
, &rectBtn
);
1374 if ( !wxUxThemeEngine::GetIfActive() )
1375 #endif // wxUSE_UXTHEME
1379 // the label is shifted by 1 pixel to create "pushed" effect
1380 OffsetRect(&rectBtn
, 1, 1);
1387 // draw the image, if any
1390 wxBitmap bmp
= m_imageData
->GetBitmap(GetButtonState(this, state
));
1392 bmp
= m_imageData
->GetBitmap(State_Normal
);
1394 const wxSize sizeBmp
= bmp
.GetSize();
1395 const wxSize margin
= m_imageData
->GetBitmapMargins();
1396 const wxSize
sizeBmpWithMargins(sizeBmp
+ 2*margin
);
1397 wxRect
rectButton(wxRectFromRECT(rectBtn
));
1399 // for simplicity, we start with centred rectangle and then move it to
1400 // the appropriate edge
1401 wxRect rectBitmap
= wxRect(sizeBmp
).CentreIn(rectButton
);
1403 // move bitmap only if we have a label, otherwise keep it centered
1406 switch ( m_imageData
->GetBitmapPosition() )
1409 wxFAIL_MSG( "invalid direction" );
1413 rectBitmap
.x
= rectButton
.x
+ margin
.x
;
1414 rectButton
.x
+= sizeBmpWithMargins
.x
;
1415 rectButton
.width
-= sizeBmpWithMargins
.x
;
1419 rectBitmap
.x
= rectButton
.GetRight() - sizeBmp
.x
- margin
.x
;
1420 rectButton
.width
-= sizeBmpWithMargins
.x
;
1424 rectBitmap
.y
= rectButton
.y
+ margin
.y
;
1425 rectButton
.y
+= sizeBmpWithMargins
.y
;
1426 rectButton
.height
-= sizeBmpWithMargins
.y
;
1430 rectBitmap
.y
= rectButton
.GetBottom() - sizeBmp
.y
- margin
.y
;
1431 rectButton
.height
-= sizeBmpWithMargins
.y
;
1436 wxDCTemp
dst((WXHDC
)hdc
);
1437 dst
.DrawBitmap(bmp
, rectBitmap
.GetPosition(), true);
1439 wxCopyRectToRECT(rectButton
, rectBtn
);
1443 // finally draw the label
1446 COLORREF colFg
= state
& ODS_DISABLED
1447 ? ::GetSysColor(COLOR_GRAYTEXT
)
1448 : wxColourToRGB(GetForegroundColour());
1450 // notice that DT_HIDEPREFIX doesn't work on old (pre-Windows 2000)
1451 // systems but by happy coincidence ODS_NOACCEL is not used under them
1452 // neither so DT_HIDEPREFIX should never be used there
1453 DrawButtonText(hdc
, &rectBtn
, GetLabel(), colFg
,
1454 state
& ODS_NOACCEL
? DT_HIDEPREFIX
: 0);
1460 #endif // wxUSE_BUTTON