Reputation: 2311
I have a dialog window which features some dynamic controls. Basically, upon construction I pass it a pointer to a structure, within this structure is a container of std::pair<wxString,wxString>
. For each of these, I need another row of three controls to be created, a wxTextCtrl
, wxChoice
and a wxButton
.
This part works, based on the available items in the container, I successfully create these upon dialog construction. I also use a wxTextValidator
to connect the wxTextCtrl
with the first wxString
in the pair. So if there's three pairs in the container, I'd have three wxTextCtrl
s created, and each has a validator to it's respective wxString
.
Upon wxID_OK
of the dialog, the text validators work and my original structure, which I passed to it as a pointer, contains the correct data.
However, I've also added a button to my dialog which should allow the creation of new std::pair<wxString,wxString>
records in the container. If I do this, upon wxID_OK
, the app crashes. DrMingW makes it appear that the pointer to the wxString
is invalid, but that can't be possible because I'm creating a new record in my container when the button is clicked, and pointing the wxTextValidator
to this newly created record. It's stored outside the dialog so it isn't being invalidated upon closing the dialog.
My Data Structure:
struct StoryNodeData
{
wxString title;
wxString text;
std::vector<std::pair<wxString,wxString>> options;
};
My Dialog header:
#ifndef STORYDIALOG_H
#define STORYDIALOG_H
#include "flowchart.hpp"
#include <map>
//(*Headers(StoryDialog)
#include <wx/bmpbuttn.h>
#include <wx/button.h>
#include <wx/choice.h>
#include <wx/dialog.h>
#include <wx/gbsizer.h>
#include <wx/scrolwin.h>
#include <wx/sizer.h>
#include <wx/textctrl.h>
//*)
class StoryDialog: public wxDialog
{
public:
StoryDialog(wxWindow* parent,StoryNodeData* data, wxArrayString titles,wxWindowID id=wxID_ANY,const wxPoint& pos=wxDefaultPosition,const wxSize& size=wxDefaultSize);
virtual ~StoryDialog();
void HandleOptionDelete(wxCommandEvent& e);
//(*Declarations(StoryDialog)
wxButton* OptionButton;
wxFlexGridSizer* OptionsSizer;
wxScrolledWindow* OptionsWindow;
wxTextCtrl* EventTextCtrl;
wxTextCtrl* TitleTextCtrl;
//*)
protected:
//(*Identifiers(StoryDialog)
static const long ID_TEXTCTRL1;
static const long ID_TEXTCTRL2;
static const long ID_BUTTON1;
static const long ID_TEXTCTRL3;
static const long ID_CHOICE1;
static const long ID_BITMAPBUTTON1;
static const long ID_SCROLLEDWINDOW1;
//*)
private:
//(*Handlers(StoryDialog)
void OnNewOptionButtonClick(wxCommandEvent& event);
void OnBitmapButton1Click(wxCommandEvent& event);
//*)
std::map<wxObject*,std::tuple<wxTextCtrl*, wxChoice*, std::size_t>> m_optionCtrls;
StoryNodeData* m_data;
wxArrayString m_titles;
void ConstructOption(wxString* text, wxString* title);
DECLARE_EVENT_TABLE()
};
#endif
And the dialog implementation:
#include "StoryDialog.h"
#include <wx/artprov.h>
#include <wx/valgen.h>
//(*InternalHeaders(StoryDialog)
#include <wx/artprov.h>
#include <wx/bitmap.h>
#include <wx/image.h>
#include <wx/intl.h>
#include <wx/string.h>
//*)
//(*IdInit(StoryDialog)
const long StoryDialog::ID_TEXTCTRL1 = wxNewId();
const long StoryDialog::ID_TEXTCTRL2 = wxNewId();
const long StoryDialog::ID_BUTTON1 = wxNewId();
const long StoryDialog::ID_TEXTCTRL3 = wxNewId();
const long StoryDialog::ID_CHOICE1 = wxNewId();
const long StoryDialog::ID_BITMAPBUTTON1 = wxNewId();
const long StoryDialog::ID_SCROLLEDWINDOW1 = wxNewId();
//*)
BEGIN_EVENT_TABLE(StoryDialog,wxDialog)
//(*EventTable(StoryDialog)
//*)
END_EVENT_TABLE()
StoryDialog::StoryDialog(wxWindow* parent,StoryNodeData* data, wxArrayString titles,wxWindowID id,const wxPoint& pos,const wxSize& size)
{
//(*Initialize(StoryDialog)
wxBitmapButton* BitmapButton1;
wxChoice* Choice1;
wxFlexGridSizer* FlexGridSizer1;
wxGridBagSizer* GridBagSizer1;
wxStaticBoxSizer* StaticBoxSizer1;
wxStdDialogButtonSizer* StdDialogButtonSizer1;
wxTextCtrl* TextCtrl1;
Create(parent, wxID_ANY, _("New Event"), wxDefaultPosition, wxDefaultSize, wxSTAY_ON_TOP|wxCAPTION|wxRESIZE_BORDER|wxCLOSE_BOX|wxMAXIMIZE_BOX|wxMINIMIZE_BOX|wxSIMPLE_BORDER, _T("wxID_ANY"));
GridBagSizer1 = new wxGridBagSizer(0, 0);
GridBagSizer1->AddGrowableCol(0);
GridBagSizer1->AddGrowableRow(1);
TitleTextCtrl = new wxTextCtrl(this, ID_TEXTCTRL1, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, wxTextValidator(wxFILTER_NONE,&data->title), _T("ID_TEXTCTRL1"));
GridBagSizer1->Add(TitleTextCtrl, wxGBPosition(0, 0), wxDefaultSpan, wxALL|wxEXPAND, 5);
EventTextCtrl = new wxTextCtrl(this, ID_TEXTCTRL2, wxEmptyString, wxDefaultPosition, wxSize(259,138), wxTE_MULTILINE|wxVSCROLL, wxTextValidator(wxFILTER_NONE,&data->text), _T("ID_TEXTCTRL2"));
GridBagSizer1->Add(EventTextCtrl, wxGBPosition(1, 0), wxDefaultSpan, wxALL|wxEXPAND, 5);
StdDialogButtonSizer1 = new wxStdDialogButtonSizer();
StdDialogButtonSizer1->AddButton(new wxButton(this, wxID_OK, wxEmptyString));
StdDialogButtonSizer1->AddButton(new wxButton(this, wxID_CANCEL, wxEmptyString));
StdDialogButtonSizer1->Realize();
GridBagSizer1->Add(StdDialogButtonSizer1, wxGBPosition(2, 0), wxGBSpan(1, 2), wxALL|wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL, 5);
StaticBoxSizer1 = new wxStaticBoxSizer(wxHORIZONTAL, this, _("Options"));
FlexGridSizer1 = new wxFlexGridSizer(2, 1, 0, 0);
FlexGridSizer1->AddGrowableCol(0);
FlexGridSizer1->AddGrowableRow(1);
OptionButton = new wxButton(this, ID_BUTTON1, _("New Option"), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, _T("ID_BUTTON1"));
FlexGridSizer1->Add(OptionButton, 1, wxALL|wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL, 5);
OptionsWindow = new wxScrolledWindow(this, ID_SCROLLEDWINDOW1, wxDefaultPosition, wxDefaultSize, wxSIMPLE_BORDER|wxVSCROLL, _T("ID_SCROLLEDWINDOW1"));
OptionsSizer = new wxFlexGridSizer(0, 3, 0, 0);
OptionsSizer->AddGrowableCol(0);
TextCtrl1 = new wxTextCtrl(OptionsWindow, ID_TEXTCTRL3, _("Text"), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, _T("ID_TEXTCTRL3"));
OptionsSizer->Add(TextCtrl1, 1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5);
Choice1 = new wxChoice(OptionsWindow, ID_CHOICE1, wxDefaultPosition, wxDefaultSize, 0, 0, 0, wxDefaultValidator, _T("ID_CHOICE1"));
OptionsSizer->Add(Choice1, 1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5);
BitmapButton1 = new wxBitmapButton(OptionsWindow, ID_BITMAPBUTTON1, wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("wxART_DELETE")),wxART_BUTTON), wxDefaultPosition, wxDefaultSize, wxBU_AUTODRAW, wxDefaultValidator, _T("ID_BITMAPBUTTON1"));
OptionsSizer->Add(BitmapButton1, 1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5);
OptionsWindow->SetSizer(OptionsSizer);
OptionsSizer->Fit(OptionsWindow);
OptionsSizer->SetSizeHints(OptionsWindow);
FlexGridSizer1->Add(OptionsWindow, 1, wxALL|wxEXPAND, 5);
StaticBoxSizer1->Add(FlexGridSizer1, 1, wxEXPAND, 5);
GridBagSizer1->Add(StaticBoxSizer1, wxGBPosition(0, 1), wxGBSpan(2, 1), wxALL|wxEXPAND, 5);
SetSizer(GridBagSizer1);
GridBagSizer1->Fit(this);
GridBagSizer1->SetSizeHints(this);
Connect(ID_BUTTON1,wxEVT_COMMAND_BUTTON_CLICKED,(wxObjectEventFunction)&StoryDialog::OnNewOptionButtonClick);
//*)
OptionsWindow->SetScrollRate(5,5);
OptionsWindow->DestroyChildren();
Bind(wxEVT_BUTTON, &StoryDialog::HandleOptionDelete, this);
m_data = data;
m_titles = titles;
// construct the existing options
for(auto itr = m_data->options.begin(); itr != m_data->options.end(); ++itr)
{
auto title = &((*itr).first);
auto text = &((*itr).second);
ConstructOption(text,title);
}
}
StoryDialog::~StoryDialog()
{
//(*Destroy(StoryDialog)
//*)
}
void StoryDialog::HandleOptionDelete(wxCommandEvent& e)
{
auto b = e.GetEventObject();
auto itr = m_optionCtrls.find(b);
if(m_optionCtrls.end() != itr)
{
auto button = itr->first;
auto pa = itr->second;
m_optionCtrls.erase(itr);
OptionsSizer->Detach(std::get<0>(pa));
OptionsSizer->Detach(std::get<1>(pa));
OptionsSizer->Detach(reinterpret_cast<wxBitmapButton*>(button));
std::get<0>(pa)->Destroy();
std::get<1>(pa)->Destroy();
reinterpret_cast<wxBitmapButton*>(button)->Destroy();
auto index = std::get<2>(pa);
auto itr = m_data->options.begin() + index;
m_data->options.erase(itr);
}
OptionsSizer->Layout();
OptionsWindow->SetSizer(OptionsSizer);
SendSizeEvent();
e.Skip(true);
}
void StoryDialog::OnNewOptionButtonClick(wxCommandEvent& event)
{
m_data->options.push_back(std::make_pair("",""));
auto itr = m_data->options.end()-1;
auto text = &((*(itr)).second);
auto title = &((*(itr)).first);
ConstructOption(text,title);
}
void StoryDialog::ConstructOption(wxString* text, wxString* title)
{
long tId = wxNewId();
long cId = wxNewId();
long bId = wxNewId();
auto optionTextCtrl = new wxTextCtrl(OptionsWindow, tId, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, wxTextValidator(wxFILTER_NONE,text));
OptionsSizer->Add(optionTextCtrl, 1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5);
auto optionTitleCtrl = new wxChoice(OptionsWindow, cId, wxDefaultPosition, wxDefaultSize, m_titles, 0, wxGenericValidator(title));
OptionsSizer->Add(optionTitleCtrl, 1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5);
auto deleteButtonCtrl = new wxBitmapButton(OptionsWindow, bId, wxArtProvider::GetBitmap(wxART_MAKE_ART_ID_FROM_STR(_T("wxART_DELETE")),wxART_BUTTON), wxDefaultPosition, wxDefaultSize, wxBU_AUTODRAW, wxDefaultValidator);
OptionsSizer->Add(deleteButtonCtrl, 1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5);
OptionsSizer->Layout();
OptionsWindow->SetSizer(OptionsSizer);
SendSizeEvent();
m_optionCtrls[deleteButtonCtrl] = std::make_tuple(optionTextCtrl,optionTitleCtrl,m_optionCtrls.size()-1);
}
You'll note that the way I create the dynamic controls is the same regardless of whether it's based on the data provided, or the user adding new data through the controls. The only difference is when adding new ones, I first create the records in the container
Upvotes: 0
Views: 67
Reputation: 22753
Pointers to elements of std::vector<>
can (and will) be invalidated when the vector contents changes, e.g. when you append more elements to it, so you can't do it like this.
Instead you need to use a container that is not going to move its values (e.g. std::[unordered_]map<>
) or store unique_ptr<>
s in your vector.
Upvotes: 2