Библиотека Qt

(перенаправлено с «QT»)
Перейти к: навигация, поиск
Icon-expand.png Эта статья является незавершенной!
Эта статья нуждается в доработке. Если Вы можете помочь, сделайте это в соответствии с руководством по оформлению.

Библиотека QT

Введение

Библиотека QT предназначена для разработки GUI, разработанная компанией Trolltech AS. Qt была представлена в 1996 году, с тех пор, с помощью этой библиотеки было создано большое количество разнообразных приложений с графическим пользовательским интерфейсом.


Qt является кроссплатформенной, есть реализации библиотеки для MS/Windows, Unix/X11 (Linux, Sun Solaris, HP-UX, Digital Unix, IBM AIX, SGI IRIX и пр.), Macintosh ( Mac OS X ) и Embedded платформ. Библиотека является объектно-ориентированной, базирующейся на компонентах и имеет богатое разнообразие различных визуальных элементов - виджетов (widgets), предоставляемых в распоряжение программиста. Qt распространяется в двух ипостасях - коммерческая реализация (имеются 'Qt Professional' и 'Qt Enterprise Editions' версии) и некоммерческая реализация, доступная для свободного скачивания http://www.trolltech.com. Основной лозунг компании "Кодируй меньше - создавай больше"


Библиотека включает в себя :

  • Среду для разработки графического интерфейса.
  • Компилятор мета-объектов.
  • Набор классов для работы.

Библиотека Qt является безусловным лидером среди имеющихся средств разработки межплатформенных программ на языке C++. Широко известная и часто используемая в мире Linux, она, благодаря распространению графической оболочки KDE, стала де-факто стандартом проектирования программного обеспечения на этой платформе. К сожалению, для разработчиков Windows-приложений библиотека Qt долгое время не выходила на передний план, поскольку для Windows существовали более доступные и удобные средства быстрой разработки программ. Но последнее время расстановка сил в корне изменилась. Во-первых, новая, 4-я версия библиотеки Qt, наконец, дотянулась по своим возможностям до тех вершин, на которых долгое время господствовали Microsoft и Borland/Inprise. Во-вторых, самой Borland пришлось обратиться к Qt, когда встал вопрос о разработке межплатформенных программ. Набор универсальных компонентов CLX в Delphi/Kylix представляет собой всего лишь оболочку, позволяющую языку Object Pascal работать с определёнными на C++ классами Qt. В-третьих, версия Qt для Windows, наконец-то, стала свободной, а не только коммерческой, как это было раньше.


Обзор классов библиотеки Qt4

Qtclasses.png


Компиляция Qt4 в Linux

Поскольку библиотека Qt является основой для KDE, то, скорее всего, она уже установлена в вашей системе. Или в составе вашего дистрибутива есть бинарные пакеты. Обратитесь к справочнику вашей операционной системы. Но даже в этом случае могут отсутствовать некоторые средства разработки, т.к. в KDevelop используются свои собственные инструменты. Кроме того, обычно вместе с дистрибутивом Linux распространяются не самые свежие версии Qt.

Предварительные условия:

  • должен быть установлен компилятор g++ или icc.

Порядок установки:

  • Распакуйте содержимое архива qt-x11-opensource-src-4.x.x.tar.gz в какой-нибудь временный каталог:
cd /tmp
gunzip qt-x11-opensource-4.x.x.tar.gz
tar xvf qt-x11-opensource-4.x.x.tar
  • Перейдите в этот каталог и запустите configure:
cd /tmp/qt-x11-opensource-4.x.x
./configure
  • При необходимости разрешается задать параметры сборки, список которых можно узнать, если при запуске configure указать ключ -help;

Не выходя из каталога установки, запустите процесс компиляции с помощью команды make и приготовьтесь к длительному ожиданию; Для копирования всех необходимых файлов в системные каталоги дайте команду su -c "make install"

  • и введите пароль суперпользователя;

добавьте в переменную окружения PATH путь к исполняемым файлам и библиотекам Qt. Для этого в случае использования оболочек bash, ksh, zsh или sh требуется в файл .profile добавить строки:

PATH=/usr/local/Qt4/bin:$PATH
export PATH

Для csh и tcsh:

setenv PATH /usr/local/Qt4/bin:$PATH


Простейшее приложение Qt4

Попробуем создать пустое окно и вывести его на экран. Листинг 1. Простейшее приложение Qt (файл examples-qt/00/00.cpp)

    1 // Простейшее приложение Qt4 (пустое окно)
    2 
    3 #include <QApplication>
    4 #include <QMainWindow>
    5 
    6 int main(int argc, char *argv[]) {
    7 
    8     QApplication app(argc, argv);
    9 
   10     QMainWindow *mw = new QMainWindow(0, Qt::Window);
   11     mw->setWindowTitle("Hello Qt4");
   12     mw->resize(400, 300);
   13     mw->show();
   14 
   15     return app.exec();
   16 }

Пояснения к программе:

  • (3-4) Подключили заголовочные файлы с определениями классов QApplication (приложение) и QMainWindow (главное окно).
  • (6) Обычный для C++ заголовок главной функции main с аргументами командной строки.
  • (8) Объявили переменную типа QApplication (приложение), передав конструктору параметры командной строки, которые, возможно, указаны при запуске программы (argc -- число параметров, argv -- указатель на массив строковых значений).
  • (10) Создали главное окно приложения. Первый параметр конструктора указывает на родительский элемент (в данном случае 0 -- окно не имеет родителя), а второй -- набор битовых флагов, влияющих на внешний вид окна (флаг Qt::Windows означает, что элемент будет выглядеть, как окно приложения, т.е. будет иметь строку заголовка с системными кнопками для сворачивания на панель задач, закрытия и т.д.).
  • (11) Задали текст заголовка окна (пока мы избегаем использовать символы кириллицы, этому вопросу будет посвящены следующие примеры).
  • (12) Определили размеры окна (ширину и высоту) в пикселах.
  • (13) Вывели окно на экран.
  • (15) Запустили цикл обработки событий, происходящих с элементами приложения. Пока в нашей программе никакие события не определены, кроме стандартных реакций на действия пользователя (изменение размеров и положения окна, нажатие кнопок в строке заголовка).

Перед компиляцией программы надо сначала создать проект Qt (файл с расширением .pro), для этого требуется войти в тот каталог, в котором находится cpp-файл с исходным текстом, и запустить утилиту qmake с параметром -project. Имя cpp-файла можно не указывать. По умолчанию имя файла проекта будет совпадать с названием каталога, в котором происходит сборка программы. Для задания другого имени pro-файла при вызове qmake надо указать параметр -o. Например, для нашего первого проекта будет создан файл 00.pro следующего содержания:

TEMPLATE = app
TARGET = 00
DEPENDPATH += .
INCLUDEPATH += .

# Input
SOURCES += 00.cpp

После создания pro-файла можно сгенерировать make-файл, для чего достаточно запустить утилиту qmake ещё раз, но уже без указания каких-либо ключей (или задать параметр -makefile ФайлПроекта.pro). Только после этого можно начинать компиляцию с помощью обычной команды mingw32-make (в случае использования компилятора MinGW), nmake (для Microsoft C++), make (для g++ в Linux) и т.п. При этом можно явно указать имя make-файла, для этого все перечисленные компиляторы распознают ключ -f. Поскольку в процессе разработки программу приходится много раз перекомпилировать, вышеописанный процесс лучше всего проводить с помощью командного файла build.cmd. Например, в системе Windows:

1  qmake -project -o 00.pro 00.cpp
2  qmake -makefile 00.pro
3
4  rem mingw32-make -f Makefile.Debug
5  mingw32-make -f Makefile.Release
6
7  pause
  • (1) Создаём файл 00.pro.
  • (2) Создаём make-файл.
  • (4) Выполняем компиляцию в режиме Debug, т.е. с информацией для отладчика (в данном случае строка закомментирована).
  • (5) Повторяем компиляцию в режиме Release (без возможности отладки на уровне исходного текста программы).
  • (7) Ждём нажатия любой клавиши (чтобы окно не закрылось раньше времени и мы успели прочитать все сообщения компилятора).

Теперь, как и обещали, обсудим вопрос о символах кириллицы. Библиотека Qt предлагает несколько решений для национальных алфавитов, отличных от стандартной латиницы. Первый вариант -- использовать функцию QString::fromLocal8Bit. Дело в том, что для работы со строковыми значениями в Qt имеется класс QString, который оперирует двухбайтными символами Unicode. Для преобразования обычных строк с однобайтными символами к типу QString как раз и предназначен метод fromLocal8Bit. При этом используется системная кодировка (в Windows --- cp1251, в Linux -- обычно koi8-r).

Недостаток данного варианта заключается в том, что если программа, например, разрабатывалась в системе Windows, а затем мы захотели скомпилировать её исходные тексты в Linux, где используется другая кодовая таблица для символов кириллицы, то предварительно придётся осуществить перекодировку исходных текстов, иначе после компиляции все русские сообщения окажутся нечитаемыми.

Изменённый текст нашей первой программы приведён в листинге 2

Листинг 2

    1 // Простейшее приложение Qt4 (пустой фрейм)
    2 // Вариант с кириллицей в заголовке
    3 
    4 #include <QApplication>
    5 #include <QMainWindow>
    6 
    7 int main(int argc, char *argv[]) {
    8 
    9     QApplication app(argc, argv);
   10 
   11     QMainWindow *mw = new QMainWindow(0, Qt::Window);
   12     mw->setWindowTitle(QString::fromLocal8Bit("Пустое окно Qt4"));
   13     mw->resize(400, 300);
   14     mw->show();
   15 
   16     return app.exec();
   17 }


Второй вариант работы с символами кириллицы -- явное использование кодеков (специальных объектов для перекодировки строк). Соответствующий класс QTextCodec определён в заголовочном файле с тем же именем (без расширения! Заголовочные файлы с расширением .h используются только в старых проектах Qt3). При создании кодека надо указать название используемой кодовой таблицы, например:

QTextCodec *codec = QTextCodec::codecForName("CP1251");

Затем надо связать этот кодек со всеми строками C++:

QTextCodec::setCodecForCStrings(codec);

Поскольку при создании кодека указана конкретная кодовая таблица, то исход компиляции исходных текстов не зависит от системной кодировки той платформы, на которой производится сборка программы: результат будет везде одним и тем же. Но файлы с исходными текстами программ в этом случае нельзя подвергать перекодировке (или после перекодировки требуется изменить название кодовой таблицы, заданной при создании кодека). В листинге 3 приведён новый вариант нашей программы.

Листинг 3

    1 // Простейшее приложение Qt4 (пустой фрейм)
    2 // Кодеки
    3 
    4 #include <QApplication>
    5 #include <QMainWindow>
    6 #include <QTextCodec>
    7 
    8 int main(int argc, char *argv[]) {
    9 
   10     QApplication app(argc, argv);
   11 
   12     QTextCodec *codec = QTextCodec::codecForName("CP1251");
   13     QTextCodec::setCodecForCStrings(codec);
   14 
   15     QMainWindow *mw = new QMainWindow(0, Qt::Window);
   16     mw->setWindowTitle("Пустое окно Qt4");
   17     mw->resize(400, 300);
   18     mw->show();
   19 
   20     return app.exec();
   21 }


Наконец, третий (наиболее предпочтительный) метод работы с символами национальных алфавитов связан с использованием специальной функции перевода tr, с помощью которой осуществляется интернационализация приложений. Подробнее этот вопрос мы обсудим позже, а пока договоримся все строковые константы, указанные в тексте программы, передавать в качестве параметра функции tr. Эта статическая функция является членом всех классов Qt, порождённых от базового класса QObject, но если, как сейчас, мы собираемся вызвать её в главной программе, а не в каком-либо методе класса, то приходится указывать какой-нибудь подходящий объект, например, QObject::tr. Для указания кодировки, используемой функцией перевода, надо создать соответствующий кодек и передать его в качестве аргумента методу setCodecForTr. Окончательный вариант нашей программы показан в листинге 4.


Листинг 4

    1 // Простейшее приложение Qt4 (пустой фрейм)
    2 // Кодеки и функция tr()
    3 
    4 #include <QApplication>
    5 #include <QMainWindow>
    6 #include <QTextCodec>
    7 
    8 int main(int argc, char *argv[]) {
    9 
   10     QApplication app(argc, argv);
   11 
   12     QTextCodec *codec = QTextCodec::codecForName("CP1251");
   13     QTextCodec::setCodecForTr(codec);
   14 
   15     QMainWindow *mw = new QMainWindow(0, Qt::Window);
   16     mw->setWindowTitle(QMainWindow::tr("Пустое окно Qt4"));
   17 
   18     mw->resize(400, 300);
   19     mw->show();
   20 
   21     return app.exec();
   22 }


Интегрированная среда разработки

Конечно, с исходным текстом программ можно работать, используя любой текстовый редактор, а компиляцию проводить с помощью командной строки. Но более удобно (во всяком случае, так считают те, кто мало знаком с миром Unix/Linux-систем) вести разработку программ в какой-нибудь интегрированной среде (IDE). В состав библиотеки Qt входит утилита Qt Designer, с помощью которой можно в диалоговом режиме вести проектирование графического интерфейса приложений. Но она не является интегрированной средой разработки, т.к. в ней нет текстового редактора и отладчика (как было объявлено, в 4-й версии команда Qt отказалась от идеи превратить Qt Designer в полноценную среду разработки, а сосредоточилась на вопросах интеграции его со стандартными инструментами).

В Linux, где Qt стала де-факто стандартом разработки программ, с этим особых проблем не возникает, поскольку практически любая IDE (например, KDevelop) поддерживает работу с Qt.

User-away.png Данная статья является устаревшей, поскольку:
выпущена уже вторая версия IDE Qt Creator

Если она в течение некоторого времени не будет обновлена, то она может быть удалена. Пожалуйста, обновите эту статью. Обратитесь к странице обсуждения этой статьи для получения подробной информации.

С системой Windows всё, как обычно, сложнее. В коммерческой версии Qt для Windows предусмотрена интеграция с Microsoft Visual Studio, но в Open Source Edition данная возможность отсутствует. Поэтому в рамках направления Open Source в настоящее время разрабатываются несколько похожих проектов, способных претендовать на звание "IDE для Qt". Среди них стоит отметить QDevelop http://qdevelop.org, рис.

Qdevelop01.png

Рис. Интегрированная среда разработки QDevelop

Разумеется, сама программа QDevelop написана с использованием Qt, поэтому её исходные тексты можно скомпилировать на любой платформе, поддерживаемой библиотекой Qt. Хотя к настоящему времени (январь 2007 года) пока выпущен только предварительный релиз 0.21, но программа уже вполне работоспособна: имеется русский перевод интерфейса, система контекстной помощи, работает отладчик, подсветка синтаксиса, а также автодополнение кода (для последнего требуется утилита ctags, её можно скачать с сайта http://ctags.sourceforge.net.

После компиляции программы QDevelop (или после её инсталляции, если вы не захотели возиться с исходными текстами) требуется указать пути к инструментам библиотеки Qt, компилятору и отладчику. Для этого надо выполнить команду меню Инструменты | Инструменты (рис.) .

Qdevelop02.png

Обработка событий

Для связывания событий, происходящий с объектами, и функций, предназначенных для обработки этих событий, в библиотеке Qt используется интересный механизм сигналов и слотов. Сигнал -- это сообщение о том, что произошло какое-либо событие, например, нажатие на кнопку или выбор пункта меню. Вся информация о событии сохраняется в полях экземпляра соответствующего класса. У сигнала есть источник (например, кнопка) и приёмник (объект, метод которого будет обрабатывать это событие). Слот -- это сама функция-обработчик события. Связь между всеми четырьмя перечисленными элементами задаётся с помощью метода connect (соединить):

bool QObject::connect (
    const QObject *sender,    // Источник события.
    const char *signal,       // Сигнал.
    const QObject *receiver,  // Объект-приёмник сигнала.
    const char *method,       // Функция-обработчик.
    Qt::ConnectionType type = Qt::AutoConnection
    ) const


Последний параметр определяет режим обработки: Qt::DirectConnection -- событие обрабатывается сразу; Qt::QueuedConnection -- событие ставится в общую очередь и будет обработано только после всех сообщений, уже имеющихся в этой очереди; Qt::AutoConnection -- если источник события находится в том же потоке, что и приёмник, то будет использован режим Qt::DirectConnection, в противном случае -- Qt::QueuedConnection.

Для определения сигнала и слота используются макросы SIGNAL и SLOT. Например, мы хотим, чтобы текстовая метка label (экземпляр класса QLabel) отображала позицию полосы прокрутки scrollBar (экземпляр класса QScrollBar). В документации на библиотеку Qt (открыв doc/html/index.html или программу assistant) находим, что в классе QAbstractSlider, потомком которого является QScrollBar, определён сигнал

void QAbstractSlider::valueChanged ( int value )

оповещающий об изменении положения ползунка полосы прокрутки. Далее, в описании класса QLabel находим, что изменение текста надписи производится с помощью функции setText(строка) или setNum(число). Тогда вызов метода connect должен выглядеть следующим образом:

QObject::connect(
    scrollBar,                 // Источник события.
    SIGNAL(valueChanged(int)), // Сигнал.
    label,                     // Объект-приёмник сигнала.
    SLOT( setNum(int) ) );     // Функция-обработчик.

Заметим, что параметрами сигнала и слота являются типы, а не переменные.

Количество параметров слота всегда не больше количества параметров сигнала. Соответствие между ними, как обычно, позиционное: при выполнении программы значением i-го параметра слота становится значение i-го параметра сигнала.

В объявлении класса методы-слоты необходимо указывать в разделе public slots или private slots. Если поместить их вместе с обычными методами класса, то программа будет скомпилирована без ошибок, но при её выполнении неправильно объявленные методы-слоты вызываться не будут.

Обычно в программе используются стандартные сигналы, но если требуется определить собственные, то их надо объявить в разделе signals. Например:

class MainWondow : public QMainWindow {
    Q_OBJECT
.............
signals:
    void mySignal();    // Сигнал.
private slots:
    void onMySignal();  // Слот.
.............

Сигнал может быть "соединён" с другим сигналом, например:

connect( myButton,
         SIGNAL( clicked() ),
         this,
         SIGNAL( buttonClicked() ) );

Один и тот же сигнал можно связать с несколькими слотами и/или другими сигналами. С одним и тем же слотом можно связать несколько сигналов. Можно указать несколько одинаковых "соединений": тогда одно событие вызовет генерацию нескольких сигналов и, соответственно, многократное выполнение метода-слота.

Чтобы разорвать связь между сигналом и слотом, используется метод disconnect:

bool QObject::disconnect ( const QObject *sender,
                           const char *signal,
                           const QObject *receiver,
                           const char *method )  [static]

В листинге 5 приведён текст небольшой программы, иллюстрирующей принцип обработки нажатия на кнопку (экземпляр класса QPushButton).

Листинг 5 (файл examples-qt/01/01.cpp)

    1 // Сигналы и слоты: кнопка в окне
    2 
    3 #include <QApplication>
    4 #include <QPushButton>
    5 
    6 int main(int argc, char *argv[]) {
    7 
    8     QApplication app(argc, argv);
    9 
   10     QPushButton *button = new QPushButton(
   11         QString::fromLocal8Bit("&Выход") ); // Кнопка.
   12     button->setFont(QFont("Times", 16, QFont::Bold));
   13     QObject::connect(
   14         button,            // Источник сигнала.
   15         SIGNAL(clicked()), // Сигнал о нажатии кнопки.
   16         &app,              // Приёмник сигнала.
   17         SLOT( quit() ) );  // Функция-слот (обработчик события).
   18     button->show();
   19 
   20     return app.exec();
   21 }

Здесь мы разместили в окне обычную кнопку (10-11) с надписью "Выход" и связали её нажатие -- сигнал clicked (15) -- с функцией-обработчиком quit (17), которая завершает приложение app. Заметим, что мы не создаём главное окно для кнопки, это будет сделано автоматически (рис.).

Символ "&" перед буквой "В" в тексте надписи на кнопке (11) позволяет активировать её не только по щелчку левой кнопкой мыши или нажатием клавиши Enter, но также с помощью комбинации клавиш Alt+в (к сожалению, только в режиме ввода кириллицы).


Меню и строка состояния

Строка состояния

Строка состояния QStatusBar создаётся автоматически в нижней части главного окна приложения, если в программе хоть раз вызвается метод MainWindow::statusBar. При наведении указателя мыши на кнопку панели инструментов или пункт меню в строке состояния на время появляется текст подсказки, если этот текст определён для данной кнопки или данного пункта.

Указатель на экземпляр QStatusBar можно получить с помощью метода QMainWindow::statusBar(). Чтобы вывести в строке состояния произвольный текст (и затереть предыдущий), используется функция showMessage:

void QStatusBar::showMessage(
    const QString &message,  // Выводимая строка.
    int timeout = 0 )        // Кол-во миллисекунд.

Если timeout>0, то сообщение автоматически исчезает через указанный промежуток времени, а на его место возвращается текст предыдущей надписи.

По умолчанию строка состояния представляется в виде одной панели, располагаемой по всей ширине родительского окна. Но её можно разбить по ширине на отдельные поля, если вставить в неё другие элементы, например, QLabel. Для этого предназначены методы addWidget, addPermanentWidget и insertWidget:

void QStatusBar::addWidget (
    QWidget *widget,  // Вставляемый элемент.
    int stretch = 0 ) // Коэффициент растяжимости.

void QStatusBar::addPermanentWidget (
    QWidget *widget,
    int stretch = 0 )

int QStatusBar::insertWidget (
    int index,        // Позиция.
    QWidget *widget,  // Вставляемый элемент.
    int stretch = 0 ) // Коэффициент растяжимости.

Элементы, добавляемые с помощью метода addPermanentWidget, располагаются в правой части строки состояния и не затираются сообщениями, выводимыми с помощью showMessage.

В нижней правой части строки состояния по умолчанию отображается специальный маркер, который можно "зацепить" указателем мыши для изменения размеров окна. Его показ можно запретить, вызвав QStatusBar::setSizeGripEnabled(false). При этом возможность изменять размеры окна по-прежнему остаётся.

Действия

Пользователь может влиять на процесс выполнения программы с помощью указателя и кнопок мыши, выбирая нужный пункт меню или кнопку на панели инструментов. Кроме того, в его распоряжении имеются клавиши со стрелками, а также различные клавиатурные комбинации. Все эти события объединены в библиотеке Qt под общим названием действие (action).

При создании действия указывается родительский элемент и, при необходимости, пиктограмма и/или текстовая метка:

QAction::QAction(QObject *parent)
QAction::QAction(const QString& text, QObject *parent)
QAction::QAction(const QIcon& icon, const QString& text, QObject *parent)

Метод QAction::setStatusTip(текст) определяет текст подсказки по данному действию, выводимую в строке состояния, а QAction::setShortcut(QKeySequence& shortcut) -- привязывает к действию некоторую комбинацию клавиш.

С каждым действием связывается функция-обработчик, для этого используется всё тот же метод connect, например:

QAction *exitAction = new QAction(tr("В&ыход"), this);
exitAction->setStatusTip(tr("Выход из программы"));
exitAction->setShortcut(tr("Ctrl+Q"));
connect(exitAction, SIGNAL(triggered()), this, SLOT(close()));

Меню

Горизонтальная панель меню QMenuBar создаётся автоматически, если мы обращаемся к ней для добавления хотя бы одного вертикального меню QMenu. Например:

// Определяем действия:
QAction *aboutAction = new QAction(tr("&О программе"), this);
QAction *exitAction = new QAction(tr("В&ыход"), this);

// Создаём вертикальное меню и добавляем
// его на автоматически созданную панель QMenuBar:
QMenu *fileMenu = menuBar()->addMenu(tr("&Файл"));
fileMenu->addAction(aboutAction); // Добавили действие 'О программе'.
fileMenu->addSeparator();         // Разделитель пунктов меню.
fileMenu->addAction(exitAction);  // Добавили действие 'Выход'

Здесь мы создали меню "Файл" с двумя пунктами "О программе" и "Выход" и горизонтальной полосой-разделителем между ними.

Обработка событий с помощью виртуальных методов

Кроме механизма сигналов и слотов, в Qt используется также старый метод виртуальных функций. Так, например, базовый класс QWidget (элемент интерфейса, виджет) и все его многочисленные потомки имеют виртуальный метод mouseMoveEvent

void QWidget::mouseMoveEvent ( QMouseEvent * event )

который вызывается при перемещении указателя мыши над данным элементом. Если требуется определить какую-либо реакцию на это событие, то достаточно в собственном классе переопределить метод mouseMoveEvent. Текущие координаты мыши в системе отсчёта данного виджета можно узнать с помощью методов QMouseEvent::x() и QMouseEvent::y(). Следует иметь в виду, что по умолчанию метод mouseMoveEvent вызывается, только если при перемещении указателя мыши удерживается любая из её кнопок. Чтобы это происходило независимо от состояния кнопок, надо для данного виджета вызвать метод setMouseTracking(true).

Аналогично можно обрабатывать событие изменения размеров любого элемента, только в этом случае для него придётся переопределить метод resizeEvent.


Наследники класса QObject и метакомпилятор

Любые пользовательские классы, разрабатываемые программистом, обычно являются наследниками базового класса QObject (или какого-нибудь из его потомков). Только в этом случае для них можно использовать механизм сигналов и слотов, а также некоторые другие возможности, реализованные в библиотеке Qt (в частности, информацию о типах во время выполнения программы и динамические свойства объектов). При этом необходимо соблюдать следующие правила:

  • в секции private при объявлении класса необходимо указать макрос Q_OBJECT
  • объявления классов, содержащих макрос Q_OBJECT, обязательно должны располагаться в заголовочном файле *.h, а не в самой программе *.cpp.

Утилита qmake просматривает заголовочные файлы проекта, и если в описании какого-нибудь класса встречается макрос Q_OBJECT, то в make-файл вставляется вызов так называемого компилятора метаобъектов moc, который автоматически формирует на языке C++ необходимый код (в папке проекта после компиляции можно найти файл moc_*.cpp).


Пример приложения с меню и строкой состояния

В листингах 6 и 7 приведён текст небольшой программы, в которой иллюстрируются все описанные в данном разделе элементы: меню, строка состояния с несколькими текстовыми полями, обработка перемещения указателя мыши и изменения размеров окна с помощью виртуальных функций, а также определение собственного класса на основе QObject. На рис. показано, как выглядит окно программы в Windows и Linux.

Qt02.png

Рис. Меню и строка состояния: внешний вид окна программы в системе Windows (верхний скриншот) и Linux (для двух различных тем рабочего стола). В строке состояния отображаются размеры окна и координаты указателя мыши

Листинг 6. Меню и строка состояния (файл examples-qt/02/02.h)

    1 #include <QtGui>
    2 
    3 class MainWindow : public QMainWindow {
    4     Q_OBJECT
    5 
    6 public:
    7     MainWindow();
    8 
    9 protected:
   10     virtual void resizeEvent(QResizeEvent *event);
   11     virtual void mouseMoveEvent(QMouseEvent *event);
   12     virtual void closeEvent(QCloseEvent *event);
   13 
   14 private slots:
   15     void about();
   16 
   17 private:
   18     QAction *aboutAction;
   19     QAction *exitAction;
   20     QMenu *fileMenu;
   21     QLabel *sb1;
   22     QLabel *sb2;
   23     QLabel *sb3;
   24 
   25     bool askOnClose();
   26 };
  • (1) Подключили заголовочный файл QtGui, в котором описаны все объекты, относящиеся к элементам графического интерфейса. Это проще, чем отдельно подключать QMainWindow, QLabel, QMenu и т.д.
  • (3) Объявили новый класс MainWindow, использовав в качестве базового класс QMainWindow (наследника QObject).
  • (4) Не забыли про обязательный макрос Q_OBJECT.
  • (7) Конструктор без параметров.
  • (10-12) Виртуальные методы, вызываемые автоматически при изменении размеров окна, при перемещении указателя мыши и при закрытии окна.
  • (14-15) Описание слотов (функций-обработчиков). В данном случае здесь упомянута только функция, вызываемая при выборе пункта меню "О программе" (т.к. все остальные обработчики -- это виртуальные функции или уже определённые методы базового класса).
  • (17-23) Переменные-члены класса: два действия для пунктов меню, само меню и три текстовые метки для строки состояния.
  • (25) Служебная функция, вызываемая при закрытии окна (просит подтверждения у пользователя).

Листинг 7. Меню и строка состояния (файл examples-qt/02/02.cpp)

    1 #include <QtGui>
    2 #include "02.h"
    3 
    4 MainWindow::MainWindow() {
    5 
    6     QTextCodec *codec = QTextCodec::codecForName("CP1251");
    7     QTextCodec::setCodecForTr(codec);
    8 
    9     aboutAction = new QAction(tr("&О программе"), this);
   10     aboutAction->setStatusTip(tr("Сведения о программе"));
   11     connect(aboutAction, SIGNAL(triggered()), this, SLOT(about()));
   12 
   13     exitAction = new QAction(tr("В&ыход"), this);
   14     exitAction->setStatusTip(tr("Выход из программы"));
   15     exitAction->setShortcut(tr("Ctrl+Q"));
   16     connect(exitAction, SIGNAL(triggered()), this, SLOT(close()));
   17 
   18     fileMenu = menuBar()->addMenu(tr("&Файл"));
   19     fileMenu->addAction(aboutAction);
   20     fileMenu->addSeparator();
   21     fileMenu->addAction(exitAction);
   22 
   23     sb1 = new QLabel(statusBar());
   24     sb2 = new QLabel(statusBar());
   25     sb3 = new QLabel(statusBar());
   26 
   27     statusBar()->setSizeGripEnabled(false);
   28     statusBar()->addWidget(sb1, 2);
   29     statusBar()->addWidget(sb2, 1);
   30     statusBar()->addWidget(sb3, 1);
   31 
   32     sb1->setText(tr("Привет!"));
   33 
   34     setMouseTracking(true);
   35 }
   36 
   37 
   38 void MainWindow::resizeEvent(QResizeEvent *event) {
   39     QSize sz = event->size();
   40     sb2->setText(
   41         QString( "(%1, %2)" ).arg( sz.width() ).arg( sz.height() ) );
   42 }
   43 
   44 void MainWindow::mouseMoveEvent(QMouseEvent *event) {
   45     QPoint pos = event->pos();
   46     sb3->setText(
   47         QString( "%1, %2" ).arg( pos.x() ).arg( pos.y() ) );
   48 }
   49 
   50 void MainWindow::about() {
   51     QMessageBox::about(
   52         this, tr("О программе"),
   53         tr("<h2>Простое приложение Qt4</h2>"
   54            "<p>Окно с меню и строкой состояния"));
   55 }
   56 
   57 bool MainWindow::askOnClose() {
   58     int r = QMessageBox::question(
   59         this, tr("Подтвердите"),
   60         tr("Выйти из программы?"),
   61         QMessageBox::Yes | QMessageBox::No,
   62         QMessageBox::Yes,
   63         QMessageBox::Cancel | QMessageBox::Escape);
   64     return (r == QMessageBox::Yes);
   65 }
   66 
   67 void MainWindow::closeEvent(QCloseEvent *event) {
   68     if (askOnClose()) {
   69         event->accept();
   70     } else {
   71         event->ignore();
   72     }
   73 }
   74 
   75 int main(int argc, char *argv[]) {
   76     QApplication app(argc, argv);
   77     MainWindow mainWin;
   78     mainWin.show();
   79     return app.exec();
   80 }
  • (1-2) Подключили модуль QtGui и свой заголовочный файл.
  • (4-35) Конструктор главного окна.
  • (6-7) Определили кодек.
  • (9-11) Создали действие "О программе", задали для него текст подсказки и связали с функцией about, реализованной ниже.
  • (13-16) Создали действие "Выход", задали для него текст подсказки и клавиатурное сокращение, связали его с функцией close, выполняющей закрытие окна (эта функция определена в базовом классе).
  • (18-21) Создали меню "Файл", состоящее из двух пунктов, с разделителем между ними.
  • (23-25) Создали три текстовые метки, в качестве родительского элемента указали строку состояния.
  • (27-30) Убрали из строки состояния маркер для изменения размеров окна (иначе правая текстовая метка не доходит до правого края окна, что выглядит не слишком эстетично) и разместили в строке три текстовые метки. Указали для первой коэффициент растяжимости, в два раза больший, чем для остальных.
  • (32) Вывели в первое поле строки состояния приветствие.
  • (34)Указали, что метод mouseMoveEvent будет вызываться при движении указателя мыши, даже если её кнопки не нажаты.
  • (38-42) Виртуальный метод, выполняемый при изменении размеров окна. Узнаём новые размеры с помощью функции QResizeEvent::size() и выводим их во второе поле строки состояния. Для преобразования целых значений к типу QString используем метасимволы %1 и %2 в строке шаблона (41), а также функцию QString::arg, которая замещает их строковым представлением своего аргумента и возвращает результат типа QString.
  • (44-48) Виртуальный метод, вызываемый при перемещении указателя мыши. Новые координаты указателя возвращает функция QMouseeEvent::pos(). Мы выводим их в третье поле строки состояния.
  • (50-55) Функция about выполняется при выборе пункта меню Файл | О программе. Для вывода на экран информации используется метод about, определённый для класса QMessageBox. Обратите внимание, что для форматирования текста используется язык разметки HTML.
  • (57-73) При закрытии окна программы (по команде меню Выход, нажатием комбинации клавиш Alt+F4 или с помощью крестика в верхнем правом углу окна) исполняется виртуальный метод closeEvent (67), в котором мы вызываем функцию askOnClose (57). Последняя выводит на экран запрос на подтверждение, используя метод question класса QMessageBox. Если пользователь отвечает утвердительно, то окно зарывается (69), иначе действие отменяется (71) и выполнение программы продолжается.

Чуть позже мы узнаем, как использовать программу Qt Designer для определения действий, создания меню и других элементов интерфейса.


Размещение элементов в окне

"Ручное" размещение

С помощью метода setGeometry(int x, int y, int w, int h) или setGeometry(const QRect&) можно задать положение и размер любого визуального элемента в пикселах. Для установки размеров без изменения положения может использоваться метод resize(int w, int h) или resize(const QSize&). Наоборот, для перемещения элемента в нужную позицию с сохранением прежних размеров служит метод move(int x, int y) или move(const QPoint&). Недостатком жёсткого варианта размещения элементов интерфейса является то, что пользователь не может изменить размер окна диалога (или изменение размеров окна не влияет на взаимное положение и размеры всех его элементов). В результате при низком разрешении монитора всё выглядит слишком крупно, а то и вовсе не помещается на экран, при высоком -- наоборот, слишком мелко. Кроме того, в различных операционных системах используются разные шрифты, поэтому надписи и поля ввода, прекрасно смотревшиеся в одной системе, при переносе на другую платформу могут не уместиться в прежних границах. К тому же в солидных программных продуктах принято давать пользователю возможность настраивать интерфейс программы по своему вкусу, в частности, изменять гарнитуру и размер шрифта. А при локализации (переводе интерфейса программы на другой язык) всё ещё больше усложняется.

Очевидный (но не лучший) способ решить хотя бы некоторые из перечисленных проблем заключается в том, чтобы в виртуальном методе resizeEvent, автоматически выполняемом при изменении размеров окна, пересчитывать размеры и положение всех элементов. Пример программирования такого диалога приведён в листингах 8 и 9, а внешний вид окна -- на рис.

Qt03.png

Рис. Перерасчёт геометрии в методе resizeEvent

Листинг 8. Перерасчёт геометрии в методе resizeEvent (файл examples-qt/03/03.h)

    1 // Перерасчёт геометрии всех элементов при изменении размеров окна
    2 
    3 #include <QtGui>
    4 
    5 class MyDialog : public QDialog {
    6     Q_OBJECT
    7 
    8 public:
    9     MyDialog();
   10 
   11 protected:
   12     virtual void resizeEvent(QResizeEvent *event);
   13 
   14 private:
   15     QLabel *lb;          // Текстовая метка.
   16     QLineEdit *le;       // Строковое поле ввода.
   17     QComboBox *cb;       // Поле ввода с раскрывающимся списком.
   18     QSpinBox *sb;        // Целочисленное поле ввода с кнопками
   19                          // инкремента/декремента.
   20     QCheckBox *chb;      // Независимый переключатель
   21                          // с двумя состояниями.
   22     QDoubleSpinBox *dsb; // Поле ввода вещественного значения
   23                          // с кнопками инкремента/декремента.
   24     QDateTimeEdit *dte;  // Поле ввода даты и времени.
   25     QGroupBox  *gb;      // Рамка с надписью вокруг группы элементов.
   26     QRadioButton *rb1;   // Три
   27     QRadioButton *rb2;   // зависимых
   28     QRadioButton *rb3;   // переключателя.
   29     QPushButton *btn1;   // Кнопка "Сохранить".
   30     QPushButton *btn2;   // Кнопка "Отменить".
   31 };
  • (5) Объявили класс MyDialog, в качестве базового использовали стандартный класс диалога библиотеки Qt.
  • (9) Конструктор диалога.
  • (15-30) Указатели на все визуальные элементы необходимо сделать членами класса, чтобы иметь к ним доступ из метода resizeEvent.

Листинг 9. Перерасчёт геометрии в методе resizeEvent (файл examples-qt/03/03.cpp)

    1 // Перерасчёт геометрии всех элементов при изменении размеров окна
    2 
    3 #include "03.h"
    4 
    5 MyDialog::MyDialog() {
    6 
    7     QTextCodec *codec = QTextCodec::codecForName("CP1251");
    8     QTextCodec::setCodecForTr(codec);
    9 
   10     lb = new QLabel(tr("Метка:"), this);
   11 
   12     le = new QLineEdit(tr("Строка"), this);
   13 
   14     cb = new QComboBox(this);
   15     cb->addItem(tr("Первый"));
   16     cb->addItem(tr("Второй"));
   17     cb->addItem(tr("Третий"));
   18     cb->setCurrentIndex(2);
   19     cb->setEditable(true);
   20     cb->setInsertPolicy(QComboBox::InsertAtBottom);
   21 
   22     sb = new QSpinBox(this);
   23     sb->setValue(5);
   24 
   25     chb = new QCheckBox(tr("Пометка"), this);
   26     chb->setCheckState(Qt::Checked);
   27 
   28     dsb = new QDoubleSpinBox(this);
   29     dsb->setMaximum(200.0);
   30     dsb->setDecimals(2);
   31     dsb->setSingleStep(0.25);
   32     dsb->setValue(100.25);
   33 
   34     dte = new QDateTimeEdit(
   35         QDateTime(QDate(2007, 2, 5), QTime(13, 35, 55, 10)),
   36         this);
   37 
   38     gb = new QGroupBox(tr("Выбрать одно из трёх:"), this);
   39 
   40     rb1 = new QRadioButton(tr("Один"), gb);
   41 
   42     rb2 = new QRadioButton(tr("Два"), gb);
   43 
   44     rb3 = new QRadioButton(tr("Три"), gb);
   45 
   46     rb2->setChecked(true);
   47 
   48     btn1 = new QPushButton(tr("Сохранить"), this);
   49 
   50     btn2 = new QPushButton(tr("Отменить"), this);
   51 
   52     setMinimumSize(160, 205);
   53 }
   54 
   55 void MyDialog::resizeEvent(QResizeEvent* /* event */ ) {
   56     int dw = width() - minimumWidth();
   57     int dh = height() - minimumHeight();
   58 
   59     lb->setGeometry(5, 6, 45, 24);
   60     le->setGeometry(55, 6, 100+dw, 24);
   61 
   62     cb->setGeometry(5, 36, 80+dw*2/3, 24);
   63     sb->setGeometry(95+dw*2/3, 36, 60+dw/3, 24);
   64 
   65     chb->setGeometry(5, 65, 70, 24);
   66     dsb->setGeometry(95, 65, 60+dw, 24);
   67 
   68     dte->setGeometry(5, 95, 150+dw, 24);
   69 
   70     gb->setGeometry(5, 120, 150+dw, 40);
   71     rb1->setGeometry(5, 15, 45+dw/3, 24);
   72     rb2->setGeometry(55+dw/3, 15, 45+dw/3, 24);
   73     rb3->setGeometry(105+dw*2/3, 15, 45+dw/3, 24);
   74 
   75     btn1->setGeometry(5+dw/4, 170+dh, 70+dw/4, 29);
   76     btn2->setGeometry(85+dw/2, 170+dh, 70+dw/4, 29);
   77 }
   78 
   79 int main(int argc, char *argv[]) {
   80 
   81     QApplication app(argc, argv);
   82 
   83     MyDialog *dlg = new MyDialog();
   84     dlg->show();
   85 
   86     return app.exec();
   87 }
  • (7-8) Кодек и установка кодировки.
  • (10) Текстовая метка. Второй параметр конструктора (указатель на родительский элемент) можно опустить.
  • (12) Однострочное поле ввода. То же самое замечание относительно параметра this.
  • (14-20) Поле ввода с раскрывающимся списком. Текущим сделан второй, считая с нуля, te последний элемент списка. Вызов (19) разрешает вводить в поле любые строковые значения, независимо от того, имеются ли они в списке выбора. В (20) указано, что новые значения, введённые пользователем, будут автоматически дописываться в конец списка.
  • (22-23) Целочисленное поле ввода с кнопками инкремента/декремента.
  • (25-26) Независимый переключатель с двумя состояниями.
  • (28-32)Поле для ввода вещественного значения с кнопками инкремента/декремета. В (29) установлено максимальное допустимое значение, в (30) -- количество десятичных цифр в дробной части, а в (31) -- величина приращения при нажатии кнопки.
  • (34-36) Поле для ввода даты и времени. Заданы год, месяц, число, часы, минуты, секунды и миллисекунды.
  • (38) Рамка с надписью вокруг группы элементов.
  • (40,42,44) Три зависимых переключателя (радиокнопки) с текстовыми метками.
  • (46) Установлен второй переключатель, остальные автоматически выключены.
  • (48,50) Две кнопки.
  • (52) Заданы минимально возможные размеры окна диалога. Если вместо setMinimumSize использовать метод setFixedSize, то пользователь не сможет изменить ни ширину, ни высоту окна.
  • (55) Виртуальный метод, автоматически вызываемый при изменении размеров окна.
  • (56,57) Вычисление приращения ширины и высоты окна относительно их минимально разрешённых значений.
  • (59) Положение и размеры текстовой метки фиксированы.
  • (60) Положение однострочного поля ввода не изменяется, а ширина увеличивается на то же значение, что и ширина всего окна. В результате правый край элемента зафиксирован относительно правой границы окна.
  • (62-63) Ширина поля ввода со списком выбора увеличивается в два раза быстрее, чем ширина поля для ввода целого значения.
  • (65) Позиция и размеры независимого переключателя фиксированы.
  • (66, 68, 70) Ширина полей для ввода вещественного значения и для ввода даты/времени, а также ширина рамки вокруг группы элементов увеличивается на то же значение, что и ширина всего окна.
  • (71-73) Ширина текстовых меток зависимых переключателей растёт одинаково, на 1/3 от прироста ширины всего окна.
  • (75-76) Кнопки в нижней части диалога увеличиваются по ширине, а расстояние от них до нижней границы окна остаётся неизменным.

Qt03 2.png

Рис. Внешний вид окна и элементов управления в системе Windows при запуске программы с параметром -style=Windows, WindowsXP, Motif, CDE, Plastique и Cleanlooks

На рис. показано, как изменяется внешний вид элементов диалога, если при запуске программы, текст которой приведён в листингах 8 и 9, указать в командной строке параметр -style=ИмяСтиля. Обратите внимание, что при одних и тех же размерах окна, устанавливаемых сразу после запуска программы, в режиме WindowsXP текстовая метка "Один" оказалась обрезанной на последней букве, а для стиля Motif высота всех полей ввода и ширина кнопок оказалась на грани критической: текстовые надписи еле "влазят" в отведённые для них границы из-за увеличенной толщины декоративных элементов. В системе Linux, где размер шрифта по умолчанию выбирается обычно больше, чем в Windows (из-за традиционно худшего качества отображения шрифтов), это различие компоновки элементов диалога для различных стилей ещё более заметно.

Менеджеры размещения

В Qt имеются классы QHBoxLayout, QVBoxLayout и QGridLayout, которые специально предназначены для управления положением и размерами элементов в окне. Первый позволяет располагать элементы друг за другом по горизонтали, второй -- по вертикали, а третий размещает виджеты в ячейках воображаемой таблицы, причём каждый элемент может занимать несколько смежных ячеек по вертикали и/или горизонтали.

В листинге 10 показаны строки, которые потребуется дописать к тексту конструктора диалога (см. листинг 9), если мы захотим использовать менеджеры размещения. Здесь все элементы располагаются в сетке QGridLayout, а для зависимых переключателей и кнопок создаются отдельные менеджеры QHBoxLayout. Разумеется, метод resizeEvent теперь не нужен.

Листинг 10. Менеджеры размещения (файл examples-qt/04/04.cpp)

          // Создаём менеджер размещения:
   52     QGridLayout *mainlay = new QGridLayout();
          
          // Размер полей вокруг сетки элементов и
          // интервалы между ячейками сетки:
   53     mainlay->setMargin(2);
   54     mainlay->setSpacing(3);
   55 
          // Размещаем элементы:
   56     mainlay->addWidget(lb, 0, 0);
   57     mainlay->addWidget(le, 0, 1, 1, 2);
   58 
   59     mainlay->addWidget(cb, 1, 0, 1, 2);
   60     mainlay->addWidget(sb, 1, 2);
   61 
   62     mainlay->addWidget(chb, 2, 0);
   63     mainlay->addWidget(dsb, 2, 1, 1, 2);
   64 
   65     mainlay->addWidget(dte, 3, 0, 1, 3);
   66 
          // Менеджер размещения для радиокнопок:
   67     QHBoxLayout *hbl = new QHBoxLayout();
   68     hbl->addWidget(rb1, 1);
   69     hbl->addWidget(rb2, 1);
   70     hbl->addWidget(rb3, 1);
   71     gb->setLayout(hbl);
   72     mainlay->addWidget(gb, 4, 0, 1, 3);
   73 
          // Менеджер размещения для кнопок диалога:
   74     QHBoxLayout *btns = new QHBoxLayout();
   75     btns->addStretch(1);
   76     btns->addWidget(btn1, 2);
   77     btns->addWidget(btn2, 2);
   78     btns->addStretch(1);
   79     mainlay->addLayout(btns, 6, 0, 1, 3);
   80 
          // Растяжимость колонок и строк:
   81     mainlay->setColumnStretch(1, 1);
   82     mainlay->setColumnStretch(2, 1);
   83     mainlay->setRowStretch(5, 1);
    
   84     setLayout(mainlay);
   85 } 

Для вставки любого виджета в нужную ячейку QGridLayout предназначен метод addWidget, при вызове которого указываются номер строки и столбца (нумерация начинается с нуля), а также, если требуется, количество соседних строк и столбцов, которые будет занимать элемент. Растяжимостью строк и столбцов сетки управляют методы setRowStretch и setColumnStretch, первый параметр которых указывает номер строки (столбца), а второй -- коэффициент растяжения. Чем он больше, тем сильнее будет растягиваться/сжиматься данная строка по вертикали (или столбец по горизонтали) по сравнению с остальными строками (столбцами) при изменении размеров всего окна.

Разумеется, в данном случае можно предложить и другие варианты, например, на внешнем уровне использовать QVBoxLayout и заполнять его менеджерами QHBoxLayout, в которые вставлять элементы каждой горизонтальной строки диалога, подобно тому, как это сделано с радиокнопками в нашем примере.

Если нас не удовлетворяет то, как ведут себя какие-либо элементы диалога при изменении размеров окна, то можно попытаться вызвать для них метод setSizePolicy, первый параметр которого задаёт политику изменения ширины элемента, а второй -- его высоты. Каждый параметр может принимать одно из значений:

  • QSizePolicy::Fixed -- размер элемента в данном направлении не изменяется;
  • QSizePolicy::Minimum -- "идеальным" размером элемента считается его минимальный размер. Элемент может растягиваться, но не может сжиматься;
  • QSizePolicy::Maximum -- "идеальным" размером элемента считается его максимальный размер. Элемент может сжиматься, но не может растягиваться;
  • QSizePolicy::Preferred -- элемент "старается" поддерживать некоторый предпочтительный для него размер, но при необходимости может растянуться или сжаться;
  • QSizePolicy::Expanding -- элемент "старается" принять максимально возможный доступный ему размер, но при необходимости может и сжиматься.


Создание диалогов с помощью Qt Designer

Для разработки интерфейса программы в Qt имеется специальный инструмент -- Qt Designer. Он используется либо как самостоятельное приложение, либо как компонент, встроенный в интегрированную среду Microsoft Visual Studio. Во втором случае доступ к его функциям производится с помощью пункта меню Qt. Покажем, как пользоваться дизайнером на примере нашего диалога (см. рис.). Откроем Qt Designer и (если диалог создания новой формы не появится автоматически) выполним команду меню File | New Form. Из представленных вариантов заготовок (диалоги с горизонтальным или вертикальным размещением кнопок, главное окно приложения или произвольный виджет) выберем первый и нажмём кнопку Create (Создать). В левом окне дизайнера Widget Box в группе Display Widget найдём элемент Label (рис.) и с помощью левой кнопки мыши перетащим его на форму. В правой части дизайнера, в окне Property Editor (Редактор свойств) укажем идентификатор элемента (значение свойства objectName), например, lb, и текст надписи (свойство text).

Аналогично разместим на форме однострочное поле ввода Line Edit (идентификатор le), поле со списком Combo Box (идентификатор cb), поле для ввода целого числа с кнопками инкремента/декремента Spin Box (идентификатор sb), независимый переключатель Check Box (идентификатор chb), поле для ввода вещественного числа с кнопками инкремента/декремента Double Spin Box (идентификатор dsb), поле для выбора даты/времени Date/Time Edit (идентификатор dte), контейнер Group Box (идентификатор groupBox) с тремя зависимыми переключателями Radio Button (идентификаторы rb1, rb2 и rb3), как показано на рис.

Пока не следует сильно заботиться о красивом размещении элементов друг относительно друга и об их точных размерах. Для первой радиокнопки зададим значение свойства checked=true. Между рамкой с зависимыми переключателями и кнопками диалога вставим элемент Vertical Spacer (вертикальный промежуток, он находится в группе Spacers). Для элемента Button Box проверим значение свойства standardButtons: галочки должны быть установлены только для кнопок Cancel и Save.

Теперь выделим мышкой первые два элемента (Label и Line Edit), находящиеся в первой горизонтальной строке диалога. Для выделения нескольких элементов можно использовать клавишу Shift. Щёлкнем по выделенной группе правой кнопкой мыши и в появившемся контекстном меню выберем команду Lay out | Lay Out Horizontally (расположить по горизонтали). В результате автоматически будет создан менеджер размещения QHBoxLayout и оба выделенных виджета окажутся внутри него. Останется только задать единичное значение параметра horizontalStretch (растяжение по горизонтали) из раздела sizePolicy для однострочного поля ввода.

Qtdesigner001.png

Рис. Внешний вид окна Qt Designer

Аналогично создадим два горизонтальных менеджера размещения для двух следующих строк нашего диалога. Поле для ввода даты/времени пропустим, т.к. оно единственное в своей строке. Далее выделим три зависимых переключателя и создадим для них всё тот же горизонтальный менеджер размещения. Для самой рамки Group Box, внутри которой расположены три зависимых переключателя, также создадим свой QHBoxLayout (хотя эта рамка и единственная в своей строке, но она является контейнером для других элементов и без менеджера размещения её высота не может быть вычислена правильно).

Кнопки диалога трогать не будем, потому что при создании формы мастер уже разместил их в элементе Button Box, который является аналогом менеджера размещения, только используется специально для кнопок. Можно изменить состав кнопок с помощью свойства standardButtons и их выравнивание по горизонтали (свойство centerButtons=true).

Наконец, нажмём правой кнопкой мышки по пустому фону диалога и в появившемся контекстном меню выберем команду Lay Out Vertically (расположить по вертикали). В результате будет создан вертикальный менеджер QVBoxLayout и внутри него будут размещены все имеющиеся на форме строки QHBoxLayout вместе с полем ввода даты/времени и вертикальным промежутком (для которых мы так и не создали горизонтального менеджера размещения).

Можно проверить, как будет выглядеть наш диалог при различном оформлении (команда меню Form | Preview in).

Qtdesigner002.png Рис. Порядок обхода элементов

Теперь надо задать порядок обхода элементов при нажатии клавиши Tab. Для этого выполним команду меню Edit | Edit Tab Order. На редактируемой форме появятся квадратики с числами (рис.). Будем щёлкать по ним левой кнопкой мыши в нужном порядке. После этого можно опять войти в режим предпросмотра Preview in и с помощью клавиши Tab убедиться, что порядок обхода элементов задан правильно.

Допустим, нам требуется сделать недоступным поле ввода вещественного числа dsb при сбросе переключателя chb. Другими словами, надо связать сигнал toggled(bool) элемента chb и слот enabled(bool) элемента dsb. Если в нижнем правом углу окна Qt Designer вы не видите панель Signal/Slot Editor, то выполните пункт меню Tools | Signal/Slot Editor. Для добавления нового соединения нажмите кнопку "+", после чего последовательно укажите все четыре параметра соединения (рис.). После этого в свойствах элемента chb надо задать начальное значение checked=false, а для элемента dsb -- значение enabled=false. Теперь можно в режиме предварительного просмотра проверить, как изменяется доступность поля ввода числа при изменении состояния переключателя.

Qtdesigner003.png

Аналогично определим ещё два соединения, соответствующие нажатию кнопок "Сохранить" и "Отмена" (см. рис.). Стандартные для модального диалога слоты accept и reject закрывают диалог и запоминают результат Accepted (принят) или Rejected (отклонён), который затем может быть прочитан с помощью метода result().

Теперь предположим, что при изменении числа в поле sb необходимо тут же выводить это же значение в поле le. Воспользуемся вторым способом определения соединений. Войдём в режим визуального редактирования сигналов и слотов, выполнив пункт меню Edit | Edit Signals/Slots или просто нажав клавишу F4. С помощью левой кнопки мыши "зацепим" поле sb -- источник сигнала и "бросим" его на элемент le -- приёмник сигнала (рис.).

Qtdesigner004.png

В открывшемся диалоге (рис.) поставим галочку Show all signals and slots (показывать все сигналы и слоты), в левом окне выберем сигнал valueChanged(QString), а в правом -- слот setText(QString). Нажмём Ok. После этого можно снова проверить, как всё работает (Form | Preview).

Qtdesigner005.png

Разумеется, для реализации более сложных действий (например, изменение доступности одних элементов диалога в зависимости от значений, вводимых пользователем в другие элементы) без настоящего программирования обойтись не получится.

Сохраним описание диалога под любым именем (например, dialog) и с расширением ui.

В листинге 11 приведён фрагмент получившегося в результате файла. Как видим, описание диалога хранится в формате XML.

Листинг 11. Файл *.ui

<ui version="4.0" >
 <class>Dialog</class>
 <widget class="QDialog" name="Dialog" >
  <property name="geometry" >
   <rect>
    <x>0</x>
    <y>0</y>
    <width>274</width>
    <height>259</height>
   </rect>
  </property>
  .......
</ui>

В листинге 12 показано, как использовать в программе диалог, разработанный с помощью Qt Designer.

Листинг 12. Загрузка ui-ресурса (examples-qt/05/05.cpp)

#include <QApplication>
#include <QDialog>

#include "ui_dialog.h"

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    Ui::Dialog ui;
    QDialog *dialog = new QDialog;
    ui.setupUi(dialog);
    dialog->show();
    return app.exec();
}

Мы подключили заголовочный файл, имя которого начинается префиксом ui_, после которого записывается имя файла, созданного в дизайнере. Откуда берётся ui_dialog.h? Этот файл формируется с помощью утилиты uic, которая автоматически вызывается при компиляции программы.

Затем объявили экземпляр класса Ui::Dialog (этот класс описан в заголовочном файле) и экземпляр стандартного класса QDialog, после чего для первого вызвали метод setupUi, указав второй в качестве параметра.

Компиляция программы проводится обычным порядком: сначала команда qmake -project, затем просто qmake и, наконец, mingw32-make или nmake.

Можете запустить программу на выполнение: на экран выводится окно диалога, доступность поля ввода вещественного числа зависит от состояния переключателя, изменяемое значение sb тут же отображается в однострочном поле ввода le, а при нажатии любой из двух кнопок диалог закрывается.

Для выполнения более сложных действий придётся описать новый класс, например, MyDialog, указав в качестве базового стандартный класс QDialog. При этом относительно разработанного в дизайнере класса Ui::Dialog имеется две возможности: либо определить экземпляр этого класса внутри MyDialog, либо сделать класс Ui::Dialog вторым родителем класса MyDialog.

Первый вариант (с одним базовым классом) приведён в листинге 13.

Листинг 13. Работа с ui-ресурсом

// В заголовочном файле *.h:
#include <QDialog>
#include "ui_dialog.h"

class MyDialog : public QDialog {
    Q_OBJECT
public:
    MyDialog(QWidget *parent = 0);
private:
     Ui::Dialog ui;
};

// В файле *.cpp:
MyDialog::MyDialog(QWidget *parent)
        : QDialog(parent) {
        
    ui.setupUi(this);
    
    ui.cb->addItem(tr("Первый"));
    ui.cb->addItem(tr("Второй"));
    ui.cb->addItem(tr("Третий"));
    ui.cb->setCurrentIndex(2);
    
    // .......
}

Второй вариант (с двумя базовыми классами) показан в листинге 14.

Листинг 14. Работа с ui-ресурсом (файлы examples-qt/06/06.h и 06.cpp)

// В заголовочном файле *.h:
#include <QDialog>
#include "ui_dialog.h"

class MyDialog : public QDialog, private Ui::Dialog {
    Q_OBJECT
public:
    MyDialog(QWidget *parent=0);
};

// В файле *.cpp:
MyDialog::MyDialog(QWidget *parent)
        : QDialog(parent) {

    setupUi(this);

    cb->addItem(tr("Первый"));
    cb->addItem(tr("Второй"));
    cb->addItem(tr("Третий"));
    cb->setCurrentIndex(2);

    // ........
}

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    QTextCodec *codec = QTextCodec::codecForName("CP1251");
    QTextCodec::setCodecForTr(codec);

    MyDialog *dlg = new MyDialog();
    dlg->show();
    return app.exec();
}

Как видим, к элементам диалога можно обращаться по тем именам, которые были указаны для них в Qt Designer.

Соединения между сигналами и слотами для класса MyDialog можно определять как обычно, с помощью метода connect (например, в конструкторе класса MyDialog), при этом пользовательские слоты должны быть перечислены в объявлении класса или унаследованы от базовых классов. Но можно воспользоваться особым правилом именования слотов:

private slots:
    on_ИмяВиджета_ИмяСигнала(ПараметрыСигнала);

В этом случае соединение между сигналом и слотом будет выполнено автоматически (в методе setupUi(), который генерируется утилитой uic). Например:

// В заголовочном файле *.h:
#include <QDialog>
#include "ui_dialog.h"

class MyDialog : public QDialog, public Ui::Dialog {
    Q_OBJECT
public:
    MyDialog(QWidget *parent=0);
private slots:
    void on_rb3_toggled(bool s);
}; 

// В файле *.cpp:
void MyDialog::on_rb3_toggled(bool s) {
    dte->setHidden(s);
}

В результате при выборе радиокнопки rb3 поле ввода даты/времени будет спрятано, а при выборе любой другой радиокнопки -- снова показано. Оба описанных варианта компоновки внешнего ресурса с основной программой являются статическими: после компиляции программы ui-файл можно удалить. Существует и вариант динамической загрузки во время выполнения программы (листинг 15). Здесь используется класс QUiLoader. При этом требуется подключить заголовочный файл QtUiTools, а в файле проекта *.pro -- добавить строку

CONFIG += uitools

После редактирования pro-файла не следует выполнять qmake с параметром -project, иначе все изменения в нём будут потеряны.

Листинг 15. Динамическая загрузка ui-ресурса (examples-qt/07/07.cpp) '

#include <QtGui>
#include <QtUiTools>

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    QTextCodec *codec = QTextCodec::codecForName("CP1251");
    QTextCodec::setCodecForTr(codec);

    QUiLoader uiLoader;
    QFile file("dialog.ui");
    file.open(QFile::ReadOnly);
    QWidget *dlg = uiLoader.load(&file);
    file.close();
    if (dlg) {
        QComboBox *cb = dlg->findChild<QComboBox*>("cb");
        cb->addItem(QObject::tr("Первый"));
        cb->addItem(QObject::tr("Второй"));
        cb->addItem(QObject::tr("Третий"));
        cb->setCurrentIndex(2);
        cb->setEditable(true);
        cb->setInsertPolicy(QComboBox::InsertAtBottom);

        QSpinBox *sb = dlg->findChild<QSpinBox*>("sb");
        sb->setValue(5);

        dlg->findChild<QCheckBox*>("chb")->setCheckState(Qt::Checked);

        // .......

        dlg->findChild<QRadioButton*>("rb2")->setChecked(true);

        dlg->show();
        return app.exec();
    }
    else
        return 1;
}

Для обращения к элементам диалога при динамической загрузке используется метод

parent->findChild<Тип *>("ИмяЭлемента")

или функция

qFindChild<Тип *>(parent, "ИмяЭлемента")

Второй вариант предназначен для компиляторов, которые не поддерживают шаблоны методов (как, например, Microsoft Visual C++ 6.0).