• Aucun résultat trouvé

Integrating Designer-generated Files into Your Qt ProjectQt Project

Dans le document Qt 4 THE BOOK of THE BOOK of (Page 93-99)

GUI Design Using the Qt Designer

3.2 Integrating Designer-generated Files into Your Qt ProjectQt Project

Alt✆key.

Alternatively, while in the normal design mode, you can set the name of the de-sired Buddy widget in the Property Editor, using the Buddy property.3 Using this ap-proach, we would set the value of the Buddy property of the QLabel object that dis-plays the Decimal text in our byte converter dialog so that it matches the value of the objectName property of the corresponding line-edit object, namely, the string decEdit.

To undo the relationship, all you need to do is click the connection line in the Buddy mode and press the✞

✝ ☎

Del✆key.

3.2 Integrating Designer-generated Files into Your Qt Project

When saving with the menu item File→Save Form or Save Form As. . . , the Designer generates a .ui file from the information it has for each widget in the form.4 This .ui file is specified in the qmake project file, as shown in the following line:

FORMS = byteconverterdialog.ui

In our case, qmake takes into account the user interface file byteconverterdialog.ui;

several files can be specified, separated by a space, or other lines can be added according to the pattern FORMS +=file.ui.

3 Although this property has been there since Qt 3.x, the Designer for Qt 4.0 does not display it.

Only in version 4.1 does it appear again.

4 Using the third menu item, Save Form As Template. . . , you can save your form as a template, which then appears in the selection dialog for newForms.

When building the project, make then relies on theuser interface compiler uic to convert Designer-generated .ui files into C/C++ header files.5 There is a fixed naming convention in this step: for example, if the class represented by the .ui file generated by the Designer is called ByteConverterDialog (the value of the object-Name property can be examined to determine the class name), then the resulting header file is given the name ui_byteconverterdialog.h by uic.

It is important here that at least one other file in the project includes this generated header file. You must add the appropriate #include statementsbeforeqmake is run. Otherwise, make won’t call uic with the relevant interface description file as an argument on its next run.

Notice that the generated header file contains only a help class with two methods:

setupUi(), which generates the GUI, and retranslateUi(), which can be called if the program is to allow the user to change the language while it is running.

Both methods expect (as an argument) a pointer to the widget to which the GUI object described in the Designer is to be bound. Even if you have already chosen a template in the Designer, you can freely choose at this point the widget class for which the interface is intended. The MainWindow template is the only one that must be used together with a QMainWindow.6

The class generated by the uic is now available as Ui::ByteConverterDialog or Ui_

ByteConverterDialog, in general as Ui::classnameor Ui_class name, whereby the class name corresponds to the objectName attribute of the form created in the Designer.

There are now three ways of using and functionally developing the widget created.

Which of these is best to use depends on the particular context.

3.2.1 Using Designer-generated Classes as Helper Classes

If you only want to display a Designer-created user interface once, without touch-ing the correspondtouch-ing object again after it is initialized, it is appropriate to directly instantiate the generated class and bind the instance to a previously created widget with setupUi(). This method fixes the GUI elements described in the .ui file on to the widget and anchors them—provided this was specified in the Designer—with layouts.

We shall demonstrate this technique using our Designer-generated ByteConverter-Dialog:

5 Note for Qt 3 users: uic no longer generates a complete QObject-based class in Qt 4, but merely a framework which can be applied to the widget of the matching type.

6 The widget created in the Designer is used in this case as the central widget for the QMainWin-dow instance, and it is positioned with setCentralWidget(), instead of with the help of a layout, as normal. In addition, the Designer menu bars and toolbars are treated separately from Qt 4.1, a functionality that is equally available only for QMainWindow instances.

// simple/main.cpp

Since the widgets of the Designer-generated dialog are available as publicly ac-cessible members of the UI class, they can be fine-tuned in the code later on by calling the methods of the respective widgets. Their signals and slots can partici-pate in signal/slot connections. Whether the class Ui::ByteConverterDialog is now instantiated in the main() function or in the constructor of a class inheriting from QDialog makes no difference.

In our example, however, the approach shown in the listing above causes problems:

We could connect the Quit button’s clicked signal to the accept() slot of the dialog, and we would then be able to connect the slots binChanged(), hexChanged(), and binChanged() to the textChanged() signals of the respective QTextEdit widgets. But then we would not be able to access the pointer to any uic-generated widget in the slot itself.

For this reason, the use of directly calling setupUi() is very limited: If we do so, we shall restrict ourselves to applying instances of the class generated by uic to instances of a standard class like QWidget or QDialog. However, in some situations this procedure could be completely sufficient, for example, in simple modal input dialogs which are called with exec(). The exec call starts a separate event loop and returns only if accept(), reject(), or another method closes the dialog. Since the dialog object does not cease to exist when the dialog has been closed, the subsequent code can fetch the values of the widgets placed inside the dialog by setupUi() without any danger, so that you can get by without the QDialog subclass in those cases.

It is important that you always call the setupUi() method of an instance of a Designer-generated class first, before trying to access member variables of the in-terface object (in the current example, those of ui). Otherwise, the program will mess around with uninitialized pointers and crash.

An argument for not instanciating Designer-generated classes directly results from the fact that public-member varibles are accessible from the outside, causing a

violation of secrecy, one of the most important principles of object-oriented pro-gramming. Secrecy enforces abstraction by only granting the class members access to their own methods.

The internal details of the class are thus “cut off” from the other classes, and you can change the internal design of the class without having to adjust the rest of the program that uses this class. As long as you use only the UI class as a short-term setup class, the infringement of the encapsulation principle is not really of any consequence.

3.2.2 Always Having Designer-generated Widgets Available

In order to deal with the shortcoming just demonstrated, it is a good idea to include the class generated by uic as a member variable. To do this, we first inherit from the desired class, which in our case is QDialog.

The main() function matches the one from Chapter 2, since ByteConverterDialog from its point of view is again a “black box.”

The crucial difference is in the declaration of the class. We declare the class gen-erated by uic as a private member of a QDialog subclass. This allows for abitrary access to the widgets within the Designer-generated class via this newly created ui member variable of the ByteConverterDialog class inherited from QWidget:

// member/byteconverterdialog.h ...

#include <QDialog>

#include "ui_byteconverterdialog.h"

class QLineEdit;

class ByteConverterDialog : public QDialog {

...

private:

Ui::ByteConverterDialog ui;

};

The constructor and all slots now access the generated class via the ui member variable:

// member/byteconverterdialog.cpp ...

ByteConverterDialog::ByteConverterDialog(QWidget *parent) : QDialog(parent)

{

ui.setupUi(this);

connect(ui.decEdit, SIGNAL(textChanged(const QString&)), this, SLOT(decChanged(const QString&)));

connect(ui.hexEdit, SIGNAL(textChanged(const QString&)), this, SLOT(hexChanged(const QString&)));

connect(ui.binEdit, SIGNAL(textChanged(const QString&)), this, SLOT(binChanged(const QString&)));

}

void ByteConverterDialog::decChanged(const QString& newValue) {

bool ok;

int num = newValue.toInt(&ok);

if (ok) {

ui.hexEdit->setText(QString::number(num, 16));

ui.binEdit->setText(QString::number(num, 2));

} else {

ui.hexEdit->setText("");

ui.binEdit->setText("");

} } ...

The overlying principle also applies here: It is essential that setupUi() is called first before we can use the UI class in any way at all. The disadvantage of this method is its indirectness, via the member variable. But the advantage of this approach is that it defuses the encapsulation problem, limiting the problem to scope of the dialog class. Any since access from outside of the dialog is not possible under any circumstances. A further bonus: It is clear from the code which widgets were gen-erated in the Designer. In addition, this approach is particularly suited for widgets in libraries that have to remain binary-compatible, because only the pointer to the instance of the generated class changes the binary layout in the compiler output.7

3.2.3 Multiple Inheritance

As the ideal solution, Trolltech recommends multiple inheritance. But like the pre-vious solution, this works only if you plan your own subclass.

In this method, the new widget inherits not only from QWidget, but also from the UI class generated by uic. A particular highlight is the use of the private keyword in the inheritance instruction. This ensures that all methods from the UI class

7 More details of binary compatibility in C++ have been compiled by the KDE project at http://developer.kde.org/documentation/other/binarycompatibility.html.

are given the status of private class variables in the new class, although they are actually publicly accessible in the former class itself:

// inherit/byteconverterdialog.h ...

class ByteConverterDialog : public QDialog,

private Ui::ByteConverterDialog ...

This method thus solves several problems at one stroke: We can use the widget pointers generated by uic as standard member variables, without going the long way round, via a help object, and they remain private, so that encapsulation to the outside is maintained.

For our example, this means that the constructor changes as follows:

// inherit/byteconverterdialog.cpp

As before, we only need to call the setupUi() method in first position, and as the argument we again use a pointer to the widget that is our current class scope.

Caution: In this approach the inheritance sequence is important. First the class must inherit from QDialog, and then from the Designer class. If this is not the case, the compiler will throw an error that is difficult to understand, and which quickly brings the programmer to despair:

moc_byteconverterdialog.cpp:43: error: ‘staticMetaObject’ is not a member of type ‘Ui::ByteConverterDialog’

moc_byteconverterdialog.cpp: In member function ‘virtual void*

ByteConverterDialog::qt_metacast(const char*)’:

moc_byteconverterdialog.cpp:60: error: ’class Ui::ByteConverterDialog’

has no member named ’qt_metacast’

moc_byteconverterdialog.cpp: In member function ‘virtual int

ByteConverterDialog::qt_metacall(QMetaObject::Call, int, void**)’:

moc_byteconverterdialog.cpp:66: error: ’class Ui::ByteConverterDialog’

has no member named ’qt_metacall’

make: *** [moc_byteconverterdialog.o] Error 1

The reason is the behavior of the meta-object compiler, which checks only in the first parent class of the inheritance list whether this inherits from QObject or not.

This also means that it is generally not possible to inherit from several classes that all have QObject as a base class.

Dans le document Qt 4 THE BOOK of THE BOOK of (Page 93-99)