Can pass value between two dialogs but not to wizard? Topic is solved

If you are using the main C++ distribution of wxWidgets, Feel free to ask any question related to wxWidgets development here. This means questions regarding to C++ and wxWidgets, not compile problems.
Post Reply
USB3pt0
In need of some credit
In need of some credit
Posts: 9
Joined: Mon May 04, 2020 3:03 pm

Can pass value between two dialogs but not to wizard?

Post by USB3pt0 » Mon May 18, 2020 1:29 pm

Hello everyone,

I'm working on passing data between classes. Right now I have two dialogs (derived from wxDialog) and a wizard (derived from wxWizard).

The config dialog is connected to the list dialog by a pointer, and calling a function in the config dialog called SetRelative, which simply returns a pointer to the list dialog for the config dialog to use. The wizard is connected to the config dialog via this method as well and can successfully send data to it.
void ConfigDialog::SetRelative(Wizard* inputWizard)
{
this->wizard = inputWizard;
}

void ConfigDialog::SetRelative(ListDialog* inputList)
{
this->listDialog = inputList;
}
The list dialog can pass a value to a text entry in the config dialog, using a setter to access the protected text entry. However, attempting to do the same with the wizard results in nothing changing in the wizard's text box.
void ListDialog::m_buttonOKOnButtonClick( wxCommandEvent& event )
{
//m_sCurrentSelectionValue is set by the OnListItemSelected event handler
configDialog->SetValue(m_sCurrentSelectionValue);
wizard->SetValue(m_sCurrentSelectionValue);

this->Close();
}



void ConfigDialog::SetValue(wxString inputValue)
{
m_textEntry->SetValue(inputValue);
}



void Wizard::SetValue(wxString inputValue)
{
m_textEntry->SetValue(inputValue);
}
Using a debug console I can see it goes into the wizard's setter function for that text entry, but the value simply does not update. The validators set on both boxes is the same. The config dialog is a singleton, while the RunWizard function seems to make the wizard modal.

I'm not sure where to go from here. Any help appreciated!

PB
Part Of The Furniture
Part Of The Furniture
Posts: 2387
Joined: Sun Jan 03, 2010 5:45 pm

Re: Can pass value between two dialogs but not to wizard?

Post by PB » Mon May 18, 2020 2:26 pm

You probably need to provide more details.

The code below demonstrates that, at least on Win10 with the current master, a modal dialog can update a control in a running wizard.

Code: Select all

#include <wx/wx.h>
#include <wx/wizard.h>

class WizardDialog;

// ListDialog declarations
class ListDialog : public wxDialog
{
public:
    ListDialog(WizardDialog* parent);
private:
    WizardDialog* m_wizardDialog;
    wxTextCtrl* m_textCtrl;
};

// WizardPage declarations
class WizardPage : public wxWizardPageSimple
{
public:
    enum
    {
        ID_SHOW_LISTDIALOG = wxID_HIGHEST + 1
    };
    
    WizardPage(WizardDialog* parent);

    wxString GetValue() const { return m_textCtrl->GetValue(); }
    void SetValue(const wxString& value) { m_textCtrl->SetValue(value); }

private:
    wxTextCtrl* m_textCtrl;
};

// WizardDialog declarations
class WizardDialog : public wxWizard
{
public:
    WizardDialog();
    
    wxString GetValue() const;
    void SetValue(const wxString& value);
};

// ListDialog definitions
ListDialog::ListDialog(WizardDialog* parent) 
    : wxDialog(parent, wxID_ANY, "ListDialog"),
      m_wizardDialog(parent)
{
    wxBoxSizer* mainSizer = new wxBoxSizer(wxVERTICAL);
    wxButton* button = nullptr;

    m_textCtrl = new wxTextCtrl(this, wxID_ANY);
    m_textCtrl->SetValue(m_wizardDialog->GetValue());

    button = new wxButton(this, wxID_ANY, "Update WizardDialog");
    button->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { m_wizardDialog->SetValue(m_textCtrl->GetValue()); });

    mainSizer->Add(m_textCtrl, wxSizerFlags().Expand().Border());
    mainSizer->Add(button, wxSizerFlags().Expand().Border());
    mainSizer->Add(new wxButton(this, wxID_CANCEL), wxSizerFlags().Expand().Border());
    SetSizerAndFit(mainSizer);
}

// WizardPage definitions
WizardPage::WizardPage(WizardDialog* parent) : wxWizardPageSimple(parent)
{
    wxBoxSizer* mainSizer = new wxBoxSizer(wxVERTICAL);
    wxButton* button = nullptr;
        
    m_textCtrl = new wxTextCtrl(this, wxID_ANY, "a string");
    button = new wxButton(this, ID_SHOW_LISTDIALOG, "Show ListDialog...");

    mainSizer->Add(m_textCtrl, wxSizerFlags().Expand().Border());
    mainSizer->Add(button, wxSizerFlags().Expand().Border());
    SetSizer(mainSizer);
}

// WizardDialog definitions
WizardDialog::WizardDialog() 
    : wxWizard(nullptr, wxID_ANY, "WizardDialog")
{
    Bind(wxEVT_BUTTON, 
        [this](wxCommandEvent&) { ListDialog(this).ShowModal(); },
        WizardPage::ID_SHOW_LISTDIALOG);
}

wxString WizardDialog::GetValue() const 
{
    const WizardPage* page = dynamic_cast<WizardPage*>(GetCurrentPage());

    return page->GetValue(); 
}
    
void WizardDialog::SetValue(const wxString& value) 
{
    WizardPage* page = dynamic_cast<WizardPage*>(GetCurrentPage());

    page->SetValue(value); 
}

// MyApp
class MyApp : public wxApp
{
public:
    bool OnInit() override
    {
        WizardDialog dlg;

        dlg.RunWizard(new WizardPage(&dlg));

        return false;
    }
}; wxIMPLEMENT_APP(MyApp);

USB3pt0
In need of some credit
In need of some credit
Posts: 9
Joined: Mon May 04, 2020 3:03 pm

Re: Can pass value between two dialogs but not to wizard?

Post by USB3pt0 » Mon May 18, 2020 6:22 pm

I wish I could offer more insight but I really can't think of anything else that would be relevant.

Oddly, when SetValue on the textctrl is called from the SetTextValue function coming from ListDialog, while the value is updated (checking by calling the ctrl's getValue before and after the SetValue call) it does not show the updated value in the text box itself. As the Next button is disabled until the value is not empty, it does not enable the Next button either.

EDIT:
Forcing the Next button to be enabled and continuing through the Wizard (which sends values to Config) results in the text box I am supposed to fill from List still sending no value.

PB
Part Of The Furniture
Part Of The Furniture
Posts: 2387
Joined: Sun Jan 03, 2010 5:45 pm

Re: Can pass value between two dialogs but not to wizard?

Post by PB » Mon May 18, 2020 6:43 pm

If you have similar dialog hierarchy as in my code, are seeing this issue on MSW and/or my code example works for you I have no idea what to do, hopefully someone else will

I suppose wxSafeYield() will not help either, TBH I am not really sure how it works.

USB3pt0
In need of some credit
In need of some credit
Posts: 9
Joined: Mon May 04, 2020 3:03 pm

Re: Can pass value between two dialogs but not to wizard?

Post by USB3pt0 » Mon May 18, 2020 7:04 pm

Your example works on my machine but I'm not sure what exactly is different between yours and mine. I do notice you subclass wxWizardPageSimple, while I just have my TextCtrl and the public SetValue for that TextCtrl in the Wizard's class file itself. Do I need to be subclassing the page for it to update correctly?

EDIT: Also, this is MSW. Visual Studio 2012.

PB
Part Of The Furniture
Part Of The Furniture
Posts: 2387
Joined: Sun Jan 03, 2010 5:45 pm

Re: Can pass value between two dialogs but not to wizard?

Post by PB » Mon May 18, 2020 8:26 pm

USB3pt0 wrote:
Mon May 18, 2020 7:04 pm
I just have my TextCtrl ... in the Wizard's class file itself.
You mean you just add controls directly to wxWizard, and not on its pages? I did not even know that was possible.
Nevertheless, I tried doing that and it still works.

Code: Select all

#include <wx/wx.h>
#include <wx/wizard.h>

class WizardDialog;

// ListDialog declarations
class ListDialog : public wxDialog
{
public:
    ListDialog(WizardDialog* parent);
private:
    WizardDialog* m_wizardDialog;
    wxTextCtrl* m_textCtrl;
};

// WizardDialog declarations
class WizardDialog : public wxWizard
{
public:
    WizardDialog();

    wxWizardPageSimple* GetFirstPage() { return m_firstPage; }

    wxString GetValue() const { return m_textCtrl->GetValue(); }
    void SetValue(const wxString& value) { m_textCtrl->SetValue(value); }
private:
    wxTextCtrl*         m_textCtrl;
    wxWizardPageSimple* m_firstPage;
    wxWizardPageSimple* m_secondPage;
};

// ListDialog definitions
ListDialog::ListDialog(WizardDialog* parent)
    : wxDialog(parent, wxID_ANY, "ListDialog"),
      m_wizardDialog(parent)
{
    wxBoxSizer* mainSizer = new wxBoxSizer(wxVERTICAL);
    wxButton* button = nullptr;

    m_textCtrl = new wxTextCtrl(this, wxID_ANY, m_wizardDialog->GetValue());

    button = new wxButton(this, wxID_ANY, "Update WizardDialog");
    button->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { m_wizardDialog->SetValue(m_textCtrl->GetValue()); });

    mainSizer->Add(m_textCtrl, wxSizerFlags().Expand().Border());
    mainSizer->Add(button, wxSizerFlags().Expand().Border());
    mainSizer->Add(new wxButton(this, wxID_CANCEL), wxSizerFlags().Expand().Border());
    SetSizerAndFit(mainSizer);
}

// WizardDialog definitions
WizardDialog::WizardDialog() : wxWizard(nullptr, wxID_ANY, "WizardDialog")
{
    wxSizer* mainSizer = GetSizer();
    wxButton* button = nullptr;

    m_textCtrl = new wxTextCtrl(this, wxID_ANY, "a string");

    button = new wxButton(this, wxID_ANY, "Show ListDialog...");
    button->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { ListDialog(this).ShowModal(); });

    mainSizer->Insert(0, m_textCtrl, wxSizerFlags().Expand().Border());
    mainSizer->Insert(1, button, wxSizerFlags().Expand().Border());

    m_firstPage = new wxWizardPageSimple(this);
    m_firstPage->SetBackgroundColour(*wxRED);

    m_secondPage = new wxWizardPageSimple(this);
    m_secondPage->SetBackgroundColour(*wxGREEN);

    m_firstPage->Chain(m_secondPage);
}

// MyApp
class MyApp : public wxApp
{
public:
    bool OnInit() override
    {
        WizardDialog dlg;

        dlg.RunWizard(dlg.GetFirstPage());

        return false;
    }
}; wxIMPLEMENT_APP(MyApp);
Your code must be doing something which prevents that from working but not knowing the code, I have no idea what it is. TBH, I do not understand your Wizard workflow.... And I do not even know how to programmatically disable the Next button (aside from pretending there is no next page).

USB3pt0
In need of some credit
In need of some credit
Posts: 9
Joined: Mon May 04, 2020 3:03 pm

Re: Can pass value between two dialogs but not to wizard?

Post by USB3pt0 » Mon May 18, 2020 8:45 pm

Well, they ARE added to the pages, but they're just all simple pages. Everything is defined in one wizard class file and header.

This was all put together in wxFormBuilder, for what it's worth. And you can grab the wxID_FORWARD, find the window associated with it, and call Enable()/Disable() on that.

Anyway, something I did horribly broke the list I was working with, causing memory leaks and breaking a function I had for adding things to the list, so I went back to an the version immediately before I tried getting them to talk to each other and am gonna try implementing it the way you have it set up.

PB
Part Of The Furniture
Part Of The Furniture
Posts: 2387
Joined: Sun Jan 03, 2010 5:45 pm

Re: Can pass value between two dialogs but not to wizard?

Post by PB » Mon May 18, 2020 9:43 pm

USB3pt0 wrote:
Mon May 18, 2020 8:45 pm
Well, they ARE added to the pages, but they're just all simple pages. Everything is defined in one wizard class file and header.
How the wizard pages are defined/declared, created, and populated should have no effect on this.

USB3pt0
In need of some credit
In need of some credit
Posts: 9
Joined: Mon May 04, 2020 3:03 pm

Re: Can pass value between two dialogs but not to wizard?

Post by USB3pt0 » Tue May 19, 2020 7:21 pm

I found out why it wasn't working and it's dumb.

I create the Wizard in the MainFrame.

But the List dialog was being created in the MainFrame as well, a button in the wizard simply showed it, while creating another Wizard to be able to call its SetValue function. But it wasn't applying that to the wizard that it was opened from, rather, it applied it to the one it created. This also explains why the Config dialog was working as well, as it is a singleton and GetInstance() brings back the only one that's created.

I've alleviated this by casting the dialog's parent to MainFrame from wxWindow and calling a function that shows the dialogs it's created (which...probably gets rid of the need for the singleton, come to think of it) and it also has a function for passing along the value to the Wizard from the List.

There's probably a million better ways to handle this but this week has been incredibly taxing on me so I'll leave it like it is for now and optimize it later.

Thanks for the insight.

Post Reply