Using QLinkedList to cycle through different window screens

76 views Asked by At

For my Qt application, I'm trying to use a QLinkedList which the user can cycle through the new window screens that the user has created. The windows are created dynamically when the user clicks the "New Window" button on the MainWindow which the MainWindow object is added to the QLinkedList.

I'm trying to use the QLinkedListIterator for the Back and Forward button to point to towards the previous screen and next screen. So far it works for the Forward button but for the Back button, it crashes and if I click Back on the first screen, it also crashes.

MainWindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QPushButton>
#include <QLabel>
#include <QTextEdit>

#include <QLinkedList>
#include <QLinkedListIterator>

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

    QLinkedList<MainWindow*> window_list;

private:
    Ui::MainWindow *ui;

    QPushButton *button;

    MainWindow* new_window;

    // Add new Window
    void input_new_window();

    // Forward and back buttons
    void input_back_button();
    void input_forward_button();

    void go_back();
    void go_forward();

    void addWindow();
};

#endif // MAINWINDOW_H

MainWindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    this->resize(800, 400);

    //---------------------------
    // Show buttons
    //----------------------------
    this->input_back_button();
    this->input_forward_button();
    this->input_new_window();
}

MainWindow::~MainWindow()
{
    delete ui;
}

// Add Window to the front of the list
void MainWindow::addWindow()
{
    // Create new Window Object
    new_window = new MainWindow();

    // Add the New Window to the list
    window_list.append(new_window);

    // Display the New Window
    new_window->show();
}


void MainWindow::go_back()
{
    // Go back to previous window in the linkedlist
    QMutableLinkedListIterator<MainWindow*> i(window_list);

    if(i.hasPrevious())
    {
        new_window->show();
        this->hide();
    }
}


void MainWindow::go_forward()
{
    // Go forward to next window in the linkedlist
    QMutableLinkedListIterator<MainWindow*> i(window_list);

    if(i.hasNext())
    {
        new_window->show();
        this->hide();
    }
}


//----------------------------------------------------------
// Open New Window
//----------------------------------------------------------
void MainWindow::input_new_window()
{
    button = new QPushButton("New Window", this);
    button->setGeometry(QRect(QPoint(10, 30), QSize(200, 50)));
    button->show();
    QObject::connect(button, &QPushButton::pressed, this, &MainWindow::addWindow);
}


//--------------------------------------------------------
// Going forward and back with windows
//--------------------------------------------------------
void MainWindow::input_back_button()
{
    button = new QPushButton("Back", this);
    button->setGeometry(QRect(QPoint(10, 340), QSize(200, 50)));
    button->show();

    // Link back button with going back a screen
    QObject::connect(button, &QPushButton::pressed, this, &MainWindow::go_back);
}


void MainWindow::input_forward_button()
{
    button = new QPushButton("Forward", this);
    button->setGeometry(QRect(QPoint(580, 340), QSize(200, 50)));
    button->show();

    // Link forward button with going forward a screen
    QObject::connect(button, &QPushButton::pressed, this, &MainWindow::go_forward);
}
1

There are 1 answers

7
cbuchart On BEST ANSWER

You are working with different lists of windows: each new window you add is holding its own list, so cycling is actually not possible. In addition, the new_window variable is not necessary as a member and I think it is creating a bit of mess for you.

The reason it is working for the forward is because new_window already holds the next window (you assigned it when calling the addWindow method, so it is actually the next window). On the other hand, the back is also using the new_window (the next), so it will not work as expected.

To solve your problem try using a shared list of windows, may be a global list, or pass it when constructing each window.

Solution below is not the most elegant and may have race conditions, but will illustrate my point. It uses a global list of windows: each time you want to move forward or backward it searches for the current window and cycles on the list. Also, I've removed the new_window member ;)

In the header file

class MainWindow ... {
public:
    static void addWindowToList(MainWindow* w);
}

In the .cpp file

// ...
#include <cassert>

namespace {
    QLinkedList<MainWindow*> window_list; // global list
}

// ...

void MainWindow::addWindowToList(MainWindow* w)
{
    window_list.append(w);
}

void MainWindow::addWindow()
{
    addWindowToList(new MainWindow());
    go_forward(); // shows new window
}

void MainWindow::go_back()
{
    if (window_list.count() == 1) return; // no other windows

    if (window_list.first() == this) { // has to go the last one
        window_list.back()->show();
    } else {
        auto it = std::find(window_list.begin(), window_list.end(), this);
        assert(it != window_list.end()); // should be in the list
        (*(--it))->show();
    }

    hide();
}

void MainWindow::go_forward()
{
    if (window_list.count() == 1) return; // no other windows

    if (window_list.back() == this) { // has to go to the first one
        window_list.first()->show();
    } else {
        auto it = std::find(window_list.begin(), window_list.end(), this);
        assert(it != window_list.end()); // should be in the list
        (*(++it))->show();
    }

    hide();
}

// ...

Now, you should also add the very first window to the list too. Probably you are using the default Qt template, like:

int main(int argc, char* argv[]) {
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

Instead of creating the object on the stack, do it dynamically and add it to the list:

int main(int argc, char* argv[]) {
    QApplication a(argc, argv);
    auto w = new MainWindow();
    MainWindow::addWindowToList(w);
    w->show();
    return a.exec();
}

  • it iterators are actually QLinkedList::iterator, and STL-like iterator, not the Java-like ones. Not a big deal, just that I'm more familiarised with the first ones.

  • I haven't been able to test the code since now I don't have access to my development environment, so please forgive me any typo or compilation error.


Improvements

  • Instead of using the addWindowToList method manually, you can do it in the MainWindow constructor instead (addWindowToList(this)) so you only have to create them and they will be added automatically.
  • The code doesn't show anything about destroying windows. You should take care of removing them from the list when closed / destroyed, otherwise you'll have a crash when cycling windows. In a similar way as done with the constructor you can do it on the destructor and remove the window from the list.