You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 21:14:56