wxPropertyGrid中嵌入按钮的异常行为问题求助
问题:wxPropertyGrid按钮事件访问类成员变量地址不一致导致功能异常
在Linux和Windows平台为wxPropertyGrid添加按钮后,界面显示正常但功能异常。具体表现为:PropertyGrid中添加两个按钮,Button1的OnClick1方法切换wxFrame类成员变量mBool的值,Button2的OnClick2方法记录该变量值。事件可正常触发,但OnClick2无法获取OnClick1修改后的mBool值,日志显示两者访问的mBool内存地址不同。
复现代码
#include "stdafx.h" #include "wx/wx.h" #include <wx/window.h> #include "wx/propgrid/propgrid.h" 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 //mutable }; class MyApp : public wxApp { public: virtual bool OnInit(); virtual int OnExit(); }; wxIMPLEMENT_APP(MyApp); class MyFrame : public wxFrame { public: MyFrame(wxWindow* parent); private: void OnClick1(wxCommandEvent& evt); void OnClick2(wxCommandEvent& evt); bool mBool; wxDECLARE_EVENT_TABLE(); }; int MyApp::OnExit() { return wxApp::OnExit(); } bool MyApp::OnInit() { if (!wxApp::OnInit()) return false; wxImage::AddHandler(new wxPNGHandler); MyFrame* frame = new MyFrame(0L); frame->Show(true); return true; } wxBEGIN_EVENT_TABLE(MyFrame, wxFrame) wxEND_EVENT_TABLE() MyFrame::MyFrame(wxWindow* parent): wxFrame(parent, wxID_ANY, " Application"), mBool(false) { wxPropertyGrid* lPG = new wxPropertyGrid(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxPG_SPLITTER_AUTO_CENTER); lPG->Append(new ButtonProperty( lPG, wxCommandEventHandler(MyFrame::OnClick1), wxString("Click me!"))); lPG->Append(new ButtonProperty( lPG, wxCommandEventHandler(MyFrame::OnClick2), "Edit this!") ); // Layout stuff lPG->SetMinSize(wxSize(300, 300)); wxBoxSizer* lGlobalVertSizer = new wxBoxSizer(wxVERTICAL); lGlobalVertSizer->Add(lPG, 1, wxEXPAND | wxALL, 0); SetSizerAndFit(lGlobalVertSizer); // Open the log window and redirect messages to it wxLogWindow* lLogWindow = new wxLogWindow(this, "Log Messages", false, false); lLogWindow->GetFrame()->Move(GetPosition().x + GetSize().x + 10,GetPosition().y); lLogWindow->Show(); } void MyFrame::OnClick1(wxCommandEvent& evt) { mBool = !mBool; wxLogMessage("OnClick1 mBool status: %s %llx", mBool ? "true": "false", (unsigned long long) & mBool); } void MyFrame::OnClick2(wxCommandEvent& evt) { wxLogMessage("OnClick2 mBool status: %s %llx", mBool ? "true": "false", (unsigned long long) & mBool); }
日志输出
11:46:36: OnClick1 mBool status: false 1ff29be5fa8 11:46:37: OnClick2 mBool status false 1ff29c739b8 11:46:39: OnClick1 mBool status: true 1ff29be5fa8 11:46:40: OnClick2 mBool status false 1ff29c739b8
问题原因
问题核心在于ButtonProperty构造函数中调用_btn->Connect()时,未指定事件处理的目标对象实例。wxWidgets的Connect()方法若不传入接收事件的对象,会默认将事件发送给按钮的父窗口(此处为wxPropertyGrid),而非我们期望的MyFrame实例。这导致事件处理函数被绑定到错误的对象上,从而访问到不同内存地址的mBool变量。
修复方案
步骤1:修改ButtonProperty构造函数
增加wxEvtHandler* handler参数,用于指定事件接收对象,并在Connect()中传入该参数:
class ButtonProperty : public wxPGProperty { public: ButtonProperty(wxWindow* parent, wxEvtHandler* handler, 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) { // 将事件绑定到指定的handler对象 _btn->Connect(wxEVT_COMMAND_BUTTON_CLICKED, func, nullptr, handler); _btn->Hide(); } // 保留原有成员和方法... };
步骤2:创建ButtonProperty时传入正确的事件接收对象
在MyFrame的构造函数中,创建ButtonProperty时传入this(即当前MyFrame实例)作为事件处理对象:
lPG->Append(new ButtonProperty( lPG, this, // 指定MyFrame为事件接收者 wxCommandEventHandler(MyFrame::OnClick1), wxString("Click me!"))); lPG->Append(new ButtonProperty( lPG, this, // 指定MyFrame为事件接收者 wxCommandEventHandler(MyFrame::OnClick2), "Edit this!") );
修复原理
通过在Connect()方法中明确指定事件接收对象为MyFrame实例,确保事件处理函数被正确绑定到目标对象上,此时两个按钮的事件处理函数访问的mBool为同一个成员变量,内存地址一致,功能即可恢复正常。
内容的提问来源于stack exchange,提问作者Joel Brenner
相关产品推荐
相关产品推荐

