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 // ----------------------------------------------------------------------------
108 // ----------------------------------------------------------------------------
110 // we use different data classes for owner drawn buttons and for themed XP ones
112 class wxButtonImageData
115 wxButtonImageData() { }
116 virtual ~wxButtonImageData() { }
118 virtual wxBitmap
GetBitmap(wxButton::State which
) const = 0;
119 virtual void SetBitmap(const wxBitmap
& bitmap
, wxButton::State which
) = 0;
121 virtual wxSize
GetBitmapMargins() const = 0;
122 virtual void SetBitmapMargins(wxCoord x
, wxCoord y
) = 0;
124 virtual wxDirection
GetBitmapPosition() const = 0;
125 virtual void SetBitmapPosition(wxDirection dir
) = 0;
128 wxDECLARE_NO_COPY_CLASS(wxButtonImageData
);
134 // the gap between button edge and the interior area used by Windows for the
136 const int OD_BUTTON_MARGIN
= 4;
138 class wxODButtonImageData
: public wxButtonImageData
141 wxODButtonImageData(wxButton
*btn
, const wxBitmap
& bitmap
)
143 SetBitmap(bitmap
, wxButton::State_Normal
);
147 // we use margins when we have both bitmap and text, but when we have
148 // only the bitmap it should take up the entire button area
149 if ( btn
->ShowsLabel() )
151 m_margin
.x
= btn
->GetCharWidth();
152 m_margin
.y
= btn
->GetCharHeight() / 2;
156 virtual wxBitmap
GetBitmap(wxButton::State which
) const
158 return m_bitmaps
[which
];
161 virtual void SetBitmap(const wxBitmap
& bitmap
, wxButton::State which
)
163 m_bitmaps
[which
] = bitmap
;
166 virtual wxSize
GetBitmapMargins() const
171 virtual void SetBitmapMargins(wxCoord x
, wxCoord y
)
173 m_margin
= wxSize(x
, y
);
176 virtual wxDirection
GetBitmapPosition() const
181 virtual void SetBitmapPosition(wxDirection dir
)
187 // just store the values passed to us to be able to retrieve them later
188 // from the drawing code
189 wxBitmap m_bitmaps
[wxButton::State_Max
];
193 wxDECLARE_NO_COPY_CLASS(wxODButtonImageData
);
198 // somehow the margin is one pixel greater than the value returned by
199 // GetThemeMargins() call
200 const int XP_BUTTON_EXTRA_MARGIN
= 1;
202 class wxXPButtonImageData
: public wxButtonImageData
205 // we must be constructed with the size of our images as we need to create
207 wxXPButtonImageData(wxButton
*btn
, const wxBitmap
& bitmap
)
208 : m_iml(bitmap
.GetWidth(), bitmap
.GetHeight(), true /* use mask */,
209 wxButton::State_Max
),
210 m_hwndBtn(GetHwndOf(btn
))
212 // initialize all bitmaps to normal state
213 for ( int n
= 0; n
< wxButton::State_Max
; n
++ )
218 m_data
.himl
= GetHimagelistOf(&m_iml
);
220 // use default margins
222 m_data
.margin
.right
= btn
->GetCharWidth();
224 m_data
.margin
.bottom
= btn
->GetCharHeight() / 2;
226 // and default alignment
227 m_data
.uAlign
= BUTTON_IMAGELIST_ALIGN_LEFT
;
232 virtual wxBitmap
GetBitmap(wxButton::State which
) const
234 return m_iml
.GetBitmap(which
);
237 virtual void SetBitmap(const wxBitmap
& bitmap
, wxButton::State which
)
239 m_iml
.Replace(which
, bitmap
);
244 virtual wxSize
GetBitmapMargins() const
246 return wxSize(m_data
.margin
.left
, m_data
.margin
.top
);
249 virtual void SetBitmapMargins(wxCoord x
, wxCoord y
)
251 RECT
& margin
= m_data
.margin
;
257 if ( !::SendMessage(m_hwndBtn
, BCM_SETTEXTMARGIN
, 0, (LPARAM
)&margin
) )
259 wxLogDebug("SendMessage(BCM_SETTEXTMARGIN) failed");
263 virtual wxDirection
GetBitmapPosition() const
265 switch ( m_data
.uAlign
)
268 wxFAIL_MSG( "invalid image alignment" );
271 case BUTTON_IMAGELIST_ALIGN_LEFT
:
274 case BUTTON_IMAGELIST_ALIGN_RIGHT
:
277 case BUTTON_IMAGELIST_ALIGN_TOP
:
280 case BUTTON_IMAGELIST_ALIGN_BOTTOM
:
285 virtual void SetBitmapPosition(wxDirection dir
)
291 wxFAIL_MSG( "invalid direction" );
295 alignNew
= BUTTON_IMAGELIST_ALIGN_LEFT
;
299 alignNew
= BUTTON_IMAGELIST_ALIGN_RIGHT
;
303 alignNew
= BUTTON_IMAGELIST_ALIGN_TOP
;
307 alignNew
= BUTTON_IMAGELIST_ALIGN_BOTTOM
;
311 if ( alignNew
!= m_data
.uAlign
)
313 m_data
.uAlign
= alignNew
;
319 void UpdateImageInfo()
321 if ( !::SendMessage(m_hwndBtn
, BCM_SETIMAGELIST
, 0, (LPARAM
)&m_data
) )
323 wxLogDebug("SendMessage(BCM_SETIMAGELIST) failed");
327 // we store image list separately to be able to use convenient wxImageList
328 // methods instead of working with raw HIMAGELIST
331 // store the rest of the data in BCM_SETIMAGELIST-friendly form
332 BUTTON_IMAGELIST m_data
;
334 // the button we're associated with
335 const HWND m_hwndBtn
;
338 wxDECLARE_NO_COPY_CLASS(wxXPButtonImageData
);
341 #endif // wxUSE_UXTHEME
343 } // anonymous namespace
345 // ----------------------------------------------------------------------------
347 // ----------------------------------------------------------------------------
349 #if wxUSE_EXTENDED_RTTI
351 WX_DEFINE_FLAGS( wxButtonStyle
)
353 wxBEGIN_FLAGS( wxButtonStyle
)
354 // new style border flags, we put them first to
355 // use them for streaming out
356 wxFLAGS_MEMBER(wxBORDER_SIMPLE
)
357 wxFLAGS_MEMBER(wxBORDER_SUNKEN
)
358 wxFLAGS_MEMBER(wxBORDER_DOUBLE
)
359 wxFLAGS_MEMBER(wxBORDER_RAISED
)
360 wxFLAGS_MEMBER(wxBORDER_STATIC
)
361 wxFLAGS_MEMBER(wxBORDER_NONE
)
363 // old style border flags
364 wxFLAGS_MEMBER(wxSIMPLE_BORDER
)
365 wxFLAGS_MEMBER(wxSUNKEN_BORDER
)
366 wxFLAGS_MEMBER(wxDOUBLE_BORDER
)
367 wxFLAGS_MEMBER(wxRAISED_BORDER
)
368 wxFLAGS_MEMBER(wxSTATIC_BORDER
)
369 wxFLAGS_MEMBER(wxBORDER
)
371 // standard window styles
372 wxFLAGS_MEMBER(wxTAB_TRAVERSAL
)
373 wxFLAGS_MEMBER(wxCLIP_CHILDREN
)
374 wxFLAGS_MEMBER(wxTRANSPARENT_WINDOW
)
375 wxFLAGS_MEMBER(wxWANTS_CHARS
)
376 wxFLAGS_MEMBER(wxFULL_REPAINT_ON_RESIZE
)
377 wxFLAGS_MEMBER(wxALWAYS_SHOW_SB
)
378 wxFLAGS_MEMBER(wxVSCROLL
)
379 wxFLAGS_MEMBER(wxHSCROLL
)
381 wxFLAGS_MEMBER(wxBU_LEFT
)
382 wxFLAGS_MEMBER(wxBU_RIGHT
)
383 wxFLAGS_MEMBER(wxBU_TOP
)
384 wxFLAGS_MEMBER(wxBU_BOTTOM
)
385 wxFLAGS_MEMBER(wxBU_EXACTFIT
)
386 wxEND_FLAGS( wxButtonStyle
)
388 IMPLEMENT_DYNAMIC_CLASS_XTI(wxButton
, wxControl
,"wx/button.h")
390 wxBEGIN_PROPERTIES_TABLE(wxButton
)
391 wxEVENT_PROPERTY( Click
, wxEVT_COMMAND_BUTTON_CLICKED
, wxCommandEvent
)
393 wxPROPERTY( Font
, wxFont
, SetFont
, GetFont
, EMPTY_MACROVALUE
, 0 /*flags*/ , wxT("Helpstring") , wxT("group"))
394 wxPROPERTY( Label
, wxString
, SetLabel
, GetLabel
, wxString(), 0 /*flags*/ , wxT("Helpstring") , wxT("group") )
396 wxPROPERTY_FLAGS( WindowStyle
, wxButtonStyle
, long , SetWindowStyleFlag
, GetWindowStyleFlag
, EMPTY_MACROVALUE
, 0 /*flags*/ , wxT("Helpstring") , wxT("group")) // style
398 wxEND_PROPERTIES_TABLE()
400 wxBEGIN_HANDLERS_TABLE(wxButton
)
401 wxEND_HANDLERS_TABLE()
403 wxCONSTRUCTOR_6( wxButton
, wxWindow
* , Parent
, wxWindowID
, Id
, wxString
, Label
, wxPoint
, Position
, wxSize
, Size
, long , WindowStyle
)
407 IMPLEMENT_DYNAMIC_CLASS(wxButton
, wxControl
)
410 // ============================================================================
412 // ============================================================================
414 // ----------------------------------------------------------------------------
415 // helper functions from wx/msw/private/button.h
416 // ----------------------------------------------------------------------------
418 void wxMSWButton::UpdateMultilineStyle(HWND hwnd
, const wxString
& label
)
420 // update BS_MULTILINE style depending on the new label (resetting it
421 // doesn't seem to do anything very useful but it shouldn't hurt and we do
422 // have to set it whenever the label becomes multi line as otherwise it
423 // wouldn't be shown correctly as we don't use BS_MULTILINE when creating
424 // the control unless it already has new lines in its label)
425 long styleOld
= ::GetWindowLong(hwnd
, GWL_STYLE
),
427 if ( label
.find(wxT('\n')) != wxString::npos
)
428 styleNew
= styleOld
| BS_MULTILINE
;
430 styleNew
= styleOld
& ~BS_MULTILINE
;
432 if ( styleNew
!= styleOld
)
433 ::SetWindowLong(hwnd
, GWL_STYLE
, styleNew
);
436 wxSize
wxMSWButton::GetFittingSize(wxWindow
*win
, const wxSize
& sizeLabel
)
438 // FIXME: this is pure guesswork, need to retrieve the real button margins
439 wxSize sizeBtn
= sizeLabel
;
441 sizeBtn
.x
+= 3*win
->GetCharWidth();
442 sizeBtn
.y
= 11*EDIT_HEIGHT_FROM_CHAR_HEIGHT(sizeLabel
.y
)/10;
447 wxSize
wxMSWButton::ComputeBestSize(wxControl
*btn
)
452 dc
.GetMultiLineTextExtent(btn
->GetLabelText(), &sizeBtn
.x
, &sizeBtn
.y
);
454 sizeBtn
= GetFittingSize(btn
, sizeBtn
);
456 // all buttons have at least the standard size unless the user explicitly
457 // wants them to be of smaller size and used wxBU_EXACTFIT style when
458 // creating the button
459 if ( !btn
->HasFlag(wxBU_EXACTFIT
) )
461 // The size of a standard button in the dialog units is 50x14, use it.
462 // Note that we intentionally don't use GetDefaultSize() here, because
463 // it's inexact -- dialog units depend on this dialog's font.
464 wxSize sizeDef
= btn
->ConvertDialogToPixels(wxSize(50, 14));
465 if ( sizeBtn
.x
< sizeDef
.x
)
466 sizeBtn
.x
= sizeDef
.x
;
467 if ( sizeBtn
.y
< sizeDef
.y
)
468 sizeBtn
.y
= sizeDef
.y
;
471 btn
->CacheBestSize(sizeBtn
);
476 // ----------------------------------------------------------------------------
477 // creation/destruction
478 // ----------------------------------------------------------------------------
480 bool wxButton::Create(wxWindow
*parent
,
486 const wxValidator
& validator
,
487 const wxString
& name
)
490 if (label
.empty() && wxIsStockID(id
))
492 // On Windows, some buttons aren't supposed to have mnemonics
493 label
= wxGetStockLabel
496 id
== wxID_OK
|| id
== wxID_CANCEL
|| id
== wxID_CLOSE
498 : wxSTOCK_WITH_MNEMONIC
502 if ( !CreateControl(parent
, id
, pos
, size
, style
, validator
, name
) )
506 WXDWORD msStyle
= MSWGetStyle(style
, &exstyle
);
508 // if the label contains several lines we must explicitly tell the button
509 // about it or it wouldn't draw it correctly ("\n"s would just appear as
512 // NB: we do it here and not in MSWGetStyle() because we need the label
513 // value and the label is not set yet when MSWGetStyle() is called
514 msStyle
|= wxMSWButton::GetMultilineStyle(label
);
516 return MSWCreateControl(wxT("BUTTON"), msStyle
, pos
, size
, label
, exstyle
);
519 wxButton::~wxButton()
521 wxTopLevelWindow
*tlw
= wxDynamicCast(wxGetTopLevelParent(this), wxTopLevelWindow
);
522 if ( tlw
&& tlw
->GetTmpDefaultItem() == this )
530 // ----------------------------------------------------------------------------
532 // ----------------------------------------------------------------------------
534 WXDWORD
wxButton::MSWGetStyle(long style
, WXDWORD
*exstyle
) const
536 // buttons never have an external border, they draw their own one
537 WXDWORD msStyle
= wxControl::MSWGetStyle
539 (style
& ~wxBORDER_MASK
) | wxBORDER_NONE
, exstyle
542 // we must use WS_CLIPSIBLINGS with the buttons or they would draw over
543 // each other in any resizeable dialog which has more than one button in
545 msStyle
|= WS_CLIPSIBLINGS
;
547 // don't use "else if" here: weird as it is, but you may combine wxBU_LEFT
548 // and wxBU_RIGHT to get BS_CENTER!
549 if ( style
& wxBU_LEFT
)
551 if ( style
& wxBU_RIGHT
)
553 if ( style
& wxBU_TOP
)
555 if ( style
& wxBU_BOTTOM
)
556 msStyle
|= BS_BOTTOM
;
559 if ( style
& wxNO_BORDER
)
561 #endif // __WXWINCE__
566 void wxButton::SetLabel(const wxString
& label
)
568 wxMSWButton::UpdateMultilineStyle(GetHwnd(), label
);
570 wxButtonBase::SetLabel(label
);
573 // ----------------------------------------------------------------------------
574 // size management including autosizing
575 // ----------------------------------------------------------------------------
577 wxSize
wxButton::DoGetBestSize() const
581 // account for the text part if we have it or if we don't have any image at
582 // all (buttons initially created with empty label should still have a non
584 if ( ShowsLabel() || !m_imageData
)
586 size
= wxMSWButton::ComputeBestSize(const_cast<wxButton
*>(this));
591 // account for the bitmap size
592 const wxSize sizeBmp
= m_imageData
->GetBitmap(State_Normal
).GetSize();
593 const wxDirection dirBmp
= m_imageData
->GetBitmapPosition();
594 if ( dirBmp
== wxLEFT
|| dirBmp
== wxRIGHT
)
597 if ( sizeBmp
.y
> size
.y
)
600 else // bitmap on top/below the text
603 if ( sizeBmp
.x
> size
.x
)
607 // account for the user-specified margins
608 size
+= 2*m_imageData
->GetBitmapMargins();
610 // and also for the margins we always add internally (unless we have no
611 // border at all in which case the button has exactly the same size as
612 // bitmap and so no margins should be used)
613 if ( !HasFlag(wxBORDER_NONE
) )
618 if ( wxUxThemeEngine::GetIfActive() )
620 wxUxThemeHandle
theme(const_cast<wxButton
*>(this), L
"BUTTON");
623 wxUxThemeEngine::Get()->GetThemeMargins(theme
, NULL
,
630 // XP doesn't draw themed buttons correctly when the client
631 // area is smaller than 8x8 - enforce this minimum size for
633 size
.IncTo(wxSize(8, 8));
635 marginH
= margins
.cxLeftWidth
+ margins
.cxRightWidth
636 + 2*XP_BUTTON_EXTRA_MARGIN
;
637 marginV
= margins
.cyTopHeight
+ margins
.cyBottomHeight
638 + 2*XP_BUTTON_EXTRA_MARGIN
;
641 #endif // wxUSE_UXTHEME
644 marginV
= OD_BUTTON_MARGIN
;
647 size
.IncBy(marginH
, marginV
);
657 wxSize
wxButtonBase::GetDefaultSize()
659 static wxSize s_sizeBtn
;
661 if ( s_sizeBtn
.x
== 0 )
664 dc
.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT
));
666 // The size of a standard button in the dialog units is 50x14,
667 // translate this to pixels.
669 // Windows' computes dialog units using average character width over
670 // upper- and lower-case ASCII alphabet and not using the average
671 // character width metadata stored in the font; see
672 // http://support.microsoft.com/default.aspx/kb/145994 for detailed
675 // NB: wxMulDivInt32() is used, because it correctly rounds the result
677 const wxSize base
= wxPrivate::GetAverageASCIILetterSize(dc
);
678 s_sizeBtn
.x
= wxMulDivInt32(50, base
.x
, 4);
679 s_sizeBtn
.y
= wxMulDivInt32(14, base
.y
, 8);
685 // ----------------------------------------------------------------------------
686 // default button handling
687 // ----------------------------------------------------------------------------
690 The comment below and all this code is probably due to not using WM_NEXTDLGCTL
691 message when changing focus (but just SetFocus() which is not enough), see
692 http://blogs.msdn.com/oldnewthing/archive/2004/08/02/205624.aspx for the
695 TODO: Do use WM_NEXTDLGCTL and get rid of all this code.
698 "Everything you ever wanted to know about the default buttons" or "Why do we
699 have to do all this?"
701 In MSW the default button should be activated when the user presses Enter
702 and the current control doesn't process Enter itself somehow. This is
703 handled by ::DefWindowProc() (or maybe ::DefDialogProc()) using DM_SETDEFID
704 Another aspect of "defaultness" is that the default button has different
705 appearance: this is due to BS_DEFPUSHBUTTON style which is completely
706 separate from DM_SETDEFID stuff (!). Also note that BS_DEFPUSHBUTTON should
707 be unset if our parent window is not active so it should be unset whenever
708 we lose activation and set back when we regain it.
710 Final complication is that when a button is active, it should be the default
711 one, i.e. pressing Enter on a button always activates it and not another
714 We handle this by maintaining a permanent and a temporary default items in
715 wxControlContainer (both may be NULL). When a button becomes the current
716 control (i.e. gets focus) it sets itself as the temporary default which
717 ensures that it has the right appearance and that Enter will be redirected
718 to it. When the button loses focus, it unsets the temporary default and so
719 the default item will be the permanent default -- that is the default button
720 if any had been set or none otherwise, which is just what we want.
722 NB: all this is quite complicated by now and the worst is that normally
723 it shouldn't be necessary at all as for the normal Windows programs
724 DefWindowProc() and IsDialogMessage() take care of all this
725 automatically -- however in wxWidgets programs this doesn't work for
726 nested hierarchies (i.e. a notebook inside a notebook) for unknown
727 reason and so we have to reproduce all this code ourselves. It would be
728 very nice if we could avoid doing it.
731 // set this button as the (permanently) default one in its panel
732 wxWindow
*wxButton::SetDefault()
734 // set this one as the default button both for wxWidgets ...
735 wxWindow
*winOldDefault
= wxButtonBase::SetDefault();
738 SetDefaultStyle(wxDynamicCast(winOldDefault
, wxButton
), false);
739 SetDefaultStyle(this, true);
741 return winOldDefault
;
744 // return the top level parent window if it's not being deleted yet, otherwise
746 static wxTopLevelWindow
*GetTLWParentIfNotBeingDeleted(wxWindow
*win
)
750 // IsTopLevel() will return false for a wxTLW being deleted, so we also
751 // need the parent test for this case
752 wxWindow
* const parent
= win
->GetParent();
753 if ( !parent
|| win
->IsTopLevel() )
755 if ( win
->IsBeingDeleted() )
764 wxASSERT_MSG( win
, wxT("button without top level parent?") );
766 wxTopLevelWindow
* const tlw
= wxDynamicCast(win
, wxTopLevelWindow
);
767 wxASSERT_MSG( tlw
, wxT("logic error in GetTLWParentIfNotBeingDeleted()") );
772 // set this button as being currently default
773 void wxButton::SetTmpDefault()
775 wxTopLevelWindow
* const tlw
= GetTLWParentIfNotBeingDeleted(GetParent());
779 wxWindow
*winOldDefault
= tlw
->GetDefaultItem();
780 tlw
->SetTmpDefaultItem(this);
782 SetDefaultStyle(wxDynamicCast(winOldDefault
, wxButton
), false);
783 SetDefaultStyle(this, true);
786 // unset this button as currently default, it may still stay permanent default
787 void wxButton::UnsetTmpDefault()
789 wxTopLevelWindow
* const tlw
= GetTLWParentIfNotBeingDeleted(GetParent());
793 tlw
->SetTmpDefaultItem(NULL
);
795 wxWindow
*winOldDefault
= tlw
->GetDefaultItem();
797 SetDefaultStyle(this, false);
798 SetDefaultStyle(wxDynamicCast(winOldDefault
, wxButton
), true);
803 wxButton::SetDefaultStyle(wxButton
*btn
, bool on
)
805 // we may be called with NULL pointer -- simpler to do the check here than
806 // in the caller which does wxDynamicCast()
810 // first, let DefDlgProc() know about the new default button
813 // we shouldn't set BS_DEFPUSHBUTTON for any button if we don't have
814 // focus at all any more
815 if ( !wxTheApp
->IsActive() )
818 wxWindow
* const tlw
= wxGetTopLevelParent(btn
);
819 wxCHECK_RET( tlw
, wxT("button without top level window?") );
821 ::SendMessage(GetHwndOf(tlw
), DM_SETDEFID
, btn
->GetId(), 0L);
823 // sending DM_SETDEFID also changes the button style to
824 // BS_DEFPUSHBUTTON so there is nothing more to do
827 // then also change the style as needed
828 long style
= ::GetWindowLong(GetHwndOf(btn
), GWL_STYLE
);
829 if ( !(style
& BS_DEFPUSHBUTTON
) == on
)
831 // don't do it with the owner drawn buttons because it will
832 // reset BS_OWNERDRAW style bit too (as BS_OWNERDRAW &
833 // BS_DEFPUSHBUTTON != 0)!
834 if ( (style
& BS_OWNERDRAW
) != BS_OWNERDRAW
)
836 ::SendMessage(GetHwndOf(btn
), BM_SETSTYLE
,
837 on
? style
| BS_DEFPUSHBUTTON
838 : style
& ~BS_DEFPUSHBUTTON
,
843 // redraw the button - it will notice itself that it's
844 // [not] the default one [any longer]
848 //else: already has correct style
851 // ----------------------------------------------------------------------------
853 // ----------------------------------------------------------------------------
855 bool wxButton::SendClickEvent()
857 wxCommandEvent
event(wxEVT_COMMAND_BUTTON_CLICKED
, GetId());
858 event
.SetEventObject(this);
860 return ProcessCommand(event
);
863 void wxButton::Command(wxCommandEvent
& event
)
865 ProcessCommand(event
);
868 // ----------------------------------------------------------------------------
869 // event/message handlers
870 // ----------------------------------------------------------------------------
872 bool wxButton::MSWCommand(WXUINT param
, WXWORD
WXUNUSED(id
))
874 bool processed
= false;
877 // NOTE: Apparently older versions (NT 4?) of the common controls send
878 // BN_DOUBLECLICKED but not a second BN_CLICKED for owner-drawn
879 // buttons, so in order to send two EVT_BUTTON events we should
880 // catch both types. Currently (Feb 2003) up-to-date versions of
881 // win98, win2k and winXP all send two BN_CLICKED messages for
882 // all button types, so we don't catch BN_DOUBLECLICKED anymore
883 // in order to not get 3 EVT_BUTTON events. If this is a problem
884 // then we need to figure out which version of the comctl32 changed
885 // this behaviour and test for it.
887 case 1: // message came from an accelerator
888 case BN_CLICKED
: // normal buttons send this
889 processed
= SendClickEvent();
896 WXLRESULT
wxButton::MSWWindowProc(WXUINT nMsg
, WXWPARAM wParam
, WXLPARAM lParam
)
898 // when we receive focus, we want to temporarily become the default button in
899 // our parent panel so that pressing "Enter" would activate us -- and when
900 // losing it we should restore the previous default button as well
901 if ( nMsg
== WM_SETFOCUS
)
905 // let the default processing take place too
907 else if ( nMsg
== WM_KILLFOCUS
)
911 else if ( nMsg
== WM_LBUTTONDBLCLK
)
913 // emulate a click event to force an owner-drawn button to change its
914 // appearance - without this, it won't do it
915 (void)wxControl::MSWWindowProc(WM_LBUTTONDOWN
, wParam
, lParam
);
917 // and continue with processing the message normally as well
920 else if ( nMsg
== WM_THEMECHANGED
)
922 // need to recalculate the best size here
923 // as the theme size might have changed
924 InvalidateBestSize();
926 #endif // wxUSE_UXTHEME
927 // must use m_mouseInWindow here instead of IsMouseInWindow()
928 // since we need to know the first time the mouse enters the window
929 // and IsMouseInWindow() would return true in this case
930 else if ( (nMsg
== WM_MOUSEMOVE
&& !m_mouseInWindow
) ||
931 nMsg
== WM_MOUSELEAVE
)
937 wxUxThemeEngine::GetIfActive() ||
938 #endif // wxUSE_UXTHEME
939 m_imageData
&& m_imageData
->GetBitmap(State_Current
).IsOk()
947 // let the base class do all real processing
948 return wxControl::MSWWindowProc(nMsg
, wParam
, lParam
);
951 // ----------------------------------------------------------------------------
953 // ----------------------------------------------------------------------------
955 wxBitmap
wxButton::DoGetBitmap(State which
) const
957 return m_imageData
? m_imageData
->GetBitmap(which
) : wxBitmap();
960 void wxButton::DoSetBitmap(const wxBitmap
& bitmap
, State which
)
962 // allocate the image data when the first bitmap is set
966 // using image list doesn't work correctly if we don't have any label
967 // (even if we use BUTTON_IMAGELIST_ALIGN_CENTER alignment and
968 // BS_BITMAP style), at least under Windows 2003 so use owner drawn
969 // strategy for bitmap-only buttons
970 if ( ShowsLabel() && wxUxThemeEngine::GetIfActive() )
972 m_imageData
= new wxXPButtonImageData(this, bitmap
);
975 #endif // wxUSE_UXTHEME
977 m_imageData
= new wxODButtonImageData(this, bitmap
);
983 m_imageData
->SetBitmap(bitmap
, which
);
986 // it should be enough to only invalidate the best size when the normal
987 // bitmap changes as all bitmaps assigned to the button should be of the
989 if ( which
== State_Normal
)
990 InvalidateBestSize();
995 wxSize
wxButton::DoGetBitmapMargins() const
997 return m_imageData
? m_imageData
->GetBitmapMargins() : wxSize(0, 0);
1000 void wxButton::DoSetBitmapMargins(wxCoord x
, wxCoord y
)
1002 wxCHECK_RET( m_imageData
, "SetBitmap() must be called first" );
1004 m_imageData
->SetBitmapMargins(x
, y
);
1007 void wxButton::DoSetBitmapPosition(wxDirection dir
)
1009 wxCHECK_RET( m_imageData
, "SetBitmap() must be called first" );
1011 m_imageData
->SetBitmapPosition(dir
);
1014 // ----------------------------------------------------------------------------
1015 // owner-drawn buttons support
1016 // ----------------------------------------------------------------------------
1022 // return the button state using both the ODS_XXX flags specified in state
1023 // parameter and the current button state
1024 wxButton::State
GetButtonState(wxButton
*btn
, UINT state
)
1026 if ( state
& ODS_DISABLED
)
1027 return wxButton::State_Disabled
;
1029 if ( state
& ODS_SELECTED
)
1030 return wxButton::State_Pressed
;
1032 if ( btn
->HasCapture() || btn
->IsMouseInWindow() )
1033 return wxButton::State_Current
;
1035 if ( state
& ODS_FOCUS
)
1036 return wxButton::State_Focused
;
1038 return wxButton::State_Normal
;
1041 void DrawButtonText(HDC hdc
,
1043 const wxString
& text
,
1047 wxTextColoursChanger
changeFg(hdc
, col
, CLR_INVALID
);
1048 wxBkModeChanger
changeBkMode(hdc
, wxBRUSHSTYLE_TRANSPARENT
);
1050 // center text horizontally in any case
1053 if ( text
.find(wxT('\n')) != wxString::npos
)
1055 // draw multiline label
1057 // first we need to compute its bounding rect
1059 ::CopyRect(&rc
, pRect
);
1060 ::DrawText(hdc
, text
.wx_str(), text
.length(), &rc
,
1061 DT_CENTER
| DT_CALCRECT
);
1063 // now center this rect inside the entire button area
1064 const LONG w
= rc
.right
- rc
.left
;
1065 const LONG h
= rc
.bottom
- rc
.top
;
1066 rc
.left
= (pRect
->right
- pRect
->left
)/2 - w
/2;
1067 rc
.right
= rc
.left
+w
;
1068 rc
.top
= (pRect
->bottom
- pRect
->top
)/2 - h
/2;
1069 rc
.bottom
= rc
.top
+h
;
1071 ::DrawText(hdc
, text
.wx_str(), text
.length(), &rc
, flags
);
1073 else // single line label
1075 // centre text vertically too (notice that we must have DT_SINGLELINE
1076 // for DT_VCENTER to work)
1077 ::DrawText(hdc
, text
.wx_str(), text
.length(), pRect
,
1078 flags
| DT_SINGLELINE
| DT_VCENTER
);
1082 void DrawRect(HDC hdc
, const RECT
& r
)
1084 wxDrawLine(hdc
, r
.left
, r
.top
, r
.right
, r
.top
);
1085 wxDrawLine(hdc
, r
.right
, r
.top
, r
.right
, r
.bottom
);
1086 wxDrawLine(hdc
, r
.right
, r
.bottom
, r
.left
, r
.bottom
);
1087 wxDrawLine(hdc
, r
.left
, r
.bottom
, r
.left
, r
.top
);
1091 The button frame looks like this normally:
1094 WHHHHHHHHHHHHHHHHGB W = white (HILIGHT)
1095 WH GB H = light grey (LIGHT)
1096 WH GB G = dark grey (SHADOW)
1097 WH GB B = black (DKSHADOW)
1102 When the button is selected, the button becomes like this (the total button
1103 size doesn't change):
1114 When the button is pushed (while selected) it is like:
1125 void DrawButtonFrame(HDC hdc
, RECT
& rectBtn
,
1126 bool selected
, bool pushed
)
1129 CopyRect(&r
, &rectBtn
);
1131 AutoHPEN
hpenBlack(GetSysColor(COLOR_3DDKSHADOW
)),
1132 hpenGrey(GetSysColor(COLOR_3DSHADOW
)),
1133 hpenLightGr(GetSysColor(COLOR_3DLIGHT
)),
1134 hpenWhite(GetSysColor(COLOR_3DHILIGHT
));
1136 SelectInHDC
selectPen(hdc
, hpenBlack
);
1145 (void)SelectObject(hdc
, hpenGrey
);
1146 ::InflateRect(&r
, -1, -1);
1156 ::InflateRect(&r
, -1, -1);
1159 wxDrawLine(hdc
, r
.left
, r
.bottom
, r
.right
, r
.bottom
);
1160 wxDrawLine(hdc
, r
.right
, r
.bottom
, r
.right
, r
.top
- 1);
1162 (void)SelectObject(hdc
, hpenWhite
);
1163 wxDrawLine(hdc
, r
.left
, r
.bottom
- 1, r
.left
, r
.top
);
1164 wxDrawLine(hdc
, r
.left
, r
.top
, r
.right
, r
.top
);
1166 (void)SelectObject(hdc
, hpenLightGr
);
1167 wxDrawLine(hdc
, r
.left
+ 1, r
.bottom
- 2, r
.left
+ 1, r
.top
+ 1);
1168 wxDrawLine(hdc
, r
.left
+ 1, r
.top
+ 1, r
.right
- 1, r
.top
+ 1);
1170 (void)SelectObject(hdc
, hpenGrey
);
1171 wxDrawLine(hdc
, r
.left
+ 1, r
.bottom
- 1, r
.right
- 1, r
.bottom
- 1);
1172 wxDrawLine(hdc
, r
.right
- 1, r
.bottom
- 1, r
.right
- 1, r
.top
);
1175 InflateRect(&rectBtn
, -OD_BUTTON_MARGIN
, -OD_BUTTON_MARGIN
);
1179 void DrawXPBackground(wxButton
*button
, HDC hdc
, RECT
& rectBtn
, UINT state
)
1181 wxUxThemeHandle
theme(button
, L
"BUTTON");
1183 // this array is indexed by wxButton::State values and so must be kept in
1185 static const int uxStates
[] =
1187 PBS_NORMAL
, PBS_HOT
, PBS_PRESSED
, PBS_DISABLED
, PBS_DEFAULTED
1190 int iState
= uxStates
[GetButtonState(button
, state
)];
1192 wxUxThemeEngine
* const engine
= wxUxThemeEngine::Get();
1194 // draw parent background if needed
1195 if ( engine
->IsThemeBackgroundPartiallyTransparent
1202 engine
->DrawThemeParentBackground(GetHwndOf(button
), hdc
, &rectBtn
);
1206 engine
->DrawThemeBackground(theme
, hdc
, BP_PUSHBUTTON
, iState
,
1209 // calculate content area margins
1211 engine
->GetThemeMargins(theme
, hdc
, BP_PUSHBUTTON
, iState
,
1212 TMT_CONTENTMARGINS
, &rectBtn
, &margins
);
1213 ::InflateRect(&rectBtn
, -margins
.cxLeftWidth
, -margins
.cyTopHeight
);
1214 ::InflateRect(&rectBtn
, -XP_BUTTON_EXTRA_MARGIN
, -XP_BUTTON_EXTRA_MARGIN
);
1216 if ( button
->UseBgCol() )
1218 COLORREF colBg
= wxColourToRGB(button
->GetBackgroundColour());
1219 AutoHBRUSH
hbrushBackground(colBg
);
1221 // don't overwrite the focus rect
1223 ::CopyRect(&rectClient
, &rectBtn
);
1224 ::InflateRect(&rectClient
, -1, -1);
1225 FillRect(hdc
, &rectClient
, hbrushBackground
);
1228 #endif // wxUSE_UXTHEME
1230 } // anonymous namespace
1232 // ----------------------------------------------------------------------------
1233 // owner drawn buttons support
1234 // ----------------------------------------------------------------------------
1236 void wxButton::MakeOwnerDrawn()
1238 long style
= GetWindowLong(GetHwnd(), GWL_STYLE
);
1239 if ( (style
& BS_OWNERDRAW
) != BS_OWNERDRAW
)
1242 style
|= BS_OWNERDRAW
;
1243 SetWindowLong(GetHwnd(), GWL_STYLE
, style
);
1247 bool wxButton::SetBackgroundColour(const wxColour
&colour
)
1249 if ( !wxControl::SetBackgroundColour(colour
) )
1262 bool wxButton::SetForegroundColour(const wxColour
&colour
)
1264 if ( !wxControl::SetForegroundColour(colour
) )
1277 bool wxButton::MSWOnDraw(WXDRAWITEMSTRUCT
*wxdis
)
1279 LPDRAWITEMSTRUCT lpDIS
= (LPDRAWITEMSTRUCT
)wxdis
;
1280 HDC hdc
= lpDIS
->hDC
;
1282 UINT state
= lpDIS
->itemState
;
1283 bool pushed
= (SendMessage(GetHwnd(), BM_GETSTATE
, 0, 0) & BST_PUSHED
) != 0;
1286 CopyRect(&rectBtn
, &lpDIS
->rcItem
);
1288 // draw the button background
1289 if ( !HasFlag(wxBORDER_NONE
) )
1292 if ( wxUxThemeEngine::GetIfActive() )
1294 DrawXPBackground(this, hdc
, rectBtn
, state
);
1297 #endif // wxUSE_UXTHEME
1299 COLORREF colBg
= wxColourToRGB(GetBackgroundColour());
1301 // first, draw the background
1302 AutoHBRUSH
hbrushBackground(colBg
);
1303 FillRect(hdc
, &rectBtn
, hbrushBackground
);
1305 // draw the border for the current state
1306 bool selected
= (state
& ODS_SELECTED
) != 0;
1310 tlw
= wxDynamicCast(wxGetTopLevelParent(this), wxTopLevelWindow
);
1313 selected
= tlw
->GetDefaultItem() == this;
1317 DrawButtonFrame(hdc
, rectBtn
, selected
, pushed
);
1320 // draw the focus rectangle if we need it
1321 if ( (state
& ODS_FOCUS
) && !(state
& ODS_NOFOCUSRECT
) )
1323 DrawFocusRect(hdc
, &rectBtn
);
1326 if ( !wxUxThemeEngine::GetIfActive() )
1327 #endif // wxUSE_UXTHEME
1331 // the label is shifted by 1 pixel to create "pushed" effect
1332 OffsetRect(&rectBtn
, 1, 1);
1339 // draw the image, if any
1342 wxBitmap bmp
= m_imageData
->GetBitmap(GetButtonState(this, state
));
1344 bmp
= m_imageData
->GetBitmap(State_Normal
);
1346 const wxSize sizeBmp
= bmp
.GetSize();
1347 const wxSize margin
= m_imageData
->GetBitmapMargins();
1348 const wxSize
sizeBmpWithMargins(sizeBmp
+ 2*margin
);
1349 wxRect
rectButton(wxRectFromRECT(rectBtn
));
1351 // for simplicity, we start with centred rectangle and then move it to
1352 // the appropriate edge
1353 wxRect rectBitmap
= wxRect(sizeBmp
).CentreIn(rectButton
);
1354 switch ( m_imageData
->GetBitmapPosition() )
1357 wxFAIL_MSG( "invalid direction" );
1361 rectBitmap
.x
= rectButton
.x
+ margin
.x
;
1362 rectButton
.x
+= sizeBmpWithMargins
.x
;
1363 rectButton
.width
-= sizeBmpWithMargins
.x
;
1367 rectBitmap
.x
= rectButton
.GetRight() - sizeBmp
.x
- margin
.x
;
1368 rectButton
.width
-= sizeBmpWithMargins
.x
;
1372 rectBitmap
.y
= rectButton
.y
+ margin
.y
;
1373 rectButton
.y
+= sizeBmpWithMargins
.y
;
1374 rectButton
.height
-= sizeBmpWithMargins
.y
;
1378 rectBitmap
.y
= rectButton
.GetBottom() - sizeBmp
.y
- margin
.y
;
1379 rectButton
.height
-= sizeBmpWithMargins
.y
;
1383 wxDCTemp
dst((WXHDC
)hdc
);
1384 dst
.DrawBitmap(bmp
, rectBitmap
.GetPosition(), true);
1386 wxCopyRectToRECT(rectButton
, rectBtn
);
1390 // finally draw the label
1393 COLORREF colFg
= state
& ODS_DISABLED
1394 ? ::GetSysColor(COLOR_GRAYTEXT
)
1395 : wxColourToRGB(GetForegroundColour());
1397 // notice that DT_HIDEPREFIX doesn't work on old (pre-Windows 2000)
1398 // systems but by happy coincidence ODS_NOACCEL is not used under them
1399 // neither so DT_HIDEPREFIX should never be used there
1400 DrawButtonText(hdc
, &rectBtn
, GetLabel(), colFg
,
1401 state
& ODS_NOACCEL
? DT_HIDEPREFIX
: 0);
1407 #endif // wxUSE_BUTTON