Использование файла пользовательского интерфейса Qt Designer в вашем приложении
Файлы пользовательского интерфейса 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 для обеих кнопок, чтобы гарантировать, что диалог не повлияет на обработку событий нажатия клавиши Enter в поле редактирования:
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.12/designer-using-a-ui-file.html