From 03a13591b9593f46d99395bcc2d7dae144438de6 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Wed, 11 Jul 2018 01:13:22 +0200 Subject: [PATCH] Add wxDataViewToggleRenderer::ShowAsRadio() This allows showing radio buttons in wxDataViewCtrl easily and natively. Notice that this approach, adding an extra function to the existing renderer class instead of creating some new wxDataViewRadioRenderer (see https://github.com/wxWidgets/wxWidgets/pull/809), was finally chosen because it is simpler to implement and, more importantly, because it will be more natural to generalize if/when we also add a 3-state check/radio renderer. Closes https://github.com/wxWidgets/wxWidgets/pull/853 --- docs/changes.txt | 1 + include/wx/generic/dvrenderers.h | 3 ++ include/wx/gtk/dvrenderers.h | 2 + include/wx/osx/dvrenderers.h | 2 + interface/wx/dataview.h | 21 +++++++++++ samples/dataview/dataview.cpp | 65 ++++++++++++++++++++++++++++++++ src/generic/datavgen.cpp | 12 +++--- src/gtk/dataview.cpp | 5 +++ src/osx/cocoa/dataview.mm | 5 +++ 9 files changed, 111 insertions(+), 5 deletions(-) diff --git a/docs/changes.txt b/docs/changes.txt index 4762e4888a..d64d16eeca 100644 --- a/docs/changes.txt +++ b/docs/changes.txt @@ -94,6 +94,7 @@ All: All (GUI): +- Add wxDataViewToggleRenderer::ShowAsRadio(). - Improve stock items consistency and aesthetics (dhowland). - Fix bug with missing items in overflowing AUI toolbar (Maarten Bent). - Revert to left-aligning wxSpinCtrl contents by default. diff --git a/include/wx/generic/dvrenderers.h b/include/wx/generic/dvrenderers.h index d85bbe0619..1f23fdee3a 100644 --- a/include/wx/generic/dvrenderers.h +++ b/include/wx/generic/dvrenderers.h @@ -132,6 +132,8 @@ public: wxDataViewCellMode mode = wxDATAVIEW_CELL_INERT, int align = wxDVR_DEFAULT_ALIGNMENT ); + void ShowAsRadio() { m_radio = true; } + virtual bool SetValue( const wxVariant &value ) wxOVERRIDE; virtual bool GetValue( wxVariant &value ) const wxOVERRIDE; #if wxUSE_ACCESSIBILITY @@ -149,6 +151,7 @@ public: const wxMouseEvent *mouseEvent) wxOVERRIDE; private: bool m_toggle; + bool m_radio; protected: wxDECLARE_DYNAMIC_CLASS_NO_COPY(wxDataViewToggleRenderer); diff --git a/include/wx/gtk/dvrenderers.h b/include/wx/gtk/dvrenderers.h index bd50a515b2..9d4091d90f 100644 --- a/include/wx/gtk/dvrenderers.h +++ b/include/wx/gtk/dvrenderers.h @@ -107,6 +107,8 @@ public: wxDataViewCellMode mode = wxDATAVIEW_CELL_INERT, int align = wxDVR_DEFAULT_ALIGNMENT ); + void ShowAsRadio(); + bool SetValue( const wxVariant &value ) wxOVERRIDE; bool GetValue( wxVariant &value ) const wxOVERRIDE; diff --git a/include/wx/osx/dvrenderers.h b/include/wx/osx/dvrenderers.h index 0aa10473dc..e9ad8edd79 100644 --- a/include/wx/osx/dvrenderers.h +++ b/include/wx/osx/dvrenderers.h @@ -198,6 +198,8 @@ public: wxDataViewCellMode mode = wxDATAVIEW_CELL_INERT, int align = wxDVR_DEFAULT_ALIGNMENT); + void ShowAsRadio(); + virtual bool MacRender(); virtual void OSXOnCellChanged(NSObject *value, diff --git a/interface/wx/dataview.h b/interface/wx/dataview.h index 22fd11fe88..c673939758 100644 --- a/interface/wx/dataview.h +++ b/interface/wx/dataview.h @@ -2272,6 +2272,9 @@ public: This class is used by wxDataViewCtrl to render toggle controls. + Note that "toggles" can be represented either by check boxes (default) or + radio buttons. + @see wxDataViewCheckIconTextRenderer @library{wxadv} @category{dvc} @@ -2292,6 +2295,24 @@ public: wxDataViewToggleRenderer(const wxString& varianttype = GetDefaultType(), wxDataViewCellMode mode = wxDATAVIEW_CELL_INERT, int align = wxDVR_DEFAULT_ALIGNMENT); + + /** + Switch to using radiobutton-like appearance instead of the default + checkbox-like one. + + By default, this renderer uses checkboxes to represent the boolean + values, but using this method its appearance can be changed to use + radio buttons instead. + + Notice that only the appearance is changed, the cells don't really + start behaving as radio buttons after a call to ShowAsRadio(), i.e. the + application code also needs to react to selecting one of the cells + shown by this renderer and clearing all the other ones in the same row + or column to actually implement radio button-like behaviour. + + @since 3.1.2 + */ + void ShowAsRadio(); }; diff --git a/samples/dataview/dataview.cpp b/samples/dataview/dataview.cpp index 26fbc1dde6..54f3666603 100644 --- a/samples/dataview/dataview.cpp +++ b/samples/dataview/dataview.cpp @@ -108,6 +108,10 @@ private: void OnPrependList(wxCommandEvent& event); void OnDeleteList(wxCommandEvent& event); + + // Third (wxDataViewListCtrl) page. + void OnListValueChanged(wxDataViewEvent& event); + // Fourth page. void OnDeleteTreeItem(wxCommandEvent& event); void OnDeleteAllTreeItems(wxCommandEvent& event); @@ -176,6 +180,9 @@ private: wxLog *m_logOld; private: + // Flag used by OnListValueChanged(), see there. + bool m_eventFromProgram; + wxDECLARE_EVENT_TABLE(); }; @@ -450,6 +457,8 @@ MyFrame::MyFrame(wxFrame *frame, const wxString &title, int x, int y, int w, int m_ctrl[2] = NULL; m_ctrl[3] = NULL; + m_eventFromProgram = false; + SetIcon(wxICON(sample)); @@ -759,6 +768,17 @@ void MyFrame::BuildDataViewCtrl(wxPanel* parent, unsigned int nPanel, unsigned l page2_model->DecRef(); lc->AppendToggleColumn( "Toggle" ); + + // We're not limited to convenience column-appending functions, it + // can also be done fully manually, which allows us to customize + // the renderer being used. + wxDataViewToggleRenderer* const rendererRadio = + new wxDataViewToggleRenderer("bool", wxDATAVIEW_CELL_ACTIVATABLE); + rendererRadio->ShowAsRadio(); + wxDataViewColumn* const colRadio = + new wxDataViewColumn("Radio", rendererRadio, 1); + lc->AppendColumn(colRadio, "bool"); + lc->AppendTextColumn( "Text" ); lc->AppendProgressColumn( "Progress" ); @@ -767,11 +787,14 @@ void MyFrame::BuildDataViewCtrl(wxPanel* parent, unsigned int nPanel, unsigned l { data.clear(); data.push_back( (i%3) == 0 ); + data.push_back( i == 7 ); // select a single (random) radio item data.push_back( wxString::Format("row %d", i) ); data.push_back( long(5*i) ); lc->AppendItem( data ); } + + lc->Bind(wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, &MyFrame::OnListValueChanged, this); } break; @@ -1390,6 +1413,48 @@ void MyFrame::OnShowAttributes(wxCommandEvent& WXUNUSED(event)) m_attributes->SetHidden(false); } +// ---------------------------------------------------------------------------- +// MyFrame - event handlers for the third (wxDataViewListCtrl) page +// ---------------------------------------------------------------------------- + +void MyFrame::OnListValueChanged(wxDataViewEvent& event) +{ + // Ignore changes coming from our own SetToggleValue() calls below. + if ( m_eventFromProgram ) + { + m_eventFromProgram = false; + return; + } + + wxDataViewListCtrl* const lc = static_cast(m_ctrl[2]); + + const int columnToggle = 1; + + // Handle selecting a radio button by unselecting all the other ones. + if ( event.GetColumn() == columnToggle ) + { + const int rowChanged = lc->ItemToRow(event.GetItem()); + if ( lc->GetToggleValue(rowChanged, columnToggle) ) + { + for ( int row = 0; row < lc->GetItemCount(); ++row ) + { + if ( row != rowChanged ) + { + m_eventFromProgram = true; + lc->SetToggleValue(false, row, columnToggle); + } + } + } + else // The item was cleared. + { + // Explicitly check it back, we want to always have exactly one + // checked radio item in this column. + m_eventFromProgram = true; + lc->SetToggleValue(true, rowChanged, columnToggle); + } + } +} + // ---------------------------------------------------------------------------- // MyFrame - event handlers for the fourth page // ---------------------------------------------------------------------------- diff --git a/src/generic/datavgen.cpp b/src/generic/datavgen.cpp index 5c45bd7409..719f6ee122 100644 --- a/src/generic/datavgen.cpp +++ b/src/generic/datavgen.cpp @@ -1260,6 +1260,7 @@ wxDataViewToggleRenderer::wxDataViewToggleRenderer( const wxString &varianttype, wxDataViewRenderer( varianttype, mode, align ) { m_toggle = false; + m_radio = false; } bool wxDataViewToggleRenderer::SetValue( const wxVariant &value ) @@ -1301,11 +1302,12 @@ bool wxDataViewToggleRenderer::Render( wxRect cell, wxDC *dc, int WXUNUSED(state size.IncTo(GetSize()); cell.SetSize(size); - wxRendererNative::Get().DrawCheckBox( - GetOwner()->GetOwner(), - *dc, - cell, - flags ); + wxRendererNative& renderer = wxRendererNative::Get(); + wxWindow* const win = GetOwner()->GetOwner(); + if ( m_radio ) + renderer.DrawRadioBitmap(win, *dc, cell, flags); + else + renderer.DrawCheckBox(win, *dc, cell, flags); return true; } diff --git a/src/gtk/dataview.cpp b/src/gtk/dataview.cpp index 2715737b86..71f9774591 100644 --- a/src/gtk/dataview.cpp +++ b/src/gtk/dataview.cpp @@ -2539,6 +2539,11 @@ wxDataViewToggleRenderer::wxDataViewToggleRenderer( const wxString &varianttype, SetAlignment(align); } +void wxDataViewToggleRenderer::ShowAsRadio() +{ + gtk_cell_renderer_toggle_set_radio(GTK_CELL_RENDERER_TOGGLE(m_renderer), TRUE); +} + bool wxDataViewToggleRenderer::SetValue( const wxVariant &value ) { bool tmp = value; diff --git a/src/osx/cocoa/dataview.mm b/src/osx/cocoa/dataview.mm index 9428747a3c..f70eaae314 100644 --- a/src/osx/cocoa/dataview.mm +++ b/src/osx/cocoa/dataview.mm @@ -3326,6 +3326,11 @@ wxDataViewToggleRenderer::wxDataViewToggleRenderer(const wxString& varianttype, [cell release]; } +void wxDataViewToggleRenderer::ShowAsRadio() +{ + [GetNativeData()->GetItemCell() setButtonType:NSRadioButton]; +} + bool wxDataViewToggleRenderer::MacRender() { [GetNativeData()->GetItemCell() setIntValue:GetValue().GetLong()];