From 07d7dd19f80347c3a4cebe796da27225f22d2be2 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Mon, 30 May 2022 23:38:17 +0100 Subject: [PATCH] Add support for custom radio button controls in wxFileDialog Update the dialogs sample to show using them too. --- include/wx/filedlgcustomize.h | 21 +++++ include/wx/private/filedlgcustomize.h | 8 ++ samples/dialogs/dialogs.cpp | 30 ++++++- src/common/fldlgcmn.cpp | 114 +++++++++++++++++++++++++- src/msw/filedlg.cpp | 106 +++++++++++++++++++++++- 5 files changed, 274 insertions(+), 5 deletions(-) diff --git a/include/wx/filedlgcustomize.h b/include/wx/filedlgcustomize.h index ba13d07850..20b7149632 100644 --- a/include/wx/filedlgcustomize.h +++ b/include/wx/filedlgcustomize.h @@ -15,6 +15,7 @@ class wxFileDialogCustomControlImpl; class wxFileDialogButtonImpl; class wxFileDialogCheckBoxImpl; +class wxFileDialogRadioButtonImpl; class wxFileDialogTextCtrlImpl; class wxFileDialogStaticTextImpl; class wxFileDialogCustomizeImpl; @@ -90,6 +91,25 @@ private: wxDECLARE_NO_COPY_CLASS(wxFileDialogCheckBox); }; +// A class representing a custom radio button. +class WXDLLIMPEXP_CORE wxFileDialogRadioButton : public wxFileDialogCustomControl +{ +public: + bool GetValue() const; + void SetValue(bool value); + + // Ctor is only used by wxWidgets itself. + explicit wxFileDialogRadioButton(wxFileDialogRadioButtonImpl* impl); + +protected: + virtual bool OnDynamicBind(wxDynamicEventTableEntry& entry) wxOVERRIDE; + +private: + wxFileDialogRadioButtonImpl* GetImpl() const; + + wxDECLARE_NO_COPY_CLASS(wxFileDialogRadioButton); +}; + // A class representing a custom text control. class WXDLLIMPEXP_CORE wxFileDialogTextCtrl : public wxFileDialogCustomControl { @@ -130,6 +150,7 @@ class WXDLLIMPEXP_CORE wxFileDialogCustomize public: wxFileDialogButton* AddButton(const wxString& label); wxFileDialogCheckBox* AddCheckBox(const wxString& label); + wxFileDialogRadioButton* AddRadioButton(const wxString& label); wxFileDialogTextCtrl* AddTextCtrl(const wxString& label = wxString()); wxFileDialogStaticText* AddStaticText(const wxString& label); diff --git a/include/wx/private/filedlgcustomize.h b/include/wx/private/filedlgcustomize.h index 5fd2aadfc4..fbd59ccc4a 100644 --- a/include/wx/private/filedlgcustomize.h +++ b/include/wx/private/filedlgcustomize.h @@ -38,6 +38,13 @@ public: virtual void SetValue(bool value) = 0; }; +class wxFileDialogRadioButtonImpl : public wxFileDialogCustomControlImpl +{ +public: + virtual bool GetValue() = 0; + virtual void SetValue(bool value) = 0; +}; + class wxFileDialogTextCtrlImpl : public wxFileDialogCustomControlImpl { public: @@ -60,6 +67,7 @@ class wxFileDialogCustomizeImpl public: virtual wxFileDialogButtonImpl* AddButton(const wxString& label) = 0; virtual wxFileDialogCheckBoxImpl* AddCheckBox(const wxString& label) = 0; + virtual wxFileDialogRadioButtonImpl* AddRadioButton(const wxString& label) = 0; virtual wxFileDialogTextCtrlImpl* AddTextCtrl(const wxString& label) = 0; virtual wxFileDialogStaticTextImpl* AddStaticText(const wxString& label) = 0; diff --git a/samples/dialogs/dialogs.cpp b/samples/dialogs/dialogs.cpp index 5f43f3c362..25b8014d3f 100644 --- a/samples/dialogs/dialogs.cpp +++ b/samples/dialogs/dialogs.cpp @@ -1605,7 +1605,8 @@ public: MyExtraPanel(wxWindow *parent); wxString GetInfo() const { - return wxString::Format("checkbox=%d, text=\"%s\"", m_checked, m_str); + return wxString::Format("paper=%s, enabled=%d, text=\"%s\"", + m_paperSize, m_checked, m_str); } private: @@ -1615,6 +1616,16 @@ private: m_btn->Enable(m_checked); } + void OnRadioButton(wxCommandEvent& event) + { + if ( event.GetEventObject() == m_radioA4 ) + m_paperSize = "A4"; + else if ( event.GetEventObject() == m_radioLetter ) + m_paperSize = "Letter"; + else + m_paperSize = "Unknown"; + } + void OnText(wxCommandEvent& event) { m_str = event.GetString(); @@ -1632,9 +1643,12 @@ private: wxString m_str; bool m_checked; + wxString m_paperSize; wxButton *m_btn; wxCheckBox *m_cb; + wxRadioButton *m_radioA4; + wxRadioButton *m_radioLetter; wxStaticText *m_label; wxTextCtrl *m_text; }; @@ -1648,6 +1662,11 @@ MyExtraPanel::MyExtraPanel(wxWindow *parent) m_btn->Enable(false); m_cb = new wxCheckBox(this, -1, "Enable Custom Button"); m_cb->Bind(wxEVT_CHECKBOX, &MyExtraPanel::OnCheckBox, this); + m_radioA4 = new wxRadioButton(this, wxID_ANY, "A4", + wxDefaultPosition, wxDefaultSize, wxRB_GROUP); + m_radioA4->Bind(wxEVT_RADIOBUTTON, &MyExtraPanel::OnRadioButton, this); + m_radioLetter = new wxRadioButton(this, wxID_ANY, "Letter"); + m_radioLetter->Bind(wxEVT_RADIOBUTTON, &MyExtraPanel::OnRadioButton, this); m_label = new wxStaticText(this, wxID_ANY, "Nothing selected"); m_label->Bind(wxEVT_UPDATE_UI, &MyExtraPanel::OnUpdateLabelUI, this); @@ -1660,6 +1679,8 @@ MyExtraPanel::MyExtraPanel(wxWindow *parent) wxSizerFlags().Centre().Border()); sizerTop->Add(m_text, wxSizerFlags(1).Centre().Border()); sizerTop->AddSpacer(10); + sizerTop->Add(m_radioA4, wxSizerFlags().Centre().Border()); + sizerTop->Add(m_radioLetter, wxSizerFlags().Centre().Border()); sizerTop->Add(m_cb, wxSizerFlags().Centre().Border()); sizerTop->AddSpacer(5); sizerTop->Add(m_btn, wxSizerFlags().Centre().Border()); @@ -1695,6 +1716,8 @@ public: // ShowModal() returns, TransferDataFromCustomControls() is the latest // moment when they can still be used. m_text = customizer.AddTextCtrl("Just some extra text:"); + m_radioA4 = customizer.AddRadioButton("A4"); + m_radioLetter = customizer.AddRadioButton("Letter"); m_cb = customizer.AddCheckBox("Enable Custom Button"); m_cb->Bind(wxEVT_CHECKBOX, &MyCustomizeHook::OnCheckBox, this); m_btn = customizer.AddButton("Custom Button"); @@ -1715,7 +1738,8 @@ public: // And another one called when the dialog is accepted. virtual void TransferDataFromCustomControls() wxOVERRIDE { - m_info.Printf("checkbox=%d, text=\"%s\"", + m_info.Printf("paper=%s, enabled=%d, text=\"%s\"", + m_radioA4->GetValue() ? "A4" : "Letter", m_cb->GetValue(), m_text->GetValue()); } @@ -1740,6 +1764,8 @@ private: wxFileDialogButton* m_btn; wxFileDialogCheckBox* m_cb; + wxFileDialogRadioButton* m_radioA4; + wxFileDialogRadioButton* m_radioLetter; wxFileDialogTextCtrl* m_text; wxFileDialogStaticText* m_label; diff --git a/src/common/fldlgcmn.cpp b/src/common/fldlgcmn.cpp index 9db33db0f8..496c409da8 100644 --- a/src/common/fldlgcmn.cpp +++ b/src/common/fldlgcmn.cpp @@ -27,6 +27,7 @@ #include "wx/button.h" #include "wx/checkbox.h" + #include "wx/radiobut.h" #include "wx/stattext.h" #include "wx/textctrl.h" #endif // WX_PRECOMP @@ -143,6 +144,34 @@ void wxFileDialogCheckBox::SetValue(bool value) GetImpl()->SetValue(value); } +wxFileDialogRadioButton::wxFileDialogRadioButton(wxFileDialogRadioButtonImpl* impl) + : wxFileDialogCustomControl(impl) +{ +} + +bool wxFileDialogRadioButton::OnDynamicBind(wxDynamicEventTableEntry& entry) +{ + if ( entry.m_eventType == wxEVT_RADIOBUTTON ) + return GetImpl()->DoBind(this); + + return wxFileDialogCustomControl::OnDynamicBind(entry); +} + +wxFileDialogRadioButtonImpl* wxFileDialogRadioButton::GetImpl() const +{ + return static_cast(m_impl); +} + +bool wxFileDialogRadioButton::GetValue() const +{ + return GetImpl()->GetValue(); +} + +void wxFileDialogRadioButton::SetValue(bool value) +{ + GetImpl()->SetValue(value); +} + wxFileDialogTextCtrl::wxFileDialogTextCtrl(wxFileDialogTextCtrlImpl* impl) : wxFileDialogCustomControl(impl) { @@ -222,6 +251,12 @@ wxFileDialogCustomize::AddCheckBox(const wxString& label) return StoreAndReturn(new wxFileDialogCheckBox(m_impl->AddCheckBox(label))); } +wxFileDialogRadioButton* +wxFileDialogCustomize::AddRadioButton(const wxString& label) +{ + return StoreAndReturn(new wxFileDialogRadioButton(m_impl->AddRadioButton(label))); +} + wxFileDialogTextCtrl* wxFileDialogCustomize::AddTextCtrl(const wxString& label) { @@ -374,6 +409,58 @@ private: wxEvtHandler* m_handler; }; +class RadioButtonImpl : public ControlImplBase +{ +public: + RadioButtonImpl(wxWindow* parent, const wxString& label) + : ControlImplBase + ( + new wxRadioButton(parent, wxID_ANY, label) + ) + { + m_handler = NULL; + } + + virtual bool GetValue() wxOVERRIDE + { + return GetRadioButton()->GetValue(); + } + + virtual void SetValue(bool value) wxOVERRIDE + { + GetRadioButton()->SetValue(value); + } + + virtual bool DoBind(wxEvtHandler* handler) wxOVERRIDE + { + if ( !m_handler ) + { + m_handler = handler; + m_win->Bind(wxEVT_RADIOBUTTON, &RadioButtonImpl::OnRadioButton, this); + } + + return true; + } + +private: + wxRadioButton* GetRadioButton() const + { + return static_cast(m_win); + } + + void OnRadioButton(wxCommandEvent& event) + { + // See comments in OnButton() above, they also apply here. + + wxCommandEvent eventCopy(event); + eventCopy.SetEventObject(m_handler); + + m_handler->ProcessEvent(eventCopy); + } + + wxEvtHandler* m_handler; +}; + class TextCtrlImpl : public ControlImplBase { public: @@ -441,7 +528,8 @@ public: Panel(wxWindow* parent, wxFileDialogCustomizeHook& customizeHook) : wxPanel(parent), wxFileDialogCustomize(this), - m_customizeHook(customizeHook) + m_customizeHook(customizeHook), + m_lastWasRadio(false) { // Use a simple horizontal sizer to layout all the controls for now. wxBoxSizer* const sizer = new wxBoxSizer(wxHORIZONTAL); @@ -467,16 +555,36 @@ public: // Implement wxFileDialogCustomizeImpl pure virtual methods. wxFileDialogButtonImpl* AddButton(const wxString& label) wxOVERRIDE { + m_lastWasRadio = false; + return AddToLayoutAndReturn(label); } wxFileDialogCheckBoxImpl* AddCheckBox(const wxString& label) wxOVERRIDE { + m_lastWasRadio = false; + return AddToLayoutAndReturn(label); } + wxFileDialogRadioButtonImpl* AddRadioButton(const wxString& label) wxOVERRIDE + { + RadioButtonImpl* const impl = AddToLayoutAndReturn(label); + if ( !m_lastWasRadio ) + { + // Select the first button of a new radio group. + impl->SetValue(true); + + m_lastWasRadio = true; + } + + return impl; + } + wxFileDialogTextCtrlImpl* AddTextCtrl(const wxString& label) wxOVERRIDE { + m_lastWasRadio = false; + if ( !label.empty() ) { AddToLayout(new wxStaticText(this, wxID_ANY, label)); @@ -487,6 +595,8 @@ public: wxFileDialogStaticTextImpl* AddStaticText(const wxString& label) wxOVERRIDE { + m_lastWasRadio = false; + return AddToLayoutAndReturn(label); } @@ -509,6 +619,8 @@ private: wxFileDialogCustomizeHook& m_customizeHook; + bool m_lastWasRadio; + wxDECLARE_NO_COPY_CLASS(Panel); }; diff --git a/src/msw/filedlg.cpp b/src/msw/filedlg.cpp index bb16f82352..8872c1d4ae 100644 --- a/src/msw/filedlg.cpp +++ b/src/msw/filedlg.cpp @@ -57,6 +57,7 @@ #include "wx/button.h" #include "wx/checkbox.h" + #include "wx/radiobut.h" #include "wx/stattext.h" #include "wx/textctrl.h" @@ -332,6 +333,47 @@ public: } }; +class wxFileDialogRadioButtonImplFDC + : public wxFileDialogImplFDC +{ +public: + wxFileDialogRadioButtonImplFDC(IFileDialogCustomize* fdc, DWORD id, DWORD item) + : wxFileDialogImplFDC(fdc, id), + m_item(item) + { + } + + virtual bool GetValue() wxOVERRIDE + { + DWORD selected = 0; + HRESULT hr = m_fdc->GetSelectedControlItem(m_id, &selected); + if ( FAILED(hr) ) + wxLogApiError(wxS("IFileDialogCustomize::GetSelectedControlItem"), hr); + + return selected == m_item; + } + + virtual void SetValue(bool value) wxOVERRIDE + { + // We can't implement it using the available API and this shouldn't be + // ever needed anyhow. + wxCHECK_RET( value, wxS("clearing radio buttons not supported") ); + + HRESULT hr = m_fdc->SetSelectedControlItem(m_id, m_item); + if ( FAILED(hr) ) + wxLogApiError(wxS("IFileDialogCustomize::SetSelectedControlItem"), hr); + } + + virtual bool DoBind(wxEvtHandler* WXUNUSED(handler)) wxOVERRIDE + { + // We don't need to do anything special to get the events here. + return true; + } + +private: + const DWORD m_item; +}; + class wxFileDialogTextCtrlImplFDC : public wxFileDialogImplFDC { @@ -391,7 +433,8 @@ public: : wxFileDialogCustomize(this) { m_lastId = - m_lastAuxId = 0; + m_lastAuxId = + m_radioListId = 0; } bool Initialize(IFileDialog* fileDialog) @@ -424,6 +467,8 @@ public: // Implement wxFileDialogCustomizeImpl pure virtual methods. wxFileDialogButtonImpl* AddButton(const wxString& label) wxOVERRIDE { + m_radioListId = 0; + HRESULT hr = m_fdc->AddPushButton(++m_lastId, label.wc_str()); if ( FAILED(hr) ) { @@ -436,6 +481,8 @@ public: wxFileDialogCheckBoxImpl* AddCheckBox(const wxString& label) wxOVERRIDE { + m_radioListId = 0; + HRESULT hr = m_fdc->AddCheckButton(++m_lastId, label.wc_str(), FALSE); if ( FAILED(hr) ) { @@ -446,8 +493,45 @@ public: return new wxFileDialogCheckBoxImplFDC(m_fdc, m_lastId); } + wxFileDialogRadioButtonImpl* AddRadioButton(const wxString& label) wxOVERRIDE + { + HRESULT hr; + + bool firstButton = false; + if ( !m_radioListId ) + { + hr = m_fdc->AddRadioButtonList(--m_lastAuxId); + if ( FAILED(hr) ) + { + wxLogApiError(wxS("IFileDialogCustomize::AddRadioButtonList"), hr); + return NULL; + } + + m_radioListId = m_lastAuxId; + firstButton = true; + } + + hr = m_fdc->AddControlItem(m_radioListId, ++m_lastId, label.wc_str()); + if ( FAILED(hr) ) + { + wxLogApiError(wxS("IFileDialogCustomize::AddControlItem"), hr); + return NULL; + } + + wxFileDialogRadioButtonImplFDC* const + impl = new wxFileDialogRadioButtonImplFDC(m_fdc, m_radioListId, m_lastId); + + // Select the first button of a new radio group. + if ( firstButton ) + impl->SetValue(true); + + return impl; + } + wxFileDialogTextCtrlImpl* AddTextCtrl(const wxString& label) wxOVERRIDE { + m_radioListId = 0; + HRESULT hr; if ( !label.empty() ) @@ -476,6 +560,8 @@ public: wxFileDialogStaticTextImpl* AddStaticText(const wxString& label) wxOVERRIDE { + m_radioListId = 0; + HRESULT hr = m_fdc->AddText(++m_lastId, label.wc_str()); if ( FAILED(hr) ) { @@ -497,6 +583,10 @@ private: // IDs used for any other controls, they're negative (which means they // decrement from USHORT_MAX down). DWORD m_lastAuxId; + + // ID of the current radio button list, i.e. the one to which the next call + // to AddRadioButton() would add a radio button. 0 if none. + DWORD m_radioListId; }; #endif // wxUSE_IFILEOPENDIALOG @@ -638,8 +728,20 @@ public: wxSTDMETHODIMP OnItemSelected(IFileDialogCustomize*, DWORD WXUNUSED(dwIDCtl), - DWORD WXUNUSED(dwIDItem)) wxOVERRIDE + DWORD dwIDItem) wxOVERRIDE { + // Note that we don't use dwIDCtl here because we use unique item IDs + // for all controls. + if ( wxFileDialogCustomControl* const + control = m_customize.FindControl(dwIDItem) ) + { + wxCommandEvent event(wxEVT_RADIOBUTTON, dwIDItem); + event.SetEventObject(control); + event.SetInt(true); // Ensure IsChecked() returns true. + + control->SafelyProcessEvent(event); + } + return S_OK; }