Spec-Zone.ru › Qt 5.13

Использование файла интерфейса Designer в вашем приложении

Файлы интерфейса Qt Designer представляют собой дерево виджетов формы в формате XML. Формы могут быть обработаны:

  • На этапе компиляции, что означает, что формы преобразуются в код C++, который может быть скомпилирован.
  • На этапе выполнения, что означает, что формы обрабатываются классом QUiLoader, который динамически строит дерево виджетов во время разбора файла XML.

Обработка формы на этапе компиляции

Вы создаете компоненты пользовательского интерфейса с помощью Qt Designer и используете интегрированные инструменты сборки Qt, qmake и uic, для генерации кода для них во время сборки приложения. Сгенерированный код содержит объект пользовательского интерфейса формы. Это структура C++, которая содержит:

  • Указатели на виджеты, макеты, элементы макета, группы кнопок и действия формы.
  • Член-функцию, названную setupUi(), для построения дерева виджетов на родительском виджете.
  • Член-функцию, названную retranslateUi(), которая обрабатывает перевод строковых свойств формы. Более подробная информация приведена в разделе Реагирование на изменения языка.

Сгенерированный код может быть включен в ваше приложение и использоваться непосредственно из него. В качестве альтернативы, вы можете использовать его для расширения подклассов стандартных виджетов.

Обработанная на этапе компиляции форма может быть использована в вашем приложении с одним из следующих подходов:

  • Прямой подход: вы создаёте виджет в качестве заполнителя для компонента и настраиваете пользовательский интерфейс внутри него.
  • Подход единого наследования: вы создаёте подкласс базового класса формы (QWidget или QDialog, например) и включаете частный экземпляр объекта пользовательского интерфейса формы.
  • Подход множественного наследования: вы создаёте подкласс как базового класса формы, так и объекта пользовательского интерфейса формы. Это позволяет напрямую использовать виджеты, определённые в форме, внутри области подкласса.

Для демонстрации мы создаём простое приложение «Калькулятор». Оно основано на исходном примере Calculator Form.

Приложение состоит из одного исходного файла, 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 является членом класса.

Этот подход используется в примере 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.13/designer-using-a-ui-file.html

Spec-Zone.ru

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