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() { }
116 virtual wxBitmap
GetBitmap(wxButton::State which
) const = 0;
117 virtual void SetBitmap(const wxBitmap
& bitmap
, wxButton::State which
) = 0;
119 virtual wxSize
GetBitmapMargins() const = 0;
120 virtual void SetBitmapMargins(wxCoord x
, wxCoord y
) = 0;
122 virtual bool IsHorizontal() const = 0;
123 virtual void SetBitmapPosition(wxDirection dir
) = 0;
126 wxDECLARE_NO_COPY_CLASS(wxButtonImageData
);
132 class wxODButtonImageData
: public wxButtonImageData
135 wxODButtonImageData() { m_dir
= wxLEFT
; }
137 virtual wxBitmap
GetBitmap(wxButton::State which
) const
139 return m_bitmaps
[which
];
142 virtual void SetBitmap(const wxBitmap
& bitmap
, wxButton::State which
)
144 m_bitmaps
[which
] = bitmap
;
147 virtual wxSize
GetBitmapMargins() const
152 virtual void SetBitmapMargins(wxCoord x
, wxCoord y
)
154 m_margin
= wxSize(x
, y
);
157 virtual bool IsHorizontal() const
159 return m_dir
== wxLEFT
|| m_dir
== wxRIGHT
;
162 virtual void SetBitmapPosition(wxDirection dir
)
168 // just store the values passed to us to be able to retrieve them later
169 // from the drawing code
170 wxBitmap m_bitmaps
[wxButton::State_Max
];
174 wxDECLARE_NO_COPY_CLASS(wxODButtonImageData
);
179 class wxXPButtonImageData
: public wxButtonImageData
182 // we must be constructed with the size of our images as we need to create
184 wxXPButtonImageData(wxButton
*btn
, const wxSize
& size
)
185 : m_iml(size
.x
, size
.y
, true /* use mask */, wxButton::State_Max
),
186 m_hwndBtn(GetHwndOf(btn
))
188 m_data
.himl
= GetHimagelistOf(&m_iml
);
190 // use default margins
192 m_data
.margin
.right
= btn
->GetCharWidth();
194 m_data
.margin
.bottom
= btn
->GetCharHeight() / 2;
196 // and default alignment
197 m_data
.uAlign
= BUTTON_IMAGELIST_ALIGN_LEFT
;
200 virtual wxBitmap
GetBitmap(wxButton::State which
) const
202 return m_iml
.GetBitmap(which
);
205 virtual void SetBitmap(const wxBitmap
& bitmap
, wxButton::State which
)
207 const int imagesToAdd
= which
- m_iml
.GetImageCount();
208 if ( imagesToAdd
>= 0 )
210 if ( imagesToAdd
> 0 )
212 const wxBitmap bmpNormal
= GetBitmap(wxButton::State_Normal
);
213 for ( int n
= 0; n
< imagesToAdd
; n
++ )
214 m_iml
.Add(bmpNormal
);
219 else // we already have this bitmap
221 m_iml
.Replace(which
, bitmap
);
227 virtual wxSize
GetBitmapMargins() const
229 return wxSize(m_data
.margin
.left
, m_data
.margin
.top
);
232 virtual void SetBitmapMargins(wxCoord x
, wxCoord y
)
234 RECT
& margin
= m_data
.margin
;
240 if ( !::SendMessage(m_hwndBtn
, BCM_SETTEXTMARGIN
, 0, (LPARAM
)&margin
) )
242 wxLogDebug("SendMessage(BCM_SETTEXTMARGIN) failed");
246 virtual bool IsHorizontal() const
248 return m_data
.uAlign
== BUTTON_IMAGELIST_ALIGN_LEFT
||
249 m_data
.uAlign
== BUTTON_IMAGELIST_ALIGN_RIGHT
;
252 virtual void SetBitmapPosition(wxDirection dir
)
258 wxFAIL_MSG( "invalid direction" );
262 alignNew
= BUTTON_IMAGELIST_ALIGN_LEFT
;
266 alignNew
= BUTTON_IMAGELIST_ALIGN_RIGHT
;
270 alignNew
= BUTTON_IMAGELIST_ALIGN_TOP
;
274 alignNew
= BUTTON_IMAGELIST_ALIGN_BOTTOM
;
278 if ( alignNew
!= m_data
.uAlign
)
280 m_data
.uAlign
= alignNew
;
286 void UpdateImageInfo()
288 if ( !::SendMessage(m_hwndBtn
, BCM_SETIMAGELIST
, 0, (LPARAM
)&m_data
) )
290 wxLogDebug("SendMessage(BCM_SETIMAGELIST) failed");
294 // we store image list separately to be able to use convenient wxImageList
295 // methods instead of working with raw HIMAGELIST
298 // store the rest of the data in BCM_SETIMAGELIST-friendly form
299 BUTTON_IMAGELIST m_data
;
301 // the button we're associated with
302 const HWND m_hwndBtn
;
305 wxDECLARE_NO_COPY_CLASS(wxXPButtonImageData
);
308 #endif // wxUSE_UXTHEME
310 } // anonymous namespace
312 // ----------------------------------------------------------------------------
314 // ----------------------------------------------------------------------------
316 #if wxUSE_EXTENDED_RTTI
318 WX_DEFINE_FLAGS( wxButtonStyle
)
320 wxBEGIN_FLAGS( wxButtonStyle
)
321 // new style border flags, we put them first to
322 // use them for streaming out
323 wxFLAGS_MEMBER(wxBORDER_SIMPLE
)
324 wxFLAGS_MEMBER(wxBORDER_SUNKEN
)
325 wxFLAGS_MEMBER(wxBORDER_DOUBLE
)
326 wxFLAGS_MEMBER(wxBORDER_RAISED
)
327 wxFLAGS_MEMBER(wxBORDER_STATIC
)
328 wxFLAGS_MEMBER(wxBORDER_NONE
)
330 // old style border flags
331 wxFLAGS_MEMBER(wxSIMPLE_BORDER
)
332 wxFLAGS_MEMBER(wxSUNKEN_BORDER
)
333 wxFLAGS_MEMBER(wxDOUBLE_BORDER
)
334 wxFLAGS_MEMBER(wxRAISED_BORDER
)
335 wxFLAGS_MEMBER(wxSTATIC_BORDER
)
336 wxFLAGS_MEMBER(wxBORDER
)
338 // standard window styles
339 wxFLAGS_MEMBER(wxTAB_TRAVERSAL
)
340 wxFLAGS_MEMBER(wxCLIP_CHILDREN
)
341 wxFLAGS_MEMBER(wxTRANSPARENT_WINDOW
)
342 wxFLAGS_MEMBER(wxWANTS_CHARS
)
343 wxFLAGS_MEMBER(wxFULL_REPAINT_ON_RESIZE
)
344 wxFLAGS_MEMBER(wxALWAYS_SHOW_SB
)
345 wxFLAGS_MEMBER(wxVSCROLL
)
346 wxFLAGS_MEMBER(wxHSCROLL
)
348 wxFLAGS_MEMBER(wxBU_LEFT
)
349 wxFLAGS_MEMBER(wxBU_RIGHT
)
350 wxFLAGS_MEMBER(wxBU_TOP
)
351 wxFLAGS_MEMBER(wxBU_BOTTOM
)
352 wxFLAGS_MEMBER(wxBU_EXACTFIT
)
353 wxEND_FLAGS( wxButtonStyle
)
355 IMPLEMENT_DYNAMIC_CLASS_XTI(wxButton
, wxControl
,"wx/button.h")
357 wxBEGIN_PROPERTIES_TABLE(wxButton
)
358 wxEVENT_PROPERTY( Click
, wxEVT_COMMAND_BUTTON_CLICKED
, wxCommandEvent
)
360 wxPROPERTY( Font
, wxFont
, SetFont
, GetFont
, EMPTY_MACROVALUE
, 0 /*flags*/ , wxT("Helpstring") , wxT("group"))
361 wxPROPERTY( Label
, wxString
, SetLabel
, GetLabel
, wxString(), 0 /*flags*/ , wxT("Helpstring") , wxT("group") )
363 wxPROPERTY_FLAGS( WindowStyle
, wxButtonStyle
, long , SetWindowStyleFlag
, GetWindowStyleFlag
, EMPTY_MACROVALUE
, 0 /*flags*/ , wxT("Helpstring") , wxT("group")) // style
365 wxEND_PROPERTIES_TABLE()
367 wxBEGIN_HANDLERS_TABLE(wxButton
)
368 wxEND_HANDLERS_TABLE()
370 wxCONSTRUCTOR_6( wxButton
, wxWindow
* , Parent
, wxWindowID
, Id
, wxString
, Label
, wxPoint
, Position
, wxSize
, Size
, long , WindowStyle
)
374 IMPLEMENT_DYNAMIC_CLASS(wxButton
, wxControl
)
377 // ============================================================================
379 // ============================================================================
381 // ----------------------------------------------------------------------------
382 // helper functions from wx/msw/private/button.h
383 // ----------------------------------------------------------------------------
385 void wxMSWButton::UpdateMultilineStyle(HWND hwnd
, const wxString
& label
)
387 // update BS_MULTILINE style depending on the new label (resetting it
388 // doesn't seem to do anything very useful but it shouldn't hurt and we do
389 // have to set it whenever the label becomes multi line as otherwise it
390 // wouldn't be shown correctly as we don't use BS_MULTILINE when creating
391 // the control unless it already has new lines in its label)
392 long styleOld
= ::GetWindowLong(hwnd
, GWL_STYLE
),
394 if ( label
.find(_T('\n')) != wxString::npos
)
395 styleNew
= styleOld
| BS_MULTILINE
;
397 styleNew
= styleOld
& ~BS_MULTILINE
;
399 if ( styleNew
!= styleOld
)
400 ::SetWindowLong(hwnd
, GWL_STYLE
, styleNew
);
403 wxSize
wxMSWButton::GetFittingSize(wxWindow
*win
, const wxSize
& sizeLabel
)
405 // FIXME: this is pure guesswork, need to retrieve the real button margins
406 wxSize sizeBtn
= sizeLabel
;
408 sizeBtn
.x
+= 3*win
->GetCharWidth();
409 sizeBtn
.y
= 11*EDIT_HEIGHT_FROM_CHAR_HEIGHT(sizeLabel
.y
)/10;
414 wxSize
wxMSWButton::ComputeBestSize(wxControl
*btn
)
419 dc
.GetMultiLineTextExtent(btn
->GetLabelText(), &sizeBtn
.x
, &sizeBtn
.y
);
421 sizeBtn
= GetFittingSize(btn
, sizeBtn
);
423 // all buttons have at least the standard size unless the user explicitly
424 // wants them to be of smaller size and used wxBU_EXACTFIT style when
425 // creating the button
426 if ( !btn
->HasFlag(wxBU_EXACTFIT
) )
428 wxSize sizeDef
= wxButton::GetDefaultSize();
429 if ( sizeBtn
.x
< sizeDef
.x
)
430 sizeBtn
.x
= sizeDef
.x
;
431 if ( sizeBtn
.y
< sizeDef
.y
)
432 sizeBtn
.y
= sizeDef
.y
;
435 btn
->CacheBestSize(sizeBtn
);
440 // ----------------------------------------------------------------------------
441 // creation/destruction
442 // ----------------------------------------------------------------------------
444 bool wxButton::Create(wxWindow
*parent
,
450 const wxValidator
& validator
,
451 const wxString
& name
)
454 if (label
.empty() && wxIsStockID(id
))
456 // On Windows, some buttons aren't supposed to have mnemonics
457 label
= wxGetStockLabel
460 id
== wxID_OK
|| id
== wxID_CANCEL
|| id
== wxID_CLOSE
462 : wxSTOCK_WITH_MNEMONIC
466 if ( !CreateControl(parent
, id
, pos
, size
, style
, validator
, name
) )
470 WXDWORD msStyle
= MSWGetStyle(style
, &exstyle
);
472 // if the label contains several lines we must explicitly tell the button
473 // about it or it wouldn't draw it correctly ("\n"s would just appear as
476 // NB: we do it here and not in MSWGetStyle() because we need the label
477 // value and the label is not set yet when MSWGetStyle() is called
478 msStyle
|= wxMSWButton::GetMultilineStyle(label
);
480 return MSWCreateControl(_T("BUTTON"), msStyle
, pos
, size
, label
, exstyle
);
483 wxButton::~wxButton()
485 wxTopLevelWindow
*tlw
= wxDynamicCast(wxGetTopLevelParent(this), wxTopLevelWindow
);
486 if ( tlw
&& tlw
->GetTmpDefaultItem() == this )
494 // ----------------------------------------------------------------------------
496 // ----------------------------------------------------------------------------
498 WXDWORD
wxButton::MSWGetStyle(long style
, WXDWORD
*exstyle
) const
500 // buttons never have an external border, they draw their own one
501 WXDWORD msStyle
= wxControl::MSWGetStyle
503 (style
& ~wxBORDER_MASK
) | wxBORDER_NONE
, exstyle
506 // we must use WS_CLIPSIBLINGS with the buttons or they would draw over
507 // each other in any resizeable dialog which has more than one button in
509 msStyle
|= WS_CLIPSIBLINGS
;
511 // don't use "else if" here: weird as it is, but you may combine wxBU_LEFT
512 // and wxBU_RIGHT to get BS_CENTER!
513 if ( style
& wxBU_LEFT
)
515 if ( style
& wxBU_RIGHT
)
517 if ( style
& wxBU_TOP
)
519 if ( style
& wxBU_BOTTOM
)
520 msStyle
|= BS_BOTTOM
;
523 if ( style
& wxNO_BORDER
)
525 #endif // __WXWINCE__
530 void wxButton::SetLabel(const wxString
& label
)
532 wxMSWButton::UpdateMultilineStyle(GetHwnd(), label
);
534 wxButtonBase::SetLabel(label
);
537 // ----------------------------------------------------------------------------
538 // size management including autosizing
539 // ----------------------------------------------------------------------------
541 wxSize
wxButton::DoGetBestSize() const
543 wxSize size
= wxMSWButton::ComputeBestSize(const_cast<wxButton
*>(this));
546 const wxSize sizeBmp
= m_imageData
->GetBitmap(State_Normal
).GetSize();
547 if ( m_imageData
->IsHorizontal() )
550 if ( sizeBmp
.y
> size
.y
)
553 else // bitmap on top/below the text
556 if ( sizeBmp
.x
> size
.x
)
560 size
+= 2*m_imageData
->GetBitmapMargins();
569 wxSize
wxButtonBase::GetDefaultSize()
571 static wxSize s_sizeBtn
;
573 if ( s_sizeBtn
.x
== 0 )
576 dc
.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT
));
578 // the size of a standard button in the dialog units is 50x14,
579 // translate this to pixels
580 // NB1: the multipliers come from the Windows convention
581 // NB2: the extra +1/+2 were needed to get the size be the same as the
582 // size of the buttons in the standard dialog - I don't know how
583 // this happens, but on my system this size is 75x23 in pixels and
584 // 23*8 isn't even divisible by 14... Would be nice to understand
585 // why these constants are needed though!
586 s_sizeBtn
.x
= (50 * (dc
.GetCharWidth() + 1))/4;
587 s_sizeBtn
.y
= ((14 * dc
.GetCharHeight()) + 2)/8;
593 // ----------------------------------------------------------------------------
594 // default button handling
595 // ----------------------------------------------------------------------------
598 "Everything you ever wanted to know about the default buttons" or "Why do we
599 have to do all this?"
601 In MSW the default button should be activated when the user presses Enter
602 and the current control doesn't process Enter itself somehow. This is
603 handled by ::DefWindowProc() (or maybe ::DefDialogProc()) using DM_SETDEFID
604 Another aspect of "defaultness" is that the default button has different
605 appearance: this is due to BS_DEFPUSHBUTTON style which is completely
606 separate from DM_SETDEFID stuff (!). Also note that BS_DEFPUSHBUTTON should
607 be unset if our parent window is not active so it should be unset whenever
608 we lose activation and set back when we regain it.
610 Final complication is that when a button is active, it should be the default
611 one, i.e. pressing Enter on a button always activates it and not another
614 We handle this by maintaining a permanent and a temporary default items in
615 wxControlContainer (both may be NULL). When a button becomes the current
616 control (i.e. gets focus) it sets itself as the temporary default which
617 ensures that it has the right appearance and that Enter will be redirected
618 to it. When the button loses focus, it unsets the temporary default and so
619 the default item will be the permanent default -- that is the default button
620 if any had been set or none otherwise, which is just what we want.
622 NB: all this is quite complicated by now and the worst is that normally
623 it shouldn't be necessary at all as for the normal Windows programs
624 DefWindowProc() and IsDialogMessage() take care of all this
625 automatically -- however in wxWidgets programs this doesn't work for
626 nested hierarchies (i.e. a notebook inside a notebook) for unknown
627 reason and so we have to reproduce all this code ourselves. It would be
628 very nice if we could avoid doing it.
631 // set this button as the (permanently) default one in its panel
632 wxWindow
*wxButton::SetDefault()
634 // set this one as the default button both for wxWidgets ...
635 wxWindow
*winOldDefault
= wxButtonBase::SetDefault();
638 SetDefaultStyle(wxDynamicCast(winOldDefault
, wxButton
), false);
639 SetDefaultStyle(this, true);
641 return winOldDefault
;
644 // return the top level parent window if it's not being deleted yet, otherwise
646 static wxTopLevelWindow
*GetTLWParentIfNotBeingDeleted(wxWindow
*win
)
650 // IsTopLevel() will return false for a wxTLW being deleted, so we also
651 // need the parent test for this case
652 wxWindow
* const parent
= win
->GetParent();
653 if ( !parent
|| win
->IsTopLevel() )
655 if ( win
->IsBeingDeleted() )
664 wxASSERT_MSG( win
, _T("button without top level parent?") );
666 wxTopLevelWindow
* const tlw
= wxDynamicCast(win
, wxTopLevelWindow
);
667 wxASSERT_MSG( tlw
, _T("logic error in GetTLWParentIfNotBeingDeleted()") );
672 // set this button as being currently default
673 void wxButton::SetTmpDefault()
675 wxTopLevelWindow
* const tlw
= GetTLWParentIfNotBeingDeleted(GetParent());
679 wxWindow
*winOldDefault
= tlw
->GetDefaultItem();
680 tlw
->SetTmpDefaultItem(this);
682 SetDefaultStyle(wxDynamicCast(winOldDefault
, wxButton
), false);
683 SetDefaultStyle(this, true);
686 // unset this button as currently default, it may still stay permanent default
687 void wxButton::UnsetTmpDefault()
689 wxTopLevelWindow
* const tlw
= GetTLWParentIfNotBeingDeleted(GetParent());
693 tlw
->SetTmpDefaultItem(NULL
);
695 wxWindow
*winOldDefault
= tlw
->GetDefaultItem();
697 SetDefaultStyle(this, false);
698 SetDefaultStyle(wxDynamicCast(winOldDefault
, wxButton
), true);
703 wxButton::SetDefaultStyle(wxButton
*btn
, bool on
)
705 // we may be called with NULL pointer -- simpler to do the check here than
706 // in the caller which does wxDynamicCast()
710 // first, let DefDlgProc() know about the new default button
713 // we shouldn't set BS_DEFPUSHBUTTON for any button if we don't have
714 // focus at all any more
715 if ( !wxTheApp
->IsActive() )
718 wxWindow
* const tlw
= wxGetTopLevelParent(btn
);
719 wxCHECK_RET( tlw
, _T("button without top level window?") );
721 ::SendMessage(GetHwndOf(tlw
), DM_SETDEFID
, btn
->GetId(), 0L);
723 // sending DM_SETDEFID also changes the button style to
724 // BS_DEFPUSHBUTTON so there is nothing more to do
727 // then also change the style as needed
728 long style
= ::GetWindowLong(GetHwndOf(btn
), GWL_STYLE
);
729 if ( !(style
& BS_DEFPUSHBUTTON
) == on
)
731 // don't do it with the owner drawn buttons because it will
732 // reset BS_OWNERDRAW style bit too (as BS_OWNERDRAW &
733 // BS_DEFPUSHBUTTON != 0)!
734 if ( (style
& BS_OWNERDRAW
) != BS_OWNERDRAW
)
736 ::SendMessage(GetHwndOf(btn
), BM_SETSTYLE
,
737 on
? style
| BS_DEFPUSHBUTTON
738 : style
& ~BS_DEFPUSHBUTTON
,
743 // redraw the button - it will notice itself that it's
744 // [not] the default one [any longer]
748 //else: already has correct style
751 // ----------------------------------------------------------------------------
753 // ----------------------------------------------------------------------------
755 bool wxButton::SendClickEvent()
757 wxCommandEvent
event(wxEVT_COMMAND_BUTTON_CLICKED
, GetId());
758 event
.SetEventObject(this);
760 return ProcessCommand(event
);
763 void wxButton::Command(wxCommandEvent
& event
)
765 ProcessCommand(event
);
768 // ----------------------------------------------------------------------------
769 // event/message handlers
770 // ----------------------------------------------------------------------------
772 bool wxButton::MSWCommand(WXUINT param
, WXWORD
WXUNUSED(id
))
774 bool processed
= false;
777 // NOTE: Apparently older versions (NT 4?) of the common controls send
778 // BN_DOUBLECLICKED but not a second BN_CLICKED for owner-drawn
779 // buttons, so in order to send two EVT_BUTTON events we should
780 // catch both types. Currently (Feb 2003) up-to-date versions of
781 // win98, win2k and winXP all send two BN_CLICKED messages for
782 // all button types, so we don't catch BN_DOUBLECLICKED anymore
783 // in order to not get 3 EVT_BUTTON events. If this is a problem
784 // then we need to figure out which version of the comctl32 changed
785 // this behaviour and test for it.
787 case 1: // message came from an accelerator
788 case BN_CLICKED
: // normal buttons send this
789 processed
= SendClickEvent();
796 WXLRESULT
wxButton::MSWWindowProc(WXUINT nMsg
, WXWPARAM wParam
, WXLPARAM lParam
)
798 // when we receive focus, we want to temporarily become the default button in
799 // our parent panel so that pressing "Enter" would activate us -- and when
800 // losing it we should restore the previous default button as well
801 if ( nMsg
== WM_SETFOCUS
)
805 // let the default processing take place too
807 else if ( nMsg
== WM_KILLFOCUS
)
811 else if ( nMsg
== WM_LBUTTONDBLCLK
)
813 // emulate a click event to force an owner-drawn button to change its
814 // appearance - without this, it won't do it
815 (void)wxControl::MSWWindowProc(WM_LBUTTONDOWN
, wParam
, lParam
);
817 // and continue with processing the message normally as well
820 else if ( nMsg
== WM_THEMECHANGED
)
822 // need to recalculate the best size here
823 // as the theme size might have changed
824 InvalidateBestSize();
826 else if ( wxUxThemeEngine::GetIfActive() )
828 // we need to Refresh() if mouse has entered or left window
829 // so we can update the hot tracking state
830 // must use m_mouseInWindow here instead of IsMouseInWindow()
831 // since we need to know the first time the mouse enters the window
832 // and IsMouseInWindow() would return true in this case
833 if ( ( nMsg
== WM_MOUSEMOVE
&& !m_mouseInWindow
) ||
834 nMsg
== WM_MOUSELEAVE
)
839 #endif // wxUSE_UXTHEME
841 // let the base class do all real processing
842 return wxControl::MSWWindowProc(nMsg
, wParam
, lParam
);
845 // ----------------------------------------------------------------------------
847 // ----------------------------------------------------------------------------
849 wxBitmap
wxButton::DoGetBitmap(State which
) const
851 return m_imageData
? m_imageData
->GetBitmap(which
) : wxBitmap();
854 void wxButton::DoSetBitmap(const wxBitmap
& bitmap
, State which
)
856 // allocate the image data when the first bitmap is set
860 if ( wxUxThemeEngine::GetIfActive() )
861 m_imageData
= new wxXPButtonImageData(this, bitmap
.GetSize());
863 #endif // wxUSE_UXTHEME
864 m_imageData
= new wxODButtonImageData
;
866 // if a bitmap was assigned to the bitmap, its best size must be
867 // changed to account for it
868 InvalidateBestSize();
871 m_imageData
->SetBitmap(bitmap
, which
);
874 void wxButton::DoSetBitmapMargins(wxCoord x
, wxCoord y
)
876 wxCHECK_RET( m_imageData
, "SetBitmap() must be called first" );
878 m_imageData
->SetBitmapMargins(x
, y
);
881 void wxButton::DoSetBitmapPosition(wxDirection dir
)
883 wxCHECK_RET( m_imageData
, "SetBitmap() must be called first" );
885 m_imageData
->SetBitmapPosition(dir
);
888 // ----------------------------------------------------------------------------
889 // owner-drawn buttons support
890 // ----------------------------------------------------------------------------
896 void DrawButtonText(HDC hdc
,
898 const wxString
& text
,
902 wxTextColoursChanger
changeFg(hdc
, col
, CLR_INVALID
);
903 wxBkModeChanger
changeBkMode(hdc
, wxBRUSHSTYLE_TRANSPARENT
);
905 // center text horizontally in any case
908 if ( text
.find(_T('\n')) != wxString::npos
)
910 // draw multiline label
912 // first we need to compute its bounding rect
914 ::CopyRect(&rc
, pRect
);
915 ::DrawText(hdc
, text
.wx_str(), text
.length(), &rc
,
916 DT_CENTER
| DT_CALCRECT
);
918 // now center this rect inside the entire button area
919 const LONG w
= rc
.right
- rc
.left
;
920 const LONG h
= rc
.bottom
- rc
.top
;
921 rc
.left
= (pRect
->right
- pRect
->left
)/2 - w
/2;
922 rc
.right
= rc
.left
+w
;
923 rc
.top
= (pRect
->bottom
- pRect
->top
)/2 - h
/2;
924 rc
.bottom
= rc
.top
+h
;
926 ::DrawText(hdc
, text
.wx_str(), text
.length(), &rc
, flags
);
928 else // single line label
930 // centre text vertically too (notice that we must have DT_SINGLELINE
931 // for DT_VCENTER to work)
932 ::DrawText(hdc
, text
.wx_str(), text
.length(), pRect
,
933 flags
| DT_SINGLELINE
| DT_VCENTER
);
937 void DrawRect(HDC hdc
, const RECT
& r
)
939 wxDrawLine(hdc
, r
.left
, r
.top
, r
.right
, r
.top
);
940 wxDrawLine(hdc
, r
.right
, r
.top
, r
.right
, r
.bottom
);
941 wxDrawLine(hdc
, r
.right
, r
.bottom
, r
.left
, r
.bottom
);
942 wxDrawLine(hdc
, r
.left
, r
.bottom
, r
.left
, r
.top
);
946 The button frame looks like this normally:
949 WHHHHHHHHHHHHHHHHGB W = white (HILIGHT)
950 WH GB H = light grey (LIGHT)
951 WH GB G = dark grey (SHADOW)
952 WH GB B = black (DKSHADOW)
957 When the button is selected, the button becomes like this (the total button
958 size doesn't change):
969 When the button is pushed (while selected) it is like:
980 void DrawButtonFrame(HDC hdc
, const RECT
& rectBtn
,
981 bool selected
, bool pushed
)
984 CopyRect(&r
, &rectBtn
);
986 AutoHPEN
hpenBlack(GetSysColor(COLOR_3DDKSHADOW
)),
987 hpenGrey(GetSysColor(COLOR_3DSHADOW
)),
988 hpenLightGr(GetSysColor(COLOR_3DLIGHT
)),
989 hpenWhite(GetSysColor(COLOR_3DHILIGHT
));
991 SelectInHDC
selectPen(hdc
, hpenBlack
);
1000 (void)SelectObject(hdc
, hpenGrey
);
1001 ::InflateRect(&r
, -1, -1);
1011 ::InflateRect(&r
, -1, -1);
1014 wxDrawLine(hdc
, r
.left
, r
.bottom
, r
.right
, r
.bottom
);
1015 wxDrawLine(hdc
, r
.right
, r
.bottom
, r
.right
, r
.top
- 1);
1017 (void)SelectObject(hdc
, hpenWhite
);
1018 wxDrawLine(hdc
, r
.left
, r
.bottom
- 1, r
.left
, r
.top
);
1019 wxDrawLine(hdc
, r
.left
, r
.top
, r
.right
, r
.top
);
1021 (void)SelectObject(hdc
, hpenLightGr
);
1022 wxDrawLine(hdc
, r
.left
+ 1, r
.bottom
- 2, r
.left
+ 1, r
.top
+ 1);
1023 wxDrawLine(hdc
, r
.left
+ 1, r
.top
+ 1, r
.right
- 1, r
.top
+ 1);
1025 (void)SelectObject(hdc
, hpenGrey
);
1026 wxDrawLine(hdc
, r
.left
+ 1, r
.bottom
- 1, r
.right
- 1, r
.bottom
- 1);
1027 wxDrawLine(hdc
, r
.right
- 1, r
.bottom
- 1, r
.right
- 1, r
.top
);
1032 void MSWDrawXPBackground(wxButton
*button
, WXDRAWITEMSTRUCT
*wxdis
)
1034 LPDRAWITEMSTRUCT lpDIS
= (LPDRAWITEMSTRUCT
)wxdis
;
1035 HDC hdc
= lpDIS
->hDC
;
1036 UINT state
= lpDIS
->itemState
;
1038 CopyRect(&rectBtn
, &lpDIS
->rcItem
);
1040 wxUxThemeHandle
theme(button
, L
"BUTTON");
1043 if ( state
& ODS_SELECTED
)
1045 iState
= PBS_PRESSED
;
1047 else if ( button
->HasCapture() || button
->IsMouseInWindow() )
1051 else if ( state
& ODS_FOCUS
)
1053 iState
= PBS_DEFAULTED
;
1055 else if ( state
& ODS_DISABLED
)
1057 iState
= PBS_DISABLED
;
1061 iState
= PBS_NORMAL
;
1064 // draw parent background if needed
1065 if ( wxUxThemeEngine::Get()->IsThemeBackgroundPartiallyTransparent(theme
,
1069 wxUxThemeEngine::Get()->DrawThemeParentBackground(GetHwndOf(button
), hdc
, &rectBtn
);
1073 wxUxThemeEngine::Get()->DrawThemeBackground(theme
, hdc
, BP_PUSHBUTTON
, iState
,
1076 // calculate content area margins
1078 wxUxThemeEngine::Get()->GetThemeMargins(theme
, hdc
, BP_PUSHBUTTON
, iState
,
1079 TMT_CONTENTMARGINS
, &rectBtn
, &margins
);
1081 ::CopyRect(&rectClient
, &rectBtn
);
1082 ::InflateRect(&rectClient
, -margins
.cxLeftWidth
, -margins
.cyTopHeight
);
1084 // if focused and !nofocus rect
1085 if ( (state
& ODS_FOCUS
) && !(state
& ODS_NOFOCUSRECT
) )
1087 DrawFocusRect(hdc
, &rectClient
);
1090 if ( button
->UseBgCol() )
1092 COLORREF colBg
= wxColourToRGB(button
->GetBackgroundColour());
1093 AutoHBRUSH
hbrushBackground(colBg
);
1095 // don't overwrite the focus rect
1096 ::InflateRect(&rectClient
, -1, -1);
1097 FillRect(hdc
, &rectClient
, hbrushBackground
);
1100 #endif // wxUSE_UXTHEME
1102 } // anonymous namespace
1104 // ----------------------------------------------------------------------------
1105 // owner drawn buttons support
1106 // ----------------------------------------------------------------------------
1108 void wxButton::MakeOwnerDrawn()
1110 long style
= GetWindowLong(GetHwnd(), GWL_STYLE
);
1111 if ( (style
& BS_OWNERDRAW
) != BS_OWNERDRAW
)
1114 style
|= BS_OWNERDRAW
;
1115 SetWindowLong(GetHwnd(), GWL_STYLE
, style
);
1119 bool wxButton::SetBackgroundColour(const wxColour
&colour
)
1121 if ( !wxControl::SetBackgroundColour(colour
) )
1134 bool wxButton::SetForegroundColour(const wxColour
&colour
)
1136 if ( !wxControl::SetForegroundColour(colour
) )
1149 bool wxButton::MSWOnDraw(WXDRAWITEMSTRUCT
*wxdis
)
1151 LPDRAWITEMSTRUCT lpDIS
= (LPDRAWITEMSTRUCT
)wxdis
;
1152 HDC hdc
= lpDIS
->hDC
;
1153 UINT state
= lpDIS
->itemState
;
1155 CopyRect(&rectBtn
, &lpDIS
->rcItem
);
1158 if ( wxUxThemeEngine::GetIfActive() )
1160 MSWDrawXPBackground(this, wxdis
);
1163 #endif // wxUSE_UXTHEME
1165 COLORREF colBg
= wxColourToRGB(GetBackgroundColour());
1167 // first, draw the background
1168 AutoHBRUSH
hbrushBackground(colBg
);
1169 FillRect(hdc
, &rectBtn
, hbrushBackground
);
1171 // draw the border for the current state
1172 bool selected
= (state
& ODS_SELECTED
) != 0;
1175 wxTopLevelWindow
*tlw
= wxDynamicCast(wxGetTopLevelParent(this), wxTopLevelWindow
);
1178 selected
= tlw
->GetDefaultItem() == this;
1181 bool pushed
= (SendMessage(GetHwnd(), BM_GETSTATE
, 0, 0) & BST_PUSHED
) != 0;
1183 DrawButtonFrame(hdc
, rectBtn
, selected
, pushed
);
1185 // if focused and !nofocus rect
1186 if ( (state
& ODS_FOCUS
) && !(state
& ODS_NOFOCUSRECT
) )
1189 CopyRect(&rectFocus
, &rectBtn
);
1191 // I don't know where does this constant come from, but this is how
1192 // Windows draws them
1193 InflateRect(&rectFocus
, -4, -4);
1195 DrawFocusRect(hdc
, &rectFocus
);
1200 // the label is shifted by 1 pixel to create "pushed" effect
1201 OffsetRect(&rectBtn
, 1, 1);
1205 COLORREF colFg
= state
& ODS_DISABLED
1206 ? ::GetSysColor(COLOR_GRAYTEXT
)
1207 : wxColourToRGB(GetForegroundColour());
1209 // notice that DT_HIDEPREFIX doesn't work on old (pre-Windows 2000) systems
1210 // but by happy coincidence ODS_NOACCEL is not used under them neither so
1211 // DT_HIDEPREFIX should never be used there
1212 DrawButtonText(hdc
, &rectBtn
, GetLabel(), colFg
,
1213 state
& ODS_NOACCEL
? DT_HIDEPREFIX
: 0);
1218 #endif // wxUSE_BUTTON