Использование файла пользовательского интерфейса Designer в приложении C++
Файлы пользовательского интерфейса 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;
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() кнопки ОК к нашему пользовательскому слоту в диалоговом окне. Сначала мы покажем пример диалогового окна, в котором слот подключен вручную, а затем сравним его с диалоговым окном, использующим автоматическое подключение.
Диалоговое окно без автоматического подключения
Мы определяем диалоговое окно так же, как и раньше, но теперь включаем слот в дополнение к конструктору:
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() кнопки ОК к слоту 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() кнопки ОК к слоту в нашем подклассе. uic автоматически генерирует код в функции setupUi() диалогового окна для этого, поэтому нам нужно только объявить и реализовать слот с именем, которое соответствует стандартной конвенции:
void on_<object name>_<signal name>(<signal parameters>);
Используя эту конвенцию, мы можем определить и реализовать слот, который реагирует на щелчки мыши по кнопке ОК:
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-6.1/designer-using-a-ui-file.html