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"
48 #include "wx/msw/uxtheme.h"
50 // no need to include tmschema.h
52 #define BP_PUSHBUTTON 1
57 #define PBS_DISABLED 4
58 #define PBS_DEFAULTED 5
60 #define TMT_CONTENTMARGINS 3602
63 // provide the necessary declarations ourselves if they're missing from
65 #ifndef BCM_SETIMAGELIST
66 #define BCM_SETIMAGELIST 0x1602
67 #define BCM_SETTEXTMARGIN 0x1604
71 BUTTON_IMAGELIST_ALIGN_LEFT
,
72 BUTTON_IMAGELIST_ALIGN_RIGHT
,
73 BUTTON_IMAGELIST_ALIGN_TOP
,
74 BUTTON_IMAGELIST_ALIGN_BOTTOM
77 struct BUTTON_IMAGELIST
84 #endif // wxUSE_UXTHEME
86 #ifndef WM_THEMECHANGED
87 #define WM_THEMECHANGED 0x031A
91 #define ODS_NOACCEL 0x0100
94 #ifndef ODS_NOFOCUSRECT
95 #define ODS_NOFOCUSRECT 0x0200
99 #define DT_HIDEPREFIX 0x00100000
102 // ----------------------------------------------------------------------------
104 // ----------------------------------------------------------------------------
106 // we use different data classes for owner drawn buttons and for themed XP ones
108 class wxButtonImageData
111 wxButtonImageData() { }
113 virtual wxBitmap
GetBitmap(wxButton
::State which
) const = 0;
114 virtual void SetBitmap(const wxBitmap
& bitmap
, wxButton
::State which
) = 0;
116 virtual wxSize
GetBitmapMargins() const = 0;
117 virtual void SetBitmapMargins(wxCoord x
, wxCoord y
) = 0;
119 virtual bool IsHorizontal() const = 0;
120 virtual void SetBitmapPosition(wxDirection dir
) = 0;
123 wxDECLARE_NO_COPY_CLASS(wxButtonImageData
);
129 class wxODButtonImageData
: public wxButtonImageData
132 wxODButtonImageData() { m_dir
= wxLEFT
; }
134 virtual wxBitmap
GetBitmap(wxButton
::State which
) const
136 return m_bitmaps
[which
];
139 virtual void SetBitmap(const wxBitmap
& bitmap
, wxButton
::State which
)
141 m_bitmaps
[which
] = bitmap
;
144 virtual wxSize
GetBitmapMargins() const
149 virtual void SetBitmapMargins(wxCoord x
, wxCoord y
)
151 m_margin
= wxSize(x
, y
);
154 virtual bool IsHorizontal() const
156 return m_dir
== wxLEFT
|| m_dir
== wxRIGHT
;
159 virtual void SetBitmapPosition(wxDirection dir
)
165 // just store the values passed to us to be able to retrieve them later
166 // from the drawing code
167 wxBitmap m_bitmaps
[wxButton
::State_Max
];
171 wxDECLARE_NO_COPY_CLASS(wxODButtonImageData
);
176 class wxXPButtonImageData
: public wxButtonImageData
179 // we must be constructed with the size of our images as we need to create
181 wxXPButtonImageData(wxButton
*btn
, const wxSize
& size
)
182 : m_iml(size
.x
, size
.y
, true /* use mask */, wxButton
::State_Max
),
183 m_hwndBtn(GetHwndOf(btn
))
185 m_data
.himl
= GetHimagelistOf(&m_iml
);
187 // use default margins
189 m_data
.margin
.right
= btn
->GetCharWidth();
191 m_data
.margin
.bottom
= btn
->GetCharHeight() / 2;
193 // and default alignment
194 m_data
.uAlign
= BUTTON_IMAGELIST_ALIGN_LEFT
;
197 virtual wxBitmap
GetBitmap(wxButton
::State which
) const
199 return m_iml
.GetBitmap(which
);
202 virtual void SetBitmap(const wxBitmap
& bitmap
, wxButton
::State which
)
204 const int imagesToAdd
= which
- m_iml
.GetImageCount();
205 if ( imagesToAdd
>= 0 )
207 if ( imagesToAdd
> 0 )
209 const wxBitmap bmpNormal
= GetBitmap(wxButton
::State_Normal
);
210 for ( int n
= 0; n
< imagesToAdd
; n
++ )
211 m_iml
.Add(bmpNormal
);
216 else // we already have this bitmap
218 m_iml
.Replace(which
, bitmap
);
224 virtual wxSize
GetBitmapMargins() const
226 return wxSize(m_data
.margin
.left
, m_data
.margin
.top
);
229 virtual void SetBitmapMargins(wxCoord x
, wxCoord y
)
231 RECT
& margin
= m_data
.margin
;
237 if ( !::SendMessage(m_hwndBtn
, BCM_SETTEXTMARGIN
, 0, (LPARAM
)&margin
) )
239 wxLogDebug("SendMessage(BCM_SETTEXTMARGIN) failed");
243 virtual bool IsHorizontal() const
245 return m_data
.uAlign
== BUTTON_IMAGELIST_ALIGN_LEFT
||
246 m_data
.uAlign
== BUTTON_IMAGELIST_ALIGN_RIGHT
;
249 virtual void SetBitmapPosition(wxDirection dir
)
255 wxFAIL_MSG( "invalid direction" );
259 alignNew
= BUTTON_IMAGELIST_ALIGN_LEFT
;
263 alignNew
= BUTTON_IMAGELIST_ALIGN_RIGHT
;
267 alignNew
= BUTTON_IMAGELIST_ALIGN_TOP
;
271 alignNew
= BUTTON_IMAGELIST_ALIGN_BOTTOM
;
275 if ( alignNew
!= m_data
.uAlign
)
277 m_data
.uAlign
= alignNew
;
283 void UpdateImageInfo()
285 if ( !::SendMessage(m_hwndBtn
, BCM_SETIMAGELIST
, 0, (LPARAM
)&m_data
) )
287 wxLogDebug("SendMessage(BCM_SETIMAGELIST) failed");
291 // we store image list separately to be able to use convenient wxImageList
292 // methods instead of working with raw HIMAGELIST
295 // store the rest of the data in BCM_SETIMAGELIST-friendly form
296 BUTTON_IMAGELIST m_data
;
298 // the button we're associated with
299 const HWND m_hwndBtn
;
302 wxDECLARE_NO_COPY_CLASS(wxXPButtonImageData
);
305 #endif // wxUSE_UXTHEME
307 } // anonymous namespace
309 // ----------------------------------------------------------------------------
311 // ----------------------------------------------------------------------------
313 #if wxUSE_EXTENDED_RTTI
315 WX_DEFINE_FLAGS( wxButtonStyle
)
317 wxBEGIN_FLAGS( wxButtonStyle
)
318 // new style border flags, we put them first to
319 // use them for streaming out
320 wxFLAGS_MEMBER(wxBORDER_SIMPLE
)
321 wxFLAGS_MEMBER(wxBORDER_SUNKEN
)
322 wxFLAGS_MEMBER(wxBORDER_DOUBLE
)
323 wxFLAGS_MEMBER(wxBORDER_RAISED
)
324 wxFLAGS_MEMBER(wxBORDER_STATIC
)
325 wxFLAGS_MEMBER(wxBORDER_NONE
)
327 // old style border flags
328 wxFLAGS_MEMBER(wxSIMPLE_BORDER
)
329 wxFLAGS_MEMBER(wxSUNKEN_BORDER
)
330 wxFLAGS_MEMBER(wxDOUBLE_BORDER
)
331 wxFLAGS_MEMBER(wxRAISED_BORDER
)
332 wxFLAGS_MEMBER(wxSTATIC_BORDER
)
333 wxFLAGS_MEMBER(wxBORDER
)
335 // standard window styles
336 wxFLAGS_MEMBER(wxTAB_TRAVERSAL
)
337 wxFLAGS_MEMBER(wxCLIP_CHILDREN
)
338 wxFLAGS_MEMBER(wxTRANSPARENT_WINDOW
)
339 wxFLAGS_MEMBER(wxWANTS_CHARS
)
340 wxFLAGS_MEMBER(wxFULL_REPAINT_ON_RESIZE
)
341 wxFLAGS_MEMBER(wxALWAYS_SHOW_SB
)
342 wxFLAGS_MEMBER(wxVSCROLL
)
343 wxFLAGS_MEMBER(wxHSCROLL
)
345 wxFLAGS_MEMBER(wxBU_LEFT
)
346 wxFLAGS_MEMBER(wxBU_RIGHT
)
347 wxFLAGS_MEMBER(wxBU_TOP
)
348 wxFLAGS_MEMBER(wxBU_BOTTOM
)
349 wxFLAGS_MEMBER(wxBU_EXACTFIT
)
350 wxEND_FLAGS( wxButtonStyle
)
352 IMPLEMENT_DYNAMIC_CLASS_XTI(wxButton
, wxControl
,"wx/button.h")
354 wxBEGIN_PROPERTIES_TABLE(wxButton
)
355 wxEVENT_PROPERTY( Click
, wxEVT_COMMAND_BUTTON_CLICKED
, wxCommandEvent
)
357 wxPROPERTY( Font
, wxFont
, SetFont
, GetFont
, EMPTY_MACROVALUE
, 0 /*flags*/ , wxT("Helpstring") , wxT("group"))
358 wxPROPERTY( Label
, wxString
, SetLabel
, GetLabel
, wxString(), 0 /*flags*/ , wxT("Helpstring") , wxT("group") )
360 wxPROPERTY_FLAGS( WindowStyle
, wxButtonStyle
, long , SetWindowStyleFlag
, GetWindowStyleFlag
, EMPTY_MACROVALUE
, 0 /*flags*/ , wxT("Helpstring") , wxT("group")) // style
362 wxEND_PROPERTIES_TABLE()
364 wxBEGIN_HANDLERS_TABLE(wxButton
)
365 wxEND_HANDLERS_TABLE()
367 wxCONSTRUCTOR_6( wxButton
, wxWindow
* , Parent
, wxWindowID
, Id
, wxString
, Label
, wxPoint
, Position
, wxSize
, Size
, long , WindowStyle
)
371 IMPLEMENT_DYNAMIC_CLASS(wxButton
, wxControl
)
374 // ============================================================================
376 // ============================================================================
378 // ----------------------------------------------------------------------------
379 // helper functions from wx/msw/private/button.h
380 // ----------------------------------------------------------------------------
382 void wxMSWButton
::UpdateMultilineStyle(HWND hwnd
, const wxString
& label
)
384 // update BS_MULTILINE style depending on the new label (resetting it
385 // doesn't seem to do anything very useful but it shouldn't hurt and we do
386 // have to set it whenever the label becomes multi line as otherwise it
387 // wouldn't be shown correctly as we don't use BS_MULTILINE when creating
388 // the control unless it already has new lines in its label)
389 long styleOld
= ::GetWindowLong(hwnd
, GWL_STYLE
),
391 if ( label
.find(_T('\n')) != wxString
::npos
)
392 styleNew
= styleOld
| BS_MULTILINE
;
394 styleNew
= styleOld
& ~BS_MULTILINE
;
396 if ( styleNew
!= styleOld
)
397 ::SetWindowLong(hwnd
, GWL_STYLE
, styleNew
);
400 wxSize wxMSWButton
::GetFittingSize(wxWindow
*win
, const wxSize
& sizeLabel
)
402 // FIXME: this is pure guesswork, need to retrieve the real button margins
403 wxSize sizeBtn
= sizeLabel
;
405 sizeBtn
.x
+= 3*win
->GetCharWidth();
406 sizeBtn
.y
= 11*EDIT_HEIGHT_FROM_CHAR_HEIGHT(sizeLabel
.y
)/10;
411 wxSize wxMSWButton
::ComputeBestSize(wxControl
*btn
)
416 dc
.GetMultiLineTextExtent(btn
->GetLabelText(), &sizeBtn
.x
, &sizeBtn
.y
);
418 sizeBtn
= GetFittingSize(btn
, sizeBtn
);
420 // all buttons have at least the standard size unless the user explicitly
421 // wants them to be of smaller size and used wxBU_EXACTFIT style when
422 // creating the button
423 if ( !btn
->HasFlag(wxBU_EXACTFIT
) )
425 wxSize sizeDef
= wxButton
::GetDefaultSize();
426 if ( sizeBtn
.x
< sizeDef
.x
)
427 sizeBtn
.x
= sizeDef
.x
;
428 if ( sizeBtn
.y
< sizeDef
.y
)
429 sizeBtn
.y
= sizeDef
.y
;
432 btn
->CacheBestSize(sizeBtn
);
437 // ----------------------------------------------------------------------------
438 // creation/destruction
439 // ----------------------------------------------------------------------------
441 bool wxButton
::Create(wxWindow
*parent
,
447 const wxValidator
& validator
,
448 const wxString
& name
)
451 if (label
.empty() && wxIsStockID(id
))
453 // On Windows, some buttons aren't supposed to have mnemonics
454 label
= wxGetStockLabel
457 id
== wxID_OK
|| id
== wxID_CANCEL
|| id
== wxID_CLOSE
459 : wxSTOCK_WITH_MNEMONIC
463 if ( !CreateControl(parent
, id
, pos
, size
, style
, validator
, name
) )
467 WXDWORD msStyle
= MSWGetStyle(style
, &exstyle
);
469 // if the label contains several lines we must explicitly tell the button
470 // about it or it wouldn't draw it correctly ("\n"s would just appear as
473 // NB: we do it here and not in MSWGetStyle() because we need the label
474 // value and the label is not set yet when MSWGetStyle() is called
475 msStyle
|= wxMSWButton
::GetMultilineStyle(label
);
477 return MSWCreateControl(_T("BUTTON"), msStyle
, pos
, size
, label
, exstyle
);
480 wxButton
::~wxButton()
482 wxTopLevelWindow
*tlw
= wxDynamicCast(wxGetTopLevelParent(this), wxTopLevelWindow
);
483 if ( tlw
&& tlw
->GetTmpDefaultItem() == this )
491 // ----------------------------------------------------------------------------
493 // ----------------------------------------------------------------------------
495 WXDWORD wxButton
::MSWGetStyle(long style
, WXDWORD
*exstyle
) const
497 // buttons never have an external border, they draw their own one
498 WXDWORD msStyle
= wxControl
::MSWGetStyle
500 (style
& ~wxBORDER_MASK
) | wxBORDER_NONE
, exstyle
503 // we must use WS_CLIPSIBLINGS with the buttons or they would draw over
504 // each other in any resizeable dialog which has more than one button in
506 msStyle
|= WS_CLIPSIBLINGS
;
508 // don't use "else if" here: weird as it is, but you may combine wxBU_LEFT
509 // and wxBU_RIGHT to get BS_CENTER!
510 if ( style
& wxBU_LEFT
)
512 if ( style
& wxBU_RIGHT
)
514 if ( style
& wxBU_TOP
)
516 if ( style
& wxBU_BOTTOM
)
517 msStyle
|= BS_BOTTOM
;
520 if ( style
& wxNO_BORDER
)
522 #endif // __WXWINCE__
527 void wxButton
::SetLabel(const wxString
& label
)
529 wxMSWButton
::UpdateMultilineStyle(GetHwnd(), label
);
531 wxButtonBase
::SetLabel(label
);
534 // ----------------------------------------------------------------------------
535 // size management including autosizing
536 // ----------------------------------------------------------------------------
538 wxSize wxButton
::DoGetBestSize() const
540 wxSize size
= wxMSWButton
::ComputeBestSize(const_cast<wxButton
*>(this));
543 const wxSize sizeBmp
= m_imageData
->GetBitmap(State_Normal
).GetSize();
544 if ( m_imageData
->IsHorizontal() )
547 if ( sizeBmp
.y
> size
.y
)
550 else // bitmap on top/below the text
553 if ( sizeBmp
.x
> size
.x
)
557 size
+= 2*m_imageData
->GetBitmapMargins();
566 wxSize wxButtonBase
::GetDefaultSize()
568 static wxSize s_sizeBtn
;
570 if ( s_sizeBtn
.x
== 0 )
573 dc
.SetFont(wxSystemSettings
::GetFont(wxSYS_DEFAULT_GUI_FONT
));
575 // the size of a standard button in the dialog units is 50x14,
576 // translate this to pixels
577 // NB1: the multipliers come from the Windows convention
578 // NB2: the extra +1/+2 were needed to get the size be the same as the
579 // size of the buttons in the standard dialog - I don't know how
580 // this happens, but on my system this size is 75x23 in pixels and
581 // 23*8 isn't even divisible by 14... Would be nice to understand
582 // why these constants are needed though!
583 s_sizeBtn
.x
= (50 * (dc
.GetCharWidth() + 1))/4;
584 s_sizeBtn
.y
= ((14 * dc
.GetCharHeight()) + 2)/8;
590 // ----------------------------------------------------------------------------
591 // default button handling
592 // ----------------------------------------------------------------------------
595 "Everything you ever wanted to know about the default buttons" or "Why do we
596 have to do all this?"
598 In MSW the default button should be activated when the user presses Enter
599 and the current control doesn't process Enter itself somehow. This is
600 handled by ::DefWindowProc() (or maybe ::DefDialogProc()) using DM_SETDEFID
601 Another aspect of "defaultness" is that the default button has different
602 appearance: this is due to BS_DEFPUSHBUTTON style which is completely
603 separate from DM_SETDEFID stuff (!). Also note that BS_DEFPUSHBUTTON should
604 be unset if our parent window is not active so it should be unset whenever
605 we lose activation and set back when we regain it.
607 Final complication is that when a button is active, it should be the default
608 one, i.e. pressing Enter on a button always activates it and not another
611 We handle this by maintaining a permanent and a temporary default items in
612 wxControlContainer (both may be NULL). When a button becomes the current
613 control (i.e. gets focus) it sets itself as the temporary default which
614 ensures that it has the right appearance and that Enter will be redirected
615 to it. When the button loses focus, it unsets the temporary default and so
616 the default item will be the permanent default -- that is the default button
617 if any had been set or none otherwise, which is just what we want.
619 NB: all this is quite complicated by now and the worst is that normally
620 it shouldn't be necessary at all as for the normal Windows programs
621 DefWindowProc() and IsDialogMessage() take care of all this
622 automatically -- however in wxWidgets programs this doesn't work for
623 nested hierarchies (i.e. a notebook inside a notebook) for unknown
624 reason and so we have to reproduce all this code ourselves. It would be
625 very nice if we could avoid doing it.
628 // set this button as the (permanently) default one in its panel
629 wxWindow
*wxButton
::SetDefault()
631 // set this one as the default button both for wxWidgets ...
632 wxWindow
*winOldDefault
= wxButtonBase
::SetDefault();
635 SetDefaultStyle(wxDynamicCast(winOldDefault
, wxButton
), false);
636 SetDefaultStyle(this, true);
638 return winOldDefault
;
641 // return the top level parent window if it's not being deleted yet, otherwise
643 static wxTopLevelWindow
*GetTLWParentIfNotBeingDeleted(wxWindow
*win
)
647 // IsTopLevel() will return false for a wxTLW being deleted, so we also
648 // need the parent test for this case
649 wxWindow
* const parent
= win
->GetParent();
650 if ( !parent
|| win
->IsTopLevel() )
652 if ( win
->IsBeingDeleted() )
661 wxASSERT_MSG( win
, _T("button without top level parent?") );
663 wxTopLevelWindow
* const tlw
= wxDynamicCast(win
, wxTopLevelWindow
);
664 wxASSERT_MSG( tlw
, _T("logic error in GetTLWParentIfNotBeingDeleted()") );
669 // set this button as being currently default
670 void wxButton
::SetTmpDefault()
672 wxTopLevelWindow
* const tlw
= GetTLWParentIfNotBeingDeleted(GetParent());
676 wxWindow
*winOldDefault
= tlw
->GetDefaultItem();
677 tlw
->SetTmpDefaultItem(this);
679 SetDefaultStyle(wxDynamicCast(winOldDefault
, wxButton
), false);
680 SetDefaultStyle(this, true);
683 // unset this button as currently default, it may still stay permanent default
684 void wxButton
::UnsetTmpDefault()
686 wxTopLevelWindow
* const tlw
= GetTLWParentIfNotBeingDeleted(GetParent());
690 tlw
->SetTmpDefaultItem(NULL
);
692 wxWindow
*winOldDefault
= tlw
->GetDefaultItem();
694 SetDefaultStyle(this, false);
695 SetDefaultStyle(wxDynamicCast(winOldDefault
, wxButton
), true);
700 wxButton
::SetDefaultStyle(wxButton
*btn
, bool on
)
702 // we may be called with NULL pointer -- simpler to do the check here than
703 // in the caller which does wxDynamicCast()
707 // first, let DefDlgProc() know about the new default button
710 // we shouldn't set BS_DEFPUSHBUTTON for any button if we don't have
711 // focus at all any more
712 if ( !wxTheApp
->IsActive() )
715 wxWindow
* const tlw
= wxGetTopLevelParent(btn
);
716 wxCHECK_RET( tlw
, _T("button without top level window?") );
718 ::SendMessage(GetHwndOf(tlw
), DM_SETDEFID
, btn
->GetId(), 0L);
720 // sending DM_SETDEFID also changes the button style to
721 // BS_DEFPUSHBUTTON so there is nothing more to do
724 // then also change the style as needed
725 long style
= ::GetWindowLong(GetHwndOf(btn
), GWL_STYLE
);
726 if ( !(style
& BS_DEFPUSHBUTTON
) == on
)
728 // don't do it with the owner drawn buttons because it will
729 // reset BS_OWNERDRAW style bit too (as BS_OWNERDRAW &
730 // BS_DEFPUSHBUTTON != 0)!
731 if ( (style
& BS_OWNERDRAW
) != BS_OWNERDRAW
)
733 ::SendMessage(GetHwndOf(btn
), BM_SETSTYLE
,
734 on ? style
| BS_DEFPUSHBUTTON
735 : style
& ~BS_DEFPUSHBUTTON
,
740 // redraw the button - it will notice itself that it's
741 // [not] the default one [any longer]
745 //else: already has correct style
748 // ----------------------------------------------------------------------------
750 // ----------------------------------------------------------------------------
752 bool wxButton
::SendClickEvent()
754 wxCommandEvent
event(wxEVT_COMMAND_BUTTON_CLICKED
, GetId());
755 event
.SetEventObject(this);
757 return ProcessCommand(event
);
760 void wxButton
::Command(wxCommandEvent
& event
)
762 ProcessCommand(event
);
765 // ----------------------------------------------------------------------------
766 // event/message handlers
767 // ----------------------------------------------------------------------------
769 bool wxButton
::MSWCommand(WXUINT param
, WXWORD
WXUNUSED(id
))
771 bool processed
= false;
774 // NOTE: Apparently older versions (NT 4?) of the common controls send
775 // BN_DOUBLECLICKED but not a second BN_CLICKED for owner-drawn
776 // buttons, so in order to send two EVT_BUTTON events we should
777 // catch both types. Currently (Feb 2003) up-to-date versions of
778 // win98, win2k and winXP all send two BN_CLICKED messages for
779 // all button types, so we don't catch BN_DOUBLECLICKED anymore
780 // in order to not get 3 EVT_BUTTON events. If this is a problem
781 // then we need to figure out which version of the comctl32 changed
782 // this behaviour and test for it.
784 case 1: // message came from an accelerator
785 case BN_CLICKED
: // normal buttons send this
786 processed
= SendClickEvent();
793 WXLRESULT wxButton
::MSWWindowProc(WXUINT nMsg
, WXWPARAM wParam
, WXLPARAM lParam
)
795 // when we receive focus, we want to temporarily become the default button in
796 // our parent panel so that pressing "Enter" would activate us -- and when
797 // losing it we should restore the previous default button as well
798 if ( nMsg
== WM_SETFOCUS
)
802 // let the default processing take place too
804 else if ( nMsg
== WM_KILLFOCUS
)
808 else if ( nMsg
== WM_LBUTTONDBLCLK
)
810 // emulate a click event to force an owner-drawn button to change its
811 // appearance - without this, it won't do it
812 (void)wxControl
::MSWWindowProc(WM_LBUTTONDOWN
, wParam
, lParam
);
814 // and continue with processing the message normally as well
817 else if ( nMsg
== WM_THEMECHANGED
)
819 // need to recalculate the best size here
820 // as the theme size might have changed
821 InvalidateBestSize();
823 else if ( wxUxThemeEngine
::GetIfActive() )
825 // we need to Refresh() if mouse has entered or left window
826 // so we can update the hot tracking state
827 // must use m_mouseInWindow here instead of IsMouseInWindow()
828 // since we need to know the first time the mouse enters the window
829 // and IsMouseInWindow() would return true in this case
830 if ( ( nMsg
== WM_MOUSEMOVE
&& !m_mouseInWindow
) ||
831 nMsg
== WM_MOUSELEAVE
)
836 #endif // wxUSE_UXTHEME
838 // let the base class do all real processing
839 return wxControl
::MSWWindowProc(nMsg
, wParam
, lParam
);
842 // ----------------------------------------------------------------------------
844 // ----------------------------------------------------------------------------
846 wxBitmap wxButton
::DoGetBitmap(State which
) const
848 return m_imageData ? m_imageData
->GetBitmap(which
) : wxBitmap();
851 void wxButton
::DoSetBitmap(const wxBitmap
& bitmap
, State which
)
853 // allocate the image data when the first bitmap is set
857 if ( wxUxThemeEngine
::GetIfActive() )
858 m_imageData
= new wxXPButtonImageData(this, bitmap
.GetSize());
860 #endif // wxUSE_UXTHEME
861 m_imageData
= new wxODButtonImageData
;
863 // if a bitmap was assigned to the bitmap, its best size must be
864 // changed to account for it
865 InvalidateBestSize();
868 m_imageData
->SetBitmap(bitmap
, which
);
871 void wxButton
::DoSetBitmapMargins(wxCoord x
, wxCoord y
)
873 wxCHECK_RET( m_imageData
, "SetBitmap() must be called first" );
875 m_imageData
->SetBitmapMargins(x
, y
);
878 void wxButton
::DoSetBitmapPosition(wxDirection dir
)
880 wxCHECK_RET( m_imageData
, "SetBitmap() must be called first" );
882 m_imageData
->SetBitmapPosition(dir
);
885 // ----------------------------------------------------------------------------
886 // owner-drawn buttons support
887 // ----------------------------------------------------------------------------
891 static void DrawButtonText(HDC hdc
,
893 const wxString
& text
,
897 COLORREF colOld
= SetTextColor(hdc
, col
);
898 int modeOld
= SetBkMode(hdc
, TRANSPARENT
);
900 // center text horizontally in any case
903 if ( text
.find(_T('\n')) != wxString
::npos
)
905 // draw multiline label
907 // first we need to compute its bounding rect
909 ::CopyRect(&rc
, pRect
);
910 ::DrawText(hdc
, text
.wx_str(), text
.length(), &rc
,
911 DT_CENTER
| DT_CALCRECT
);
913 // now center this rect inside the entire button area
914 const LONG w
= rc
.right
- rc
.left
;
915 const LONG h
= rc
.bottom
- rc
.top
;
916 rc
.left
= (pRect
->right
- pRect
->left
)/2 - w
/2;
917 rc
.right
= rc
.left
+w
;
918 rc
.top
= (pRect
->bottom
- pRect
->top
)/2 - h
/2;
919 rc
.bottom
= rc
.top
+h
;
921 ::DrawText(hdc
, text
.wx_str(), text
.length(), &rc
, flags
);
923 else // single line label
925 // centre text vertically too (notice that we must have DT_SINGLELINE
926 // for DT_VCENTER to work)
927 ::DrawText(hdc
, text
.wx_str(), text
.length(), pRect
,
928 flags
| DT_SINGLELINE
| DT_VCENTER
);
931 SetBkMode(hdc
, modeOld
);
932 SetTextColor(hdc
, colOld
);
935 static void DrawRect(HDC hdc
, const RECT
& r
)
937 wxDrawLine(hdc
, r
.left
, r
.top
, r
.right
, r
.top
);
938 wxDrawLine(hdc
, r
.right
, r
.top
, r
.right
, r
.bottom
);
939 wxDrawLine(hdc
, r
.right
, r
.bottom
, r
.left
, r
.bottom
);
940 wxDrawLine(hdc
, r
.left
, r
.bottom
, r
.left
, r
.top
);
943 void wxButton
::MakeOwnerDrawn()
945 long style
= GetWindowLong(GetHwnd(), GWL_STYLE
);
946 if ( (style
& BS_OWNERDRAW
) != BS_OWNERDRAW
)
949 style
|= BS_OWNERDRAW
;
950 SetWindowLong(GetHwnd(), GWL_STYLE
, style
);
954 bool wxButton
::SetBackgroundColour(const wxColour
&colour
)
956 if ( !wxControl
::SetBackgroundColour(colour
) )
969 bool wxButton
::SetForegroundColour(const wxColour
&colour
)
971 if ( !wxControl
::SetForegroundColour(colour
) )
985 The button frame looks like this normally:
988 WHHHHHHHHHHHHHHHHGB W = white (HILIGHT)
989 WH GB H = light grey (LIGHT)
990 WH GB G = dark grey (SHADOW)
991 WH GB B = black (DKSHADOW)
996 When the button is selected, the button becomes like this (the total button
997 size doesn't change):
1008 When the button is pushed (while selected) it is like:
1020 static void DrawButtonFrame(HDC hdc
, const RECT
& rectBtn
,
1021 bool selected
, bool pushed
)
1024 CopyRect(&r
, &rectBtn
);
1026 HPEN hpenBlack
= CreatePen(PS_SOLID
, 1, GetSysColor(COLOR_3DDKSHADOW
)),
1027 hpenGrey
= CreatePen(PS_SOLID
, 1, GetSysColor(COLOR_3DSHADOW
)),
1028 hpenLightGr
= CreatePen(PS_SOLID
, 1, GetSysColor(COLOR_3DLIGHT
)),
1029 hpenWhite
= CreatePen(PS_SOLID
, 1, GetSysColor(COLOR_3DHILIGHT
));
1031 HPEN hpenOld
= (HPEN
)SelectObject(hdc
, hpenBlack
);
1040 (void)SelectObject(hdc
, hpenGrey
);
1041 ::InflateRect(&r
, -1, -1);
1051 ::InflateRect(&r
, -1, -1);
1054 wxDrawLine(hdc
, r
.left
, r
.bottom
, r
.right
, r
.bottom
);
1055 wxDrawLine(hdc
, r
.right
, r
.bottom
, r
.right
, r
.top
- 1);
1057 (void)SelectObject(hdc
, hpenWhite
);
1058 wxDrawLine(hdc
, r
.left
, r
.bottom
- 1, r
.left
, r
.top
);
1059 wxDrawLine(hdc
, r
.left
, r
.top
, r
.right
, r
.top
);
1061 (void)SelectObject(hdc
, hpenLightGr
);
1062 wxDrawLine(hdc
, r
.left
+ 1, r
.bottom
- 2, r
.left
+ 1, r
.top
+ 1);
1063 wxDrawLine(hdc
, r
.left
+ 1, r
.top
+ 1, r
.right
- 1, r
.top
+ 1);
1065 (void)SelectObject(hdc
, hpenGrey
);
1066 wxDrawLine(hdc
, r
.left
+ 1, r
.bottom
- 1, r
.right
- 1, r
.bottom
- 1);
1067 wxDrawLine(hdc
, r
.right
- 1, r
.bottom
- 1, r
.right
- 1, r
.top
);
1070 (void)SelectObject(hdc
, hpenOld
);
1071 DeleteObject(hpenWhite
);
1072 DeleteObject(hpenLightGr
);
1073 DeleteObject(hpenGrey
);
1074 DeleteObject(hpenBlack
);
1079 void MSWDrawXPBackground(wxButton
*button
, WXDRAWITEMSTRUCT
*wxdis
)
1081 LPDRAWITEMSTRUCT lpDIS
= (LPDRAWITEMSTRUCT
)wxdis
;
1082 HDC hdc
= lpDIS
->hDC
;
1083 UINT state
= lpDIS
->itemState
;
1085 CopyRect(&rectBtn
, &lpDIS
->rcItem
);
1087 wxUxThemeHandle
theme(button
, L
"BUTTON");
1090 if ( state
& ODS_SELECTED
)
1092 iState
= PBS_PRESSED
;
1094 else if ( button
->HasCapture() || button
->IsMouseInWindow() )
1098 else if ( state
& ODS_FOCUS
)
1100 iState
= PBS_DEFAULTED
;
1102 else if ( state
& ODS_DISABLED
)
1104 iState
= PBS_DISABLED
;
1108 iState
= PBS_NORMAL
;
1111 // draw parent background if needed
1112 if ( wxUxThemeEngine
::Get()->IsThemeBackgroundPartiallyTransparent(theme
,
1116 wxUxThemeEngine
::Get()->DrawThemeParentBackground(GetHwndOf(button
), hdc
, &rectBtn
);
1120 wxUxThemeEngine
::Get()->DrawThemeBackground(theme
, hdc
, BP_PUSHBUTTON
, iState
,
1123 // calculate content area margins
1125 wxUxThemeEngine
::Get()->GetThemeMargins(theme
, hdc
, BP_PUSHBUTTON
, iState
,
1126 TMT_CONTENTMARGINS
, &rectBtn
, &margins
);
1128 ::CopyRect(&rectClient
, &rectBtn
);
1129 ::InflateRect(&rectClient
, -margins
.cxLeftWidth
, -margins
.cyTopHeight
);
1131 // if focused and !nofocus rect
1132 if ( (state
& ODS_FOCUS
) && !(state
& ODS_NOFOCUSRECT
) )
1134 DrawFocusRect(hdc
, &rectClient
);
1137 if ( button
->UseBgCol() )
1139 COLORREF colBg
= wxColourToRGB(button
->GetBackgroundColour());
1140 HBRUSH hbrushBackground
= ::CreateSolidBrush(colBg
);
1142 // don't overwrite the focus rect
1143 ::InflateRect(&rectClient
, -1, -1);
1144 FillRect(hdc
, &rectClient
, hbrushBackground
);
1145 ::DeleteObject(hbrushBackground
);
1148 #endif // wxUSE_UXTHEME
1150 bool wxButton
::MSWOnDraw(WXDRAWITEMSTRUCT
*wxdis
)
1152 LPDRAWITEMSTRUCT lpDIS
= (LPDRAWITEMSTRUCT
)wxdis
;
1153 HDC hdc
= lpDIS
->hDC
;
1154 UINT state
= lpDIS
->itemState
;
1156 CopyRect(&rectBtn
, &lpDIS
->rcItem
);
1159 if ( wxUxThemeEngine
::GetIfActive() )
1161 MSWDrawXPBackground(this, wxdis
);
1164 #endif // wxUSE_UXTHEME
1166 COLORREF colBg
= wxColourToRGB(GetBackgroundColour());
1168 // first, draw the background
1169 HBRUSH hbrushBackground
= ::CreateSolidBrush(colBg
);
1170 FillRect(hdc
, &rectBtn
, hbrushBackground
);
1171 ::DeleteObject(hbrushBackground
);
1173 // draw the border for the current state
1174 bool selected
= (state
& ODS_SELECTED
) != 0;
1177 wxTopLevelWindow
*tlw
= wxDynamicCast(wxGetTopLevelParent(this), wxTopLevelWindow
);
1180 selected
= tlw
->GetDefaultItem() == this;
1183 bool pushed
= (SendMessage(GetHwnd(), BM_GETSTATE
, 0, 0) & BST_PUSHED
) != 0;
1185 DrawButtonFrame(hdc
, rectBtn
, selected
, pushed
);
1187 // if focused and !nofocus rect
1188 if ( (state
& ODS_FOCUS
) && !(state
& ODS_NOFOCUSRECT
) )
1191 CopyRect(&rectFocus
, &rectBtn
);
1193 // I don't know where does this constant come from, but this is how
1194 // Windows draws them
1195 InflateRect(&rectFocus
, -4, -4);
1197 DrawFocusRect(hdc
, &rectFocus
);
1202 // the label is shifted by 1 pixel to create "pushed" effect
1203 OffsetRect(&rectBtn
, 1, 1);
1207 COLORREF colFg
= state
& ODS_DISABLED
1208 ?
::GetSysColor(COLOR_GRAYTEXT
)
1209 : wxColourToRGB(GetForegroundColour());
1211 // notice that DT_HIDEPREFIX doesn't work on old (pre-Windows 2000) systems
1212 // but by happy coincidence ODS_NOACCEL is not used under them neither so
1213 // DT_HIDEPREFIX should never be used there
1214 DrawButtonText(hdc
, &rectBtn
, GetLabel(), colFg
,
1215 state
& ODS_NOACCEL ? DT_HIDEPREFIX
: 0);
1220 #endif // wxUSE_BUTTON