Spec-Zone.ru › Qt 5.15

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

Для обработки форм во время выполнения требуется файл ресурсов, содержащий файл 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() кнопки Cancel к слоту 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-5.15/designer-using-a-ui-file.html

Spec-Zone.ru

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