您必须创建一个自定义 wxPGEditor,它根据存储在属性中的数据中继按钮事件。wxPGProperty 可以存储用户定义的数据(wxClientData
),这些数据可用于存储回调函数。
首先,您必须定义将在按钮事件上调用的处理程序的函数签名。
typedef bool (wxEvtHandler::*ButtonEventMethod)(wxPGProperty*);
处理程序接收附加属性作为参数,如果属性被修改,则应返回 true。
现在需要一个基于 wxClientData 的类来存储回调:
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;
};
这个类只是存储了方法的地址和它所属的类。编辑器将从属性中提取此对象并执行该call()
方法,该方法又执行回调:
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);
}
};
总结一下,可以编写一个将方法“绑定”到属性的函数:
void BindButton(wxPGProperty* property, ButtonEventMethod method, wxEvtHandler* handler) {
property->SetClientObject(new ButtonData(method,handler));
}
还有一个宏使它更简单:
#define BIND_BUTTON(property,method,handler,editor) \
property->SetEditor(editor); \
BindButton(property,static_cast<ButtonEventMethod>(method),handler)
现在您可以将成员函数绑定到属性:
// 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);
该方法foo
可能如下所示:
bool Frame::foo(wxPGProperty* p) {
p->SetValue("foo");
return true;
}
这只是演示 wxPGProperty 如何存储回调的示例(而不是创建多个 wxPGEditor)。回调存储(此处为 ButtonData)可以修改为存储std::function
or boost::function
(这需要 C++11 或 boost)。
另一种方法是将回调信息存储在 wxPGEditor 实例中。但是,这将需要多个编辑器实例,一个用于每个不同的回调函数。
更新
在您单击字符串字段之前,实际的“按钮”不会显示
在选择属性之前,不会激活编辑器。您必须创建一个自定义wxPGProperty
返回一个自定义wxPGCellRenderer
来控制属性的表示(当它没有被编辑时)。不幸的是,这会产生一种错觉并要求用户单击按钮两次:首先激活属性(和编辑器),然后才是实际的按钮。想到的一种解决方案是Click to edit
在单元格中显示文本或属性值的简短摘要。
虽然可以让自定义属性创建一个按钮并忽略编辑器系统,但属性网格并非设计为以这种方式工作,这种“黑客”可能会导致一些问题。但是,我在最后添加了那个hack。
有一个字符串字段,当我只想让按钮占据整个属性时
这只是我用作示例基类的编辑器的副作用,并且很容易更改。由于不需要 wxTextCtrl,只需wxPGEditor
直接基于编辑器并自己创建控件(单个按钮)即可:
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;
}
};
使用这种方法,似乎只能有一个属性编辑器
如果您的意思是 total:wxPropertyGrid::RegisterEditorClass
只需向属性网格注册一个编辑器(网格将获得编辑器的所有权,并在属性网格被销毁时自动将其删除)。您可以注册任意数量的编辑器。您将编辑器设置为特定属性,而不是整个属性网格。
如果您的意思是同时:是的,不幸的是,在任何给定时间只能激活一个属性编辑器。
黑客
让我用我之前提到的 hack 来完成这个。
首先我们需要一个自定义wxPGCellRenderer
来定位属性网格上的按钮:
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;
};
现在我们可以创建自定义属性:
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's off the grid, it's 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
};
不再需要编辑器后,您现在可以将事件处理程序直接附加到属性:
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!")
);
处理程序看起来像这样:
void OnClick1(wxCommandEvent& event) {
//TODO ...
}