Time for action – polishing the dialog

Now that the GUI itself works as we intended it to, we can focus on giving the dialog some more polish.

Accelerators and label buddies

The first thing we are going to do is add accelerators to our widgets. These are keyboard shortcuts that, when activated, cause particular widgets to gain keyboard focus or perform a predetermined action (for example, toggle a checkbox or push a button). Accelerators are usually marked by underlining them, as shown in the following figure:

We will set accelerators to our line edits so that when the user activates an accelerator for the first field, it will gain focus. Through this we can enter the name of the first player, and similarly, when the accelerator for the second line edit is triggered, we can start typing in the name for the second player.

Start by selecting the label on the left-hand side of the first line edit. Press F2 or double-click on the label (alternatively, find the text property of the label in the property editor and activate its value field). This enables us to change the text of the label. Navigate using cursor keys so that the text cursor is placed before the character 1 and type the & character. This character marks the character directly after it as an accelerator for the widget. For widgets that are composed of both text and the actual functionality (for example, a button), this is enough to make accelerators work. However, since QLineEdit does not have any text associated with it, we have to use a separate widget for that. This is why we have set the accelerator on the label. Now, we need to associate the label with the line edit so that the activation of the label's accelerator will forward it to the widget of our choice. This is done by setting a so-called buddy for the label. You can do this in code using the setBuddy method of the QLabel class or using Creator's form designer. Since we're already in the Design mode, we'll use the latter approach. For that, we need to activate a dedicated mode in the form designer.

Look at the upper part of Creator's window; directly above the form, you will find a toolbar containing a couple of icons. Click on the one labeled Edit buddies or just press F5 on your keyboard. Now, move the mouse cursor over the label, press the mouse button, and drag from the label towards the line edit. When you drag the label over the line edit, you'll see a graphical visualization of a connection being set between the label and the line edit. If you release the button now, the association will be made permanent. You should notice that when such an association is made, the ampersand character (&) vanishes from the label and the character behind it gets an underscore. Repeat this for the other label and corresponding line edit. Now, you can preview the form again and check whether accelerators work as expected.

The tab order

While you're previewing the form, you can check another aspect of the UI design. Start by pressing the Tab key and see how the focus moves from widget to widget. There is a good chance that the focus will start jumping back and forth between buttons and line edits instead of a linear progress from top to bottom (which is an intuitive order for this particular dialog). To check and modify the order of focus, leave the preview and switch to the tab order editing mode by clicking on the icon called Edit Tab Order in the toolbar.

This mode associates a box with a number to each focusable widget. By clicking on the rectangle in the order you wish the widgets to gain focus, you can reorder values, thus reordering focus. Now, make it so that the order is as shown in the following figure:

Enter the preview again and check whether the focus changes according to what you've set.

Tip

When deciding about the tab order, it is good to consider which fields in the dialog are mandatory and which are optional. It is a good habit to allow the user to tab through all the mandatory fields first, then to the dialog confirmation button (for example, one that says OK or Accept), and then cycle through all the optional fields. Thanks to this, the user will be able to quickly fill all the mandatory fields and accept the dialog without the need to cycle through all the optional fields that the user wants to leave at their default values.

Signals and slots

The last thing we are going to do right now is make sure that the signal-slot connections are set up properly. To do this, switch to the signal-slot editor mode by pressing F4 or choosing Edit Signals/Slots from the toolbar. The Dialog with Buttons Bottom widget template predefines two connections for us, which should now become visible in the main canvas area:

The QDialog class that implements dialogs in Qt has two useful slots—accept() and reject()—which inform the caller whether the action represented by the dialog was accepted or not. For our convenience, these slots should already be connected to the respective accepted() and rejected() signals from the group of buttons (which is an instance of the QDialogButtonBox class) that by default, contain the OK and Cancel buttons. If you click on any of them signal accepted() or respectively, rejected() will be emitted by the box.

At this point, we can add some more connections to make our dialog more functional. Let's make it such that the button to accept the dialog is only enabled when neither of the two line edits is empty (that is, when both the fields contain player names). While we will implement the logic itself later, we can now make connections to a slot that will perform the task.

Since no such slot exists by default, we need to inform the form editor that such a slot will exist at the time when the application is compiled. To do this, we need to switch back to the default mode of the form editor by pressing F3 or choosing Edit Widgets from the toolbar. Then, you can invoke the form's context menu and choose Change signals/slots. A window will pop up such as the one shown in the following figure that lists the available signals and slots:

Click on the + button in the Slots group and create a slot called updateOKButtonState():

Then, accept the dialog and go back to the Signals/Slots mode. Create a new connection by grabbing one of the line edits with your mouse. When you move the cursor outside the widget, you will notice a red line following your pointer. If the line encounters a valid target, the line will change to an arrow and the target object will be highlighted. The form itself can also be a target (or a source); in this case, the line will end with a ground mark (two short horizontal lines).

When you release the mouse button, a window will pop up, listing all the signals of the source object and all the slots of the target object. Choose the textChanged(QString) signal. Note that when you do this, some of the available slots will disappear. This is because the tool only allows us to choose from slots that are compatible with the highlighted signal. Select our newly created slot and accept the dialog. Repeat the same for the other line edit.

What we have done here is that we've created two connections that will trigger when the text of either of the two line edits is changed. They will execute a slot that doesn't exist yet—by "creating" the slot, we only declared our intention to implement it in a QDialog subclass that was also created for us. You can now go ahead and save the form.

What just happened?

We performed a number of tasks that make our form follow standard behaviors known from many applications—this makes form navigation easy and shows the user which actions can be undertaken and which are currently not available.

Using designer forms

If you open the form in a text editor (for example, by switching to the Creator's Edit pane), you will notice that it is really an XML file. So how do we use this file?

As part of the build process, Qt calls a special tool called User Interface Compiler (uic) that reads the file and generates a C++ class that contains a setupUi() method. This method accepts a pointer to a widget and contains code, which instantiates all the widgets, sets their properties, and establishes signal-slot connections, and it is our responsibility to call it to prepare the GUI. The class itself, which is named after your form (that is after the value of the objectName property of the form object) with a Ui namespace prepended to it (for example, Ui::MyForm) is not derived from a widget class but is rather meant to be used with one. There are basically three ways of doing this.

Direct approach

The most basic way to use a Qt Designer form is to instantiate a widget and the form object and to call setupUi on the widget, like this:

QWidget *widget = new QWidget
Ui_form ui * = new Ui_form;
ui->setupUi(widget);

This approach has a number of flaws. First of all, it creates a potential memory leak of the ui object (remember, it is not QObject, so you can't set a parent to it so that it's deleted when the parent is deleted). Second, since all the widgets of the form are variables of the ui object that is not tied to the widget object, it breaks encapsulation, which is one of the most important paradigms of object-oriented programming. However, there is a situation when such a construct is acceptable. This is when you create a simple short-lived modal dialog. You surely need to remember that to show regular widgets, we have been using the show() method. This is fine for non-modal widgets, but for modal dialogs you should instead call the exec()method that is defined in the QDialog class. This is a blocking method that doesn't return until the dialog is closed. This allows us to modify the code so that it becomes:

QDialog dialog;
Ui_form ui;
ui.setupUi(&dialog);
dialog.exec();

Since we're creating objects on the stack, the compiler will take care of deleting them when the local scope ends.

The multiple-inheritance approach

The second way of using Designer forms is to create a class derived from both QWidget (or one of its subclasses) and the form class itself. We can then call setupUi from the constructor:

class Widget : public QWidget, private Ui::MyForm {
public:
  Widget(QWidget *parent = 0) : QWidget(parent) {
    setupUi(this);
  }
};

This way, we keep the encapsulation as our class inherits fields and methods from the Ui class, and we can call any of them directly from within the class code while restricting access from the outside world by using private inheritance. The drawback of this approach is that we pollute the class namespace, for example, if we had a name object in Ui::MyForm, we wouldn't be able to create a name method in Widget.

The single inheritance approach

Fortunately, we can work around this using the composition instead of inheritance. We can derive our widget class only from QWidget and instead of also subclassing Ui::MyForm, we can make an instance of it a private member of the new class:

class Widget : public QWidget {
public:
  Widget(QWidget *parent = 0) : QWidget(parent) {
    ui = new Ui::MyForm;
    ui->setupUi(this);
  }
  ~Widget() { delete ui; }
private:
  Ui::MyForm *ui;
};

At the cost of having to manually create and destroy the instance of Ui::MyForm, we can have the additional benefit of containing all variables and code of the form in a dedicated object, which prevents the aforementioned namespace pollution.

This is the recommended way of using Designer forms, and it's also the default mode of operation when you tell Qt Creator to generate a Designer form class for you.