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/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"
48 using namespace wxMSWImpl
;
51 #include "wx/msw/uxtheme.h"
53 // no need to include tmschema.h
55 #define BP_PUSHBUTTON 1
60 #define PBS_DISABLED 4
61 #define PBS_DEFAULTED 5
63 #define TMT_CONTENTMARGINS 3602
66 // provide the necessary declarations ourselves if they're missing from
68 #ifndef BCM_SETIMAGELIST
69 #define BCM_SETIMAGELIST 0x1602
70 #define BCM_SETTEXTMARGIN 0x1604
74 BUTTON_IMAGELIST_ALIGN_LEFT
,
75 BUTTON_IMAGELIST_ALIGN_RIGHT
,
76 BUTTON_IMAGELIST_ALIGN_TOP
,
77 BUTTON_IMAGELIST_ALIGN_BOTTOM
80 struct BUTTON_IMAGELIST
87 #endif // wxUSE_UXTHEME
89 #ifndef WM_THEMECHANGED
90 #define WM_THEMECHANGED 0x031A
94 #define ODS_NOACCEL 0x0100
97 #ifndef ODS_NOFOCUSRECT
98 #define ODS_NOFOCUSRECT 0x0200
101 #ifndef DT_HIDEPREFIX
102 #define DT_HIDEPREFIX 0x00100000
105 // ----------------------------------------------------------------------------
107 // ----------------------------------------------------------------------------
109 // we use different data classes for owner drawn buttons and for themed XP ones
111 class wxButtonImageData
114 wxButtonImageData() { }
115 virtual ~wxButtonImageData() { }
117 virtual wxBitmap
GetBitmap(wxButton::State which
) const = 0;
118 virtual void SetBitmap(const wxBitmap
& bitmap
, wxButton::State which
) = 0;
120 virtual wxSize
GetBitmapMargins() const = 0;
121 virtual void SetBitmapMargins(wxCoord x
, wxCoord y
) = 0;
123 virtual wxDirection
GetBitmapPosition() const = 0;
124 virtual void SetBitmapPosition(wxDirection dir
) = 0;
127 wxDECLARE_NO_COPY_CLASS(wxButtonImageData
);
133 // the gap between button edge and the interior area used by Windows for the
135 const int OD_BUTTON_MARGIN
= 4;
137 class wxODButtonImageData
: public wxButtonImageData
140 wxODButtonImageData(wxButton
*btn
, const wxBitmap
& bitmap
)
142 SetBitmap(bitmap
, wxButton::State_Normal
);
146 m_margin
.x
= btn
->GetCharWidth();
147 m_margin
.y
= btn
->GetCharHeight() / 2;
150 virtual wxBitmap
GetBitmap(wxButton::State which
) const
152 return m_bitmaps
[which
];
155 virtual void SetBitmap(const wxBitmap
& bitmap
, wxButton::State which
)
157 m_bitmaps
[which
] = bitmap
;
160 virtual wxSize
GetBitmapMargins() const
162 return m_margin
+ wxSize(OD_BUTTON_MARGIN
, OD_BUTTON_MARGIN
);
165 virtual void SetBitmapMargins(wxCoord x
, wxCoord y
)
167 m_margin
= wxSize(x
, y
);
170 virtual wxDirection
GetBitmapPosition() const
175 virtual void SetBitmapPosition(wxDirection dir
)
181 // just store the values passed to us to be able to retrieve them later
182 // from the drawing code
183 wxBitmap m_bitmaps
[wxButton::State_Max
];
187 wxDECLARE_NO_COPY_CLASS(wxODButtonImageData
);
192 class wxXPButtonImageData
: public wxButtonImageData
195 // we must be constructed with the size of our images as we need to create
197 wxXPButtonImageData(wxButton
*btn
, const wxBitmap
& bitmap
)
198 : m_iml(bitmap
.GetWidth(), bitmap
.GetHeight(), true /* use mask */,
199 wxButton::State_Max
),
200 m_hwndBtn(GetHwndOf(btn
))
202 // initialize all bitmaps to normal state
203 for ( int n
= 0; n
< wxButton::State_Max
; n
++ )
208 m_data
.himl
= GetHimagelistOf(&m_iml
);
210 // use default margins
212 m_data
.margin
.right
= btn
->GetCharWidth();
214 m_data
.margin
.bottom
= btn
->GetCharHeight() / 2;
216 // and default alignment
217 m_data
.uAlign
= BUTTON_IMAGELIST_ALIGN_LEFT
;
220 virtual wxBitmap
GetBitmap(wxButton::State which
) const
222 return m_iml
.GetBitmap(which
);
225 virtual void SetBitmap(const wxBitmap
& bitmap
, wxButton::State which
)
227 m_iml
.Replace(which
, bitmap
);
232 virtual wxSize
GetBitmapMargins() const
234 return wxSize(m_data
.margin
.left
, m_data
.margin
.top
);
237 virtual void SetBitmapMargins(wxCoord x
, wxCoord y
)
239 RECT
& margin
= m_data
.margin
;
245 if ( !::SendMessage(m_hwndBtn
, BCM_SETTEXTMARGIN
, 0, (LPARAM
)&margin
) )
247 wxLogDebug("SendMessage(BCM_SETTEXTMARGIN) failed");
251 virtual wxDirection
GetBitmapPosition() const
253 switch ( m_data
.uAlign
)
256 wxFAIL_MSG( "invalid image alignment" );
259 case BUTTON_IMAGELIST_ALIGN_LEFT
:
262 case BUTTON_IMAGELIST_ALIGN_RIGHT
:
265 case BUTTON_IMAGELIST_ALIGN_TOP
:
268 case BUTTON_IMAGELIST_ALIGN_BOTTOM
:
273 virtual void SetBitmapPosition(wxDirection dir
)
279 wxFAIL_MSG( "invalid direction" );
283 alignNew
= BUTTON_IMAGELIST_ALIGN_LEFT
;
287 alignNew
= BUTTON_IMAGELIST_ALIGN_RIGHT
;
291 alignNew
= BUTTON_IMAGELIST_ALIGN_TOP
;
295 alignNew
= BUTTON_IMAGELIST_ALIGN_BOTTOM
;
299 if ( alignNew
!= m_data
.uAlign
)
301 m_data
.uAlign
= alignNew
;
307 void UpdateImageInfo()
309 if ( !::SendMessage(m_hwndBtn
, BCM_SETIMAGELIST
, 0, (LPARAM
)&m_data
) )
311 wxLogDebug("SendMessage(BCM_SETIMAGELIST) failed");
315 // we store image list separately to be able to use convenient wxImageList
316 // methods instead of working with raw HIMAGELIST
319 // store the rest of the data in BCM_SETIMAGELIST-friendly form
320 BUTTON_IMAGELIST m_data
;
322 // the button we're associated with
323 const HWND m_hwndBtn
;
326 wxDECLARE_NO_COPY_CLASS(wxXPButtonImageData
);
329 #endif // wxUSE_UXTHEME
331 } // anonymous namespace
333 // ----------------------------------------------------------------------------
335 // ----------------------------------------------------------------------------
337 #if wxUSE_EXTENDED_RTTI
339 WX_DEFINE_FLAGS( wxButtonStyle
)
341 wxBEGIN_FLAGS( wxButtonStyle
)
342 // new style border flags, we put them first to
343 // use them for streaming out
344 wxFLAGS_MEMBER(wxBORDER_SIMPLE
)
345 wxFLAGS_MEMBER(wxBORDER_SUNKEN
)
346 wxFLAGS_MEMBER(wxBORDER_DOUBLE
)
347 wxFLAGS_MEMBER(wxBORDER_RAISED
)
348 wxFLAGS_MEMBER(wxBORDER_STATIC
)
349 wxFLAGS_MEMBER(wxBORDER_NONE
)
351 // old style border flags
352 wxFLAGS_MEMBER(wxSIMPLE_BORDER
)
353 wxFLAGS_MEMBER(wxSUNKEN_BORDER
)
354 wxFLAGS_MEMBER(wxDOUBLE_BORDER
)
355 wxFLAGS_MEMBER(wxRAISED_BORDER
)
356 wxFLAGS_MEMBER(wxSTATIC_BORDER
)
357 wxFLAGS_MEMBER(wxBORDER
)
359 // standard window styles
360 wxFLAGS_MEMBER(wxTAB_TRAVERSAL
)
361 wxFLAGS_MEMBER(wxCLIP_CHILDREN
)
362 wxFLAGS_MEMBER(wxTRANSPARENT_WINDOW
)
363 wxFLAGS_MEMBER(wxWANTS_CHARS
)
364 wxFLAGS_MEMBER(wxFULL_REPAINT_ON_RESIZE
)
365 wxFLAGS_MEMBER(wxALWAYS_SHOW_SB
)
366 wxFLAGS_MEMBER(wxVSCROLL
)
367 wxFLAGS_MEMBER(wxHSCROLL
)
369 wxFLAGS_MEMBER(wxBU_LEFT
)
370 wxFLAGS_MEMBER(wxBU_RIGHT
)
371 wxFLAGS_MEMBER(wxBU_TOP
)
372 wxFLAGS_MEMBER(wxBU_BOTTOM
)
373 wxFLAGS_MEMBER(wxBU_EXACTFIT
)
374 wxEND_FLAGS( wxButtonStyle
)
376 IMPLEMENT_DYNAMIC_CLASS_XTI(wxButton
, wxControl
,"wx/button.h")
378 wxBEGIN_PROPERTIES_TABLE(wxButton
)
379 wxEVENT_PROPERTY( Click
, wxEVT_COMMAND_BUTTON_CLICKED
, wxCommandEvent
)
381 wxPROPERTY( Font
, wxFont
, SetFont
, GetFont
, EMPTY_MACROVALUE
, 0 /*flags*/ , wxT("Helpstring") , wxT("group"))
382 wxPROPERTY( Label
, wxString
, SetLabel
, GetLabel
, wxString(), 0 /*flags*/ , wxT("Helpstring") , wxT("group") )
384 wxPROPERTY_FLAGS( WindowStyle
, wxButtonStyle
, long , SetWindowStyleFlag
, GetWindowStyleFlag
, EMPTY_MACROVALUE
, 0 /*flags*/ , wxT("Helpstring") , wxT("group")) // style
386 wxEND_PROPERTIES_TABLE()
388 wxBEGIN_HANDLERS_TABLE(wxButton
)
389 wxEND_HANDLERS_TABLE()
391 wxCONSTRUCTOR_6( wxButton
, wxWindow
* , Parent
, wxWindowID
, Id
, wxString
, Label
, wxPoint
, Position
, wxSize
, Size
, long , WindowStyle
)
395 IMPLEMENT_DYNAMIC_CLASS(wxButton
, wxControl
)
398 // ============================================================================
400 // ============================================================================
402 // ----------------------------------------------------------------------------
403 // helper functions from wx/msw/private/button.h
404 // ----------------------------------------------------------------------------
406 void wxMSWButton::UpdateMultilineStyle(HWND hwnd
, const wxString
& label
)
408 // update BS_MULTILINE style depending on the new label (resetting it
409 // doesn't seem to do anything very useful but it shouldn't hurt and we do
410 // have to set it whenever the label becomes multi line as otherwise it
411 // wouldn't be shown correctly as we don't use BS_MULTILINE when creating
412 // the control unless it already has new lines in its label)
413 long styleOld
= ::GetWindowLong(hwnd
, GWL_STYLE
),
415 if ( label
.find(_T('\n')) != wxString::npos
)
416 styleNew
= styleOld
| BS_MULTILINE
;
418 styleNew
= styleOld
& ~BS_MULTILINE
;
420 if ( styleNew
!= styleOld
)
421 ::SetWindowLong(hwnd
, GWL_STYLE
, styleNew
);
424 wxSize
wxMSWButton::GetFittingSize(wxWindow
*win
, const wxSize
& sizeLabel
)
426 // FIXME: this is pure guesswork, need to retrieve the real button margins
427 wxSize sizeBtn
= sizeLabel
;
429 sizeBtn
.x
+= 3*win
->GetCharWidth();
430 sizeBtn
.y
= 11*EDIT_HEIGHT_FROM_CHAR_HEIGHT(sizeLabel
.y
)/10;
435 wxSize
wxMSWButton::ComputeBestSize(wxControl
*btn
)
440 dc
.GetMultiLineTextExtent(btn
->GetLabelText(), &sizeBtn
.x
, &sizeBtn
.y
);
442 sizeBtn
= GetFittingSize(btn
, sizeBtn
);
444 // all buttons have at least the standard size unless the user explicitly
445 // wants them to be of smaller size and used wxBU_EXACTFIT style when
446 // creating the button
447 if ( !btn
->HasFlag(wxBU_EXACTFIT
) )
449 wxSize sizeDef
= wxButton::GetDefaultSize();
450 if ( sizeBtn
.x
< sizeDef
.x
)
451 sizeBtn
.x
= sizeDef
.x
;
452 if ( sizeBtn
.y
< sizeDef
.y
)
453 sizeBtn
.y
= sizeDef
.y
;
456 btn
->CacheBestSize(sizeBtn
);
461 // ----------------------------------------------------------------------------
462 // creation/destruction
463 // ----------------------------------------------------------------------------
465 bool wxButton::Create(wxWindow
*parent
,
471 const wxValidator
& validator
,
472 const wxString
& name
)
475 if (label
.empty() && wxIsStockID(id
))
477 // On Windows, some buttons aren't supposed to have mnemonics
478 label
= wxGetStockLabel
481 id
== wxID_OK
|| id
== wxID_CANCEL
|| id
== wxID_CLOSE
483 : wxSTOCK_WITH_MNEMONIC
487 if ( !CreateControl(parent
, id
, pos
, size
, style
, validator
, name
) )
491 WXDWORD msStyle
= MSWGetStyle(style
, &exstyle
);
493 // if the label contains several lines we must explicitly tell the button
494 // about it or it wouldn't draw it correctly ("\n"s would just appear as
497 // NB: we do it here and not in MSWGetStyle() because we need the label
498 // value and the label is not set yet when MSWGetStyle() is called
499 msStyle
|= wxMSWButton::GetMultilineStyle(label
);
501 return MSWCreateControl(_T("BUTTON"), msStyle
, pos
, size
, label
, exstyle
);
504 wxButton::~wxButton()
506 wxTopLevelWindow
*tlw
= wxDynamicCast(wxGetTopLevelParent(this), wxTopLevelWindow
);
507 if ( tlw
&& tlw
->GetTmpDefaultItem() == this )
515 // ----------------------------------------------------------------------------
517 // ----------------------------------------------------------------------------
519 WXDWORD
wxButton::MSWGetStyle(long style
, WXDWORD
*exstyle
) const
521 // buttons never have an external border, they draw their own one
522 WXDWORD msStyle
= wxControl::MSWGetStyle
524 (style
& ~wxBORDER_MASK
) | wxBORDER_NONE
, exstyle
527 // we must use WS_CLIPSIBLINGS with the buttons or they would draw over
528 // each other in any resizeable dialog which has more than one button in
530 msStyle
|= WS_CLIPSIBLINGS
;
532 // don't use "else if" here: weird as it is, but you may combine wxBU_LEFT
533 // and wxBU_RIGHT to get BS_CENTER!
534 if ( style
& wxBU_LEFT
)
536 if ( style
& wxBU_RIGHT
)
538 if ( style
& wxBU_TOP
)
540 if ( style
& wxBU_BOTTOM
)
541 msStyle
|= BS_BOTTOM
;
544 if ( style
& wxNO_BORDER
)
546 #endif // __WXWINCE__
551 void wxButton::SetLabel(const wxString
& label
)
553 wxMSWButton::UpdateMultilineStyle(GetHwnd(), label
);
555 wxButtonBase::SetLabel(label
);
558 // ----------------------------------------------------------------------------
559 // size management including autosizing
560 // ----------------------------------------------------------------------------
562 wxSize
wxButton::DoGetBestSize() const
564 wxSize size
= wxMSWButton::ComputeBestSize(const_cast<wxButton
*>(this));
567 const wxSize sizeBmp
= m_imageData
->GetBitmap(State_Normal
).GetSize();
568 const wxDirection dirBmp
= m_imageData
->GetBitmapPosition();
569 if ( dirBmp
== wxLEFT
|| dirBmp
== wxRIGHT
)
572 if ( sizeBmp
.y
> size
.y
)
575 else // bitmap on top/below the text
578 if ( sizeBmp
.x
> size
.x
)
582 size
+= 2*m_imageData
->GetBitmapMargins();
591 wxSize
wxButtonBase::GetDefaultSize()
593 static wxSize s_sizeBtn
;
595 if ( s_sizeBtn
.x
== 0 )
598 dc
.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT
));
600 // the size of a standard button in the dialog units is 50x14,
601 // translate this to pixels
602 // NB1: the multipliers come from the Windows convention
603 // NB2: the extra +1/+2 were needed to get the size be the same as the
604 // size of the buttons in the standard dialog - I don't know how
605 // this happens, but on my system this size is 75x23 in pixels and
606 // 23*8 isn't even divisible by 14... Would be nice to understand
607 // why these constants are needed though!
608 s_sizeBtn
.x
= (50 * (dc
.GetCharWidth() + 1))/4;
609 s_sizeBtn
.y
= ((14 * dc
.GetCharHeight()) + 2)/8;
615 // ----------------------------------------------------------------------------
616 // default button handling
617 // ----------------------------------------------------------------------------
620 "Everything you ever wanted to know about the default buttons" or "Why do we
621 have to do all this?"
623 In MSW the default button should be activated when the user presses Enter
624 and the current control doesn't process Enter itself somehow. This is
625 handled by ::DefWindowProc() (or maybe ::DefDialogProc()) using DM_SETDEFID
626 Another aspect of "defaultness" is that the default button has different
627 appearance: this is due to BS_DEFPUSHBUTTON style which is completely
628 separate from DM_SETDEFID stuff (!). Also note that BS_DEFPUSHBUTTON should
629 be unset if our parent window is not active so it should be unset whenever
630 we lose activation and set back when we regain it.
632 Final complication is that when a button is active, it should be the default
633 one, i.e. pressing Enter on a button always activates it and not another
636 We handle this by maintaining a permanent and a temporary default items in
637 wxControlContainer (both may be NULL). When a button becomes the current
638 control (i.e. gets focus) it sets itself as the temporary default which
639 ensures that it has the right appearance and that Enter will be redirected
640 to it. When the button loses focus, it unsets the temporary default and so
641 the default item will be the permanent default -- that is the default button
642 if any had been set or none otherwise, which is just what we want.
644 NB: all this is quite complicated by now and the worst is that normally
645 it shouldn't be necessary at all as for the normal Windows programs
646 DefWindowProc() and IsDialogMessage() take care of all this
647 automatically -- however in wxWidgets programs this doesn't work for
648 nested hierarchies (i.e. a notebook inside a notebook) for unknown
649 reason and so we have to reproduce all this code ourselves. It would be
650 very nice if we could avoid doing it.
653 // set this button as the (permanently) default one in its panel
654 wxWindow
*wxButton::SetDefault()
656 // set this one as the default button both for wxWidgets ...
657 wxWindow
*winOldDefault
= wxButtonBase::SetDefault();
660 SetDefaultStyle(wxDynamicCast(winOldDefault
, wxButton
), false);
661 SetDefaultStyle(this, true);
663 return winOldDefault
;
666 // return the top level parent window if it's not being deleted yet, otherwise
668 static wxTopLevelWindow
*GetTLWParentIfNotBeingDeleted(wxWindow
*win
)
672 // IsTopLevel() will return false for a wxTLW being deleted, so we also
673 // need the parent test for this case
674 wxWindow
* const parent
= win
->GetParent();
675 if ( !parent
|| win
->IsTopLevel() )
677 if ( win
->IsBeingDeleted() )
686 wxASSERT_MSG( win
, _T("button without top level parent?") );
688 wxTopLevelWindow
* const tlw
= wxDynamicCast(win
, wxTopLevelWindow
);
689 wxASSERT_MSG( tlw
, _T("logic error in GetTLWParentIfNotBeingDeleted()") );
694 // set this button as being currently default
695 void wxButton::SetTmpDefault()
697 wxTopLevelWindow
* const tlw
= GetTLWParentIfNotBeingDeleted(GetParent());
701 wxWindow
*winOldDefault
= tlw
->GetDefaultItem();
702 tlw
->SetTmpDefaultItem(this);
704 SetDefaultStyle(wxDynamicCast(winOldDefault
, wxButton
), false);
705 SetDefaultStyle(this, true);
708 // unset this button as currently default, it may still stay permanent default
709 void wxButton::UnsetTmpDefault()
711 wxTopLevelWindow
* const tlw
= GetTLWParentIfNotBeingDeleted(GetParent());
715 tlw
->SetTmpDefaultItem(NULL
);
717 wxWindow
*winOldDefault
= tlw
->GetDefaultItem();
719 SetDefaultStyle(this, false);
720 SetDefaultStyle(wxDynamicCast(winOldDefault
, wxButton
), true);
725 wxButton::SetDefaultStyle(wxButton
*btn
, bool on
)
727 // we may be called with NULL pointer -- simpler to do the check here than
728 // in the caller which does wxDynamicCast()
732 // first, let DefDlgProc() know about the new default button
735 // we shouldn't set BS_DEFPUSHBUTTON for any button if we don't have
736 // focus at all any more
737 if ( !wxTheApp
->IsActive() )
740 wxWindow
* const tlw
= wxGetTopLevelParent(btn
);
741 wxCHECK_RET( tlw
, _T("button without top level window?") );
743 ::SendMessage(GetHwndOf(tlw
), DM_SETDEFID
, btn
->GetId(), 0L);
745 // sending DM_SETDEFID also changes the button style to
746 // BS_DEFPUSHBUTTON so there is nothing more to do
749 // then also change the style as needed
750 long style
= ::GetWindowLong(GetHwndOf(btn
), GWL_STYLE
);
751 if ( !(style
& BS_DEFPUSHBUTTON
) == on
)
753 // don't do it with the owner drawn buttons because it will
754 // reset BS_OWNERDRAW style bit too (as BS_OWNERDRAW &
755 // BS_DEFPUSHBUTTON != 0)!
756 if ( (style
& BS_OWNERDRAW
) != BS_OWNERDRAW
)
758 ::SendMessage(GetHwndOf(btn
), BM_SETSTYLE
,
759 on
? style
| BS_DEFPUSHBUTTON
760 : style
& ~BS_DEFPUSHBUTTON
,
765 // redraw the button - it will notice itself that it's
766 // [not] the default one [any longer]
770 //else: already has correct style
773 // ----------------------------------------------------------------------------
775 // ----------------------------------------------------------------------------
777 bool wxButton::SendClickEvent()
779 wxCommandEvent
event(wxEVT_COMMAND_BUTTON_CLICKED
, GetId());
780 event
.SetEventObject(this);
782 return ProcessCommand(event
);
785 void wxButton::Command(wxCommandEvent
& event
)
787 ProcessCommand(event
);
790 // ----------------------------------------------------------------------------
791 // event/message handlers
792 // ----------------------------------------------------------------------------
794 bool wxButton::MSWCommand(WXUINT param
, WXWORD
WXUNUSED(id
))
796 bool processed
= false;
799 // NOTE: Apparently older versions (NT 4?) of the common controls send
800 // BN_DOUBLECLICKED but not a second BN_CLICKED for owner-drawn
801 // buttons, so in order to send two EVT_BUTTON events we should
802 // catch both types. Currently (Feb 2003) up-to-date versions of
803 // win98, win2k and winXP all send two BN_CLICKED messages for
804 // all button types, so we don't catch BN_DOUBLECLICKED anymore
805 // in order to not get 3 EVT_BUTTON events. If this is a problem
806 // then we need to figure out which version of the comctl32 changed
807 // this behaviour and test for it.
809 case 1: // message came from an accelerator
810 case BN_CLICKED
: // normal buttons send this
811 processed
= SendClickEvent();
818 WXLRESULT
wxButton::MSWWindowProc(WXUINT nMsg
, WXWPARAM wParam
, WXLPARAM lParam
)
820 // when we receive focus, we want to temporarily become the default button in
821 // our parent panel so that pressing "Enter" would activate us -- and when
822 // losing it we should restore the previous default button as well
823 if ( nMsg
== WM_SETFOCUS
)
827 // let the default processing take place too
829 else if ( nMsg
== WM_KILLFOCUS
)
833 else if ( nMsg
== WM_LBUTTONDBLCLK
)
835 // emulate a click event to force an owner-drawn button to change its
836 // appearance - without this, it won't do it
837 (void)wxControl::MSWWindowProc(WM_LBUTTONDOWN
, wParam
, lParam
);
839 // and continue with processing the message normally as well
842 else if ( nMsg
== WM_THEMECHANGED
)
844 // need to recalculate the best size here
845 // as the theme size might have changed
846 InvalidateBestSize();
848 #endif // wxUSE_UXTHEME
849 // must use m_mouseInWindow here instead of IsMouseInWindow()
850 // since we need to know the first time the mouse enters the window
851 // and IsMouseInWindow() would return true in this case
852 else if ( (nMsg
== WM_MOUSEMOVE
&& !m_mouseInWindow
) ||
853 nMsg
== WM_MOUSELEAVE
)
857 wxUxThemeEngine::GetIfActive() ||
858 #endif // wxUSE_UXTHEME
859 m_imageData
&& m_imageData
->GetBitmap(State_Current
).IsOk()
866 // let the base class do all real processing
867 return wxControl::MSWWindowProc(nMsg
, wParam
, lParam
);
870 // ----------------------------------------------------------------------------
872 // ----------------------------------------------------------------------------
874 wxBitmap
wxButton::DoGetBitmap(State which
) const
876 return m_imageData
? m_imageData
->GetBitmap(which
) : wxBitmap();
879 void wxButton::DoSetBitmap(const wxBitmap
& bitmap
, State which
)
881 // allocate the image data when the first bitmap is set
885 if ( wxUxThemeEngine::GetIfActive() )
887 m_imageData
= new wxXPButtonImageData(this, bitmap
);
890 #endif // wxUSE_UXTHEME
892 m_imageData
= new wxODButtonImageData(this, bitmap
);
896 // if a bitmap was assigned to the bitmap, its best size must be
897 // changed to account for it
898 InvalidateBestSize();
902 m_imageData
->SetBitmap(bitmap
, which
);
906 void wxButton::DoSetBitmapMargins(wxCoord x
, wxCoord y
)
908 wxCHECK_RET( m_imageData
, "SetBitmap() must be called first" );
910 m_imageData
->SetBitmapMargins(x
, y
);
913 void wxButton::DoSetBitmapPosition(wxDirection dir
)
915 wxCHECK_RET( m_imageData
, "SetBitmap() must be called first" );
917 m_imageData
->SetBitmapPosition(dir
);
920 // ----------------------------------------------------------------------------
921 // owner-drawn buttons support
922 // ----------------------------------------------------------------------------
928 // return the button state using both the ODS_XXX flags specified in state
929 // parameter and the current button state
930 wxButton::State
GetButtonState(wxButton
*btn
, UINT state
)
932 if ( state
& ODS_DISABLED
)
933 return wxButton::State_Disabled
;
935 if ( state
& ODS_SELECTED
)
936 return wxButton::State_Pressed
;
938 if ( btn
->HasCapture() || btn
->IsMouseInWindow() )
939 return wxButton::State_Current
;
941 if ( state
& ODS_FOCUS
)
942 return wxButton::State_Focused
;
944 return wxButton::State_Normal
;
947 void DrawButtonText(HDC hdc
,
949 const wxString
& text
,
953 wxTextColoursChanger
changeFg(hdc
, col
, CLR_INVALID
);
954 wxBkModeChanger
changeBkMode(hdc
, wxBRUSHSTYLE_TRANSPARENT
);
956 // center text horizontally in any case
959 if ( text
.find(_T('\n')) != wxString::npos
)
961 // draw multiline label
963 // first we need to compute its bounding rect
965 ::CopyRect(&rc
, pRect
);
966 ::DrawText(hdc
, text
.wx_str(), text
.length(), &rc
,
967 DT_CENTER
| DT_CALCRECT
);
969 // now center this rect inside the entire button area
970 const LONG w
= rc
.right
- rc
.left
;
971 const LONG h
= rc
.bottom
- rc
.top
;
972 rc
.left
= (pRect
->right
- pRect
->left
)/2 - w
/2;
973 rc
.right
= rc
.left
+w
;
974 rc
.top
= (pRect
->bottom
- pRect
->top
)/2 - h
/2;
975 rc
.bottom
= rc
.top
+h
;
977 ::DrawText(hdc
, text
.wx_str(), text
.length(), &rc
, flags
);
979 else // single line label
981 // centre text vertically too (notice that we must have DT_SINGLELINE
982 // for DT_VCENTER to work)
983 ::DrawText(hdc
, text
.wx_str(), text
.length(), pRect
,
984 flags
| DT_SINGLELINE
| DT_VCENTER
);
988 void DrawRect(HDC hdc
, const RECT
& r
)
990 wxDrawLine(hdc
, r
.left
, r
.top
, r
.right
, r
.top
);
991 wxDrawLine(hdc
, r
.right
, r
.top
, r
.right
, r
.bottom
);
992 wxDrawLine(hdc
, r
.right
, r
.bottom
, r
.left
, r
.bottom
);
993 wxDrawLine(hdc
, r
.left
, r
.bottom
, r
.left
, r
.top
);
997 The button frame looks like this normally:
1000 WHHHHHHHHHHHHHHHHGB W = white (HILIGHT)
1001 WH GB H = light grey (LIGHT)
1002 WH GB G = dark grey (SHADOW)
1003 WH GB B = black (DKSHADOW)
1008 When the button is selected, the button becomes like this (the total button
1009 size doesn't change):
1020 When the button is pushed (while selected) it is like:
1031 void DrawButtonFrame(HDC hdc
, RECT
& rectBtn
,
1032 bool selected
, bool pushed
)
1035 CopyRect(&r
, &rectBtn
);
1037 AutoHPEN
hpenBlack(GetSysColor(COLOR_3DDKSHADOW
)),
1038 hpenGrey(GetSysColor(COLOR_3DSHADOW
)),
1039 hpenLightGr(GetSysColor(COLOR_3DLIGHT
)),
1040 hpenWhite(GetSysColor(COLOR_3DHILIGHT
));
1042 SelectInHDC
selectPen(hdc
, hpenBlack
);
1051 (void)SelectObject(hdc
, hpenGrey
);
1052 ::InflateRect(&r
, -1, -1);
1062 ::InflateRect(&r
, -1, -1);
1065 wxDrawLine(hdc
, r
.left
, r
.bottom
, r
.right
, r
.bottom
);
1066 wxDrawLine(hdc
, r
.right
, r
.bottom
, r
.right
, r
.top
- 1);
1068 (void)SelectObject(hdc
, hpenWhite
);
1069 wxDrawLine(hdc
, r
.left
, r
.bottom
- 1, r
.left
, r
.top
);
1070 wxDrawLine(hdc
, r
.left
, r
.top
, r
.right
, r
.top
);
1072 (void)SelectObject(hdc
, hpenLightGr
);
1073 wxDrawLine(hdc
, r
.left
+ 1, r
.bottom
- 2, r
.left
+ 1, r
.top
+ 1);
1074 wxDrawLine(hdc
, r
.left
+ 1, r
.top
+ 1, r
.right
- 1, r
.top
+ 1);
1076 (void)SelectObject(hdc
, hpenGrey
);
1077 wxDrawLine(hdc
, r
.left
+ 1, r
.bottom
- 1, r
.right
- 1, r
.bottom
- 1);
1078 wxDrawLine(hdc
, r
.right
- 1, r
.bottom
- 1, r
.right
- 1, r
.top
);
1081 InflateRect(&rectBtn
, -OD_BUTTON_MARGIN
, -OD_BUTTON_MARGIN
);
1085 void DrawXPBackground(wxButton
*button
, HDC hdc
, RECT
& rectBtn
, UINT state
)
1087 wxUxThemeHandle
theme(button
, L
"BUTTON");
1089 // this array is indexed by wxButton::State values and so must be kept in
1091 static const uxStates
[] =
1093 PBS_NORMAL
, PBS_HOT
, PBS_PRESSED
, PBS_DISABLED
, PBS_DEFAULTED
1096 int iState
= uxStates
[GetButtonState(button
, state
)];
1098 wxUxThemeEngine
* const engine
= wxUxThemeEngine::Get();
1100 // draw parent background if needed
1101 if ( engine
->IsThemeBackgroundPartiallyTransparent
1108 engine
->DrawThemeParentBackground(GetHwndOf(button
), hdc
, &rectBtn
);
1112 engine
->DrawThemeBackground(theme
, hdc
, BP_PUSHBUTTON
, iState
,
1115 // calculate content area margins
1117 engine
->GetThemeMargins(theme
, hdc
, BP_PUSHBUTTON
, iState
,
1118 TMT_CONTENTMARGINS
, &rectBtn
, &margins
);
1119 ::InflateRect(&rectBtn
, -margins
.cxLeftWidth
, -margins
.cyTopHeight
);
1121 if ( button
->UseBgCol() )
1123 COLORREF colBg
= wxColourToRGB(button
->GetBackgroundColour());
1124 AutoHBRUSH
hbrushBackground(colBg
);
1126 // don't overwrite the focus rect
1128 ::CopyRect(&rectClient
, &rectBtn
);
1129 ::InflateRect(&rectClient
, -1, -1);
1130 FillRect(hdc
, &rectClient
, hbrushBackground
);
1133 #endif // wxUSE_UXTHEME
1135 } // anonymous namespace
1137 // ----------------------------------------------------------------------------
1138 // owner drawn buttons support
1139 // ----------------------------------------------------------------------------
1141 void wxButton::MakeOwnerDrawn()
1143 long style
= GetWindowLong(GetHwnd(), GWL_STYLE
);
1144 if ( (style
& BS_OWNERDRAW
) != BS_OWNERDRAW
)
1147 style
|= BS_OWNERDRAW
;
1148 SetWindowLong(GetHwnd(), GWL_STYLE
, style
);
1152 bool wxButton::SetBackgroundColour(const wxColour
&colour
)
1154 if ( !wxControl::SetBackgroundColour(colour
) )
1167 bool wxButton::SetForegroundColour(const wxColour
&colour
)
1169 if ( !wxControl::SetForegroundColour(colour
) )
1182 bool wxButton::MSWOnDraw(WXDRAWITEMSTRUCT
*wxdis
)
1184 LPDRAWITEMSTRUCT lpDIS
= (LPDRAWITEMSTRUCT
)wxdis
;
1185 HDC hdc
= lpDIS
->hDC
;
1187 UINT state
= lpDIS
->itemState
;
1188 bool pushed
= (SendMessage(GetHwnd(), BM_GETSTATE
, 0, 0) & BST_PUSHED
) != 0;
1191 CopyRect(&rectBtn
, &lpDIS
->rcItem
);
1193 // draw the button background
1195 if ( wxUxThemeEngine::GetIfActive() )
1197 DrawXPBackground(this, hdc
, rectBtn
, state
);
1200 #endif // wxUSE_UXTHEME
1202 COLORREF colBg
= wxColourToRGB(GetBackgroundColour());
1204 // first, draw the background
1205 AutoHBRUSH
hbrushBackground(colBg
);
1206 FillRect(hdc
, &rectBtn
, hbrushBackground
);
1208 // draw the border for the current state
1209 bool selected
= (state
& ODS_SELECTED
) != 0;
1213 tlw
= wxDynamicCast(wxGetTopLevelParent(this), wxTopLevelWindow
);
1216 selected
= tlw
->GetDefaultItem() == this;
1220 DrawButtonFrame(hdc
, rectBtn
, selected
, pushed
);
1223 // draw the focus rectangle if we need it
1224 if ( (state
& ODS_FOCUS
) && !(state
& ODS_NOFOCUSRECT
) )
1226 DrawFocusRect(hdc
, &rectBtn
);
1229 if ( !wxUxThemeEngine::GetIfActive() )
1230 #endif // wxUSE_UXTHEME
1234 // the label is shifted by 1 pixel to create "pushed" effect
1235 OffsetRect(&rectBtn
, 1, 1);
1241 // draw the image, if any
1244 wxBitmap bmp
= m_imageData
->GetBitmap(GetButtonState(this, state
));
1246 bmp
= m_imageData
->GetBitmap(State_Normal
);
1248 const wxSize sizeBmp
= bmp
.GetSize();
1249 const wxSize margin
= m_imageData
->GetBitmapMargins();
1250 const wxSize
sizeBmpWithMargins(sizeBmp
+ 2*margin
);
1251 wxRect
rectButton(wxRectFromRECT(rectBtn
));
1253 // for simplicity, we start with centred rectangle and then move it to
1254 // the appropriate edge
1255 wxRect rectBitmap
= wxRect(sizeBmp
).CentreIn(rectButton
);
1256 switch ( m_imageData
->GetBitmapPosition() )
1259 wxFAIL_MSG( "invalid direction" );
1263 rectBitmap
.x
= rectButton
.x
+ margin
.x
;
1264 rectButton
.x
+= sizeBmpWithMargins
.x
;
1265 rectButton
.width
-= sizeBmpWithMargins
.x
;
1269 rectBitmap
.x
= rectButton
.GetRight() - sizeBmp
.x
- margin
.x
;
1270 rectButton
.width
-= sizeBmpWithMargins
.x
;
1274 rectBitmap
.y
= rectButton
.y
+ margin
.y
;
1275 rectButton
.y
+= sizeBmpWithMargins
.y
;
1276 rectButton
.height
-= sizeBmpWithMargins
.y
;
1280 rectBitmap
.y
= rectButton
.GetBottom() - sizeBmp
.y
- margin
.y
;
1281 rectButton
.height
-= sizeBmpWithMargins
.y
;
1285 wxDCTemp
dst((WXHDC
)hdc
);
1286 dst
.DrawBitmap(bmp
, rectBitmap
.GetPosition(), true);
1288 wxCopyRectToRECT(rectButton
, rectBtn
);
1292 // finally draw the label
1293 COLORREF colFg
= state
& ODS_DISABLED
1294 ? ::GetSysColor(COLOR_GRAYTEXT
)
1295 : wxColourToRGB(GetForegroundColour());
1297 // notice that DT_HIDEPREFIX doesn't work on old (pre-Windows 2000) systems
1298 // but by happy coincidence ODS_NOACCEL is not used under them neither so
1299 // DT_HIDEPREFIX should never be used there
1300 DrawButtonText(hdc
, &rectBtn
, GetLabel(), colFg
,
1301 state
& ODS_NOACCEL
? DT_HIDEPREFIX
: 0);
1306 #endif // wxUSE_BUTTON