Button in PropertyGrid

Is there a way to set a property as a button or add a button to a property? Looking through the grid pattern, I noticed that you can do something like

wxPGEditor* editorButton = wxPropertyGrid::RegisterEditorClass(someButton); propertyGrid->SetPropertyEditor(wxT("Name"), editorButton ); 

However, I would like to have different buttons, and not just one type that will support.

EDIT : By using different buttons, I mean buttons that do not need to be copied using the editor. What I would like to do is set the onClick callback function, as usual for wxButton, but set it as a property.

EDIT2 . To be clear, I would like the property field to be just a button by itself.

+4
source share
1 answer

You need to create a custom wxPGEditor that passes the button event based on the data stored in the property. WxPGProperty can store user data ( wxClientData ) that can be used to store the callback function.

First you must determine the signature of the function handler that will be called in the button event.

 typedef bool (wxEvtHandler::*ButtonEventMethod)(wxPGProperty*); 

The handler receives the attached property as a parameter and should return true if the property has been changed.

Now, to store the callback, a class based on wxClientData is required:

 struct ButtonData : wxClientData { ButtonData(ButtonEventMethod method, wxEvtHandler* handler) : _method(method), _handler(handler) {} bool call(wxPGProperty* property) { return (*_handler.*_method)(property); } private: ButtonEventMethod _method; wxEvtHandler* _handler; }; 

This class simply stores the address of the method and the class to which it belongs. The editor will extract this object from the property and execute the call() method, which, in turn, performs a callback:

 class ButtonEventEditor : public wxPGTextCtrlAndButtonEditor { protected: virtual bool OnEvent(wxPropertyGrid* propgrid, wxPGProperty* property, wxWindow* wnd_primary, wxEvent& event) const { // handle the button event if( event.GetEventType() == wxEVT_COMMAND_BUTTON_CLICKED ) // extract the client data from the property if( ButtonData* btn = dynamic_cast<ButtonData*>(property->GetClientObject()) ) // call the method return btn->call(property); return wxPGTextCtrlAndButtonEditor::OnEvent(propgrid,property,wnd_primary,event); } }; 

To wrap it, you can write a function to “bind” a method to a property:

 void BindButton(wxPGProperty* property, ButtonEventMethod method, wxEvtHandler* handler) { property->SetClientObject(new ButtonData(method,handler)); } 

And a macro to make it even easier:

 #define BIND_BUTTON(property,method,handler,editor) \ property->SetEditor(editor); \ BindButton(property,static_cast<ButtonEventMethod>(method),handler) 

Now you can bind member functions to properties:

 // register the editor wxPGEditor* editor = propertyGrid->RegisterEditorClass(new ButtonEventEditor()); // create a property wxPGProperty* prop = propertyGrid->Append(new wxStringProperty("StringProperty")); // bind method foo to the property BIND_BUTTON(prop,&Frame::foo,this,editor); 

And the foo method might look like this:

 bool Frame::foo(wxPGProperty* p) { p->SetValue("foo"); return true; } 

This is just an example demonstrating how wxPGProperty can store a callback (instead of creating multiple wxPGEditors). The callback store (here ButtonData) can be changed to store std::function or boost::function (which would require C ++ 11 or boost).

An alternative approach would be to store the callback information in an instance of wxPGEditor. However, this will require several instances of the editor, one for each callback function.


UPDATE

the actual "button" does not appear until the line field is clicked

The editor is not activated until a property is selected. You will need to create a custom wxPGProperty that returns a custom wxPGCellRenderer to take control of the property view (until it is edited). Unfortunately, this would create an illusion and require the user to double-click the button: first to activate the property (and the editor), and then the actual button. One solution that comes to mind is to either display text of type Click to edit in the cell, or a brief description of the value of the property.

Although it is possible that a custom property creates a button and ignores the editor system, the property grid is not designed to work this way, and this “hacking” can cause some problems. However, I added that hacking at the end.

there is a string field when I just want the button to occupy the whole property

This is just a side effect of the editor that I used as the base class for the example, and it's easy to change. Since wxTextCtrl is not required, just install the editor on wxPGEditor directly and create the controls yourself (one button):

 class ButtonEventEditor : public wxPGEditor { protected: virtual wxPGWindowList CreateControls(wxPropertyGrid* propgrid, wxPGProperty* property, const wxPoint& pos, const wxSize& size) const { // create and return a single button to be used as editor // size and pos represent the entire value cell: use that to position the button return wxPGWindowList(new wxButton(propgrid,wxPG_SUBID1,"Edit",pos,size)); } // since the editor does not need to change the primary control (the button) // to reflect changes, UpdateControl is just a no-op virtual void UpdateControl(wxPGProperty* property, wxWindow* ctrl) const {} // and here we remove the call to the base class because it is abstract virtual bool OnEvent(wxPropertyGrid* propgrid, wxPGProperty* property, wxWindow* wnd_primary, wxEvent& event) const { if( event.GetEventType() == wxEVT_COMMAND_BUTTON_CLICKED ) if( ButtonData* btn = dynamic_cast<ButtonData*>(property->GetClientObject()) ) return btn->call(property); return false; } }; 

with this method, it seems that there can only be one property editor

If you mean total: wxPropertyGrid::RegisterEditorClass just registers the editor with the property grid (the grid goes into the editor’s property and automatically deletes it when the property grid is destroyed). You can register as many editors as you want. You set the editor for a specific property, not for the entire property grid.

If you mean at the same time: yes, unfortunately, only one property editor can be active at any given time.


Hack

Let me finish this with the hack that I already mentioned.

First we need a custom wxPGCellRenderer to put the button in the property grid:

 class ButtonMover : public wxPGCellRenderer { public: // pointer to the button from the property ButtonMover(wxButton* btn) : _btn(btn) {} protected: virtual bool Render(wxDC &dc, const wxRect &rect, const wxPropertyGrid *propertyGrid, wxPGProperty *property, int column, int item, int flags) const { if( column == 0 ) { // 0 = label, 1 = value // instead of actually drawing the cell, // move the button to the cell position: wxRect rc(rect); // calculate the full property width rc.SetWidth(propertyGrid->GetClientRect().width-rect.GetX()); _btn->SetSize(rc); // move button _btn->Show(); // initially hidden, show once 'rendered' (moved) } return true; } private: wxButton* _btn; }; 

Now we can create a custom property:

 class ButtonProperty : public wxPGProperty { public: // [parent] should be the property grid // [func] is the event handler // [button] is the button label // [label] is the property display name (sort name with autosort) // [name] is the internal property name ButtonProperty(wxWindow* parent, wxObjectEventFunction func, const wxString& button, const wxString& label=wxPG_LABEL, const wxString& name=wxPG_LABEL) : wxPGProperty(label,name), _btn(new wxButton(parent,wxID_ANY,button)), _renderer(_btn) { // connect the handler to the button _btn->Connect(wxEVT_COMMAND_BUTTON_CLICKED,func); _btn->Hide(); // when it off the grid, it not rendered // (thus not moved properly) } protected: virtual wxPGCellRenderer* GetCellRenderer(int column) const { return &_renderer; // return button mover } virtual const wxPGEditor* DoGetEditorClass () const { return 0; // not using an editor } private: wxButton* _btn; // the button attached to the property mutable ButtonMover _renderer; // the button mover }; 

By removing the need for an editor, you can now bind an event handler to a property:

 propertyGrid->Append( new ButtonProperty( propertyGrid, // parent window wxCommandEventHandler(Frame::OnClick1), // event handler "Click me!") // button label ); propertyGrid->Append( new ButtonProperty( propertyGrid, wxCommandEventHandler(Frame::OnClick2), "Edit this!") ); 

And the handlers will look like this:

 void OnClick1(wxCommandEvent& event) { //TODO ... } 
+1
source

Source: https://habr.com/ru/post/1446149/


All Articles