
Time for action – implementing a tic-tac-toe game board
We will now create a widget that implements a game board for tic-tac-toe using buttons.
Open the tictactoewidget.h
file in Creator and update it by adding the highlighted code:
#ifndef TICTACTOEWIDGET_H #define TICTACTOEWIDGET_H #include <QWidget> class QPushButton; class TicTacToeWidget : public QWidget { Q_OBJECT public: TicTacToeWidget(QWidget *parent = 0); ~TicTacToeWidget(); private: QList<QPushButton*> board; }; #endif // TICTACTOEWIDGET_H
Our additions create a list that can hold pointers to instances of the QPushButton
class, which is the most commonly used button class in Qt. It will represent our game board. We have to teach the compiler to understand the classes that we use; thus, we add a forward declaration of the QPushButton
class.
The next step is to create a method that will help us create all the buttons and use a layout to manage their geometries. Go to the header file again and add a void setupBoard();
declaration in the private
section of the class. To quickly implement a freshly declared method, we can ask Qt Creator to create the skeleton code for us by positioning the text cursor just before after the method declaration (before the semicolon), pressing Alt + Enter on the keyboard, and choosing Add definition in tictactoewidget.cpp from the pop-up.
Tip
It also works the other way around. You can write the method body first and then position the cursor on the method signature, press Alt + Enter, and choose Add public declaration from the quick fix menu. There are also various other context-dependent fixes that are available in Creator.
Because in the header file we only forward-declared QPushButton
, we now need to provide a full class definition for it by including an appropriate header file. In Qt, all classes are declared in the header files that are called exactly the same as the classes themselves. Thus, to include a header file for QPushButton
, we need to add a #include <QPushButton>
line to the implementation file. We are also going to use the QGridLayout
class to manage the space in our widget, so we need #include <QGridLayout>
as well.
Tip
From now on, this book will not remind you about adding the include
directives to your source code—you will have to take care of this by yourself. This is really easy, just remember that to use a Qt class, you need to include a file named after that class.
Now, let's add the code to the body of the setupBoard
method. First, let's create a layout that will hold our buttons:
QGridLayout *gridLayout = new QGridLayout;
Then, we can start adding buttons to the layout:
for(int row = 0; row < 3; ++row) { for(int column = 0; column < 3; ++column) { QPushButton *button = new QPushButton; button->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); button->setText(" "); gridLayout->addWidget(button, row, column); board.append(button); } }
The code creates a loop over rows and columns of the board. In each iteration, it creates an instance of the QPushButton
class and sets the button's size policy to Minimum
/Minimum
so that when we resize the widget, buttons also get resized. A button is assigned a single space as its content so that it gets the correct initial size. Then, we add the button to the layout in row
and column
. At the end, we store the pointer to the button in the list that was declared earlier. This lets us reference any of the buttons later on. They are stored in the list in such an order that the first three buttons of the first row are stored first, then the buttons from the second row, and finally those from the last row.
The last thing to do is to tell our widget that gridLayout
is going to manage its size:
setLayout(gridLayout);
Alternatively, we might have passed this as a parameter to the layout's constructor.
Now that we have code that will prepare our board, we need to have it invoked somewhere. A good place to do this is the class constructor:
TicTacToeWidget::TicTacToeWidget(QWidget *parent)
: QWidget(parent)
{
setupBoard();
}
Now, build and run the program.
What just happened?
You should get a window containing nine buttons positioned in a grid-like fashion. If you start resizing the window, the buttons are going to be resized as well. This is because we set a grid layout with three columns and three rows that evenly distributes widgets in the managed area, as shown in the following figure:

While we're here, add another public
method to the class and name it initNewGame
. We will use this method to clear the board when a new game is started. The body of the method should look as follows:
void TicTacToeWidget::initNewGame() { for(int i=0; i<9; ++i) board.at(i)->setText(" "); }
Tip
You might have noticed that although we created a number of objects in setupBoard
using the new
operator, we didn't destroy those objects anywhere (for example, in the destructor). This is because of the way the memory is managed by Qt. Qt doesn't do any garbage collecting (as Java does), but it has this nice feature related to QObject
parent-child hierarchies. The rule is that whenever a QObject
instance is destroyed, it also deletes all its children. Since both the layout object and the buttons are the children of the TicTacToeWidget
instance, they will all be deleted when the main widget is destroyed. This is another reason to set parents to the objects that we create—if we do this, we don't have to care about explicitly freeing any memory.