From 573e4fa0ecf2f8197f416fd899675bd875320135 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Wed, 8 Sep 2021 19:38:28 +0100 Subject: [PATCH] Allow using custom main window in wxComboCtrl Add wxComboCtrl::SetMainControl() which can be used to use some other window instead of the default wxTextCtrl as the main window of the combo control. Update the sample and the documentation to show the new function. --- include/wx/combo.h | 10 ++++- interface/wx/combo.h | 41 ++++++++++++++++++ samples/combo/combo.cpp | 26 +++++++++++- src/common/combocmn.cpp | 92 +++++++++++++++++++++++++++-------------- 4 files changed, 137 insertions(+), 32 deletions(-) diff --git a/include/wx/combo.h b/include/wx/combo.h index aefae67cb8..c9910e8486 100644 --- a/include/wx/combo.h +++ b/include/wx/combo.h @@ -197,6 +197,10 @@ public: // get the popup window containing the popup control wxWindow *GetPopupWindow() const { return m_winPopup; } + // Set the control to use instead of the default text control for the main + // (always visible) part of the combobox. + void SetMainControl(wxWindow* win); + // Get the text control which is part of the combobox. wxTextCtrl *GetTextCtrl() const { return m_text; } @@ -623,8 +627,12 @@ protected: // This is used when control is unfocused and m_valueString is empty wxString m_hintText; - // the text control and button we show all the time + // This pointer is non-null if we use a text control, and not some other + // window, as the main control. wxTextCtrl* m_text; + + // the window and button we show all the time + wxWindow* m_mainWindow; wxWindow* m_btn; // wxPopupWindow or similar containing the window managed by the interface. diff --git a/interface/wx/combo.h b/interface/wx/combo.h index bbce09b047..c772aec966 100644 --- a/interface/wx/combo.h +++ b/interface/wx/combo.h @@ -751,6 +751,47 @@ public: */ virtual void SetInsertionPointEnd(); + /** + Uses the given window instead of the default text control as the main + window of the combo control. + + By default, combo controls without @c wxCB_READONLY style create a + wxTextCtrl which shows the current value and allows to edit it. This + method allows to use some other window instead of this wxTextCtrl. + + This method can be called after creating the combo fully, however in + this case a wxTextCtrl is unnecessarily created just to be immediately + destroyed when it's replaced by a custom window. If you wish to avoid + this, you can use the following approach, also shown in the combo + sample: + + @code + // Create the combo control using its default ctor. + wxComboCtrl* combo = new wxComboCtrl(); + + // Create the custom main control using its default ctor too. + SomeWindow* main = new SomeWindow(); + + // Set the custom main control before creating the combo. + combo->SetMainControl(main); + + // And only create it now: wxTextCtrl won't be unnecessarily + // created because the combo already has a main window. + combo->Create(panel, wxID_ANY, wxEmptyString); + + // Finally create the main window itself, now that its parent was + // created. + main->Create(combo, ...); + @endcode + + Note that when a custom main window is used, none of the methods of + this class inherited from wxTextEntry, such as SetValue(), Replace(), + SetInsertionPoint() etc do anything and simply return. + + @since 3.1.6 + */ + void SetMainControl(wxWindow* win); + //@{ /** Attempts to set the control margins. When margins are given as wxPoint, diff --git a/samples/combo/combo.cpp b/samples/combo/combo.cpp index 9c4d86c450..cddbc62ce4 100644 --- a/samples/combo/combo.cpp +++ b/samples/combo/combo.cpp @@ -884,7 +884,7 @@ MyFrame::MyFrame(const wxString& title) // rowSizer = new wxBoxSizer( wxHORIZONTAL ); rowSizer->Add( new wxStaticText(panel,wxID_ANY, - "wxComboCtrl with custom button action:"), 1, + "wxComboCtrl with custom button and custom main control:"), 1, wxALIGN_CENTER_VERTICAL|wxRIGHT, 4 ); @@ -898,7 +898,31 @@ MyFrame::MyFrame(const wxString& title) (long)0 ); + // This is a perfectly useless control, as the popup and main control + // don't interact with each other, but it shows that we can use something + // other than wxTextCtrl for the main part of wxComboCtrl too. + // + // In a real program, custom popup and main control would work together, + // i.e. changing selection in one of them would update the other one. + // + // Also note the complicated dance we need to go through to create the + // controls in the right order: we want to create the custom main control + // before actually creating the wxComboCtrl window, as otherwise it would + // unnecessarily create a wxTextCtrl by default, forcing us to use its + // default ctor and Create() later, but this, in turn, also requires using + // default ctor for the main control and creating it later too, as it can't + // be created before its parent window is. + wxComboCtrl* comboCustom = new wxComboCtrl(); + wxCheckBox* cbox = new wxCheckBox(); + comboCustom->SetMainControl(cbox); + comboCustom->Create(panel, wxID_ANY, wxEmptyString); + cbox->Create(comboCustom, wxID_ANY, "Checkbox as main control"); + cbox->SetBackgroundColour(*wxWHITE); + + comboCustom->SetPopupControl(new ListViewComboPopup()); + rowSizer->Add( fsc, 1, wxALIGN_CENTER_VERTICAL|wxALL, 4 ); + rowSizer->Add( comboCustom, 1, wxALIGN_CENTER_VERTICAL|wxALL, 4 ); colSizer->Add( rowSizer, 0, wxEXPAND|wxALL, 5 ); diff --git a/src/common/combocmn.cpp b/src/common/combocmn.cpp index 02f082a9fc..307240e424 100644 --- a/src/common/combocmn.cpp +++ b/src/common/combocmn.cpp @@ -748,6 +748,7 @@ void wxComboCtrlBase::Init() m_popupWinState = Hidden; m_btn = NULL; m_text = NULL; + m_mainWindow = NULL; m_popupInterface = NULL; #if !wxUSE_POPUPWIN @@ -825,9 +826,34 @@ bool wxComboCtrlBase::Create(wxWindow *parent, return true; } +void +wxComboCtrlBase::SetMainControl(wxWindow* win) +{ + // We can't have both a custom window and a text control, so get rid of the + // latter if we have it. + if ( m_text ) + { + m_text->Destroy(); + + // Note that we currently always set it to NULL, even if the custom + // window is a (subclass of) wxTextCtrl because our m_text must be a + // wxComboCtrlTextCtrl for things to work correctly. + m_text = NULL; + } + + // We don't do anything with the previous main window, if any, it's the + // caller's responsibility to delete or hide it, as needed. + m_mainWindow = win; +} + void wxComboCtrlBase::CreateTextCtrl(int style) { + // If we're using a custom main window explicitly set using + // SetMainControl(), don't recreate it and just keep using it. + if ( m_mainWindow && !m_text ) + return; + if ( !(m_windowStyle & wxCB_READONLY) ) { if ( m_text ) @@ -843,6 +869,8 @@ wxComboCtrlBase::CreateTextCtrl(int style) style |= wxTE_PROCESS_ENTER; m_text = new wxComboCtrlTextCtrl(); + m_mainWindow = m_text; + m_text->Create(this, wxID_ANY, m_valueString, wxDefaultPosition, wxSize(10,-1), style); @@ -1036,7 +1064,7 @@ void wxComboCtrlBase::CalculateAreas( int btnWidth ) m_tcArea.height = sz.y - ((m_widthCustomBorder+FOCUS_RING)*2); /* - if ( m_text ) + if ( m_mainWindow ) { ::wxMessageBox(wxString::Format(wxT("ButtonArea (%i,%i,%i,%i)\n"),m_btnArea.x,m_btnArea.y,m_btnArea.width,m_btnArea.height) + wxString::Format(wxT("TextCtrlArea (%i,%i,%i,%i)"),m_tcArea.x,m_tcArea.y,m_tcArea.width,m_tcArea.height)); @@ -1046,12 +1074,14 @@ void wxComboCtrlBase::CalculateAreas( int btnWidth ) void wxComboCtrlBase::PositionTextCtrl( int textCtrlXAdjust, int textCtrlYAdjust ) { - if ( !m_text ) + if ( !m_mainWindow || !m_mainWindow->GetHandle() ) return; wxSize sz = GetClientSize(); - if ( (m_text->GetWindowStyleFlag() & wxBORDER_MASK) == wxNO_BORDER ) + // This function actually positions any main window, not just a text + // control, but it only does any special adjustments for m_text. + if ( m_text && (m_text->GetWindowStyleFlag() & wxBORDER_MASK) == wxNO_BORDER ) { int x; @@ -1096,10 +1126,11 @@ void wxComboCtrlBase::PositionTextCtrl( int textCtrlXAdjust, int textCtrlYAdjust } else { - // If it has border, have textctrl fill the entire text field. + // If the main window has border or is not a text control at all, have + // it fill the entire available space. int w = m_tcArea.width - m_widthCustomPaint; if (w < 0) w = 0; - m_text->SetSize( m_tcArea.x + m_widthCustomPaint, + m_mainWindow->SetSize( m_tcArea.x + m_widthCustomPaint, m_tcArea.y, w, m_tcArea.height ); @@ -1108,7 +1139,8 @@ void wxComboCtrlBase::PositionTextCtrl( int textCtrlXAdjust, int textCtrlYAdjust wxSize wxComboCtrlBase::DoGetBestSize() const { - int width = m_text ? m_text->GetBestSize().x : FromDIP(80); + int width = m_mainWindow && m_mainWindow->GetHandle() + ? m_mainWindow->GetBestSize().x : FromDIP(80); return GetSizeFromTextSize(width); } @@ -1229,8 +1261,8 @@ bool wxComboCtrlBase::Enable(bool enable) if ( m_btn ) m_btn->Enable(enable); - if ( m_text ) - m_text->Enable(enable); + if ( m_mainWindow ) + m_mainWindow->Enable(enable); Refresh(); @@ -1245,8 +1277,8 @@ bool wxComboCtrlBase::Show(bool show) if (m_btn) m_btn->Show(show); - if (m_text) - m_text->Show(show); + if (m_mainWindow) + m_mainWindow->Show(show); return true; } @@ -1256,14 +1288,14 @@ bool wxComboCtrlBase::SetFont ( const wxFont& font ) if ( !wxControl::SetFont(font) ) return false; - if ( m_text ) + if ( m_mainWindow ) { // Without hiding the wxTextCtrl there would be some // visible 'flicker' (at least on Windows XP). - m_text->Hide(); - m_text->SetFont(font); + m_mainWindow->Hide(); + m_mainWindow->SetFont(font); OnResize(); - m_text->Show(); + m_mainWindow->Show(); } return true; @@ -1278,12 +1310,12 @@ void wxComboCtrlBase::DoSetToolTip(wxToolTip *tooltip) if ( tooltip ) { const wxString &tip = tooltip->GetTip(); - if ( m_text ) m_text->SetToolTip(tip); + if ( m_mainWindow ) m_mainWindow->SetToolTip(tip); if ( m_btn ) m_btn->SetToolTip(tip); } else { - if ( m_text ) m_text->SetToolTip( NULL ); + if ( m_mainWindow ) m_mainWindow->SetToolTip( NULL ); if ( m_btn ) m_btn->SetToolTip( NULL ); } } @@ -1293,8 +1325,8 @@ bool wxComboCtrlBase::SetForegroundColour(const wxColour& colour) { if ( wxControl::SetForegroundColour(colour) ) { - if ( m_text ) - m_text->SetForegroundColour(colour); + if ( m_mainWindow ) + m_mainWindow->SetForegroundColour(colour); return true; } return false; @@ -1302,8 +1334,8 @@ bool wxComboCtrlBase::SetForegroundColour(const wxColour& colour) bool wxComboCtrlBase::SetBackgroundColour(const wxColour& colour) { - if ( m_text ) - m_text->SetBackgroundColour(colour); + if ( m_mainWindow ) + m_mainWindow->SetBackgroundColour(colour); m_tcBgCol = colour; m_hasTcBgCol = true; return true; @@ -1311,8 +1343,8 @@ bool wxComboCtrlBase::SetBackgroundColour(const wxColour& colour) wxColour wxComboCtrlBase::GetBackgroundColour() const { - if ( m_text ) - return m_text->GetBackgroundColour(); + if ( m_mainWindow ) + return m_mainWindow->GetBackgroundColour(); return m_tcBgCol; } @@ -1824,10 +1856,10 @@ void wxComboCtrlBase::OnFocusEvent( wxFocusEvent& event ) if ( event.GetEventType() == wxEVT_SET_FOCUS ) { - if ( !m_resetFocus && GetTextCtrl() && !GetTextCtrl()->HasFocus() ) + if ( !m_resetFocus && m_mainWindow && !m_mainWindow->HasFocus() ) { m_resetFocus = true; - GetTextCtrl()->SetFocus(); + m_mainWindow->SetFocus(); m_resetFocus = false; } } @@ -1840,8 +1872,8 @@ void wxComboCtrlBase::OnIdleEvent( wxIdleEvent& WXUNUSED(event) ) if ( m_resetFocus ) { m_resetFocus = false; - if ( GetTextCtrl() ) - GetTextCtrl()->SetFocus(); + if ( m_mainWindow ) + m_mainWindow->SetFocus(); } } @@ -2387,14 +2419,14 @@ void wxComboCtrlBase::SetButtonBitmaps( const wxBitmap& bmpNormal, void wxComboCtrlBase::SetCustomPaintWidth( int width ) { - if ( m_text ) + if ( m_mainWindow ) { - // move textctrl accordingly - wxRect r = m_text->GetRect(); + // move the main window accordingly + wxRect r = m_mainWindow->GetRect(); int inc = width - m_widthCustomPaint; r.x += inc; r.width -= inc; - m_text->SetSize( r ); + m_mainWindow->SetSize( r ); } m_widthCustomPaint = width;