Использование файла интерфейса Designer в вашем приложении C++
Файлы интерфейса Qt Designer представляют собой дерево виджетов формы в формате XML. Обработка форм может осуществляться:
- Во время компиляции, что означает преобразование форм в код C++, который можно скомпилировать.
- Во время выполнения, что означает обработку форм классом QUiLoader, который динамически создаёт дерево виджетов во время парсинга XML-файла.
Обработка формы во время компиляции
Вы создаёте компоненты пользовательского интерфейса с помощью Qt Designer и используете интегрированные инструменты сборки Qt, qmake и uic, для генерации кода для них во время сборки приложения. Сгенерированный код содержит объект пользовательского интерфейса формы. Это C++ структура, которая содержит:
- Указатели на виджеты, макеты, элементы макета, группы кнопок и действия формы.
- Член-функцию, называемую
setupUi(), для построения дерева виджетов на родительском виджете. - Член-функцию, называемую
retranslateUi(), которая обрабатывает перевод строковых свойств формы. Дополнительную информацию см. в разделе Реагирование на изменения языка.
Сгенерированный код можно включить в ваше приложение и использовать непосредственно из него. Также можно использовать его для расширения подклассов стандартных виджетов.
Обработанную во время компиляции форму можно использовать в вашем приложении одним из следующих способов:
- Прямой подход: вы создаёте виджет в качестве заглушки для компонента и настраиваете пользовательский интерфейс внутри него.
- Подход с одиночным наследованием: вы создаёте подкласс базового класса формы (QWidget или QDialog, например) и включаете в него частную экземпляр объекта пользовательского интерфейса формы.
- Подход с множественным наследованием: вы создаёте подкласс как базового класса формы, так и объекта пользовательского интерфейса формы. Это позволяет напрямую использовать виджеты, определённые в форме, изнутри подкласса.
Для демонстрации мы создаём простое приложение «Калькулятор». Оно основано на оригинальном примере Калькулятор Form.
Приложение состоит из одного исходного файла, main.cpp, и файла интерфейса.
Файл интерфейса calculatorform.ui, разработанный с помощью Qt Designer, показан ниже:
Мы будем использовать qmake для построения исполняемого файла, поэтому нам нужно написать файл .pro.
HEADERS = calculatorform.h
Особенностью этого файла является объявление FORMS, которое сообщает qmake какие файлы обработать с помощью uic. В данном случае, файл calculatorform.ui используется для создания файла ui_calculatorform.h, который может быть использован любым файлом, указанным в объявлении SOURCES.
Примечание: Вы можете использовать Qt Creator для создания проекта «Калькулятор Form». Он автоматически генерирует файлы 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 является членом класса.
Этот подход используется в примере Калькулятор Form.
Для того, чтобы мы могли использовать пользовательский интерфейс, нам необходимо включить заголовочный файл, который uic генерирует, перед использованием Ui::CalculatorForm:
#include "ui_calculatorform.h"
Это означает, что файл .pro должен быть обновлён, чтобы включить calculatorform.h:
HEADERS = calculatorform.h
Подкласс определяется следующим образом:
%%%CODE_BLOCK_29%%>Важной особенностью класса является частный ui объект, который предоставляет код для настройки и управления пользовательским интерфейсом.
Конструктор подкласса создает и настраивает все виджеты и макеты диалога, просто вызвав функцию setupUi() объекта ui. После этого можно изменять пользовательский интерфейс по мере необходимости.
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
Требуется файл ресурсов, содержащий файл пользовательского интерфейса, для обработки форм во время выполнения. Также приложение должно быть настроено для использования модуля 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-6.2/designer-using-a-ui-file.html