A QDialog subclass

Something more interesting in this post: we are about to create a dialog in a completely programmatic way.

What we should do is extending the Qt class named QDialog that provides the standard implementation for a dialog. In our dialog we what to show the user a line edit (aka: input line) with a relative label that shows what he should input there, a couple of check boxes to specify some options, a couple of button to act or cancel the action.

Given that, we see that the class definition is quite strightforward, the only nuisance here is that Qt uses a few pseudo-keyword to implement some basic Qt concepts. The easier thing is having a look at the code and figure out what is the sense ot it.

So, here is our new class, a dialog that allow the user to pass a few parameters required by the application to perform a search:

#include <QtGui/QDialog>

class QCheckBox;
class QLabel;
class QLineEdit;
class QPushButton;

class DlgFind : public QDialog
{
Q_OBJECT // 1.

public:
DlgFind(QWidget* parent = 0); // 2.

signals: // 3.
void findNext(const QString& str, Qt::CaseSensitivity cs);
void findPrev(const QString& str, Qt::CaseSensitivity cs);

private slots: // 4.
void findClicked();
void enableBtnFind(const QString& text);

private:
QHBoxLayout* createLayout(); // 5.

QLabel* label;
QLineEdit* what;
QCheckBox* cbCase;
QCheckBox* cbBack;
QPushButton* btnFind;
QPushButton* btnClose;
};

1. this macro is required by all the classes having signals or slots.
2. we could pass to the dialog constructor the pointer to another widget that will be considered as the parent. By default, no parent is assumed.
3. "signals" is a Qt macro, and it is used to identify the messages emitted by the dialog.
4. "slots" is another Qt macro, used to identify the member functions of this class that could be called in connection with the call to another function (that acts as a signal).
5. utility function to create the dialog layout. Not stricly a necessity, but I feel that it makes the code a bit clearer.

Let's have now a look at the code implementation for this class. Here is the complete source file, some notes follow:

#include <QtGui/QtGui> // 1.
#include "DlgFind.h"

DlgFind::DlgFind(QWidget* parent) : QDialog(parent)
{
label = new QLabel("Find &what:"); // 2.
what = new QLineEdit();
label->setBuddy(what); // 3.

cbCase = new QCheckBox("Match &case");
cbBack = new QCheckBox("Search &backward");

btnFind = new QPushButton("&Find"); // 4.
btnFind->setDefault(true);
btnFind->setEnabled(false);

btnClose = new QPushButton("Close");

// 5.
connect(what, SIGNAL(textChanged(const QString&)), this, SLOT(enableBtnFind(const QString&)));
connect(btnFind, SIGNAL(clicked()), this, SLOT(findClicked()));
connect(btnClose, SIGNAL(clicked()), this, SLOT(close()));

setLayout(createLayout());
setWindowTitle("Find");
setFixedHeight(sizeHint().height()); // 6.
}

QHBoxLayout* DlgFind::createLayout() // 7.
{
QHBoxLayout* topLeft = new QHBoxLayout();
topLeft->addWidget(label);
topLeft->addWidget(what);

QVBoxLayout* left = new QVBoxLayout();
left->addLayout(topLeft);
left->addWidget(cbCase);
left->addWidget(cbBack);

QVBoxLayout* right = new QVBoxLayout();
right->addWidget(btnFind);
right->addWidget(btnClose);
right->addStretch();

QHBoxLayout* main = new QHBoxLayout();
main->addLayout(left);
main->addLayout(right);

return main;
}

void DlgFind::findClicked() // 8.
{
QString text = what->text();
Qt::CaseSensitivity cs = cbCase->isChecked() ? Qt::CaseSensitive : Qt::CaseInsensitive;
if(cbBack->isChecked())
{
emit findPrev(text, cs);
}
else
{
emit findNext(text, cs);
}
}

void DlgFind::enableBtnFind(const QString& text) // 9.
{
btnFind->setEnabled(!text.isEmpty());
}

1. actually, this include is just laziness. It means "include any Qt core and GUI components", and usually this is a bit of overkilling.
2. Jasmin & Mark suggest that it is a good habit to use the function tr() for marking the strings that have to be translated. But I am quite sure I don't want to internationalize this application. Notice the ampersand to specify which letter is shortcut for the widget.
3. the label has a shortcut, the line edit is a buddy of the label so, when we press the key for the label we get in focus its buddy.
4. the Find button is the default one (selected when we press enter) and initially disable - so we can't give the order to find something before we specify what we want to find.
5. a delicate spot in the class: here we connect signals and slots. When textChanged() is called on what, we want enableBtnFind() to be called on this. When btnFind is clicked, findClicked() on this. And when btnClose is clicked we want to close the dialog.
6. our dialog has fixed height, but variable width. You can have (a limited) fun resizing it.
7. the idea of the layout for this dialog having a first box, topLeft, horizontally organized for the label and the line; a second box, left, that contains vertically the first one and the two check boxes; a third, right, for the buttons and a space to fill the gap, again vertically; and finally a big box in which we put left and right horizontally side by side. Quite boring to do this programmatically, isn't it? Luckly this is not the only way to do it.
8. that's the slot we call when the user click on the Find button. He could click it only if he has already put something in the line edit, so just have to check the other options the user has specified using the check boxes and emit the relative signal. Notice that currently there is no definition for our signals, so what happens is just nothing.
9. and this is the slot for the changes in the text of our line edit. We check the text passed and we enable or disable the button accordingly.

A good thing about using Qt Creator is that we can just compile this stuff and run it. If we are using another IDE that doesn't know enough about Qt we need an intermediate step before we can see the result of our job, since there is a bit of code that Qt should generate to manage all this signal-slot business and it is put in a "moc" file. So, that file has to be generated and added to the makefile for the project. If your IDE doesn't do that for you directly you should help it running by hand qmake. Otherwise you get some error like "unresolved external symbol" (for VC++) for a qt_metacall() guy.

I wrote this post as homework while reading "C++ GUI Programming with Qt 4, Second Edition" by Jasmin Blanchette and Mark Summerfield.

No comments:

Post a Comment