1 ///////////////////////////////////////////////////////////////////////////// 
   2 // Name:        msw/spinctrl.cpp 
   3 // Purpose:     wxSpinCtrl class implementation for Win32 
   4 // Author:      Vadim Zeitlin 
   8 // Copyright:   (c) Vadim Zeitlin 
   9 // Licence:     wxWindows licence 
  10 ///////////////////////////////////////////////////////////////////////////// 
  12 // ============================================================================ 
  14 // ============================================================================ 
  16 #if defined(__GNUG__) && !defined(NO_GCC_PRAGMA) 
  17     #pragma implementation "spinctrlbase.h" 
  18     #pragma implementation "spinctrl.h" 
  21 // ---------------------------------------------------------------------------- 
  23 // ---------------------------------------------------------------------------- 
  25 // for compilers that support precompilation, includes "wx.h". 
  26 #include "wx/wxprec.h" 
  38 #if defined(__WIN95__) 
  40 #include "wx/spinctrl.h" 
  41 #include "wx/msw/private.h" 
  43 #if defined(__WIN95__) && !(defined(__GNUWIN32_OLD__) && !defined(__CYGWIN10__)) 
  47 #include <limits.h>         // for INT_MIN 
  49 // ---------------------------------------------------------------------------- 
  51 // ---------------------------------------------------------------------------- 
  53 #if wxUSE_EXTENDED_RTTI 
  54 WX_DEFINE_FLAGS( wxSpinCtrlStyle 
) 
  56 wxBEGIN_FLAGS( wxSpinCtrlStyle 
) 
  57     // new style border flags, we put them first to 
  58     // use them for streaming out 
  59     wxFLAGS_MEMBER(wxBORDER_SIMPLE
) 
  60     wxFLAGS_MEMBER(wxBORDER_SUNKEN
) 
  61     wxFLAGS_MEMBER(wxBORDER_DOUBLE
) 
  62     wxFLAGS_MEMBER(wxBORDER_RAISED
) 
  63     wxFLAGS_MEMBER(wxBORDER_STATIC
) 
  64     wxFLAGS_MEMBER(wxBORDER_NONE
) 
  66     // old style border flags 
  67     wxFLAGS_MEMBER(wxSIMPLE_BORDER
) 
  68     wxFLAGS_MEMBER(wxSUNKEN_BORDER
) 
  69     wxFLAGS_MEMBER(wxDOUBLE_BORDER
) 
  70     wxFLAGS_MEMBER(wxRAISED_BORDER
) 
  71     wxFLAGS_MEMBER(wxSTATIC_BORDER
) 
  72     wxFLAGS_MEMBER(wxBORDER
) 
  74     // standard window styles 
  75     wxFLAGS_MEMBER(wxTAB_TRAVERSAL
) 
  76     wxFLAGS_MEMBER(wxCLIP_CHILDREN
) 
  77     wxFLAGS_MEMBER(wxTRANSPARENT_WINDOW
) 
  78     wxFLAGS_MEMBER(wxWANTS_CHARS
) 
  79     wxFLAGS_MEMBER(wxFULL_REPAINT_ON_RESIZE
) 
  80     wxFLAGS_MEMBER(wxALWAYS_SHOW_SB 
) 
  81     wxFLAGS_MEMBER(wxVSCROLL
) 
  82     wxFLAGS_MEMBER(wxHSCROLL
) 
  84     wxFLAGS_MEMBER(wxSP_HORIZONTAL
) 
  85     wxFLAGS_MEMBER(wxSP_VERTICAL
) 
  86     wxFLAGS_MEMBER(wxSP_ARROW_KEYS
) 
  87     wxFLAGS_MEMBER(wxSP_WRAP
) 
  89 wxEND_FLAGS( wxSpinCtrlStyle 
) 
  91 IMPLEMENT_DYNAMIC_CLASS_XTI(wxSpinCtrl
, wxControl
,"wx/spinbut.h") 
  93 wxBEGIN_PROPERTIES_TABLE(wxSpinCtrl
) 
  94     wxEVENT_RANGE_PROPERTY( Spin 
, wxEVT_SCROLL_TOP 
, wxEVT_SCROLL_ENDSCROLL 
, wxSpinEvent 
) 
  95     wxEVENT_PROPERTY( Updated 
, wxEVT_COMMAND_SPINCTRL_UPDATED 
, wxCommandEvent 
) 
  96     wxEVENT_PROPERTY( TextUpdated 
, wxEVT_COMMAND_TEXT_UPDATED 
, wxCommandEvent 
)  
  97     wxEVENT_PROPERTY( TextEnter 
, wxEVT_COMMAND_TEXT_ENTER 
, wxCommandEvent 
) 
  99     wxPROPERTY( ValueString 
, wxString 
, SetValue 
, GetValue 
, , 0 /*flags*/ , wxT("Helpstring") , wxT("group")) ; 
 100         wxPROPERTY( Value 
, int , SetValue
, GetValue
, 0 , 0 /*flags*/ , wxT("Helpstring") , wxT("group")) 
 101         wxPROPERTY( Min 
, int , SetMin
, GetMin
, 0, 0 /*flags*/ , wxT("Helpstring") , wxT("group") ) 
 102         wxPROPERTY( Max 
, int , SetMax
, GetMax
, 0 , 0 /*flags*/ , wxT("Helpstring") , wxT("group")) 
 103     wxPROPERTY_FLAGS( WindowStyle 
, wxSpinCtrlStyle 
, long , SetWindowStyleFlag 
, GetWindowStyleFlag 
, , 0 /*flags*/ , wxT("Helpstring") , wxT("group")) // style 
 106                 style wxSP_ARROW_KEYS 
 108 wxEND_PROPERTIES_TABLE() 
 110 wxBEGIN_HANDLERS_TABLE(wxSpinCtrl
) 
 111 wxEND_HANDLERS_TABLE() 
 113 wxCONSTRUCTOR_6( wxSpinCtrl 
, wxWindow
* , Parent 
, wxWindowID 
, Id 
, wxString 
, ValueString 
, wxPoint 
, Position 
, wxSize 
, Size 
, long , WindowStyle 
)  
 115 IMPLEMENT_DYNAMIC_CLASS(wxSpinCtrl
, wxControl
) 
 118 BEGIN_EVENT_TABLE(wxSpinCtrl
, wxSpinButton
) 
 119     EVT_CHAR(wxSpinCtrl::OnChar
) 
 121     EVT_SET_FOCUS(wxSpinCtrl::OnSetFocus
) 
 123     EVT_SPIN(-1, wxSpinCtrl::OnSpinChange
) 
 126 #define GetBuddyHwnd()      (HWND)(m_hwndBuddy) 
 128 // ---------------------------------------------------------------------------- 
 130 // ---------------------------------------------------------------------------- 
 132 // the margin between the up-down control and its buddy (can be arbitrary, 
 133 // choose what you like - or may be decide during run-time depending on the 
 135 static const int MARGIN_BETWEEN 
= 1; 
 137 // ============================================================================ 
 139 // ============================================================================ 
 141 wxArraySpins 
wxSpinCtrl::ms_allSpins
; 
 143 // ---------------------------------------------------------------------------- 
 144 // wnd proc for the buddy text ctrl 
 145 // ---------------------------------------------------------------------------- 
 147 LRESULT APIENTRY _EXPORT 
wxBuddyTextWndProc(HWND hwnd
, 
 152     wxSpinCtrl 
*spin 
= (wxSpinCtrl 
*)wxGetWindowUserData(hwnd
); 
 154     // forward some messages (the key and focus ones only so far) to 
 159             // if the focus comes from the spin control itself, don't set it 
 160             // back to it -- we don't want to go into an infinite loop 
 161             if ( (WXHWND
)wParam 
== spin
->GetHWND() ) 
 170             spin
->MSWWindowProc(message
, wParam
, lParam
); 
 172             // The control may have been deleted at this point, so check. 
 173             if ( !::IsWindow(hwnd
) || wxGetWindowUserData(hwnd
) != spin 
) 
 178             // we want to get WXK_RETURN in order to generate the event for it 
 179             return DLGC_WANTCHARS
; 
 182     return ::CallWindowProc(CASTWNDPROC spin
->GetBuddyWndProc(), 
 183                             hwnd
, message
, wParam
, lParam
); 
 187 wxSpinCtrl 
*wxSpinCtrl::GetSpinForTextCtrl(WXHWND hwndBuddy
) 
 189     wxSpinCtrl 
*spin 
= (wxSpinCtrl 
*)wxGetWindowUserData((HWND
)hwndBuddy
); 
 191     int i 
= ms_allSpins
.Index(spin
); 
 193     if ( i 
== wxNOT_FOUND 
) 
 197     wxASSERT_MSG( spin
->m_hwndBuddy 
== hwndBuddy
, 
 198                   _T("wxSpinCtrl has incorrect buddy HWND!") ); 
 203 // process a WM_COMMAND generated by the buddy text control 
 204 bool wxSpinCtrl::ProcessTextCommand(WXWORD cmd
, WXWORD 
WXUNUSED(id
)) 
 210             wxCommandEvent 
event(wxEVT_COMMAND_TEXT_UPDATED
, GetId()); 
 211             event
.SetEventObject(this); 
 212             wxString val 
= wxGetWindowText(m_hwndBuddy
); 
 213             event
.SetString(val
); 
 214             event
.SetInt(GetValue()); 
 215             return GetEventHandler()->ProcessEvent(event
); 
 220             wxFocusEvent 
event(cmd 
== EN_KILLFOCUS 
? wxEVT_KILL_FOCUS
 
 223             event
.SetEventObject( this ); 
 224             return GetEventHandler()->ProcessEvent(event
); 
 234 void wxSpinCtrl::OnChar(wxKeyEvent
& event
) 
 236     switch ( event
.GetKeyCode() ) 
 240                 wxCommandEvent 
event(wxEVT_COMMAND_TEXT_ENTER
, m_windowId
); 
 241                 InitCommandEvent(event
); 
 242                 wxString val 
= wxGetWindowText(m_hwndBuddy
); 
 243                 event
.SetString(val
); 
 244                 event
.SetInt(GetValue()); 
 245                 if ( GetEventHandler()->ProcessEvent(event
) ) 
 251             // always produce navigation event - even if we process TAB 
 252             // ourselves the fact that we got here means that the user code 
 253             // decided to skip processing of this TAB - probably to let it 
 254             // do its default job. 
 256                 wxNavigationKeyEvent eventNav
; 
 257                 eventNav
.SetDirection(!event
.ShiftDown()); 
 258                 eventNav
.SetWindowChange(event
.ControlDown()); 
 259                 eventNav
.SetEventObject(this); 
 261                 if ( GetParent()->GetEventHandler()->ProcessEvent(eventNav
) ) 
 267     // no, we didn't process it 
 271 void wxSpinCtrl::OnSetFocus(wxFocusEvent
& event
) 
 273     // when we get focus, give it to our buddy window as it needs it more than 
 275     ::SetFocus((HWND
)m_hwndBuddy
); 
 280 // ---------------------------------------------------------------------------- 
 282 // ---------------------------------------------------------------------------- 
 284 bool wxSpinCtrl::Create(wxWindow 
*parent
, 
 286                         const wxString
& value
, 
 290                         int min
, int max
, int initial
, 
 291                         const wxString
& name
) 
 293     // before using DoGetBestSize(), have to set style to let the base class 
 294     // know whether this is a horizontal or vertical control (we're always 
 296     style 
|= wxSP_VERTICAL
; 
 298     if ( (style 
& wxBORDER_MASK
) == wxBORDER_DEFAULT 
) 
 299         style 
|= wxBORDER_SUNKEN
; 
 301     SetWindowStyle(style
); 
 304     WXDWORD msStyle 
= MSWGetStyle(GetWindowStyle(), & exStyle
) ; 
 306     // calculate the sizes: the size given is the toal size for both controls 
 307     // and we need to fit them both in the given width (height is the same) 
 308     wxSize 
sizeText(size
), sizeBtn(size
); 
 309     sizeBtn
.x 
= wxSpinButton::DoGetBestSize().x
; 
 310     if ( sizeText
.x 
<= 0 ) 
 312         // DEFAULT_ITEM_WIDTH is the default width for the text control 
 313         sizeText
.x 
= DEFAULT_ITEM_WIDTH 
+ MARGIN_BETWEEN 
+ sizeBtn
.x
; 
 316     sizeText
.x 
-= sizeBtn
.x 
+ MARGIN_BETWEEN
; 
 317     if ( sizeText
.x 
<= 0 ) 
 319         wxLogDebug(_T("not enough space for wxSpinCtrl!")); 
 323     posBtn
.x 
+= sizeText
.x 
+ MARGIN_BETWEEN
; 
 325     // we must create the text control before the spin button for the purpose 
 326     // of the dialog navigation: if there is a static text just before the spin 
 327     // control, activating it by Alt-letter should give focus to the text 
 328     // control, not the spin and the dialog navigation code will give focus to 
 329     // the next control (at Windows level), not the one after it 
 331     // create the text window 
 333     m_hwndBuddy 
= (WXHWND
)::CreateWindowEx
 
 335                      exStyle
,                // sunken border 
 336                      _T("EDIT"),             // window class 
 337                      NULL
,                   // no window title 
 338                      msStyle
,                // style (will be shown later) 
 339                      pos
.x
, pos
.y
,           // position 
 340                      0, 0,                   // size (will be set later) 
 341                      GetHwndOf(parent
),      // parent 
 342                      (HMENU
)-1,              // control id 
 343                      wxGetInstance(),        // app instance 
 344                      NULL                    
// unused client data 
 349         wxLogLastError(wxT("CreateWindow(buddy text window)")); 
 355     // create the spin button 
 356     if ( !wxSpinButton::Create(parent
, id
, posBtn
, sizeBtn
, style
, name
) ) 
 364     // subclass the text ctrl to be able to intercept some events 
 365     wxSetWindowUserData(GetBuddyHwnd(), this); 
 366     m_wndProcBuddy 
= (WXFARPROC
)wxSetWindowProc(GetBuddyHwnd(), 
 369     // should have the same font as the other controls 
 370     SetFont(GetParent()->GetFont()); 
 372     // set the size of the text window - can do it only now, because we 
 373     // couldn't call DoGetBestSize() before as font wasn't set 
 374     if ( sizeText
.y 
<= 0 ) 
 377         wxGetCharSize(GetHWND(), &cx
, &cy
, &GetFont()); 
 379         sizeText
.y 
= EDIT_HEIGHT_FROM_CHAR_HEIGHT(cy
); 
 382     DoMoveWindow(pos
.x
, pos
.y
, 
 383                  sizeText
.x 
+ sizeBtn
.x 
+ MARGIN_BETWEEN
, sizeText
.y
); 
 385     (void)::ShowWindow(GetBuddyHwnd(), SW_SHOW
); 
 387     // associate the text window with the spin button 
 388     (void)::SendMessage(GetHwnd(), UDM_SETBUDDY
, (WPARAM
)m_hwndBuddy
, 0); 
 390     if ( !value
.IsEmpty() ) 
 395     // do it after finishing with m_hwndBuddy creation to avoid generating 
 396     // initial wxEVT_COMMAND_TEXT_UPDATED message 
 397     ms_allSpins
.Add(this); 
 402 wxSpinCtrl::~wxSpinCtrl() 
 404     ms_allSpins
.Remove(this); 
 406     // This removes spurious memory leak reporting 
 407     if (ms_allSpins
.GetCount() == 0) 
 410     // destroy the buddy window because this pointer which wxBuddyTextWndProc 
 411     // uses will not soon be valid any more 
 412     ::DestroyWindow(GetBuddyHwnd()); 
 415 // ---------------------------------------------------------------------------- 
 416 // wxTextCtrl-like methods 
 417 // ---------------------------------------------------------------------------- 
 419 void wxSpinCtrl::SetValue(const wxString
& text
) 
 421     if ( !::SetWindowText(GetBuddyHwnd(), text
.c_str()) ) 
 423         wxLogLastError(wxT("SetWindowText(buddy)")); 
 427 int wxSpinCtrl::GetValue() const 
 429     wxString val 
= wxGetWindowText(m_hwndBuddy
); 
 432     if ( (wxSscanf(val
, wxT("%lu"), &n
) != 1) ) 
 438 void wxSpinCtrl::SetSelection(long from
, long to
) 
 440     // if from and to are both -1, it means (in wxWindows) that all text should 
 441     // be selected - translate into Windows convention 
 442     if ( (from 
== -1) && (to 
== -1) ) 
 447     ::SendMessage((HWND
)m_hwndBuddy
, EM_SETSEL
, (WPARAM
)from
, (LPARAM
)to
); 
 450 // ---------------------------------------------------------------------------- 
 451 // forward some methods to subcontrols 
 452 // ---------------------------------------------------------------------------- 
 454 bool wxSpinCtrl::SetFont(const wxFont
& font
) 
 456     if ( !wxWindowBase::SetFont(font
) ) 
 462     WXHANDLE hFont 
= GetFont().GetResourceHandle(); 
 463     (void)::SendMessage(GetBuddyHwnd(), WM_SETFONT
, (WPARAM
)hFont
, TRUE
); 
 468 bool wxSpinCtrl::Show(bool show
) 
 470     if ( !wxControl::Show(show
) ) 
 475     ::ShowWindow(GetBuddyHwnd(), show 
? SW_SHOW 
: SW_HIDE
); 
 480 bool wxSpinCtrl::Enable(bool enable
) 
 482     if ( !wxControl::Enable(enable
) ) 
 487     ::EnableWindow(GetBuddyHwnd(), enable
); 
 492 void wxSpinCtrl::SetFocus() 
 494     ::SetFocus(GetBuddyHwnd()); 
 497 // ---------------------------------------------------------------------------- 
 499 // ---------------------------------------------------------------------------- 
 501 void wxSpinCtrl::OnSpinChange(wxSpinEvent
& eventSpin
) 
 503     wxCommandEvent 
event(wxEVT_COMMAND_SPINCTRL_UPDATED
, GetId()); 
 504     event
.SetEventObject(this); 
 505     event
.SetInt(eventSpin
.GetPosition()); 
 507     (void)GetEventHandler()->ProcessEvent(event
); 
 509     if ( eventSpin
.GetSkipped() ) 
 515 // ---------------------------------------------------------------------------- 
 517 // ---------------------------------------------------------------------------- 
 519 wxSize 
wxSpinCtrl::DoGetBestSize() const 
 521     wxSize sizeBtn 
= wxSpinButton::DoGetBestSize(); 
 522     sizeBtn
.x 
+= DEFAULT_ITEM_WIDTH 
+ MARGIN_BETWEEN
; 
 525     wxGetCharSize(GetHWND(), NULL
, &y
, &GetFont()); 
 526     y 
= EDIT_HEIGHT_FROM_CHAR_HEIGHT(y
); 
 528     // JACS: we should always use the height calculated 
 529     // from above, because otherwise we'll get a spin control 
 530     // that's too big. So never use the height calculated 
 531     // from wxSpinButton::DoGetBestSize(). 
 533     // if ( sizeBtn.y < y ) 
 535         // make the text tall enough 
 542 void wxSpinCtrl::DoMoveWindow(int x
, int y
, int width
, int height
) 
 544     int widthBtn 
= wxSpinButton::DoGetBestSize().x
; 
 545     int widthText 
= width 
- widthBtn 
- MARGIN_BETWEEN
; 
 546     if ( widthText 
<= 0 ) 
 548         wxLogDebug(_T("not enough space for wxSpinCtrl!")); 
 551     if ( !::MoveWindow(GetBuddyHwnd(), x
, y
, widthText
, height
, TRUE
) ) 
 553         wxLogLastError(wxT("MoveWindow(buddy)")); 
 556     x 
+= widthText 
+ MARGIN_BETWEEN
; 
 557     if ( !::MoveWindow(GetHwnd(), x
, y
, widthBtn
, height
, TRUE
) ) 
 559         wxLogLastError(wxT("MoveWindow")); 
 563 // get total size of the control 
 564 void wxSpinCtrl::DoGetSize(int *x
, int *y
) const 
 566     RECT spinrect
, textrect
, ctrlrect
; 
 567     GetWindowRect(GetHwnd(), &spinrect
); 
 568     GetWindowRect(GetBuddyHwnd(), &textrect
); 
 569     UnionRect(&ctrlrect
,&textrect
, &spinrect
); 
 572         *x 
= ctrlrect
.right 
- ctrlrect
.left
; 
 574         *y 
= ctrlrect
.bottom 
- ctrlrect
.top
; 
 577 void wxSpinCtrl::DoGetPosition(int *x
, int *y
) const 
 579     // hack: pretend that our HWND is the text control just for a moment 
 580     WXHWND hWnd 
= GetHWND(); 
 581     wxConstCast(this, wxSpinCtrl
)->m_hWnd 
= m_hwndBuddy
; 
 583     wxSpinButton::DoGetPosition(x
, y
); 
 585     wxConstCast(this, wxSpinCtrl
)->m_hWnd 
= hWnd
;