1 ///////////////////////////////////////////////////////////////////////////// 
   2 // Name:        src/msw/spinctrl.cpp 
   3 // Purpose:     wxSpinCtrl class implementation for Win32 
   4 // Author:      Vadim Zeitlin 
   8 // Copyright:   (c) 1999-2005 Vadim Zeitlin 
   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/spinctrl.h" 
  32     #include "wx/msw/wrapcctl.h" // include <commctrl.h> "properly" 
  34     #include "wx/textctrl.h" 
  37 #include "wx/msw/private.h" 
  40     #include "wx/tooltip.h" 
  41 #endif // wxUSE_TOOLTIPS 
  43 #include <limits.h>         // for INT_MIN 
  45 // ---------------------------------------------------------------------------- 
  47 // ---------------------------------------------------------------------------- 
  49 #if wxUSE_EXTENDED_RTTI 
  50 WX_DEFINE_FLAGS( wxSpinCtrlStyle 
) 
  52 wxBEGIN_FLAGS( wxSpinCtrlStyle 
) 
  53     // new style border flags, we put them first to 
  54     // use them for streaming out 
  55     wxFLAGS_MEMBER(wxBORDER_SIMPLE
) 
  56     wxFLAGS_MEMBER(wxBORDER_SUNKEN
) 
  57     wxFLAGS_MEMBER(wxBORDER_DOUBLE
) 
  58     wxFLAGS_MEMBER(wxBORDER_RAISED
) 
  59     wxFLAGS_MEMBER(wxBORDER_STATIC
) 
  60     wxFLAGS_MEMBER(wxBORDER_NONE
) 
  62     // old style border flags 
  63     wxFLAGS_MEMBER(wxSIMPLE_BORDER
) 
  64     wxFLAGS_MEMBER(wxSUNKEN_BORDER
) 
  65     wxFLAGS_MEMBER(wxDOUBLE_BORDER
) 
  66     wxFLAGS_MEMBER(wxRAISED_BORDER
) 
  67     wxFLAGS_MEMBER(wxSTATIC_BORDER
) 
  68     wxFLAGS_MEMBER(wxBORDER
) 
  70     // standard window styles 
  71     wxFLAGS_MEMBER(wxTAB_TRAVERSAL
) 
  72     wxFLAGS_MEMBER(wxCLIP_CHILDREN
) 
  73     wxFLAGS_MEMBER(wxTRANSPARENT_WINDOW
) 
  74     wxFLAGS_MEMBER(wxWANTS_CHARS
) 
  75     wxFLAGS_MEMBER(wxFULL_REPAINT_ON_RESIZE
) 
  76     wxFLAGS_MEMBER(wxALWAYS_SHOW_SB 
) 
  77     wxFLAGS_MEMBER(wxVSCROLL
) 
  78     wxFLAGS_MEMBER(wxHSCROLL
) 
  80     wxFLAGS_MEMBER(wxSP_HORIZONTAL
) 
  81     wxFLAGS_MEMBER(wxSP_VERTICAL
) 
  82     wxFLAGS_MEMBER(wxSP_ARROW_KEYS
) 
  83     wxFLAGS_MEMBER(wxSP_WRAP
) 
  85 wxEND_FLAGS( wxSpinCtrlStyle 
) 
  87 IMPLEMENT_DYNAMIC_CLASS_XTI(wxSpinCtrl
, wxControl
,"wx/spinbut.h") 
  89 wxBEGIN_PROPERTIES_TABLE(wxSpinCtrl
) 
  90     wxEVENT_RANGE_PROPERTY( Spin 
, wxEVT_SCROLL_TOP 
, wxEVT_SCROLL_ENDSCROLL 
, wxSpinEvent 
) 
  91     wxEVENT_PROPERTY( Updated 
, wxEVT_COMMAND_SPINCTRL_UPDATED 
, wxCommandEvent 
) 
  92     wxEVENT_PROPERTY( TextUpdated 
, wxEVT_COMMAND_TEXT_UPDATED 
, wxCommandEvent 
) 
  93     wxEVENT_PROPERTY( TextEnter 
, wxEVT_COMMAND_TEXT_ENTER 
, wxCommandEvent 
) 
  95     wxPROPERTY( ValueString 
, wxString 
, SetValue 
, GetValue 
, EMPTY_MACROVALUE 
, 0 /*flags*/ , wxT("Helpstring") , wxT("group")) ; 
  96     wxPROPERTY( Value 
, int , SetValue
, GetValue
, 0 , 0 /*flags*/ , wxT("Helpstring") , wxT("group")) 
  97     wxPROPERTY( Min 
, int , SetMin
, GetMin
, 0, 0 /*flags*/ , wxT("Helpstring") , wxT("group") ) 
  98     wxPROPERTY( Max 
, int , SetMax
, GetMax
, 0 , 0 /*flags*/ , wxT("Helpstring") , wxT("group")) 
  99     wxPROPERTY_FLAGS( WindowStyle 
, wxSpinCtrlStyle 
, long , SetWindowStyleFlag 
, GetWindowStyleFlag 
, EMPTY_MACROVALUE 
, 0 /*flags*/ , wxT("Helpstring") , wxT("group")) // style 
 102         style wxSP_ARROW_KEYS 
 104 wxEND_PROPERTIES_TABLE() 
 106 wxBEGIN_HANDLERS_TABLE(wxSpinCtrl
) 
 107 wxEND_HANDLERS_TABLE() 
 109 wxCONSTRUCTOR_6( wxSpinCtrl 
, wxWindow
* , Parent 
, wxWindowID 
, Id 
, wxString 
, ValueString 
, wxPoint 
, Position 
, wxSize 
, Size 
, long , WindowStyle 
) 
 111 IMPLEMENT_DYNAMIC_CLASS(wxSpinCtrl
, wxControl
) 
 115 BEGIN_EVENT_TABLE(wxSpinCtrl
, wxSpinButton
) 
 116     EVT_CHAR(wxSpinCtrl::OnChar
) 
 118     EVT_SET_FOCUS(wxSpinCtrl::OnSetFocus
) 
 119     EVT_KILL_FOCUS(wxSpinCtrl::OnKillFocus
) 
 120     EVT_SPIN(wxID_ANY
, wxSpinCtrl::OnSpinChange
) 
 123 #define GetBuddyHwnd()      (HWND)(m_hwndBuddy) 
 125 // ---------------------------------------------------------------------------- 
 127 // ---------------------------------------------------------------------------- 
 129 // the margin between the up-down control and its buddy (can be arbitrary, 
 130 // choose what you like - or may be decide during run-time depending on the 
 132 static const int MARGIN_BETWEEN 
= 1; 
 134 // ============================================================================ 
 136 // ============================================================================ 
 138 wxArraySpins 
wxSpinCtrl::ms_allSpins
; 
 140 // ---------------------------------------------------------------------------- 
 141 // wnd proc for the buddy text ctrl 
 142 // ---------------------------------------------------------------------------- 
 144 LRESULT APIENTRY _EXPORT 
wxBuddyTextWndProc(HWND hwnd
, 
 149     wxSpinCtrl 
*spin 
= (wxSpinCtrl 
*)wxGetWindowUserData(hwnd
); 
 151     // forward some messages (the key and focus ones only so far) to 
 156             // if the focus comes from the spin control itself, don't set it 
 157             // back to it -- we don't want to go into an infinite loop 
 158             if ( (WXHWND
)wParam 
== spin
->GetHWND() ) 
 167             spin
->MSWWindowProc(message
, wParam
, lParam
); 
 169             // The control may have been deleted at this point, so check. 
 170             if ( !::IsWindow(hwnd
) || wxGetWindowUserData(hwnd
) != spin 
) 
 175             // we want to get WXK_RETURN in order to generate the event for it 
 176             return DLGC_WANTCHARS
; 
 179     return ::CallWindowProc(CASTWNDPROC spin
->GetBuddyWndProc(), 
 180                             hwnd
, message
, wParam
, lParam
); 
 184 wxSpinCtrl 
*wxSpinCtrl::GetSpinForTextCtrl(WXHWND hwndBuddy
) 
 186     wxSpinCtrl 
*spin 
= (wxSpinCtrl 
*)wxGetWindowUserData((HWND
)hwndBuddy
); 
 188     int i 
= ms_allSpins
.Index(spin
); 
 190     if ( i 
== wxNOT_FOUND 
) 
 194     wxASSERT_MSG( spin
->m_hwndBuddy 
== hwndBuddy
, 
 195                   _T("wxSpinCtrl has incorrect buddy HWND!") ); 
 200 // process a WM_COMMAND generated by the buddy text control 
 201 bool wxSpinCtrl::ProcessTextCommand(WXWORD cmd
, WXWORD 
WXUNUSED(id
)) 
 203     if ( cmd 
== EN_CHANGE 
) 
 205         wxCommandEvent 
event(wxEVT_COMMAND_TEXT_UPDATED
, GetId()); 
 206         event
.SetEventObject(this); 
 207         wxString val 
= wxGetWindowText(m_hwndBuddy
); 
 208         event
.SetString(val
); 
 209         event
.SetInt(GetValue()); 
 210         return GetEventHandler()->ProcessEvent(event
); 
 217 void wxSpinCtrl::OnChar(wxKeyEvent
& event
) 
 219     switch ( event
.GetKeyCode() ) 
 223                 wxCommandEvent 
event(wxEVT_COMMAND_TEXT_ENTER
, m_windowId
); 
 224                 InitCommandEvent(event
); 
 225                 wxString val 
= wxGetWindowText(m_hwndBuddy
); 
 226                 event
.SetString(val
); 
 227                 event
.SetInt(GetValue()); 
 228                 if ( GetEventHandler()->ProcessEvent(event
) ) 
 234             // always produce navigation event - even if we process TAB 
 235             // ourselves the fact that we got here means that the user code 
 236             // decided to skip processing of this TAB - probably to let it 
 237             // do its default job. 
 239                 wxNavigationKeyEvent eventNav
; 
 240                 eventNav
.SetDirection(!event
.ShiftDown()); 
 241                 eventNav
.SetWindowChange(event
.ControlDown()); 
 242                 eventNav
.SetEventObject(this); 
 244                 if ( GetParent()->GetEventHandler()->ProcessEvent(eventNav
) ) 
 250     // no, we didn't process it 
 254 void wxSpinCtrl::OnKillFocus(wxFocusEvent
& event
) 
 256     // ensure that a correct value is shown by the control 
 261 void wxSpinCtrl::OnSetFocus(wxFocusEvent
& event
) 
 263     // when we get focus, give it to our buddy window as it needs it more than 
 265     ::SetFocus((HWND
)m_hwndBuddy
); 
 270 void wxSpinCtrl::NormalizeValue() 
 272     int value 
= GetValue(); 
 274     if (value 
!= m_oldValue
) 
 276         wxCommandEvent 
event( wxEVT_COMMAND_SPINCTRL_UPDATED
, GetId() ); 
 277         event
.SetEventObject( this ); 
 278         event
.SetInt( value 
); 
 279         GetEventHandler()->ProcessEvent( event 
); 
 284 // ---------------------------------------------------------------------------- 
 286 // ---------------------------------------------------------------------------- 
 288 bool wxSpinCtrl::Create(wxWindow 
*parent
, 
 290                         const wxString
& value
, 
 294                         int min
, int max
, int initial
, 
 295                         const wxString
& name
) 
 297     // before using DoGetBestSize(), have to set style to let the base class 
 298     // know whether this is a horizontal or vertical control (we're always 
 300     style 
|= wxSP_VERTICAL
; 
 302     if ( (style 
& wxBORDER_MASK
) == wxBORDER_DEFAULT 
) 
 304         style 
|= wxBORDER_SIMPLE
; 
 306         style 
|= wxBORDER_SUNKEN
; 
 309     SetWindowStyle(style
); 
 312     WXDWORD msStyle 
= MSWGetStyle(GetWindowStyle(), & exStyle
) ; 
 314     // calculate the sizes: the size given is the toal size for both controls 
 315     // and we need to fit them both in the given width (height is the same) 
 316     wxSize 
sizeText(size
), sizeBtn(size
); 
 317     sizeBtn
.x 
= wxSpinButton::DoGetBestSize().x
; 
 318     if ( sizeText
.x 
<= 0 ) 
 320         // DEFAULT_ITEM_WIDTH is the default width for the text control 
 321         sizeText
.x 
= DEFAULT_ITEM_WIDTH 
+ MARGIN_BETWEEN 
+ sizeBtn
.x
; 
 324     sizeText
.x 
-= sizeBtn
.x 
+ MARGIN_BETWEEN
; 
 325     if ( sizeText
.x 
<= 0 ) 
 327         wxLogDebug(_T("not enough space for wxSpinCtrl!")); 
 331     posBtn
.x 
+= sizeText
.x 
+ MARGIN_BETWEEN
; 
 333     // we must create the text control before the spin button for the purpose 
 334     // of the dialog navigation: if there is a static text just before the spin 
 335     // control, activating it by Alt-letter should give focus to the text 
 336     // control, not the spin and the dialog navigation code will give focus to 
 337     // the next control (at Windows level), not the one after it 
 339     // create the text window 
 341     m_hwndBuddy 
= (WXHWND
)::CreateWindowEx
 
 343                      exStyle
,                // sunken border 
 344                      _T("EDIT"),             // window class 
 345                      NULL
,                   // no window title 
 346                      msStyle
,                // style (will be shown later) 
 347                      pos
.x
, pos
.y
,           // position 
 348                      0, 0,                   // size (will be set later) 
 349                      GetHwndOf(parent
),      // parent 
 350                      (HMENU
)-1,              // control id 
 351                      wxGetInstance(),        // app instance 
 352                      NULL                    
// unused client data 
 357         wxLogLastError(wxT("CreateWindow(buddy text window)")); 
 363     // create the spin button 
 364     if ( !wxSpinButton::Create(parent
, id
, posBtn
, sizeBtn
, style
, name
) ) 
 372     m_oldValue 
= initial
; 
 374     // subclass the text ctrl to be able to intercept some events 
 375     wxSetWindowUserData(GetBuddyHwnd(), this); 
 376     m_wndProcBuddy 
= (WXFARPROC
)wxSetWindowProc(GetBuddyHwnd(), 
 379     // set up fonts and colours  (This is nomally done in MSWCreateControl) 
 382         SetFont(GetDefaultAttributes().font
); 
 384     // set the size of the text window - can do it only now, because we 
 385     // couldn't call DoGetBestSize() before as font wasn't set 
 386     if ( sizeText
.y 
<= 0 ) 
 389         wxGetCharSize(GetHWND(), &cx
, &cy
, GetFont()); 
 391         sizeText
.y 
= EDIT_HEIGHT_FROM_CHAR_HEIGHT(cy
); 
 396     (void)::ShowWindow(GetBuddyHwnd(), SW_SHOW
); 
 398     // associate the text window with the spin button 
 399     (void)::SendMessage(GetHwnd(), UDM_SETBUDDY
, (WPARAM
)m_hwndBuddy
, 0); 
 401     if ( !value
.empty() ) 
 406     // do it after finishing with m_hwndBuddy creation to avoid generating 
 407     // initial wxEVT_COMMAND_TEXT_UPDATED message 
 408     ms_allSpins
.Add(this); 
 413 wxSpinCtrl::~wxSpinCtrl() 
 415     ms_allSpins
.Remove(this); 
 417     // This removes spurious memory leak reporting 
 418     if (ms_allSpins
.GetCount() == 0) 
 421     // destroy the buddy window because this pointer which wxBuddyTextWndProc 
 422     // uses will not soon be valid any more 
 423     ::DestroyWindow(GetBuddyHwnd()); 
 426 // ---------------------------------------------------------------------------- 
 427 // wxTextCtrl-like methods 
 428 // ---------------------------------------------------------------------------- 
 430 void wxSpinCtrl::SetValue(const wxString
& text
) 
 432     if ( !::SetWindowText(GetBuddyHwnd(), text
.c_str()) ) 
 434         wxLogLastError(wxT("SetWindowText(buddy)")); 
 438 void  wxSpinCtrl::SetValue(int val
) 
 440     wxSpinButton::SetValue(val
); 
 442     // normally setting the value of the spin button is enough as it updates 
 443     // its buddy control automatically ... 
 444     if ( wxGetWindowText(m_hwndBuddy
).empty() ) 
 446         // ... but sometimes it doesn't, notably when the value is 0 and the 
 447         // text control is currently empty, the spin button seems to be happy 
 448         // to leave it like this, while we really want to always show the 
 449         // current value in the control, so do it manually 
 450         ::SetWindowText(GetBuddyHwnd(), wxString::Format(_T("%d"), val
)); 
 453     m_oldValue 
= GetValue(); 
 456 int wxSpinCtrl::GetValue() const 
 458     wxString val 
= wxGetWindowText(m_hwndBuddy
); 
 461     if ( (wxSscanf(val
, wxT("%ld"), &n
) != 1) ) 
 472 void wxSpinCtrl::SetSelection(long from
, long to
) 
 474     // if from and to are both -1, it means (in wxWidgets) that all text should 
 475     // be selected - translate into Windows convention 
 476     if ( (from 
== -1) && (to 
== -1) ) 
 481     ::SendMessage(GetBuddyHwnd(), EM_SETSEL
, (WPARAM
)from
, (LPARAM
)to
); 
 484 // ---------------------------------------------------------------------------- 
 485 // forward some methods to subcontrols 
 486 // ---------------------------------------------------------------------------- 
 488 bool wxSpinCtrl::SetFont(const wxFont
& font
) 
 490     if ( !wxWindowBase::SetFont(font
) ) 
 496     WXHANDLE hFont 
= GetFont().GetResourceHandle(); 
 497     (void)::SendMessage(GetBuddyHwnd(), WM_SETFONT
, (WPARAM
)hFont
, TRUE
); 
 502 bool wxSpinCtrl::Show(bool show
) 
 504     if ( !wxControl::Show(show
) ) 
 509     ::ShowWindow(GetBuddyHwnd(), show 
? SW_SHOW 
: SW_HIDE
); 
 514 bool wxSpinCtrl::Enable(bool enable
) 
 516     if ( !wxControl::Enable(enable
) ) 
 521     ::EnableWindow(GetBuddyHwnd(), enable
); 
 526 void wxSpinCtrl::SetFocus() 
 528     ::SetFocus(GetBuddyHwnd()); 
 533 void wxSpinCtrl::DoSetToolTip(wxToolTip 
*tip
) 
 535     wxSpinButton::DoSetToolTip(tip
); 
 538         tip
->Add(m_hwndBuddy
); 
 541 #endif // wxUSE_TOOLTIPS 
 543 // ---------------------------------------------------------------------------- 
 545 // ---------------------------------------------------------------------------- 
 547 void wxSpinCtrl::OnSpinChange(wxSpinEvent
& eventSpin
) 
 549     wxCommandEvent 
event(wxEVT_COMMAND_SPINCTRL_UPDATED
, GetId()); 
 550     event
.SetEventObject(this); 
 551     int value 
= eventSpin
.GetPosition(); 
 552     event
.SetInt( value 
); 
 554     if (value 
!= m_oldValue
) 
 555         (void)GetEventHandler()->ProcessEvent(event
); 
 557     if ( eventSpin
.GetSkipped() ) 
 565 // ---------------------------------------------------------------------------- 
 567 // ---------------------------------------------------------------------------- 
 569 wxSize 
wxSpinCtrl::DoGetBestSize() const 
 571     wxSize sizeBtn 
= wxSpinButton::DoGetBestSize(); 
 572     sizeBtn
.x 
+= DEFAULT_ITEM_WIDTH 
+ MARGIN_BETWEEN
; 
 575     wxGetCharSize(GetHWND(), NULL
, &y
, GetFont()); 
 576     y 
= EDIT_HEIGHT_FROM_CHAR_HEIGHT(y
); 
 578     // JACS: we should always use the height calculated 
 579     // from above, because otherwise we'll get a spin control 
 580     // that's too big. So never use the height calculated 
 581     // from wxSpinButton::DoGetBestSize(). 
 583     // if ( sizeBtn.y < y ) 
 585         // make the text tall enough 
 592 void wxSpinCtrl::DoMoveWindow(int x
, int y
, int width
, int height
) 
 594     int widthBtn 
= wxSpinButton::DoGetBestSize().x
; 
 595     int widthText 
= width 
- widthBtn 
- MARGIN_BETWEEN
; 
 596     if ( widthText 
<= 0 ) 
 598         wxLogDebug(_T("not enough space for wxSpinCtrl!")); 
 601     // 1) The buddy window 
 602     DoMoveSibling(m_hwndBuddy
, x
, y
, widthText
, height
); 
 604     // 2) The button window 
 605     x 
+= widthText 
+ MARGIN_BETWEEN
; 
 606     wxSpinButton::DoMoveWindow(x
, y
, widthBtn
, height
); 
 609 // get total size of the control 
 610 void wxSpinCtrl::DoGetSize(int *x
, int *y
) const 
 612     RECT spinrect
, textrect
, ctrlrect
; 
 613     GetWindowRect(GetHwnd(), &spinrect
); 
 614     GetWindowRect(GetBuddyHwnd(), &textrect
); 
 615     UnionRect(&ctrlrect
,&textrect
, &spinrect
); 
 618         *x 
= ctrlrect
.right 
- ctrlrect
.left
; 
 620         *y 
= ctrlrect
.bottom 
- ctrlrect
.top
; 
 623 void wxSpinCtrl::DoGetPosition(int *x
, int *y
) const 
 625     // hack: pretend that our HWND is the text control just for a moment 
 626     WXHWND hWnd 
= GetHWND(); 
 627     wxConstCast(this, wxSpinCtrl
)->m_hWnd 
= m_hwndBuddy
; 
 629     wxSpinButton::DoGetPosition(x
, y
); 
 631     wxConstCast(this, wxSpinCtrl
)->m_hWnd 
= hWnd
; 
 634 #endif // wxUSE_SPINCTRL