Как создать оконное приложение на Qt в среде QtCreator

В этой статье мы рассмотрим процесс создания простого оконного приложения с помощью библиотеки Qt в среде разработки QtCreator. Мы поработаем с лэйаутами, то есть с разметкой элементов окна, добавим в окно таблицу, несколько полей ввода данных и кнопку.

Библиотека Qt - это набор классов C++ для создания кросс-платформенных приложений с графическим интерфейсом. С помощью этой библиотеки созданы, например, среда разработки QtCreator и инструмент для записи видео OBS Studio.

Для работы потребуется: компилятор C++; библиотека Qt, соответствующая компилятору (неплохая инструкция по установке); среда разработки QtCreator (или любая другая).

Статья доступна в видео-формате: YouTube | ВКонтакте

Читайте больше о разработке в моем Телеграм: t.me/mediocre_developer

Макет приложения

На макете ниже показано как будет выглядеть наше приложение. С левой стороны разместим таблицу со списком людей, определяемых именем, фамилией и возрастом. А справа - панель добавления данных в таблицу. Тут будут те же самые поля, что и в таблице, а также кнопка для добавления.

Исходный код

Для начала создадим новый проект Qt Widgets в QtCreator на базе CMake.

Обращаю внимание, что нам не нужно создавать файл MainWindow.ui, так как весь интерфейс окна мы будем задавать в коде, избегая инструментов QtDesigner. Поэтому снимаем соответствующую галочку.

В результате работы мастера будет создан проект из 4-х файлов:

  • Файл проекта CMakeLists.txt;

  • Главный файл программы с точкой входа main.cpp;

  • Класс окна MainWindow с заголовочным файлом MainWindow.h и реализацией MainWindow.cpp.

MainWindow.h

#pragma once

#include <QMainWindow>

class QTableWidget;
class QLineEdit;
class QSpinBox;

class MainWindow : public QMainWindow
{
  Q_OBJECT

public:
  MainWindow(QWidget *parent = nullptr);
  ~MainWindow();

private slots:
  void onAddClicked();

private:
  QTableWidget* table;
  QLineEdit* nameInput;
  QLineEdit* secondNameInput;
  QSpinBox* ageInput;
};

В заголовочном файле мы объявляем слот void onAddClicked();, который будет содержать реализацию обработчика нажатия на кнопку. Слот в Qt - это специальный метод класса, участвующий в концепции сигнал/слот. Слоты всегда объявляются в заголовочных файлах с указанием ключевого слова slots. Например, наш слот определен в закрытой секции класса private slots:.

💡
Если коротко, то концепция сигналов и слотов в Qt - это что-то вроде событийной системы. Сигналы - это методы классов, определяющие некоторое событие. У этих методов нет реализации, мы используем от них только имена и аргументы. Слоты же - это методы классов, отвечающие за обработку событий. Вызов конкретного сигнала всегда будет инициировать вызов присоединенного к нему слота. Об их соединении поговорим ниже.

Также в заголовочном файле мы объявляем указатели на те виджеты, к которым нам понадобится доступ внутри слота.

private:
  QTableWidget* table;
  QLineEdit* nameInput;
  QLineEdit* secondNameInput;
  QSpinBox* ageInput;

MainWindow.cpp

Весь исходный код реализации класса MainWindow приведен ниже. Создание всего интерфейса окна происходит в конструкторе. Рассмотрим некоторые его моменты подробнее.

#include "MainWindow.h"
#include <QHBoxLayout>
#include <QTableWidget>
#include <QLabel>
#include <QLineEdit>
#include <QSpinBox>
#include <QPushButton>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
  QWidget* centralWidget = new QWidget;
  setCentralWidget(centralWidget);
  QHBoxLayout* mainLayout = new QHBoxLayout;
  centralWidget->setLayout(mainLayout);

  table = new QTableWidget;
  table->setColumnCount(3);
  table->setHorizontalHeaderLabels(QStringList() << "Name" << "Second name" << "Age");
  mainLayout->addWidget(table);

  QVBoxLayout* verticalLayout = new QVBoxLayout;
  mainLayout->addLayout(verticalLayout);

  verticalLayout->addWidget(new QLabel("Name:"));
  nameInput = new QLineEdit;
  verticalLayout->addWidget(nameInput);

  verticalLayout->addWidget(new QLabel("Second name:"));
  secondNameInput = new QLineEdit;
  verticalLayout->addWidget(secondNameInput);

  verticalLayout->addWidget(new QLabel("Age:"));
  ageInput = new QSpinBox;
  verticalLayout->addWidget(ageInput);

  QPushButton* addButton = new QPushButton("Add");
  connect(addButton, &QPushButton::clicked, this, &MainWindow::onAddClicked);
  verticalLayout->addWidget(addButton);

  verticalLayout->addStretch();
}

MainWindow::~MainWindow()
{
}

void MainWindow::onAddClicked()
{
  const QString name = nameInput->text();
  const QString secondName = secondNameInput->text();
  const int age = ageInput->value();

  const int rowCount = table->rowCount();
  table->setRowCount(rowCount + 1);

  table->setItem(rowCount, 0, new QTableWidgetItem(name));
  table->setItem(rowCount, 1, new QTableWidgetItem(secondName));
  table->setItem(rowCount, 2, new QTableWidgetItem(QString::number(age)));
}

Конструктор

В первую очередь мы создаем главный горизонтальный лэйаут mainLayout (QHBoxLayout) нашего окна.

💡
Лэйауты в Qt предназначены для управления размещением виджетов в окне. Он задают не только место и порядок размещения виджетов, но и их размер, который вычисляется динамически при изменении размеров окна. Виджетами в Qt называют все графические элементы, которые могут быть размещены в окне (поля ввода, кнопки, таблицы, списки и т.д.)

Горизонтальный лэйаут подразумевает, что все виджеты, добавленные на него, будут размещены по горизонтали друг за другом слева направо в порядке добавления.

QWidget* centralWidget = new QWidget;
setCentralWidget(centralWidget);
QHBoxLayout* mainLayout = new QHBoxLayout;
centralWidget->setLayout(mainLayout);

Дальше мы добавляем в лэйаут пустую таблицу QTableWidget с тремя колонками под именами “Name” (имя человека), “Second name” (его фамилия) и “Age” (возраст).

table = new QTableWidget;
table->setColumnCount(3);
table->setHorizontalHeaderLabels(QStringList() << "Name" << "Second name" << "Age");
mainLayout->addWidget(table);

Теперь дело за правой панелью. Как видно на макете выше виджеты правой панели размещены по вертикали. Чтобы этого добиться в Qt, мы должны использовать вертикальный лэйаут QVBoxLayout, который добавляем внутрь горизонтального. Да, мы можем вкладывать лэйауты друг в друга.

QVBoxLayout* verticalLayout = new QVBoxLayout;
mainLayout->addLayout(verticalLayout);

Как только вертикальный лэйаут создан, мы можем его заполнять виджетами сверху вниз.

В верхней части добавляем статический текст “Name:” с помощью виджета QLabel. Сразу после него размещаем поле ввода однострочного текста QLineEdit, которое будет обрабатывать ввод имени.

verticalLayout->addWidget(new QLabel("Name:"));
nameInput = new QLineEdit;
verticalLayout->addWidget(nameInput);

Дальше идет похожий блок кода, только для ввода фамилии.

verticalLayout->addWidget(new QLabel("Second name:"));
secondNameInput = new QLineEdit;
verticalLayout->addWidget(secondNameInput);

Третий элемент ввода отличается от остальных, так как он отвечает за ввод целочисленного значения возраста человека. Ввод целочисленных значений выполним с помощью виджета QSpinBox.

verticalLayout->addWidget(new QLabel("Age:"));
ageInput = new QSpinBox;
verticalLayout->addWidget(ageInput);

И последний элемент окна - кнопка QPushButton.

Здесь как раз появляется вызов connect(), отвечающий за соединение сигналов и слотов. Этот вызов можно прочитать так: “Присоединить вызов сигнала &QPushButton::clicked объекта addButton к слоту &MainWindow::onAddClicked объекта this (то есть самого класса MainWindow)”. Таким образом клик по кнопке испускает сигнал clicked(), который инициирует вызов метода onAddClicked().

QPushButton* addButton = new QPushButton("Add");
connect(addButton, &QPushButton::clicked, this, &MainWindow::onAddClicked);
verticalLayout->addWidget(addButton);

В завершение мы добавляем в вертикальный лэйаут “пружинку”, которая как бы подожмет все элементы лэйаута к верхней его части. Если не добавить “пружинку”, то все элементы лэйаута равномерно распределятся по всей его площади. При больших размерах окна это будет смотреться несобранно, а это нам не подходит.

verticalLayout->addStretch();

Обработчик нажатия кнопки

Слот onAddClicked(), реализующий обработку нажатия кнопки, включает три условных этапа. Сначала мы получаем текущие данные из полей ввода.

const QString name = nameInput->text();
const QString secondName = secondNameInput->text();
const int age = ageInput->value();

Затем увеличиваем количество строк в таблице на 1. Тем самым создаем новую пустую строку в конце таблицы.

const int rowCount = table->rowCount();
table->setRowCount(rowCount + 1);

И, наконец, заполняем эту строку данными путем добавления текстовых элементов QTableWidgetItem в каждую ячейку. При этом целочисленную переменную age предварительно конвертируем в строку QString с помощью вызова статического метода QString::number().

table->setItem(rowCount, 0, new QTableWidgetItem(name));
table->setItem(rowCount, 1, new QTableWidgetItem(secondName));
table->setItem(rowCount, 2, new QTableWidgetItem(QString::number(age)));

main.cpp

Главный файл программы остается практически без изменений. Добавляем только свое название окна вызовом setWindowTitle(“People”); и изменяем изначальный размер окна - делаем его немного больше вызовом resize(800, 600);.

#include "MainWindow.h"
#include <QApplication>

int main(int argc, char *argv[])
{
  QApplication a(argc, argv);
  MainWindow w;
  w.setWindowTitle("People");
  w.resize(800, 600);
  w.show();
  return a.exec();
}

CMakeLists.txt

Файл проекта CMakeLists.txt изменений не потребует и останется точно таким каким его создал мастер во время создания проекта. Например, его содержимое может выглядеть так:

cmake_minimum_required(VERSION 3.5)

project(qt_example VERSION 0.1 LANGUAGES CXX)

# Включаем генераторы Qt
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Находим библиотеку Qt
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets)

# Перечисляем исходные файлы проекта
set(PROJECT_SOURCES
        main.cpp
        MainWindow.cpp
        MainWindow.h
)

# Создаем исполняемый файл проекта
add_executable(qt_example ${PROJECT_SOURCES})

# Прилинковываем библиотеку QtWidgets к исполняемому файлу проекта
target_link_libraries(qt_example PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)

Результат

После сборки и запуска проекта мы увидим наше окно.

Вводим данные и нажимаем кнопку “Add”. Новая запись появляется в таблице.

По умолчанию данные таблицы можно редактировать. Делаем двойной клик на интересующей ячейке и вводим новые данные. Например, меняем имя с “Ivan” на “Petr”.

На этом все.

В статье мы рассмотрели самую верхушку айсберга Qt: горизонтальные и вертикальные лэйауты и несколько виджетов. Но принципы построения окна во всех остальных случаях будут точно такие же. Изучайте и пробуйте.


Статья доступна в видео-формате: YouTube | ВКонтакте

Читайте больше о разработке в моем Телеграм: t.me/mediocre_developer

0
Subscribe to my newsletter

Read articles from Alexander Trotsenko directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Alexander Trotsenko
Alexander Trotsenko

Software Developer