]> git.saurik.com Git - wxWidgets.git/blobdiff - src/msw/spinctrl.cpp
Several corrections to wxDocManager fields documentation.
[wxWidgets.git] / src / msw / spinctrl.cpp
index 133cba42e74b5c2b3e2aeb79554e50a4d38d8172..6cc84eac58138af1991eb4583a8fdb37dd7c8612 100644 (file)
@@ -1,11 +1,11 @@
 /////////////////////////////////////////////////////////////////////////////
-// Name:        msw/spinctrl.cpp
+// Name:        src/msw/spinctrl.cpp
 // Purpose:     wxSpinCtrl class implementation for Win32
 // Author:      Vadim Zeitlin
 // Modified by:
 // Created:     22.07.99
 // RCS-ID:      $Id$
-// Copyright:   (c) Vadim Zeitlin
+// Copyright:   (c) 1999-2005 Vadim Zeitlin
 // Licence:     wxWindows licence
 /////////////////////////////////////////////////////////////////////////////
 
 // declarations
 // ============================================================================
 
-#ifdef __GNUG__
-    #pragma implementation "spinctrlbase.h"
-    #pragma implementation "spinctrl.h"
-#endif
-
 // ----------------------------------------------------------------------------
 // headers
 // ----------------------------------------------------------------------------
     #pragma hdrstop
 #endif
 
+#if wxUSE_SPINCTRL
+
+#include "wx/spinctrl.h"
+
 #ifndef WX_PRECOMP
-    #include "wx/wx.h"
+    #include "wx/hashmap.h"
+    #include "wx/msw/wrapcctl.h" // include <commctrl.h> "properly"
+    #include "wx/event.h"
+    #include "wx/textctrl.h"
+    #include "wx/wxcrtvararg.h"
 #endif
 
-#if defined(__WIN95__)
-
-#include "wx/spinctrl.h"
 #include "wx/msw/private.h"
 
-#include <commctrl.h>
+#if wxUSE_TOOLTIPS
+    #include "wx/tooltip.h"
+#endif // wxUSE_TOOLTIPS
+
+#include <limits.h>         // for INT_MIN
 
 // ----------------------------------------------------------------------------
 // macros
 // ----------------------------------------------------------------------------
 
-#if !USE_SHARED_LIBRARY
-    IMPLEMENT_DYNAMIC_CLASS(wxSpinCtrl, wxControl)
-#endif
+BEGIN_EVENT_TABLE(wxSpinCtrl, wxSpinButton)
+    EVT_CHAR(wxSpinCtrl::OnChar)
+    EVT_SET_FOCUS(wxSpinCtrl::OnSetFocus)
+    EVT_KILL_FOCUS(wxSpinCtrl::OnKillFocus)
+END_EVENT_TABLE()
+
+#define GetBuddyHwnd()      (HWND)(m_hwndBuddy)
 
 // ----------------------------------------------------------------------------
 // constants
 // ----------------------------------------------------------------------------
 
-// the margin between the up-down control and its buddy
-static const int MARGIN_BETWEEN = 5;
+// the margin between the up-down control and its buddy (can be arbitrary,
+// choose what you like - or may be decide during run-time depending on the
+// font size?)
+static const int MARGIN_BETWEEN = 1;
+
+
+// ---------------------------------------------------------------------------
+// global vars
+// ---------------------------------------------------------------------------
+
+namespace
+{
+
+// Global hash used to find the spin control corresponding to the given buddy
+// text control HWND.
+WX_DECLARE_HASH_MAP(HWND, wxSpinCtrl *,
+                    wxPointerHash, wxPointerEqual,
+                    SpinForTextCtrl);
+
+SpinForTextCtrl gs_spinForTextCtrl;
+
+} // anonymous namespace
 
 // ============================================================================
 // implementation
 // ============================================================================
 
+// ----------------------------------------------------------------------------
+// wnd proc for the buddy text ctrl
+// ----------------------------------------------------------------------------
+
+LRESULT APIENTRY _EXPORT wxBuddyTextWndProc(HWND hwnd,
+                                            UINT message,
+                                            WPARAM wParam,
+                                            LPARAM lParam)
+{
+    wxSpinCtrl * const spin = wxSpinCtrl::GetSpinForTextCtrl(hwnd);
+
+    // forward some messages (mostly the key and focus ones) to the spin ctrl
+    switch ( message )
+    {
+        case WM_SETFOCUS:
+            // if the focus comes from the spin control itself, don't set it
+            // back to it -- we don't want to go into an infinite loop
+            if ( (WXHWND)wParam == spin->GetHWND() )
+                break;
+            //else: fall through
+
+        case WM_KILLFOCUS:
+        case WM_CHAR:
+        case WM_DEADCHAR:
+        case WM_KEYUP:
+        case WM_KEYDOWN:
+#ifdef WM_HELP
+        // we need to forward WM_HELP too to ensure that the context help
+        // associated with wxSpinCtrl is shown when the text control part of it
+        // is clicked with the "?" cursor
+        case WM_HELP:
+#endif
+            {
+                WXLRESULT result;
+                if ( spin->MSWHandleMessage(&result, message, wParam, lParam) )
+                {
+                    // Do not let the message be processed by the window proc
+                    // of the text control if it had been already handled at wx
+                    // level, this is consistent with what happens for normal,
+                    // non-composite controls.
+                    return 0;
+                }
+
+                // The control may have been deleted at this point, so check.
+                if ( !::IsWindow(hwnd) )
+                    return 0;
+            }
+            break;
+
+        case WM_GETDLGCODE:
+            if ( spin->HasFlag(wxTE_PROCESS_ENTER) )
+            {
+                long dlgCode = ::CallWindowProc
+                                 (
+                                    CASTWNDPROC spin->GetBuddyWndProc(),
+                                    hwnd,
+                                    message,
+                                    wParam,
+                                    lParam
+                                 );
+                dlgCode |= DLGC_WANTMESSAGE;
+                return dlgCode;
+            }
+            break;
+    }
+
+    return ::CallWindowProc(CASTWNDPROC spin->GetBuddyWndProc(),
+                            hwnd, message, wParam, lParam);
+}
+
+/* static */
+wxSpinCtrl *wxSpinCtrl::GetSpinForTextCtrl(WXHWND hwndBuddy)
+{
+    const SpinForTextCtrl::const_iterator
+        it = gs_spinForTextCtrl.find(hwndBuddy);
+    if ( it == gs_spinForTextCtrl.end() )
+        return NULL;
+
+    wxSpinCtrl * const spin = it->second;
+
+    // sanity check
+    wxASSERT_MSG( spin->m_hwndBuddy == hwndBuddy,
+                  wxT("wxSpinCtrl has incorrect buddy HWND!") );
+
+    return spin;
+}
+
+// process a WM_COMMAND generated by the buddy text control
+bool wxSpinCtrl::ProcessTextCommand(WXWORD cmd, WXWORD WXUNUSED(id))
+{
+    if ( (cmd == EN_CHANGE) && (!m_blockEvent ))
+    {
+        wxCommandEvent event(wxEVT_COMMAND_TEXT_UPDATED, GetId());
+        event.SetEventObject(this);
+        wxString val = wxGetWindowText(m_hwndBuddy);
+        event.SetString(val);
+        event.SetInt(GetValue());
+        return HandleWindowEvent(event);
+    }
+
+    // not processed
+    return false;
+}
+
+void wxSpinCtrl::OnChar(wxKeyEvent& event)
+{
+    switch ( event.GetKeyCode() )
+    {
+        case WXK_RETURN:
+            {
+                wxCommandEvent event(wxEVT_COMMAND_TEXT_ENTER, m_windowId);
+                InitCommandEvent(event);
+                wxString val = wxGetWindowText(m_hwndBuddy);
+                event.SetString(val);
+                event.SetInt(GetValue());
+                if ( HandleWindowEvent(event) )
+                    return;
+                break;
+            }
+
+        case WXK_TAB:
+            // always produce navigation event - even if we process TAB
+            // ourselves the fact that we got here means that the user code
+            // decided to skip processing of this TAB - probably to let it
+            // do its default job.
+            {
+                wxNavigationKeyEvent eventNav;
+                eventNav.SetDirection(!event.ShiftDown());
+                eventNav.SetWindowChange(event.ControlDown());
+                eventNav.SetEventObject(this);
+
+                if ( GetParent()->HandleWindowEvent(eventNav) )
+                    return;
+            }
+            break;
+    }
+
+    // no, we didn't process it
+    event.Skip();
+}
+
+void wxSpinCtrl::OnKillFocus(wxFocusEvent& event)
+{
+    // ensure that a correct value is shown by the control
+    NormalizeValue();
+    event.Skip();
+}
+
+void wxSpinCtrl::OnSetFocus(wxFocusEvent& event)
+{
+    // when we get focus, give it to our buddy window as it needs it more than
+    // we do
+    ::SetFocus((HWND)m_hwndBuddy);
+
+    event.Skip();
+}
+
+void wxSpinCtrl::NormalizeValue()
+{
+    const int value = GetValue();
+    const bool changed = value != m_oldValue;
+
+    // notice that we have to call SetValue() even if the value didn't change
+    // because otherwise we could be left with empty buddy control when value
+    // is 0, see comment in SetValue()
+    SetValue(value);
+
+    if ( changed )
+    {
+        SendSpinUpdate(value);
+    }
+}
+
 // ----------------------------------------------------------------------------
 // construction
 // ----------------------------------------------------------------------------
 
 bool wxSpinCtrl::Create(wxWindow *parent,
                         wxWindowID id,
+                        const wxString& value,
                         const wxPoint& pos,
                         const wxSize& size,
                         long style,
                         int min, int max, int initial,
                         const wxString& name)
 {
+    m_blockEvent = false;
+
+    // this should be in ctor/init function but I don't want to add one to 2.8
+    // to avoid problems with default ctor which can be inlined in the user
+    // code and so might not get this fix without recompilation
+    m_oldValue = INT_MIN;
+
     // before using DoGetBestSize(), have to set style to let the base class
     // know whether this is a horizontal or vertical control (we're always
     // vertical)
-    SetWindowStyle(style | wxSP_VERTICAL);
+    style |= wxSP_VERTICAL;
+
+    if ( (style & wxBORDER_MASK) == wxBORDER_DEFAULT )
+#ifdef __WXWINCE__
+        style |= wxBORDER_SIMPLE;
+#else
+        style |= wxBORDER_SUNKEN;
+#endif
+
+    SetWindowStyle(style);
+
+    WXDWORD exStyle = 0;
+    WXDWORD msStyle = MSWGetStyle(GetWindowStyle(), & exStyle) ;
 
-    // calculate the sizes: the size given is the toal size for both controls
+    // propagate text alignment style to text ctrl
+    if ( style & wxALIGN_RIGHT )
+        msStyle |= ES_RIGHT;
+    else if ( style & wxALIGN_CENTER )
+        msStyle |= ES_CENTER;
+
+    // calculate the sizes: the size given is the total size for both controls
     // and we need to fit them both in the given width (height is the same)
     wxSize sizeText(size), sizeBtn(size);
     sizeBtn.x = wxSpinButton::DoGetBestSize().x;
+    if ( sizeText.x <= 0 )
+    {
+        // DEFAULT_ITEM_WIDTH is the default width for the text control
+        sizeText.x = DEFAULT_ITEM_WIDTH + MARGIN_BETWEEN + sizeBtn.x;
+    }
+
     sizeText.x -= sizeBtn.x + MARGIN_BETWEEN;
     if ( sizeText.x <= 0 )
     {
-        wxLogDebug(_T("not enough space for wxSpinCtrl!"));
+        wxLogDebug(wxT("not enough space for wxSpinCtrl!"));
     }
 
     wxPoint posBtn(pos);
     posBtn.x += sizeText.x + MARGIN_BETWEEN;
 
+    // we must create the text control before the spin button for the purpose
+    // of the dialog navigation: if there is a static text just before the spin
+    // control, activating it by Alt-letter should give focus to the text
+    // control, not the spin and the dialog navigation code will give focus to
+    // the next control (at Windows level), not the one after it
+
+    // create the text window
+
+    m_hwndBuddy = (WXHWND)::CreateWindowEx
+                    (
+                     exStyle,                // sunken border
+                     wxT("EDIT"),             // window class
+                     NULL,                   // no window title
+                     msStyle,                // style (will be shown later)
+                     pos.x, pos.y,           // position
+                     0, 0,                   // size (will be set later)
+                     GetHwndOf(parent),      // parent
+                     (HMENU)-1,              // control id
+                     wxGetInstance(),        // app instance
+                     NULL                    // unused client data
+                    );
+
+    if ( !m_hwndBuddy )
+    {
+        wxLogLastError(wxT("CreateWindow(buddy text window)"));
+
+        return false;
+    }
+
+
     // create the spin button
     if ( !wxSpinButton::Create(parent, id, posBtn, sizeBtn, style, name) )
     {
-        return FALSE;
+        return false;
     }
 
-    SetRange(min, max);
-    SetValue(initial);
+    wxSpinButtonBase::SetRange(min, max);
 
-    // create the text window
+    // subclass the text ctrl to be able to intercept some events
+    gs_spinForTextCtrl[GetBuddyHwnd()] = this;
+
+    m_wndProcBuddy = (WXFARPROC)wxSetWindowProc(GetBuddyHwnd(),
+                                                wxBuddyTextWndProc);
+
+    // set up fonts and colours  (This is nomally done in MSWCreateControl)
+    InheritAttributes();
+    if (!m_hasFont)
+        SetFont(GetDefaultAttributes().font);
+
+    // set the size of the text window - can do it only now, because we
+    // couldn't call DoGetBestSize() before as font wasn't set
     if ( sizeText.y <= 0 )
     {
-        // make it the same height as the button then
-        int x, y;
-        wxSpinButton::DoGetSize(&x, &y);
+        int cx, cy;
+        wxGetCharSize(GetHWND(), &cx, &cy, GetFont());
 
-        sizeText.y = y;
+        sizeText.y = EDIT_HEIGHT_FROM_CHAR_HEIGHT(cy);
     }
 
-    m_hwndBuddy = (WXHWND)::CreateWindowEx
-                    (
-                     WS_EX_CLIENTEDGE,                  // sunken border
-                     _T("EDIT"),                        // window class
-                     NULL,                              // no window title
-                     WS_CHILD | WS_VISIBLE | WS_BORDER, // style
-                     pos.x, pos.y,                      // position
-                     sizeText.x, sizeText.y,            // size
-                     GetHwndOf(parent),                 // parent
-                     (HMENU)-1,                         // control id
-                     wxGetInstance(),                   // app instance
-                     NULL                               // unused client data
-                    );
+    SetInitialSize(size);
 
-    if ( !m_hwndBuddy )
+    (void)::ShowWindow(GetBuddyHwnd(), SW_SHOW);
+
+    // associate the text window with the spin button
+    (void)::SendMessage(GetHwnd(), UDM_SETBUDDY, (WPARAM)m_hwndBuddy, 0);
+
+    SetValue(initial);
+
+    // Set the range in the native control
+    SetRange(min, max);
+
+    if ( !value.empty() )
     {
-        wxLogLastError("CreateWindow(buddy text window)");
+        SetValue(value);
+        m_oldValue = (int) wxAtol(value);
+    }
+    else
+    {
+        SetValue(wxString::Format(wxT("%d"), initial));
+        m_oldValue = initial;
+    }
+
+    return true;
+}
+
+wxSpinCtrl::~wxSpinCtrl()
+{
+    // destroy the buddy window because this pointer which wxBuddyTextWndProc
+    // uses will not soon be valid any more
+    ::DestroyWindow( GetBuddyHwnd() );
+
+    gs_spinForTextCtrl.erase(GetBuddyHwnd());
+}
+
+// ----------------------------------------------------------------------------
+// wxTextCtrl-like methods
+// ----------------------------------------------------------------------------
 
-        return FALSE;
+void wxSpinCtrl::SetValue(const wxString& text)
+{
+    if ( !::SetWindowText(GetBuddyHwnd(), text.c_str()) )
+    {
+        wxLogLastError(wxT("SetWindowText(buddy)"));
     }
+}
 
-    // should have the same font as the other controls
-    WXHANDLE hFont = GetParent()->GetFont().GetResourceHandle();
-    ::SendMessage((HWND)m_hwndBuddy, WM_SETFONT, (WPARAM)hFont, TRUE);
+void  wxSpinCtrl::SetValue(int val)
+{
+    m_blockEvent = true;
 
-    // associate the text window with the spin button
-    (void)SendMessage(GetHwnd(), UDM_SETBUDDY, (WPARAM)m_hwndBuddy, 0);
+    wxSpinButton::SetValue(val);
+
+    // normally setting the value of the spin button is enough as it updates
+    // its buddy control automatically ...
+    if ( wxGetWindowText(m_hwndBuddy).empty() )
+    {
+        // ... but sometimes it doesn't, notably when the value is 0 and the
+        // text control is currently empty, the spin button seems to be happy
+        // to leave it like this, while we really want to always show the
+        // current value in the control, so do it manually
+        ::SetWindowText(GetBuddyHwnd(),
+                        wxString::Format(wxT("%d"), val).wx_str());
+    }
+
+    m_oldValue = GetValue();
+
+    m_blockEvent = false;
+}
+
+int wxSpinCtrl::GetValue() const
+{
+    wxString val = wxGetWindowText(m_hwndBuddy);
+
+    long n;
+    if ( (wxSscanf(val, wxT("%ld"), &n) != 1) )
+        n = INT_MIN;
+
+    if ( n < m_min )
+        n = m_min;
+    if ( n > m_max )
+        n = m_max;
+
+    return n;
+}
+
+void wxSpinCtrl::SetSelection(long from, long to)
+{
+    // if from and to are both -1, it means (in wxWidgets) that all text should
+    // be selected - translate into Windows convention
+    if ( (from == -1) && (to == -1) )
+    {
+        from = 0;
+    }
+
+    ::SendMessage(GetBuddyHwnd(), EM_SETSEL, (WPARAM)from, (LPARAM)to);
+}
+
+// ----------------------------------------------------------------------------
+// wxSpinButton methods
+// ----------------------------------------------------------------------------
+
+void wxSpinCtrl::SetRange(int minVal, int maxVal)
+{
+    wxSpinButton::SetRange(minVal, maxVal);
+
+    // this control is used for numeric entry so restrict the input to numeric
+    // keys only -- but only if we don't need to be able to enter "-" in it as
+    // otherwise this would become impossible
+    const DWORD styleOld = ::GetWindowLong(GetBuddyHwnd(), GWL_STYLE);
+    DWORD styleNew;
+    if ( minVal < 0 )
+        styleNew = styleOld & ~ES_NUMBER;
+    else
+        styleNew = styleOld | ES_NUMBER;
+
+    if ( styleNew != styleOld )
+        ::SetWindowLong(GetBuddyHwnd(), GWL_STYLE, styleNew);
+}
+
+// ----------------------------------------------------------------------------
+// forward some methods to subcontrols
+// ----------------------------------------------------------------------------
+
+bool wxSpinCtrl::SetFont(const wxFont& font)
+{
+    if ( !wxWindowBase::SetFont(font) )
+    {
+        // nothing to do
+        return false;
+    }
+
+    WXHANDLE hFont = GetFont().GetResourceHandle();
+    (void)::SendMessage(GetBuddyHwnd(), WM_SETFONT, (WPARAM)hFont, TRUE);
+
+    return true;
+}
+
+bool wxSpinCtrl::Show(bool show)
+{
+    if ( !wxControl::Show(show) )
+    {
+        return false;
+    }
+
+    ::ShowWindow(GetBuddyHwnd(), show ? SW_SHOW : SW_HIDE);
+
+    return true;
+}
+
+bool wxSpinCtrl::Reparent(wxWindowBase *newParent)
+{
+    // Reparenting both the updown control and its buddy does not seem to work:
+    // they continue to be connected somehow, but visually there is no feedback
+    // on the buddy edit control. To avoid this problem, we reparent the buddy
+    // window normally, but we recreate the updown control and reassign its
+    // buddy.
+
+    // Get the position before changing the parent as it would be offset after
+    // changing it.
+    const wxRect rect = GetRect();
+
+    if ( !wxWindowBase::Reparent(newParent) )
+        return false;
+
+    newParent->GetChildren().DeleteObject(this);
+
+    // destroy the old spin button after detaching it from this wxWindow object
+    // (notice that m_hWnd will be reset by UnsubclassWin() so save it first)
+    const HWND hwndOld = GetHwnd();
+    UnsubclassWin();
+    if ( !::DestroyWindow(hwndOld) )
+    {
+        wxLogLastError(wxT("DestroyWindow"));
+    }
+
+    // create and initialize the new one
+    if ( !wxSpinButton::Create(GetParent(), GetId(),
+                               rect.GetPosition(), rect.GetSize(),
+                               GetWindowStyle(), GetName()) )
+        return false;
+
+    // reapply our values to wxSpinButton
+    wxSpinButton::SetValue(GetValue());
+    SetRange(m_min, m_max);
+
+    // also set the size again with wxSIZE_ALLOW_MINUS_ONE flag: this is
+    // necessary if our original position used -1 for either x or y
+    SetSize(rect, wxSIZE_ALLOW_MINUS_ONE);
+
+    // associate it with the buddy control again
+    ::SetParent(GetBuddyHwnd(), GetHwndOf(GetParent()));
+    (void)::SendMessage(GetHwnd(), UDM_SETBUDDY, (WPARAM)GetBuddyHwnd(), 0);
+
+    return true;
+}
+
+bool wxSpinCtrl::Enable(bool enable)
+{
+    if ( !wxControl::Enable(enable) )
+    {
+        return false;
+    }
+
+    MSWEnableHWND(GetBuddyHwnd(), enable);
+
+    return true;
+}
+
+void wxSpinCtrl::SetFocus()
+{
+    ::SetFocus(GetBuddyHwnd());
+}
+
+#if wxUSE_TOOLTIPS
+
+void wxSpinCtrl::DoSetToolTip(wxToolTip *tip)
+{
+    wxSpinButton::DoSetToolTip(tip);
+
+    if ( tip )
+        tip->Add(m_hwndBuddy);
+}
+
+#endif // wxUSE_TOOLTIPS
+
+// ----------------------------------------------------------------------------
+// events processing and generation
+// ----------------------------------------------------------------------------
+
+void wxSpinCtrl::SendSpinUpdate(int value)
+{
+    wxCommandEvent event(wxEVT_COMMAND_SPINCTRL_UPDATED, GetId());
+    event.SetEventObject(this);
+    event.SetInt(value);
+
+    (void)HandleWindowEvent(event);
+
+    m_oldValue = value;
+}
+
+bool wxSpinCtrl::MSWOnScroll(int WXUNUSED(orientation), WXWORD wParam,
+                               WXWORD pos, WXHWND control)
+{
+    wxCHECK_MSG( control, false, wxT("scrolling what?") );
+
+    if ( wParam != SB_THUMBPOSITION )
+    {
+        // probable SB_ENDSCROLL - we don't react to it
+        return false;
+    }
+
+    int new_value = (short) pos;
+    if (m_oldValue != new_value)
+       SendSpinUpdate( new_value );
 
     return TRUE;
 }
 
+bool wxSpinCtrl::MSWOnNotify(int WXUNUSED(idCtrl), WXLPARAM lParam, WXLPARAM *result)
+{
+    NM_UPDOWN *lpnmud = (NM_UPDOWN *)lParam;
+
+    if (lpnmud->hdr.hwndFrom != GetHwnd()) // make sure it is the right control
+        return false;
+
+    *result = 0;  // never reject UP and DOWN events
+
+    return TRUE;
+}
+
+
 // ----------------------------------------------------------------------------
 // size calculations
 // ----------------------------------------------------------------------------
 
+wxSize wxSpinCtrl::DoGetBestSize() const
+{
+    wxSize sizeBtn = wxSpinButton::DoGetBestSize();
+    sizeBtn.x += DEFAULT_ITEM_WIDTH + MARGIN_BETWEEN;
+
+    int y;
+    wxGetCharSize(GetHWND(), NULL, &y, GetFont());
+    y = EDIT_HEIGHT_FROM_CHAR_HEIGHT(y);
+
+    // JACS: we should always use the height calculated
+    // from above, because otherwise we'll get a spin control
+    // that's too big. So never use the height calculated
+    // from wxSpinButton::DoGetBestSize().
+
+    // if ( sizeBtn.y < y )
+    {
+        // make the text tall enough
+        sizeBtn.y = y;
+    }
+
+    return sizeBtn;
+}
+
 void wxSpinCtrl::DoMoveWindow(int x, int y, int width, int height)
 {
-    int widthBtn = DoGetBestSize().x;
+    int widthBtn = wxSpinButton::DoGetBestSize().x;
     int widthText = width - widthBtn - MARGIN_BETWEEN;
     if ( widthText <= 0 )
     {
-        wxLogDebug(_T("not enough space for wxSpinCtrl!"));
+        wxLogDebug(wxT("not enough space for wxSpinCtrl!"));
     }
 
-    if ( !::MoveWindow((HWND)m_hwndBuddy, x, y, widthText, height, TRUE) )
-    {
-        wxLogLastError("MoveWindow(buddy)");
-    }
+    // 1) The buddy window
+    DoMoveSibling(m_hwndBuddy, x, y, widthText, height);
 
+    // 2) The button window
     x += widthText + MARGIN_BETWEEN;
-    if ( !::MoveWindow(GetHwnd(), x, y, widthBtn, height, TRUE) )
-    {
-        wxLogLastError("MoveWindow");
-    }
+    wxSpinButton::DoMoveWindow(x, y, widthBtn, height);
+}
+
+// get total size of the control
+void wxSpinCtrl::DoGetSize(int *x, int *y) const
+{
+    RECT spinrect, textrect, ctrlrect;
+    GetWindowRect(GetHwnd(), &spinrect);
+    GetWindowRect(GetBuddyHwnd(), &textrect);
+    UnionRect(&ctrlrect,&textrect, &spinrect);
+
+    if ( x )
+        *x = ctrlrect.right - ctrlrect.left;
+    if ( y )
+        *y = ctrlrect.bottom - ctrlrect.top;
+}
+
+void wxSpinCtrl::DoGetClientSize(int *x, int *y) const
+{
+    RECT spinrect = wxGetClientRect(GetHwnd());
+    RECT textrect = wxGetClientRect(GetBuddyHwnd());
+    RECT ctrlrect;
+    UnionRect(&ctrlrect,&textrect, &spinrect);
+
+    if ( x )
+        *x = ctrlrect.right - ctrlrect.left;
+    if ( y )
+        *y = ctrlrect.bottom - ctrlrect.top;
+}
+
+void wxSpinCtrl::DoGetPosition(int *x, int *y) const
+{
+    // hack: pretend that our HWND is the text control just for a moment
+    WXHWND hWnd = GetHWND();
+    wxConstCast(this, wxSpinCtrl)->m_hWnd = m_hwndBuddy;
+
+    wxSpinButton::DoGetPosition(x, y);
+
+    wxConstCast(this, wxSpinCtrl)->m_hWnd = hWnd;
 }
 
-#endif // __WIN95__
+#endif // wxUSE_SPINCTRL