1
/////////////////////////////////////////////////////////////////////////////
2
// Name: src/msw/spinctrl.cpp
3
// Purpose: wxSpinCtrl class implementation for Win32
4
// Author: Vadim Zeitlin
7
// RCS-ID: $Id: spinctrl.cpp 71640 2012-06-03 19:16:59Z VZ $
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/hashmap.h"
33
#include "wx/msw/wrapcctl.h" // include <commctrl.h> "properly"
35
#include "wx/textctrl.h"
36
#include "wx/wxcrtvararg.h"
39
#include "wx/msw/private.h"
42
#include "wx/tooltip.h"
43
#endif // wxUSE_TOOLTIPS
45
#include <limits.h> // for INT_MIN
47
// ----------------------------------------------------------------------------
49
// ----------------------------------------------------------------------------
51
BEGIN_EVENT_TABLE(wxSpinCtrl, wxSpinButton)
52
EVT_CHAR(wxSpinCtrl::OnChar)
53
EVT_SET_FOCUS(wxSpinCtrl::OnSetFocus)
54
EVT_KILL_FOCUS(wxSpinCtrl::OnKillFocus)
57
#define GetBuddyHwnd() (HWND)(m_hwndBuddy)
59
// ----------------------------------------------------------------------------
61
// ----------------------------------------------------------------------------
63
// the margin between the up-down control and its buddy (can be arbitrary,
64
// choose what you like - or may be decide during run-time depending on the
66
static const int MARGIN_BETWEEN = 1;
69
// ---------------------------------------------------------------------------
71
// ---------------------------------------------------------------------------
76
// Global hash used to find the spin control corresponding to the given buddy
78
WX_DECLARE_HASH_MAP(HWND, wxSpinCtrl *,
79
wxPointerHash, wxPointerEqual,
82
SpinForTextCtrl gs_spinForTextCtrl;
84
} // anonymous namespace
86
// ============================================================================
88
// ============================================================================
90
// ----------------------------------------------------------------------------
91
// wnd proc for the buddy text ctrl
92
// ----------------------------------------------------------------------------
94
LRESULT APIENTRY _EXPORT wxBuddyTextWndProc(HWND hwnd,
99
wxSpinCtrl * const spin = wxSpinCtrl::GetSpinForTextCtrl(hwnd);
101
// forward some messages (mostly the key and focus ones) to the spin ctrl
105
// if the focus comes from the spin control itself, don't set it
106
// back to it -- we don't want to go into an infinite loop
107
if ( (WXHWND)wParam == spin->GetHWND() )
117
// we need to forward WM_HELP too to ensure that the context help
118
// associated with wxSpinCtrl is shown when the text control part of it
119
// is clicked with the "?" cursor
124
if ( spin->MSWHandleMessage(&result, message, wParam, lParam) )
126
// Do not let the message be processed by the window proc
127
// of the text control if it had been already handled at wx
128
// level, this is consistent with what happens for normal,
129
// non-composite controls.
133
// The control may have been deleted at this point, so check.
134
if ( !::IsWindow(hwnd) )
140
if ( spin->HasFlag(wxTE_PROCESS_ENTER) )
142
long dlgCode = ::CallWindowProc
144
CASTWNDPROC spin->GetBuddyWndProc(),
150
dlgCode |= DLGC_WANTMESSAGE;
156
return ::CallWindowProc(CASTWNDPROC spin->GetBuddyWndProc(),
157
hwnd, message, wParam, lParam);
161
wxSpinCtrl *wxSpinCtrl::GetSpinForTextCtrl(WXHWND hwndBuddy)
163
const SpinForTextCtrl::const_iterator
164
it = gs_spinForTextCtrl.find(hwndBuddy);
165
if ( it == gs_spinForTextCtrl.end() )
168
wxSpinCtrl * const spin = it->second;
171
wxASSERT_MSG( spin->m_hwndBuddy == hwndBuddy,
172
wxT("wxSpinCtrl has incorrect buddy HWND!") );
177
// process a WM_COMMAND generated by the buddy text control
178
bool wxSpinCtrl::ProcessTextCommand(WXWORD cmd, WXWORD WXUNUSED(id))
180
if ( (cmd == EN_CHANGE) && (!m_blockEvent ))
182
wxCommandEvent event(wxEVT_COMMAND_TEXT_UPDATED, GetId());
183
event.SetEventObject(this);
184
wxString val = wxGetWindowText(m_hwndBuddy);
185
event.SetString(val);
186
event.SetInt(GetValue());
187
return HandleWindowEvent(event);
194
void wxSpinCtrl::OnChar(wxKeyEvent& event)
196
switch ( event.GetKeyCode() )
200
wxCommandEvent event(wxEVT_COMMAND_TEXT_ENTER, m_windowId);
201
InitCommandEvent(event);
202
wxString val = wxGetWindowText(m_hwndBuddy);
203
event.SetString(val);
204
event.SetInt(GetValue());
205
if ( HandleWindowEvent(event) )
211
// always produce navigation event - even if we process TAB
212
// ourselves the fact that we got here means that the user code
213
// decided to skip processing of this TAB - probably to let it
214
// do its default job.
216
wxNavigationKeyEvent eventNav;
217
eventNav.SetDirection(!event.ShiftDown());
218
eventNav.SetWindowChange(event.ControlDown());
219
eventNav.SetEventObject(this);
221
if ( GetParent()->HandleWindowEvent(eventNav) )
227
// no, we didn't process it
231
void wxSpinCtrl::OnKillFocus(wxFocusEvent& event)
233
// ensure that a correct value is shown by the control
238
void wxSpinCtrl::OnSetFocus(wxFocusEvent& event)
240
// when we get focus, give it to our buddy window as it needs it more than
242
::SetFocus((HWND)m_hwndBuddy);
247
void wxSpinCtrl::NormalizeValue()
249
const int value = GetValue();
250
const bool changed = value != m_oldValue;
252
// notice that we have to call SetValue() even if the value didn't change
253
// because otherwise we could be left with empty buddy control when value
254
// is 0, see comment in SetValue()
259
SendSpinUpdate(value);
263
// ----------------------------------------------------------------------------
265
// ----------------------------------------------------------------------------
267
void wxSpinCtrl::Init()
269
m_blockEvent = false;
271
m_wndProcBuddy = NULL;
272
m_oldValue = INT_MIN;
275
bool wxSpinCtrl::Create(wxWindow *parent,
277
const wxString& value,
281
int min, int max, int initial,
282
const wxString& name)
284
// before using DoGetBestSize(), have to set style to let the base class
285
// know whether this is a horizontal or vertical control (we're always
287
style |= wxSP_VERTICAL;
289
if ( (style & wxBORDER_MASK) == wxBORDER_DEFAULT )
291
style |= wxBORDER_SIMPLE;
293
style |= wxBORDER_SUNKEN;
296
SetWindowStyle(style);
299
WXDWORD msStyle = MSWGetStyle(GetWindowStyle(), & exStyle) ;
301
// Scroll text automatically if there is not enough space to show all of
302
// it, this is better than not allowing to enter more digits at all.
303
msStyle |= ES_AUTOHSCROLL;
305
// propagate text alignment style to text ctrl
306
if ( style & wxALIGN_RIGHT )
308
else if ( style & wxALIGN_CENTER )
309
msStyle |= ES_CENTER;
311
// calculate the sizes: the size given is the total size for both controls
312
// and we need to fit them both in the given width (height is the same)
313
wxSize sizeText(size), sizeBtn(size);
314
sizeBtn.x = wxSpinButton::DoGetBestSize().x;
315
if ( sizeText.x <= 0 )
317
// DEFAULT_ITEM_WIDTH is the default width for the text control
318
sizeText.x = DEFAULT_ITEM_WIDTH + MARGIN_BETWEEN + sizeBtn.x;
321
sizeText.x -= sizeBtn.x + MARGIN_BETWEEN;
322
if ( sizeText.x <= 0 )
324
wxLogDebug(wxT("not enough space for wxSpinCtrl!"));
328
posBtn.x += sizeText.x + MARGIN_BETWEEN;
330
// we must create the text control before the spin button for the purpose
331
// of the dialog navigation: if there is a static text just before the spin
332
// control, activating it by Alt-letter should give focus to the text
333
// control, not the spin and the dialog navigation code will give focus to
334
// the next control (at Windows level), not the one after it
336
// create the text window
338
m_hwndBuddy = (WXHWND)::CreateWindowEx
340
exStyle, // sunken border
341
wxT("EDIT"), // window class
342
NULL, // no window title
343
msStyle, // style (will be shown later)
344
pos.x, pos.y, // position
345
0, 0, // size (will be set later)
346
GetHwndOf(parent), // parent
347
(HMENU)-1, // control id
348
wxGetInstance(), // app instance
349
NULL // unused client data
354
wxLogLastError(wxT("CreateWindow(buddy text window)"));
360
// create the spin button
361
if ( !wxSpinButton::Create(parent, id, posBtn, sizeBtn, style, name) )
366
wxSpinButtonBase::SetRange(min, max);
368
// subclass the text ctrl to be able to intercept some events
369
gs_spinForTextCtrl[GetBuddyHwnd()] = this;
371
m_wndProcBuddy = (WXFARPROC)wxSetWindowProc(GetBuddyHwnd(),
374
// set up fonts and colours (This is nomally done in MSWCreateControl)
377
SetFont(GetDefaultAttributes().font);
379
// set the size of the text window - can do it only now, because we
380
// couldn't call DoGetBestSize() before as font wasn't set
381
if ( sizeText.y <= 0 )
384
wxGetCharSize(GetHWND(), &cx, &cy, GetFont());
386
sizeText.y = EDIT_HEIGHT_FROM_CHAR_HEIGHT(cy);
389
SetInitialSize(size);
391
(void)::ShowWindow(GetBuddyHwnd(), SW_SHOW);
393
// associate the text window with the spin button
394
(void)::SendMessage(GetHwnd(), UDM_SETBUDDY, (WPARAM)m_hwndBuddy, 0);
396
// If the initial text value is actually a number, it overrides the
397
// "initial" argument specified later.
398
long initialFromText;
399
if ( value.ToLong(&initialFromText) )
400
initial = initialFromText;
404
m_oldValue = initial;
406
// Set the range in the native control
409
// Also set the text part of the control if it was specified independently
410
// but don't generate an event for this, it would be unexpected.
412
if ( !value.empty() )
414
m_blockEvent = false;
419
wxSpinCtrl::~wxSpinCtrl()
421
// destroy the buddy window because this pointer which wxBuddyTextWndProc
422
// uses will not soon be valid any more
423
::DestroyWindow( GetBuddyHwnd() );
425
gs_spinForTextCtrl.erase(GetBuddyHwnd());
428
// ----------------------------------------------------------------------------
429
// wxTextCtrl-like methods
430
// ----------------------------------------------------------------------------
432
void wxSpinCtrl::SetValue(const wxString& text)
434
if ( !::SetWindowText(GetBuddyHwnd(), text.c_str()) )
436
wxLogLastError(wxT("SetWindowText(buddy)"));
440
void wxSpinCtrl::SetValue(int val)
444
wxSpinButton::SetValue(val);
446
// normally setting the value of the spin button is enough as it updates
447
// its buddy control automatically ...
448
if ( wxGetWindowText(m_hwndBuddy).empty() )
450
// ... but sometimes it doesn't, notably when the value is 0 and the
451
// text control is currently empty, the spin button seems to be happy
452
// to leave it like this, while we really want to always show the
453
// current value in the control, so do it manually
454
::SetWindowText(GetBuddyHwnd(),
455
wxString::Format(wxT("%d"), val).t_str());
458
m_oldValue = GetValue();
460
m_blockEvent = false;
463
int wxSpinCtrl::GetValue() const
465
wxString val = wxGetWindowText(m_hwndBuddy);
468
if ( (wxSscanf(val, wxT("%ld"), &n) != 1) )
479
void wxSpinCtrl::SetSelection(long from, long to)
481
// if from and to are both -1, it means (in wxWidgets) that all text should
482
// be selected - translate into Windows convention
483
if ( (from == -1) && (to == -1) )
488
::SendMessage(GetBuddyHwnd(), EM_SETSEL, (WPARAM)from, (LPARAM)to);
491
// ----------------------------------------------------------------------------
492
// wxSpinButton methods
493
// ----------------------------------------------------------------------------
495
void wxSpinCtrl::SetRange(int minVal, int maxVal)
497
wxSpinButton::SetRange(minVal, maxVal);
499
// this control is used for numeric entry so restrict the input to numeric
500
// keys only -- but only if we don't need to be able to enter "-" in it as
501
// otherwise this would become impossible
502
const DWORD styleOld = ::GetWindowLong(GetBuddyHwnd(), GWL_STYLE);
505
styleNew = styleOld & ~ES_NUMBER;
507
styleNew = styleOld | ES_NUMBER;
509
if ( styleNew != styleOld )
510
::SetWindowLong(GetBuddyHwnd(), GWL_STYLE, styleNew);
513
// ----------------------------------------------------------------------------
514
// forward some methods to subcontrols
515
// ----------------------------------------------------------------------------
517
bool wxSpinCtrl::SetFont(const wxFont& font)
519
if ( !wxWindowBase::SetFont(font) )
525
WXHANDLE hFont = GetFont().GetResourceHandle();
526
(void)::SendMessage(GetBuddyHwnd(), WM_SETFONT, (WPARAM)hFont, TRUE);
531
bool wxSpinCtrl::Show(bool show)
533
if ( !wxControl::Show(show) )
538
::ShowWindow(GetBuddyHwnd(), show ? SW_SHOW : SW_HIDE);
543
bool wxSpinCtrl::Reparent(wxWindowBase *newParent)
545
// Reparenting both the updown control and its buddy does not seem to work:
546
// they continue to be connected somehow, but visually there is no feedback
547
// on the buddy edit control. To avoid this problem, we reparent the buddy
548
// window normally, but we recreate the updown control and reassign its
551
// Get the position before changing the parent as it would be offset after
553
const wxRect rect = GetRect();
555
if ( !wxWindowBase::Reparent(newParent) )
558
newParent->GetChildren().DeleteObject(this);
560
// destroy the old spin button after detaching it from this wxWindow object
561
// (notice that m_hWnd will be reset by UnsubclassWin() so save it first)
562
const HWND hwndOld = GetHwnd();
564
if ( !::DestroyWindow(hwndOld) )
566
wxLogLastError(wxT("DestroyWindow"));
569
// create and initialize the new one
570
if ( !wxSpinButton::Create(GetParent(), GetId(),
571
rect.GetPosition(), rect.GetSize(),
572
GetWindowStyle(), GetName()) )
575
// reapply our values to wxSpinButton
576
wxSpinButton::SetValue(GetValue());
577
SetRange(m_min, m_max);
579
// also set the size again with wxSIZE_ALLOW_MINUS_ONE flag: this is
580
// necessary if our original position used -1 for either x or y
581
SetSize(rect, wxSIZE_ALLOW_MINUS_ONE);
583
// associate it with the buddy control again
584
::SetParent(GetBuddyHwnd(), GetHwndOf(GetParent()));
585
(void)::SendMessage(GetHwnd(), UDM_SETBUDDY, (WPARAM)GetBuddyHwnd(), 0);
590
bool wxSpinCtrl::Enable(bool enable)
592
if ( !wxControl::Enable(enable) )
597
MSWEnableHWND(GetBuddyHwnd(), enable);
602
void wxSpinCtrl::SetFocus()
604
::SetFocus(GetBuddyHwnd());
609
void wxSpinCtrl::DoSetToolTip(wxToolTip *tip)
611
wxSpinButton::DoSetToolTip(tip);
614
tip->AddOtherWindow(m_hwndBuddy);
617
#endif // wxUSE_TOOLTIPS
619
// ----------------------------------------------------------------------------
620
// events processing and generation
621
// ----------------------------------------------------------------------------
623
void wxSpinCtrl::SendSpinUpdate(int value)
625
wxCommandEvent event(wxEVT_COMMAND_SPINCTRL_UPDATED, GetId());
626
event.SetEventObject(this);
629
(void)HandleWindowEvent(event);
634
bool wxSpinCtrl::MSWOnScroll(int WXUNUSED(orientation), WXWORD wParam,
635
WXWORD pos, WXHWND control)
637
wxCHECK_MSG( control, false, wxT("scrolling what?") );
639
if ( wParam != SB_THUMBPOSITION )
641
// probable SB_ENDSCROLL - we don't react to it
645
int new_value = (short) pos;
646
if (m_oldValue != new_value)
647
SendSpinUpdate( new_value );
652
bool wxSpinCtrl::MSWOnNotify(int WXUNUSED(idCtrl), WXLPARAM lParam, WXLPARAM *result)
654
NM_UPDOWN *lpnmud = (NM_UPDOWN *)lParam;
656
if (lpnmud->hdr.hwndFrom != GetHwnd()) // make sure it is the right control
659
*result = 0; // never reject UP and DOWN events
665
// ----------------------------------------------------------------------------
667
// ----------------------------------------------------------------------------
669
wxSize wxSpinCtrl::DoGetBestSize() const
671
wxSize sizeBtn = wxSpinButton::DoGetBestSize();
672
sizeBtn.x += DEFAULT_ITEM_WIDTH + MARGIN_BETWEEN;
675
wxGetCharSize(GetHWND(), NULL, &y, GetFont());
676
y = EDIT_HEIGHT_FROM_CHAR_HEIGHT(y);
678
// JACS: we should always use the height calculated
679
// from above, because otherwise we'll get a spin control
680
// that's too big. So never use the height calculated
681
// from wxSpinButton::DoGetBestSize().
683
// if ( sizeBtn.y < y )
685
// make the text tall enough
692
void wxSpinCtrl::DoMoveWindow(int x, int y, int width, int height)
694
int widthBtn = wxSpinButton::DoGetBestSize().x;
695
int widthText = width - widthBtn - MARGIN_BETWEEN;
696
if ( widthText <= 0 )
698
wxLogDebug(wxT("not enough space for wxSpinCtrl!"));
701
// 1) The buddy window
702
DoMoveSibling(m_hwndBuddy, x, y, widthText, height);
704
// 2) The button window
705
x += widthText + MARGIN_BETWEEN;
706
wxSpinButton::DoMoveWindow(x, y, widthBtn, height);
709
// get total size of the control
710
void wxSpinCtrl::DoGetSize(int *x, int *y) const
712
RECT spinrect, textrect, ctrlrect;
713
GetWindowRect(GetHwnd(), &spinrect);
714
GetWindowRect(GetBuddyHwnd(), &textrect);
715
UnionRect(&ctrlrect,&textrect, &spinrect);
718
*x = ctrlrect.right - ctrlrect.left;
720
*y = ctrlrect.bottom - ctrlrect.top;
723
void wxSpinCtrl::DoGetClientSize(int *x, int *y) const
725
RECT spinrect = wxGetClientRect(GetHwnd());
726
RECT textrect = wxGetClientRect(GetBuddyHwnd());
728
UnionRect(&ctrlrect,&textrect, &spinrect);
731
*x = ctrlrect.right - ctrlrect.left;
733
*y = ctrlrect.bottom - ctrlrect.top;
736
void wxSpinCtrl::DoGetPosition(int *x, int *y) const
738
// hack: pretend that our HWND is the text control just for a moment
739
WXHWND hWnd = GetHWND();
740
wxConstCast(this, wxSpinCtrl)->m_hWnd = m_hwndBuddy;
742
wxSpinButton::DoGetPosition(x, y);
744
wxConstCast(this, wxSpinCtrl)->m_hWnd = hWnd;
747
#endif // wxUSE_SPINCTRL