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