Francesco Noferi
Francesco Noferi

Reputation: 482

wxWidgets: unbind event as its triggered

I'm coding a wxWidgets GUI that involves dynamically adding and removing rows of controls inside a gridsizer through buttons.

Each row of controls has a "remove" button that triggers an event that:

Now I don't think unbinding the event inside the event handler itself is a good thing. Is there a better way to achieve this behaviour?

this is how I dynamically create my controls

bool filtermanager::add()
{
    if (!grid || !box || !form || !bsizer)
        return false;

    dbgcode(log->d(tag, "add: adding a new filter row"));

    // add and index filter in the filter map and refresh layout
    filter *flt = new filter(this, box, grid);
    filters[flt->removebutton()->GetId()] = flt;
    refreshlayout();

    return true;
}

filtermanager::filter::filter(filtermanager *parent, 
    wxStaticBox *box, wxGridSizer *grid)
    : parent(parent), grid(grid)
{
    controlpool *ctl = parent->ctl;

    // initialize filter row elements
    property = ctl->makeComboBox(box, "property");
    value = ctl->makeTextCtrl(box, "value");
    button = ctl->makeButton(box, "Remove");

    // add filter row to the grid sizer
    grid->SetRows(grid->GetRows() + 1);
    grid->Add(property, 0, wxALL | wxEXPAND, 0);
    grid->Add(value, 0, wxALL | wxEXPAND, 0);
    grid->Add(button, 0, wxALL | wxEXPAND, 0);

    // bind remove button
    button->Bind(wxEVT_COMMAND_BUTTON_CLICKED, &filtermanager::OnRemoveClicked, parent);
}

this is the event handler:

void filtermanager::OnRemoveClicked(wxCommandEvent &e)
{
    wxButton *b = dynamic_cast<wxButton *>(e.GetEventObject());
    filter *flt = filters[b->GetId()];

    dbgcode(log->d(tag, 
        strfmt() << "OnRemoveClicked: remove button event caught" << 
        " property=" << flt->propertycombo() << 
        " value=" << flt->valuetext() << 
        " button=" << flt->removebutton())
    );

    removebyflt(flt);
}

void filtermanager::removebyflt(filter *flt)
{
    int id = flt->id();

    // dealloc filter from the map
    delete filters[id];
    filters[id] = NULL;
    filters.erase(id);
}

filtermanager::filter::~filter()
{
    controlpool *ctl = parent->ctl;

    // unbind button
    button->Unbind(wxEVT_COMMAND_BUTTON_CLICKED, &filtermanager::OnRemoveClicked, parent);

    // remove the filter from the gui
    if (!grid->Detach(property))
        parent->log->e(tag, "~filter: failed to remove property from sizer");

    if (!grid->Detach(value))
        parent->log->e(tag, "~filter: failed to remove value from sizer");

    if (!grid->Detach(button))
        parent->log->e(tag, "~filter: failed to remove button from sizer");

    grid->SetRows(grid->GetRows() - 1);

    // refresh panels as usual
    parent->refreshlayout();

    ctl->free(property);
    ctl->free(value);
    ctl->free(button);
}

By the way, the purpose of this GUI is adding search filters for properties in a loaded XML file.

Upvotes: 2

Views: 1653

Answers (1)

VZ.
VZ.

Reputation: 22688

It's fine to unbind the event from its handler, I really don't see any problem, even potentially, with doing this.

Also, wxWidgets doesn't have any special problems with deleting the controls during run-time, generally speaking, and using a pool of hidden controls seems more trouble than just deleting and recreating them as needed. However, deleting a control from a handler for the event generated by this control is indeed a bad idea and may result in crashes. The canonical solution for this is to hide the control immediately and delete it "slightly later", i.e. during the next event loop iteration. This can be done by explicitly using wxEVT_IDLE in all version of wxWidgets or, more conveniently, by using CallAfter() in wxWidgets 2.9.5 or later.

Upvotes: 3

Related Questions