user96649
user96649

Reputation: 511

Gtkmm - Proper way to close a window and then show another

I am building a gtkmm application. The program open with a setting window asking the user to specify some information, and when sanity checks are done, this window should be closed, and the maih window of the application should open.

Right now, opening the main window, and hiding the setting window completely close the application. From the setting windows, I am doing:

MainWindow* main_window = new MainWindow();
main_window->show();                       
this->hide();                              

How can I get the behavior described above ? Apparently, you can add and remove windows from a Gtk::App. Would it does what I described, and does it mean I would have to pass to my window the Gtk::App pointer ? Thanks.

Upvotes: 0

Views: 2734

Answers (3)

Simon Pio.
Simon Pio.

Reputation: 135

Although the question is quite old, I will show my approach which may help someone else to deal with this task.

I use a general application object which holds all window objects: MainApplication.cpp

MainApplication::MainApplication(int argc, char **argv)
{
    // Creating the main application object as first
    mainApp = Gtk::Application::create(argc, argv, APPLICATION_ID);

    // Create the entry window
    createEntryWindow();
}

int MainApplication::run()
{
    if (!isRunning) {

        // Set the current window to entry window for startup
        currentWindow = entryWindow;
        return mainApp->run(*entryWindow);
    } else {
        return -1;
    }
}

void MainApplication::createEntryWindow()
{
    // Load the entry_window.glade layout with the Gtk::Builder Api
    Glib::RefPtr<Gtk::Builder> builder = Gtk::Builder::create_from_file("../layout/entry_window.glade");

    // Calls the constructor of my own derived widget class which details are specified inside the builder file
    builder->get_widget_derived(WND_ENTRY, entryWindow);

    // Set this main application object to the new window
    entryWindow->setMainApplicationContext(this);
}

MainApplication.h

static const int WS_ENTRY = 100;
static const int WS_SOMETHING = 200;

class MainApplication {
public:
    MainApplication(int argc, char* argv[]);

    int run();

    void switchCurrentWindow(int specifier);

private:
    void createEntryWindow();

private:
    Glib::RefPtr<Gtk::Application> mainApp;
    Gtk::Window* currentWindow = nullptr;
    EntryWindow* entryWindow = nullptr;

    bool isRunning = false;
};

The MainApplication object will be created inside the main() and after that run() is called: main.cpp

int main(int argc, char* argv[])
{
    // Create main application object
    MainApplication mainApplication(argc, argv);

    // Starts the event loop
    // No events propagate until this has been called
    return mainApplication.run();
}

The EntryWindow.cpp looks like this (just a simple example):

EntryWindow::EntryWindow(BaseObjectType* object, const Glib::RefPtr<Gtk::Builder>& refGlade)
    : Gtk::Window(object), builder(refGlade)
{
    // Set widgets to builder
    builder->get_widget(btnName, btn);

    // Set on click methods for signal_clicked
    btn->signal_clicked().connect(sigc::mem_fun(*this, &EntryWindow::onBtnClicked));
}

void EntryWindow::onBtnClicked()
{
    mainApplicationContext->switchCurrentWindow(WS_SOMETHING);
}

void EntryWindow::setMainApplicationContext(MainApplication* mainApplication)
{
    this->mainApplicationContext = mainApplication;
}

EntryWindow.h:

class EntryWindow : public Gtk::Window {
public:
    EntryWindow(BaseObjectType* object, const Glib::RefPtr<Gtk::Builder>& refGlade);
    void setMainApplicationContext(MainApplication* mainApplication);

protected:
    void onBtnClicked();

protected:
    const Glib::RefPtr<Gtk::Builder> builder;
    Gtk::Button* btn;

private:
    MainApplication* mainApplicationContext = nullptr;
    const Glib::ustring btnName = BTN_NAME;    
};

So now when the button was clicked, you can switch the windows with following function inside the MainApplication class:

void MainApplication::switchCurrentWindow(int specifier)
{
    // Check if the passed specifier exist
    int tmpSpecifier = 0;
    switch (specifier) {
        case WS_ENTRY:
            tmpSpecifier = WS_ENTRY;
            break;
        case WS_SOMETHING:
            tmpSpecifier = WS_SOMETHING;
            break;
        default:
            tmpSpecifier = 0;
    }

    // If the specifier exist
    if (tmpSpecifier != 0) {

        // Increase the use counter of the main application object
        mainApp->hold();

        // Hide the current window
        currentWindow->hide();

        // Remove the current window
        mainApp->remove_window(*currentWindow);
    } else {
        return;
    }

    switch (tmpSpecifier) {
        case WS_ENTRY:
            currentWindow = entryWindow;
            break;
        case WS_SOMETHING:
            currentWindow = somethingWindow;
            break;
    }

    // Add the new current window
    mainApp->add_window(*currentWindow);

    // Show the new window
    currentWindow->show();

    // Decrease the use counter of the main application object
    mainApp->release();
}

Summary: Create an Object which holds all windows. So whenever you need to have a new window, you have to create it inside this object. This main application object will be called by the main() and there it will call run() when the application is ready to start. After that you will handle which window is shown and hidden by the main application object only.

Upvotes: 1

underscore_d
underscore_d

Reputation: 6791

In response to your answer: delete->this is a pure syntax error, and even without ->, writing delete this is usually a code smell. Barring that, what you did seems like it will work, if perhaps not be as intuitive as it could be.

However, doing things in that sequence is not always possible. For example, you may not know what the next Window will be. Perhaps which window opens next depends on an HTTP response that may take a while to arrive.

The general solution is to call Application.hold() before removing the Window. Calling .hold() increments the use count of the GApplication, just as adding a window does. The app quits when its use count is zero. Saying its life is controlled by windows is just a quick way to approximate an explanation of that (and is obviously only relevant to GtkApplication, not the base GApplication). Removing a window decrements the use count.

So, in this case, you would now go from a use count of 2 to 1 - not 1 to 0 - so removing the 1st window would no longer make the app quit. Then, after you add the 2nd window, however much later that occurs, call .release() to remove the extra use count, so the remaining window(s) now exclusively control the application's lifetime again.

Upvotes: 0

user96649
user96649

Reputation: 511

What seems to be the proper solution is to pass to the window the application pointer (m_app), add the new window to it, show that window and hide the current one. Removing the current one from the application will let the run() function return:

MainWindow* main_window = new MainWindow(m_app);
m_app->add_window(*main_window);                
main_window->show();                            
this->hide();                                   
m_app->remove_window(*this);                    
delete->this;

This work, but this might not be the proper way of doing things.

Upvotes: 1

Related Questions