在属性网格中的按钮

5
有没有一种方法可以将属性设置为按钮或向属性添加按钮?在查看网格示例时,我注意到您可以做出这样的操作:
wxPGEditor* editorButton = wxPropertyGrid::RegisterEditorClass(someButton);
propertyGrid->SetPropertyEditor(wxT("Name"), editorButton );

然而,我希望有不同类型的按钮,而不仅仅是一种类型,这样才能支持。

编辑: 所谓不同类型的按钮,是指不必依附于使用编辑器的按钮。我想要做的是像设置wxButton的onClick回调函数那样设置一个属性。

编辑2:明确一点,我希望属性字段本身只是一个按钮。


请解释一下您所说的“不同按钮”是什么意思。 - ravenspoint
1个回答

4

您需要创建一个自定义的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::functionboost::function (这需要 C++11 或 boost)。

另一种方法是将回调信息存储在 wxPGEditor 实例中。但是,这将需要多个编辑器实例,每个实例对应一个不同的回调函数。


更新

实际的“按钮”直到点击字符串字段才会显示出来

编辑器在属性被选择之前不会激活。 您需要创建一个自定义的 wxPGProperty 并返回一个自定义的 wxPGCellRenderer,以控制属性的表示方式(在未进行编辑时)。 不幸的是,这会创建一种假象,并要求用户点击两次按钮:首先激活属性(和编辑器),然后再单击实际的按钮。 一个解决方案是在单元格中显示像 点击以编辑 这样的文本或属性值的简短摘要。

虽然可以让自定义属性创建按钮并忽略编辑器系统,但是属性网格不是设计成那样工作的,这种“黑客”方式可能会导致一些问题。但是,我在最后添加了这个 hack。

当我只想让按钮占用整个属性时,有一个字符串字段

这只是使用示例基类 wxTextCtrl 的副作用,很容易更改。由于不需要 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;
    }
};

用这种方法,似乎只能有一个属性编辑器。
如果您的意思是总数:wxPropertyGrid :: RegisterEditorClass只是在属性网格中注册一个编辑器(网格将拥有编辑器并在销毁属性网格时自动删除它)。您可以注册任意数量的编辑器。您将编辑器设置为特定属性,而不是整个属性网格。
如果您的意思是同时:是的,遗憾的是,在任何给定时间只能有一个属性编辑器处于活动状态。
黑客方法 让我以前提到的黑客方式完成。
首先,我们需要一个自定义的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 ...
}

正如我在问题中提到的,我正在使用wxPGEditor,但是我想要多个按钮而不仅仅是一种操作(比如它们只是编辑器)。我只想让按钮按下时调用一个函数,就像wxButton onClick所做的那样。我已经查看了属性网格示例,它展示了你所说的内容,但这不是我要找的。 - mmurphy
这很接近,但有两件事,也许三件事,阻止它成为我想要的东西。第一,实际的“按钮”直到您单击字符串字段才会显示出来。第二,有一个字符串字段,而我只想让按钮占据整个属性。第三,使用此方法,似乎只能有一个属性编辑器。 - mmurphy
@mmurphy 我已经更新了我的答案,并包含了一个替代方法,应该可以完全满足您的需求(在更新的末尾)。然而,这是一种hack方法,我不能保证它不会引起任何问题。 - Anonymous Coward

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接