Spec-Zone.ru › Qt 6.0

Использование файла интерфейса дизайнера в вашем приложении 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

Для обработки форм во время выполнения необходим файл ресурсов, содержащий файл интерфейса. Кроме того, приложение должно быть настроено для использования модуля QtUiTools. Это делается путем включения следующего объявления в файл проекта qmake приложения, гарантируя, что приложение скомпилировано и связаны соответствующим образом.

QT += uitools

Класс QUiLoader предоставляет объект загрузчика форм для построения пользовательского интерфейса. Этот пользовательский интерфейс может быть получен из любого QIODevice, например, объекта QFile, чтобы получить форму, хранящуюся в файле ресурсов проекта. Функция QUiLoader::load() строит виджет формы, используя описание пользовательского интерфейса, содержащееся в файле.

Классы модуля QtUiTools могут быть включены с помощью следующей директивы:

#include <QtUiTools>

Функция QUiLoader::load() вызывается, как показано в этом коде из примера Text Finder:

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();
};

Ещё один пример автоматического подключения сигналов и слотов — это Text Finder со своим слотом 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.0/designer-using-a-ui-file.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API