COMP 2012H Honors Object-Oriented Programming and Data Structures

Supplementary Lab GUI Programming using Qt

Introduction


In this lab we will implement a GUI for the 8 puzzle game. The following description will guide you complete the skeleton code.

Set Up Qt Environment


Install Qt on your own computer

Download the installer from here or the offline installer here. Login to your account (or just skip it) and choose the install directory. Choose the following 2 components.



You can choose other version of MinGW if you have special reason as long as the 2 components are consistent.

For MacOS:



Note that the version of Qt installed in lab is not the latest version. But you should have no problem using the common features.

Lab Work


Overview of Qt

Qt is a cross-platform application development framework. In this course we mainly utilize the API for creating GUI for your program. In Qt, the widgets on the interface (buttons, input box, etc.) are maintained by classes. The widget can be created by creating the class, and its properties can be set by modifying the member variables of the class. These predefined classes can also be inherited so as to be customizable.

Unlike the normal console applications you wrote in this course before, the programs with GUI do not run sequentially. Instead, they wait for the events triggered by user inputs and call the corresponding handlers after the initialization of the program. Qt uses signals and slots mechanism to register the handlers for various events.


Overview of the skeleton code

Use QtCreator to open 8puzzle.pro. If QtCreator complains about there are no correct settings, you need to choose the toolchain. Choose MinGW in our case.



First double click Forms/mainwindow.ui to go to the UI editing view. You can see 9 buttons for the puzzle and one generate button to generate a new puzzle.

Currently all the buttons show 0. This is because this UI editing tool is best suitable for static content. Since the numbers will be changed according to user input, we will initialize the numbers by codes.

The widgets list on the right side shows that the number buttons have type NumCell while the generate button has type QPushButton, which is a built-in type. This is because we want to store the row and column index in the number cells so that they know their own location. To achieve this, we create NumCell that inherits QPushButton and also has additional member variables to store the location.

Now go back to the code view and see main.cpp. The main function creates and executes the main window, which is defined in MainWindow.cpp. In the constructor the main window, we set the row and column index for every cells and generate a puzzle. Note there is a line ui->setupUi(this) that initializes the widgets created in the UI editor. The content in the object ui is automatically generated by the Qt framework in the building process. You can access the widgets by ui-><name_of_widget>, where the name is defined by setting the objectName attribute in the UI editor.

Now the program finishes the initialization. In the following tasks, we will bind the input events with the corresponding handler.


Task 1 - Generate button

We expect that when we click the generate button, the 9 cells will show a new puzzle. We now have prepared the generate function that can generate a valid puzzle and update the UI. All we need to do is to bind the click event to this function.

We have 3 ways to do the binding. One is to explicitly bind by connect function. And if the widget is created in the UI editor, we have the second option, which is to let Qt do the binding by following the naming convention. The third one is to inherit the predefined class and override the predefined event handler. Here we use the second one. And in the following tasks we will try the first one.

In Qt, if we want to create the slot (handler) for the signal (event) <signal> emitted by <emit_instance>, we just need to create a function named on_<emit_instance>_<signal> and Qt will automatically connect the slot to the signal. In our case, <emit_instance> is generate, which is the name of the generate button, and <signal> is clicked, which is predefined by Qt. One special point is that we need to put a predefined macro slots after the access control indicator. In our case, we need to add the following code to the declaration of MainWindow.

private slots: void on_generate_clicked();

And this function just call the prepared generate() function.

void MainWindow::on_generate_clicked() { generate(); }

Task 2 - Inherit QPushButton

We hope the number buttons know their location so as to judge whether number can be moved. However, predefined QPushButton doesn't have these member variables. So we create NumCell which derives from the QPushButton. NumCell still preserves all the features of the push button and at the same time knows it's location by storing it in the new variables.

Complete the declaration of NumCell as follows and implement the accessors/mutators.

class NumCell: public QPushButton { ... private: int row, col, num; public: NumCell(QWidget *parent); int get_row(); void set_row(int row); int get_col(); void set_col(int col); int get_num(); void set_num(int num); };

Note that the constructor takes QWidget *parent as an argument. It is required by the base class. So implement the constructor as follows.

NumCell::NumCell(QWidget *parent) :QPushButton(parent), row(0), col(0), num(0) {}

Task 3 - Move the numbers

In this task we connect the click signal of the NumCell with the operate slot of the MainWindow. In this task we will define a new signal with arguments, which can pass the clicked location to the slot function.

The event chain is as follows: for each NumCell, the predefined clicked signal is connected to a custom on_clicked slot. The on_clicked slot manually emits a custom operate_signal(int row, int col) signal with arguments. The operate_signal from all the NumCell are connected with the prepared operate(int row, int col) of MainWindow. Here we create a new signal because the predefined signal cannot pass parameters to the slot.

We need to add the following to the NumCell.

// numcell.h class NumCell: public QPushButton { ... public slots: void on_clicked(); signals: void operate_signal(int rol, int col); }; // numcell.cpp void NumCell::on_clicked() { emit operate_signal(row, col); }

In this case we need to manually connect the signal and the slot.

// numcell.cpp NumCell::NumCell(QWidget *parent) :QPushButton(parent), row(0), col(0), num(0) { connect(this, &NumCell::clicked, this, &NumCell::on_clicked); }

And add the following to the MainWindow.

// mainwindow.h class MainWindow : public QMainWindow { ... private slots: void operate(int row, int col); void on_generate_clicked(); ... }; // mainwindow.cpp void MainWindow::on_generate_clicked() { generate(); }

Also we need to connect the signals from every number cells.

// mainwindow.cpp MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ... for (int i=0; i<3; i++) { for (int j=0; j<3; j++) { num_cells[i][j]->set_row(i); num_cells[i][j]->set_col(j); connect(num_cells[i][j], &NumCell::operate_signal, this, &MainWindow::operate); } } ... }

Bonus

Modify the program so that it supports moving numbers by WASD. You will need the third method of handler definition. You may want to check this.


Tips for writing your own program

This lab aims at introducing the basic logic of the Qt programs. But we cannot cover all the topics. If you would like to add more features, try to search on the internet for similar questions (e.g. stackoverflow) and check official document for details of API.

Resources & Sample I/O

Page maintained by
Homepage