Как создать оконное приложение на 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:
.
Также в заголовочном файле мы объявляем указатели на те виджеты, к которым нам понадобится доступ внутри слота.
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) нашего окна.
Горизонтальный лэйаут подразумевает, что все виджеты, добавленные на него, будут размещены по горизонтали друг за другом слева направо в порядке добавления.
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
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