Использование файла интерфейса проектировщика в приложении
Файлы интерфейса Qt Designer представляют собой дерево виджетов формы в формате XML. Формы можно обрабатывать:
- Во время компиляции, что означает, что формы преобразуются в код на C++, который можно скомпилировать.
- Во время выполнения, что означает, что формы обрабатываются классом QUiLoader, который динамически строит дерево виджетов во время парсинга файла XML.
Обработка формы во время компиляции
Вы создаёте компоненты пользовательского интерфейса с помощью Qt Designer и используете интегрированные инструменты сборки Qt, qmake и uic, для генерации кода для них во время сборки приложения. Сгенерированный код содержит объект пользовательского интерфейса формы. Это структура C++, которая содержит:
- Указатели на виджеты, макеты, элементы макета, группы кнопок и действия формы.
- Функцию-член, названную
setupUi(), для построения дерева виджетов на родительском виджете. - Функцию-член, названную
retranslateUi(), которая обрабатывает перевод строковых свойств формы. Дополнительную информацию см. в разделе Реагирование на изменения языка.
Сгенерированный код можно включить в ваше приложение и использовать непосредственно из него. Кроме того, вы можете использовать его для расширения подклассов стандартных виджетов.
Обработанную во время компиляции форму можно использовать в вашем приложении одним из следующих способов:
- Прямой подход: вы создаёте виджет в качестве заглушки для компонента и настраиваете пользовательский интерфейс внутри него.
- Подход с одиночным наследованием: вы подклассифицируете базовый класс формы (например, QWidget или QDialog) и включаете в него частную экземпляр объекта пользовательского интерфейса формы.
- Подход с множественным наследованием: вы подклассифицируете как базовый класс формы, так и объект пользовательского интерфейса формы. Это позволяет использовать виджеты, определённые в форме, непосредственно из области действия подкласса.
Для демонстрации мы создаём простое приложение «Калькулятор». Оно основано на оригинальном примере «Форма калькулятора» .
Приложение состоит из одного исходного файла, main.cpp, и файла интерфейса.
Файл calculatorform.ui, разработанный с помощью Qt Designer, показан ниже:
Мы будем использовать qmake для создания исполняемого файла, поэтому нам нужно написать файл .pro.
HEADERS = calculatorform.h
Особеностью этого файла является объявление FORMS, которое сообщает qmake, какие файлы обработать с помощью uic. В этом случае файл calculatorform.ui используется для создания файла ui_calculatorform.h, который может использоваться любым файлом, указанным в объявлении SOURCES.
Примечание: Вы можете использовать Qt Creator для создания проекта «Форма калькулятора». Он автоматически генерирует файлы main.cpp, UI и .pro, которые вы затем можете изменить.
Прямой подход
Для использования прямого подхода мы включаем файл ui_calculatorform.h непосредственно в main.cpp:
#include "ui_calculatorform.h"
Функция main создаёт виджет калькулятора, создавая стандартный QWidget, который мы используем для размещения пользовательского интерфейса, описанного в файле calculatorform.ui.
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QWidget *widget = new QWidget;
Ui::CalculatorForm ui;
ui.setupUi(widget);
widget->show();
return app.exec();
} В этом случае Ui::CalculatorForm — это объект описания интерфейса из файла ui_calculatorform.h, который настраивает все виджеты диалога и связи между его сигналами и слотами.
Прямой подход предоставляет быстрый и простой способ использования простых, автономных компонентов в приложениях. Однако компоненты, созданные с помощью Qt Designer, часто требуют тесной интеграции с остальным кодом приложения. Например, код CalculatorForm выше будет скомпилирован и запущен, но объекты QSpinBox не будут взаимодействовать с объектами QLabel, так как нам нужен пользовательский слот для выполнения операции сложения и отображения результата в QLabel. Для этого необходимо использовать подход с одиночным наследованием.
Подход с одиночным наследованием
Для использования подхода с одиночным наследованием мы подклассифицируем стандартный виджет Qt и включим в него частную экземпляр объекта пользовательского интерфейса формы. Это может быть:
- Член-переменная
- Указатель на член-переменную
Использование член-переменной
В этом подходе мы подклассифицируем виджет Qt и настраиваем пользовательский интерфейс из конструктора. Компоненты, используемые таким образом, предоставляют виджеты и макеты, используемые в форме, подклассу виджетов Qt и обеспечивают стандартную систему для создания связей «сигнал-слот» между пользовательским интерфейсом и другими объектами в вашем приложении. Сгенерированная структура Ui::CalculatorForm является членом класса.
Этот подход используется в примере «Форма калькулятора» .
Чтобы гарантировать, что мы можем использовать пользовательский интерфейс, нам необходимо включить заголовочный файл, который uic генерирует, прежде чем ссылаться на Ui::CalculatorForm:
#include "ui_calculatorform.h"
Это означает, что файл .pro должен быть обновлён для включения calculatorform.h:
HEADERS = calculatorform.h
Подкласс определяется следующим образом:
class CalculatorForm : public QWidget
{
Q_OBJECT
public:
explicit CalculatorForm(QWidget *parent = nullptr);
private slots:
void on_inputSpinBox1_valueChanged(int value);
void on_inputSpinBox2_valueChanged(int value);
private:
Ui::CalculatorForm ui;
}; Важной особенностью класса является частный объект ui, который предоставляет код для настройки и управления пользовательским интерфейсом.
Конструктор подкласса создаёт и настраивает все виджеты и макеты для диалога, просто вызвав функцию ui объекта setupUi(). После этого можно изменять пользовательский интерфейс по мере необходимости.
CalculatorForm::CalculatorForm(QWidget *parent)
: QWidget(parent)
{
ui.setupUi(this);
} Мы можем подключить сигналы и слоты к виджетам пользовательского интерфейса стандартным образом, добавив префикс on_<имя объекта>. Дополнительную информацию см. в разделе widgets-and-dialogs-with-auto-connect.
Преимущества этого подхода заключаются в простом использовании наследования для предоставления интерфейса на основе QWidget и инкапсуляции переменных виджетов пользовательского интерфейса в член-данных ui. Этот метод можно использовать для определения нескольких пользовательских интерфейсов в одном виджете, каждый из которых содержится в своём пространстве имён, и наложения (или компоновки) их. Этот подход может быть использован, например, для создания отдельных вкладок из существующих форм.
Использование указателя на член-переменную
В качестве альтернативы, структура Ui::CalculatorForm может быть указателем на член класса. Заголовочный файл тогда выглядит следующим образом:
namespace Ui {
class CalculatorForm;
}
class CalculatorForm : public QWidget
...
virtual ~CalculatorForm();
...
private:
Ui::CalculatorForm *ui;
... Соответствующий исходный файл выглядит следующим образом:
#include "ui_calculatorform.h"
CalculatorForm::CalculatorForm(QWidget *parent) :
QWidget(parent), ui(new Ui::CalculatorForm)
{
ui->setupUi(this);
}
CalculatorForm::~CalculatorForm()
{
delete ui;
} Преимущества этого подхода заключаются в том, что объект пользовательского интерфейса можно объявить вперёд, что означает, что нам не нужно включать сгенерированный файл ui_calculatorform.h в заголовок. Затем форму можно изменить без перекомпиляции зависимых исходных файлов. Это особенно важно, если класс подвержен ограничениям бинарной совместимости.
Мы обычно рекомендуем этот подход для библиотек и крупных приложений. Дополнительную информацию см. в разделе Создание библиотек общего пользования.
Подход с множественным наследованием
Формы, созданные с помощью Qt Designer, могут быть подклассифицированы вместе со стандартным классом QWidget. Этот подход делает все компоненты пользовательского интерфейса, определённые в форме, непосредственно доступными в области действия подкласса и позволяет создавать связи «сигнал-слот» стандартным способом с помощью функции connect().
Этот подход используется в примере «Множественное наследование» .
Нам необходимо включить заголовочный файл, который uic генерирует из файла calculatorform.ui, следующим образом:
#include "ui_calculatorform.h"
Класс определяется аналогично тому, что используется в подходе с одиночным наследованием, за исключением того, что на этот раз мы наследуем как от QWidget, так и от Ui::CalculatorForm, как показано ниже:
class CalculatorForm : public QWidget, private Ui::CalculatorForm
{
Q_OBJECT
public:
explicit CalculatorForm(QWidget *parent = nullptr);
private slots:
void on_inputSpinBox1_valueChanged(int value);
void on_inputSpinBox2_valueChanged(int value);
}; Мы наследуем Ui::CalculatorForm в частном порядке, чтобы гарантировать, что объекты пользовательского интерфейса являются частными в нашем подклассе. Мы также можем унаследовать его с ключевыми словами public или protected аналогично тому, как мы могли сделать ui общедоступными или защищёнными в предыдущем случае.
Конструктор подкласса выполняет многие из тех же задач, что и конструктор, используемый в примере с одиночным наследованием:
CalculatorForm::CalculatorForm(QWidget *parent)
: QWidget(parent)
{
setupUi(this);
} В этом случае виджеты, используемые в пользовательском интерфейсе, можно получить так же, как и виджет, созданный вручную в коде. Нам больше не нужен префикс ui для доступа к ним.
Реагирование на изменения языка
Qt уведомляет приложения, если меняется язык пользовательского интерфейса, отправляя событие типа QEvent::LanguageChange. Для вызова функции-члена retranslateUi() объекта пользовательского интерфейса мы переопределяем функцию QWidget::changeEvent() в классе формы следующим образом:
void CalculatorForm::changeEvent(QEvent *e)
{
QWidget::changeEvent(e);
switch (e->type()) {
case QEvent::LanguageChange:
ui->retranslateUi(this);
break;
default:
break;
}
} Обработка формы во время выполнения
В качестве альтернативы, формы можно обрабатывать во время выполнения, создавая динамически генерируемые пользовательские интерфейсы. Это можно сделать, используя модуль QtUiTools, который предоставляет класс QUiLoader для обработки форм, созданных с помощью Qt Designer.
Подход UiTools
Для обработки форм во время выполнения требуется файл ресурсов, содержащий файл UI. Кроме того, приложение должно быть настроено для использования модуля QtUiTools. Это делается путём включения следующего объявления в файл проекта qmake для правильной компиляции и линковки приложения.
QT += uitools
Класс QUiLoader предоставляет объект загрузчика формы для построения пользовательского интерфейса. Этот пользовательский интерфейс можно получить из любого QIODevice, например, из объекта QFile, чтобы получить форму, хранящуюся в файле ресурсов проекта. Функция QUiLoader::load() строит виджет формы, используя описание пользовательского интерфейса, содержащееся в файле.
Классы модуля QtUiTools можно включить с помощью следующей директивы:
#include <QtUiTools>
Функция QUiLoader::load() вызывается, как показано в этом коде из примера Поиска текста:
static QWidget *loadUiFile(QWidget *parent)
{
QFile file(":/forms/textfinder.ui");
file.open(QIODevice::ReadOnly);
QUiLoader loader;
return loader.load(&file, parent);
} В классе, использующем QtUiTools для построения пользовательского интерфейса во время выполнения, мы можем находить объекты в форме с помощью QObject::findChild(). Например, в следующем коде мы находим некоторые компоненты на основе их имен объектов и типов виджетов:
ui_findButton = findChild<QPushButton*>("findButton");
ui_textEdit = findChild<QTextEdit*>("textEdit");
ui_lineEdit = findChild<QLineEdit*>("lineEdit"); Обработка форм во время выполнения предоставляет разработчику свободу изменения пользовательского интерфейса программы просто путем изменения файла пользовательского интерфейса. Это полезно при настройке программ под различные потребности пользователя, например, для использования дополнительных больших значков или другой цветовой схемы для поддержки доступности.
Автоматические соединения
Соединения сигналов и слотов, определенные для форм во время компиляции или выполнения, могут быть настроены вручную или автоматически, используя возможности QMetaObject для создания соединений между сигналами и соответствующими слотами с именами.
Как правило, в QDialog, если мы хотим обработать информацию, введенную пользователем, перед ее принятием, нам необходимо подключить сигнал clicked() кнопки OK к пользовательскому слоту в нашем диалоге. Сначала мы покажем пример диалога, в котором слот подключен вручную, а затем сравним его с диалогом, использующим автоматическое подключение.
Диалог без автоматического подключения
Мы определяем диалог так же, как и раньше, но теперь включаем слот в дополнение к конструктору:
class ImageDialog : public QDialog, private Ui::ImageDialog
{
Q_OBJECT
public:
ImageDialog(QWidget *parent = 0);
private slots:
void checkValues();
}; Слот checkValues() будет использоваться для проверки введенных пользователем значений.
В конструкторе диалога мы настраиваем виджеты, как и раньше, и подключаем сигнал clicked() кнопки Отмена к слоту reject() диалога. Мы также отключаем свойство autoDefault в обеих кнопках, чтобы гарантировать, что диалог не вмешивается в обработку событий нажатия клавиши возврата в поле ввода:
ImageDialog::ImageDialog(QWidget *parent)
: QDialog(parent)
{
setupUi(this);
okButton->setAutoDefault(false);
cancelButton->setAutoDefault(false);
...
connect(okButton, SIGNAL(clicked()), this, SLOT(checkValues()));
} Мы подключаем сигнал clicked() кнопки OK к слоту checkValues() диалога, который мы реализуем следующим образом:
void ImageDialog::checkValues()
{
if (nameLineEdit->text().isEmpty())
(void) QMessageBox::information(this, tr("No Image Name"),
tr("Please supply a name for the image."), QMessageBox::Cancel);
else
accept();
} Этот пользовательский слот выполняет минимально необходимую проверку для обеспечения того, что введенные пользователем данные являются допустимыми — он принимает входные данные только в том случае, если для изображения было указано имя.
Виджеты и диалоги с автоматическим подключением
Хотя легко реализовать пользовательский слот в диалоге и подключить его в конструкторе, мы можем вместо этого использовать возможности автоматического подключения QMetaObject для подключения сигнала clicked() кнопки OK к слоту в нашем подклассе. uic автоматически генерирует код в функции setupUi() диалога для этого, поэтому нам нужно только объявить и реализовать слот с именем, следуя стандартной конвенции:
void on_<object name>_<signal name>(<signal parameters>);
Используя эту конвенцию, мы можем определить и реализовать слот, который реагирует на щелчки мыши по кнопке OK:
class ImageDialog : public QDialog, private Ui::ImageDialog
{
Q_OBJECT
public:
ImageDialog(QWidget *parent = 0);
private slots:
void on_okButton_clicked();
}; Еще одним примером автоматического соединения сигналов и слотов является Поиск текста со своим слотом on_findButton_clicked().
Мы используем систему QMetaObject для включения соединений сигналов и слотов:
QMetaObject::connectSlotsByName(this);
Это позволяет нам реализовать слот, как показано ниже:
void TextFinder::on_findButton_clicked()
{
QString searchString = ui_lineEdit->text();
QTextDocument *document = ui_textEdit->document();
bool found = false;
// undo previous change (if any)
document->undo();
if (searchString.isEmpty()) {
QMessageBox::information(this, tr("Empty Search Field"),
tr("The search field is empty. "
"Please enter a word and click Find."));
} else {
QTextCursor highlightCursor(document);
QTextCursor cursor(document);
cursor.beginEditBlock();
...
cursor.endEditBlock();
if (found == false) {
QMessageBox::information(this, tr("Word Not Found"),
tr("Sorry, the word cannot be found."));
}
}
} Автоматическое соединение сигналов и слотов обеспечивает как стандартную конвенцию именования, так и явный интерфейс для работы разработчиков пользовательского интерфейса. Предоставляя исходный код, реализующий заданный интерфейс, разработчики пользовательского интерфейса могут проверить, что их проекты действительно работают, не прибегая к написанию кода самим.
© The Qt Company Ltd
Licensed under the GNU Free Documentation License, Version 1.3.
https://doc.qt.io/qt-5.9/designer-using-a-ui-file.html