From 05b980aba1db842b9be77deae58af3bfffb92d1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C4=83t=C4=83lin=20R=C4=83ceanu?= Date: Sat, 11 Feb 2017 23:37:35 +0100 Subject: [PATCH] Fix wxMSW wxSpinCtrl appearance: show arrows inside the control As recommended in the "Spin Controls" MSDN documentation (see https://msdn.microsoft.com/en-us/library/windows/desktop/dn742439.aspx), put the spin control inside the associated "buddy" edit control and not near it. Closes #12297. Closes https://github.com/wxWidgets/wxWidgets/pull/410 --- docs/changes.txt | 1 + include/wx/msw/spinctrl.h | 3 + src/msw/spinbutt.cpp | 3 +- src/msw/spinctrl.cpp | 112 ++++++++++++++++++++------------------ 4 files changed, 64 insertions(+), 55 deletions(-) diff --git a/docs/changes.txt b/docs/changes.txt index 754620929f..a8630936dd 100644 --- a/docs/changes.txt +++ b/docs/changes.txt @@ -141,6 +141,7 @@ wxMSW: - Add support for building with Microsoft Visual Studio 2017 (Tobias Taschner). - Enable wxStackWalker in MinGW64 builds. - Fix crash when using wxCHMHelpController() in 64 bit builds (Xlord2). +- Fix wxSpinCtrl appearance: show arrows inside the control (Catalin Raceanu). - Fix MDI menu display after failure to create a child frame (troelsk). - Fix wxScreenDC::GetSize() with multiple monitors (iwbnwif). - Fix background colour returned by wxTextCtrl::GetStyle() (Andreas Falkenhahn). diff --git a/include/wx/msw/spinctrl.h b/include/wx/msw/spinctrl.h index 2c864ce378..a85b215ce0 100644 --- a/include/wx/msw/spinctrl.h +++ b/include/wx/msw/spinctrl.h @@ -159,6 +159,9 @@ private: // hexadecimal prefix, ...) in it. void UpdateBuddyStyle(); + // Determine the (horizontal) pixel overlap between the spin button + // (up-down control) and the text control (buddy window). + int GetOverlap() const; wxDECLARE_DYNAMIC_CLASS(wxSpinCtrl); wxDECLARE_EVENT_TABLE(); diff --git a/src/msw/spinbutt.cpp b/src/msw/spinbutt.cpp index 7ac8abfe1c..9c7959d8d5 100644 --- a/src/msw/spinbutt.cpp +++ b/src/msw/spinbutt.cpp @@ -94,7 +94,8 @@ bool wxSpinButton::Create(wxWindow *parent, // translate the styles DWORD wstyle = WS_VISIBLE | WS_CHILD | WS_TABSTOP | /* WS_CLIPSIBLINGS | */ UDS_NOTHOUSANDS | // never useful, sometimes harmful - UDS_SETBUDDYINT; // it doesn't harm if we don't have buddy + UDS_ALIGNRIGHT | // these styles are effectively used only + UDS_SETBUDDYINT; // by wxSpinCtrl but do no harm otherwise if ( m_windowStyle & wxCLIP_SIBLINGS ) wstyle |= WS_CLIPSIBLINGS; diff --git a/src/msw/spinctrl.cpp b/src/msw/spinctrl.cpp index a59da9107f..9aaf19a1d9 100644 --- a/src/msw/spinctrl.cpp +++ b/src/msw/spinctrl.cpp @@ -55,15 +55,6 @@ wxEND_EVENT_TABLE() #define GetBuddyHwnd() (HWND)(m_hwndBuddy) -// ---------------------------------------------------------------------------- -// constants -// ---------------------------------------------------------------------------- - -// 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 @@ -278,9 +269,7 @@ bool wxSpinCtrl::Create(wxWindow *parent, int min, int max, int initial, const wxString& name) { - // 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) + // set style for the base class style |= wxSP_VERTICAL; if ( (style & wxBORDER_MASK) == wxBORDER_DEFAULT ) @@ -301,27 +290,6 @@ bool wxSpinCtrl::Create(wxWindow *parent, 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 = FromDIP(DEFAULT_ITEM_WIDTH) + MARGIN_BETWEEN + sizeBtn.x; - } - - sizeText.x -= sizeBtn.x + MARGIN_BETWEEN; - if ( sizeText.x <= 0 ) - { - wxLogDebug(wxS("wxSpinCtrl \"%s\": initial width %d is too small, ") - wxS("at least %d pixels needed."), - name, size.x, sizeBtn.x + MARGIN_BETWEEN + 1); - } - - 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 @@ -353,7 +321,7 @@ bool wxSpinCtrl::Create(wxWindow *parent, // create the spin button - if ( !wxSpinButton::Create(parent, id, posBtn, sizeBtn, style, name) ) + if ( !wxSpinButton::Create(parent, id, wxPoint(0, 0), wxSize(0, 0), style, name) ) { return false; } @@ -366,28 +334,46 @@ bool wxSpinCtrl::Create(wxWindow *parent, m_wndProcBuddy = (WXFARPROC)wxSetWindowProc(GetBuddyHwnd(), wxBuddyTextWndProc); + // associate the text window with the spin button + (void)::SendMessage(GetHwnd(), UDM_SETBUDDY, (WPARAM)m_hwndBuddy, 0); + // 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 ) + // finally deal with the size, now that both windows are created and the + // font is set + const wxSize sizeBtn = wxSpinButton::DoGetBestSize(); + const int effectiveBtnWidth = sizeBtn.x - GetOverlap(); + wxSize sizeCtrl(size); + if ( sizeCtrl.x <= 0 ) + { + // DEFAULT_ITEM_WIDTH is the default width for the text control + sizeCtrl.x = FromDIP(DEFAULT_ITEM_WIDTH) + effectiveBtnWidth; + } + else if ( sizeCtrl.x <= effectiveBtnWidth ) + { + wxLogDebug(wxS("wxSpinCtrl \"%s\": initial width %d is too small, ") + wxS("at least %d pixels needed."), + name, size.x, effectiveBtnWidth); + } + { + } + + // adjust an invalid height for text control + if ( sizeCtrl.y <= 0 ) { int cx, cy; wxGetCharSize(GetHWND(), &cx, &cy, GetFont()); - sizeText.y = EDIT_HEIGHT_FROM_CHAR_HEIGHT(cy); + sizeCtrl.y = EDIT_HEIGHT_FROM_CHAR_HEIGHT(cy); } - SetInitialSize(size); + SetInitialSize(sizeCtrl); (void)::ShowWindow(GetBuddyHwnd(), SW_SHOW); - // associate the text window with the spin button - (void)::SendMessage(GetHwnd(), UDM_SETBUDDY, (WPARAM)m_hwndBuddy, 0); - // If the initial text value is actually a number, it overrides the // "initial" argument specified later. long initialFromText; @@ -632,7 +618,7 @@ bool wxSpinCtrl::Reparent(wxWindowBase *newParent) // create and initialize the new one if ( !wxSpinButton::Create(GetParent(), GetId(), - rect.GetPosition(), rect.GetSize(), + wxPoint(0, 0), wxSize(0, 0), // it will have a buddy GetWindowStyle(), GetName()) ) return false; @@ -640,14 +626,14 @@ bool wxSpinCtrl::Reparent(wxWindowBase *newParent) 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); + // 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); + return true; } @@ -732,6 +718,21 @@ bool wxSpinCtrl::MSWOnNotify(int WXUNUSED(idCtrl), WXLPARAM lParam, WXLPARAM *re // size calculations // ---------------------------------------------------------------------------- +int wxSpinCtrl::GetOverlap() const +{ + if ( !GetHwnd() ) + { + // We can be called from GetSizeFromTextSize() before the window is + // created and still need to return something reasonable in this case, + // so return the overlap equal to the default border size. + return FromDIP(2); + } + + // The sign here is correct because the button is positioned inside its + // buddy window. + return wxGetWindowRect(m_hwndBuddy).right - wxGetWindowRect(GetHwnd()).left; +} + wxSize wxSpinCtrl::DoGetBestSize() const { return DoGetSizeFromTextSize(DEFAULT_ITEM_WIDTH); @@ -748,7 +749,7 @@ wxSize wxSpinCtrl::DoGetSizeFromTextSize(int xlen, int ylen) const // that's too big. So never use the height calculated // from wxSpinButton::DoGetBestSize(). - wxSize tsize(xlen + sizeBtn.x + MARGIN_BETWEEN + 3*y/10 + 10, + wxSize tsize(xlen + sizeBtn.x - GetOverlap(), EDIT_HEIGHT_FROM_CHAR_HEIGHT(y)); // Check if the user requested a non-standard height. @@ -760,8 +761,11 @@ wxSize wxSpinCtrl::DoGetSizeFromTextSize(int xlen, int ylen) const void wxSpinCtrl::DoMoveWindow(int x, int y, int width, int height) { + // make sure the given width will be the total of both controls + const int overlap = GetOverlap(); int widthBtn = wxSpinButton::DoGetBestSize().x; - int widthText = width - widthBtn - MARGIN_BETWEEN; + int widthText = width - widthBtn + overlap; + if ( widthText < 0 ) { // This can happen during the initial window layout when it's total @@ -775,6 +779,9 @@ void wxSpinCtrl::DoMoveWindow(int x, int y, int width, int height) widthText = 0; } + if ( widthBtn > width ) + widthBtn = width; + // Because both subcontrols are positioned relatively // to the parent which can have different layout direction // then our control, we need to mirror their positions manually. @@ -785,9 +792,7 @@ void wxSpinCtrl::DoMoveWindow(int x, int y, int width, int height) DoMoveSibling(m_hwndBuddy, x, y, widthText, height); // 2) The button window - if ( widthText > 0 ) - x += widthText + MARGIN_BETWEEN; - wxSpinButton::DoMoveWindow(x, y, widthBtn, height); + wxSpinButton::DoMoveWindow(x + widthText - overlap, y, widthBtn, height); } else { @@ -796,8 +801,7 @@ void wxSpinCtrl::DoMoveWindow(int x, int y, int width, int height) wxSpinButton::DoMoveWindow(x, y, widthBtn, height); // 2) The buddy window - x += widthBtn + MARGIN_BETWEEN; - DoMoveSibling(m_hwndBuddy, x, y, widthText, height); + DoMoveSibling(m_hwndBuddy, x + widthBtn - overlap, y, widthText, height); } }