Ну что, друзья, вот мы и добрались до программирования стандартных графических приложений с помощью Qt5. На этом уроке мы попробуем создать наши первые программы, которые будут содержать такие графические элементы, как: кнопка, значок приложения, всплывающая подсказка, различные курсоры мыши, а также рассмотрим, как можно центрировать окно нашей программы и бегло коснемся вопроса работы механизма «сигнал-слот».
Простой пример
А начнем мы с простого примера, который отображает на экране стандартное окно:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#include <QApplication> #include <QWidget> int main(int argc, char *argv[]) { // Каждое Qt5-приложение (за исключением консольных) должно включать следующую строку QApplication app(argc, argv); // Главный виджет, который представляет окно нашей программы QWidget window; window.resize(250, 150); // изменяем размер виджета в пикселях window.setWindowTitle("Simple example"); // устанавливаем заголовок для главного окна window.show(); // выводим виджет на экран // С помощью метода exec() запускаем основной цикл нашей программы return app.exec(); } |
Результат выполнения программы:
Всплывающая подсказка
Всплывающая подсказка — это небольшой всплывающий прямоугольник с некоторым текстом, который появляется, когда вы наводите курсор на какой-нибудь элемент в приложении. В нижеследующем примере мы создадим всплывающую подсказку для нашего основного окна:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#include <QApplication> #include <QWidget> int main(int argc, char *argv[]) { QApplication app(argc, argv); QWidget window; window.resize(250, 150); window.move(300, 300); window.setWindowTitle("ToolTip"); // устанавливаем заголовок для окна window.setToolTip("QWidget"); // устанавливаем всплывающую подсказку для виджета window.show(); // выводим на экран return app.exec(); } |
Результат выполнения программы:
Иконка для приложения
В следующем примере мы зададим иконку для приложения. Большинство оконных менеджеров отображают этот значок в левом углу заголовка и на панели задач.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#include <QApplication> #include <QWidget> #include <QIcon> int main(int argc, char *argv[]) { QApplication app(argc, argv); QWidget window; window.resize(250, 150); window.setWindowTitle("Icon"); window.setWindowIcon(QIcon("web.png")); window.show(); return app.exec(); } |
Для отображения иконки мы используем метод setWindowIcon() и класс QIcon. Иконка представляет собой небольшой .png-файл, расположенный в текущем рабочем каталоге:
1 |
window.setWindowIcon(QIcon("web.png")); |
Результат выполнения программы:
Курсор
Курсор — это небольшой значок, отображающий на экране положение указателя мыши. В наших программах мы можем использовать различные виды курсоров. В следующем примере мы задействуем три фрейма, каждый из которых будет иметь свой курсор:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
#include <QApplication> #include <QWidget> #include <QFrame> #include <QGridLayout> class Cursors : public QWidget { public: Cursors(QWidget *parent = 0); }; Cursors::Cursors(QWidget *parent) : QWidget(parent) { QFrame *frame1 = new QFrame(this); // создаем виджет frame1->setFrameStyle(QFrame::Box); // устанавливаем рамки фрейма frame1->setCursor(Qt::SizeAllCursor); // задаем тип курсора SizeAllCursor для фрейма QFrame *frame2 = new QFrame(this); // создаем виджет frame2->setFrameStyle(QFrame::Box); // устанавливаем рамки фрейма frame2->setCursor(Qt::WaitCursor); // задаем тип курсора WaitCursor для фрейма QFrame *frame3 = new QFrame(this); // создаем виджет frame3->setFrameStyle(QFrame::Box); // устанавливаем рамки фрейма frame3->setCursor(Qt::PointingHandCursor); // задаем тип курсора PointingHandCursor для фрейма // Группируем все фреймы в одну строку QGridLayout *grid = new QGridLayout(this); grid->addWidget(frame1, 0, 0); grid->addWidget(frame2, 0, 1); grid->addWidget(frame3, 0, 2); setLayout(grid); } int main(int argc, char *argv[]) { QApplication app(argc, argv); Cursors window; window.resize(350, 150); window.setWindowTitle("Cursors"); window.show(); return app.exec(); } |
Результат выполнения программы:
Кнопка
В следующем примере мы добавим на нашу форму самую обычную кнопку. Нажатие на кнопку приведет к закрытию приложения. И здесь же впервые будут использованы понятия сигналов и слотов:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
#include <QApplication> #include <QWidget> #include <QPushButton> class MyButton : public QWidget { public: MyButton(QWidget *parent = 0); }; MyButton::MyButton(QWidget *parent) : QWidget(parent) { QPushButton *quitBtn = new QPushButton("Quit", this); // создаем новую кнопку quitBtn->setGeometry(50, 40, 75, 30); // изменяем размеры кнопки в пикселях и помещаем на форму окна connect(quitBtn, &QPushButton::clicked, qApp, &QApplication::quit); } int main(int argc, char *argv[]) { QApplication app(argc, argv); MyButton window; window.resize(250, 150); window.setWindowTitle("QPushButton"); window.show(); return app.exec(); } |
При нажатии на кнопку, генерируется сигнал clicked
. Слот — это метод, который реагирует на сигнал. В нашем случае это будет слот quit
основного объекта приложения. QApp
— это глобальный указатель на объект приложения. Он определен в заголовочном файле QApplication.
1 |
connect(quitBtn, &QPushButton::clicked, qApp, &QApplication::quit); |
Результат выполнения программы:
Взаимодействие виджетов
Мы заканчиваем этот урок примером, который демонстрирует возможность виджетов взаимодействовать друг с другом. Код данного примера разделен на три файла.
Следующий код является заголовочным файлом, в котором мы определяем два слота и виджет lbl
.
Заголовочный файл — plusminus.h:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
#pragma once #include <QWidget> #include <QApplication> #include <QPushButton> #include <QLabel> class PlusMinus : public QWidget { Q_OBJECT // этот макрос должен включаться в классы, которые объявляют свои собственные сигналы и слоты public: PlusMinus(QWidget *parent = 0); private slots: void OnPlus(); void OnMinus(); private: QLabel *lbl; }; |
У нас есть две кнопки и виджет lbl
. С помощью кнопок мы увеличиваем или уменьшаем число, отображаемое в lbl
.
Файл реализации — plusminus.cpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
#include "plusminus.h" #include <QGridLayout> PlusMinus::PlusMinus(QWidget *parent) : QWidget(parent) { QPushButton *plsBtn = new QPushButton("+", this); QPushButton *minBtn = new QPushButton("-", this); lbl = new QLabel("0", this); QGridLayout *grid = new QGridLayout(this); grid->addWidget(plsBtn, 0, 0); grid->addWidget(minBtn, 0, 1); grid->addWidget(lbl, 1, 1); setLayout(grid); connect(plsBtn, &QPushButton::clicked, this, &PlusMinus::OnPlus); connect(minBtn, &QPushButton::clicked, this, &PlusMinus::OnMinus); } void PlusMinus::OnPlus() { int val = lbl->text().toInt(); val++; lbl->setText(QString::number(val)); } void PlusMinus::OnMinus() { int val = lbl->text().toInt(); val--; lbl->setText(QString::number(val)); } |
А здесь происходит соединение сигналов clicked
с соответствующими слотами:
1 2 |
connect(plsBtn, &QPushButton::clicked, this, &PlusMinus::OnPlus); connect(minBtn, &QPushButton::clicked, this, &PlusMinus::OnMinus); |
В методе OnPlus() мы определяем текущее значение в lbl
. Виджет lbl
отображает строковое значение, поэтому сначала его нужно преобразовать в целое число. Затем мы это число увеличиваем, конвертируем получившееся число снова в строковое значение и устанавливаем новый текст для label
:
1 2 3 4 5 |
void PlusMinus::OnPlus() { int val = lbl->text().toInt(); // выполняем конвертацию в тип int val++; // выполняем инкремент значения lbl->setText(QString::number(val)); // конвертируем обратно в Qstring и устанавливаем новый текст для label } |
Метод OnMinus() отличается от метода OnPlus() только тем, что в OnMinus() значение не инкрементируется, а декрементируется (уменьшается на единицу).
А это уже наш основной файл — main.cpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#include "plusminus.h" int main(int argc, char *argv[]) { QApplication app(argc, argv); PlusMinus window; window.resize(300, 190); window.setWindowTitle("Plus minus"); window.show(); return app.exec(); } |
Результат выполнения программы:
Заключение
На этом уроке мы создали наши первые графические программы с помощью Qt5, а на следующем уроке мы рассмотрим подключение стандартных меню и панелей инструментов (тулбаров).
выдало три ошибки
main.obj : error LNK2001: неразрешенный внешний символ «»public: virtual struct QMetaObject const * __thiscall PlusMinus::metaObject(void)const » (?metaObject@PlusMinus@@UBEPBUQMetaObject@@XZ)»
main.obj : error LNK2001: неразрешенный внешний символ «»public: virtual void * __thiscall PlusMinus::qt_metacast(char const *)» (?qt_metacast@PlusMinus@@UAEPAXPBD@Z)»
main.obj : error LNK2001: неразрешенный внешний символ «»public: virtual int __thiscall PlusMinus::qt_metacall(enum QMetaObject::Call,int,void * *)» (?qt_metacall@PlusMinus@@UAEHW4Call@QMetaObject@@HPAPAX@Z)»
Когда закоментировал
//Q_OBJECT // этот макрос должен включаться в классы, которые объявляют свои собственные сигналы и слоты
всё заработало…
Магия ? 🙂
Ах да… я в VS тренируюсь… тут ещё много непонятного, чего приходится чуть ли не «методом тыка» добиваться.
С консольным приложением всё было хорошо. А проекта типа GUI нет в списке, есть Widget. Но библиотеки почему-то не находило.
Пришлось тупо создать консольное приложение и в свойствах проекта QtModules и добавить gui и widgets. А потом просто отключить вылезающую консоль.
Почему-то начинает казаться что все эти мастеры создания спецпроектов фуфло. Не лучше ли создавать пустое приложение и в настройках добавлять всё что нужно и отключать не нужное?
Насчёт добавления иконки — приведённый код не захотел работать.
Делал в Qt creator 4.14.2 (Qt 5.15.2)
Как я его заставил работать:
1. Положил иконку в папку с проектом. Я использовал файл с расширением *.ico
2. В QT-Creator ПКМ по проекту — "добавить новый…" — раздел в шаблонах "Qt" — "Файл ресурсов Qt" — выбрать — пишем имя_файла_ресурсов (например res) — Далее — завершить
3. По умолчанию файл ресурсов должен открыться в редакторе. Если не открылся: ПКМ по файлу ресурсов — "открыть в редакторе"
4. "Добавить префикс" — поменять имя. Я использовал "/ico". Имя запомнить. В дальнейшем это важно.
5. Выделяем появившуюся папку с префиксом "/ico" — "Добавить файлы" — выбираем файл с иконкой
6. В коде меняем адрес файла по следующему образцу
Если не получится — проверяйте наличие в файле проекта имя_проекта.pro наличие строчки RESOURCES += \имя_файла_ресурсов.qrc
Здравствуйте, прямоугольные кнопки с текстом — это хорошо, но, скажите, пожалуйста, есть ли возможность вставить картинку и сделать её кнопкой? (Т.е. чтобы при нажатии на картинку, например, закрывалась программа?)
Да. У картинки есть конструктор, принимающий Pixmap. Потом соединяем QPushButton::clicked и QApplication::quit и всё
Подскажите пожалуйста, почему в примере про взаимодействие виджетов в классе PlusMinus в списке private членов указан только один label, а две кнопки — не указаны, и создаются в конструкторе?
Потому что виджет lbl мы используем в методах OnPlus/OnMinus, т.е. нам нужно иметь доступ к этому виджету. В то время как кнопки нам нужно только создать
Расскажите пожалуйста о методе move().
Тут всё очень просто: метод move(300, 300) перемещает виджет (в нашем случае — окно) в позицию с координатами (300,300).
P.S.: Попробуйте поставить другие значения координат и посмотреть на результат 🙂
Добрый день!
Пытаюсь изучать с вашей помощью QT, за что Вам огромное спасибо!)
У меня возник такой вопрос) в программе есть такая штука как дизайнер, с помощью которой удобно создавать объекты на нашем окне приложения и сигналы со слотами)
Какие у этого способа минусы и каким способом лучше писать приложения?)
И вам спасибо за вашу благодарность.
По поводу вопроса — лучше выбирать тот способ, которым вам удобнее пользоваться.
Плюсы Qt-дизайнера в том, что вы сразу можете видеть результат вашей работы. Как следствие — меньше головной боли, особенно если в приложении используются различные (в том числе вложенные) компоновки.
Минусы же его состоят в том, что, если вам нужно сделать что-то, чего нет в стандартном наборе компонентов, то добро пожаловать в написание кода "голыми руками" 🙂
#include <QApplication> пишет 'QApplication' file not found.
Какого типа проект нужно создавать , может , в этом дело?
Походу да — нужно выбирать что типа QT widget application.
Для меня тоже немного сложновато, некоторых неочевидных вещей нехватает. Плюс еще плаваю в самом C++
Как решили ситуацию?(У меня сейчас подобное происходит)
Создайте проект типа "Приложение Qt Widgets"…
Нужно создавать проект типа "Приложение Qt Widgets".
Можно в qmake файле подправить настройки билда, и все заработает
QT+=core gui widgets
CONFIG += c++11
Пишет, что С++ требует type specifier for all declarations.
Вообще я так посмотрел, без увернного знания С++ в QT делать нечего? Смотрю как баран на новые ворота на все жти квадраточия, наследования классов, т.п. Перывые пару уроков еще зашли, а дальше банальное "переписывание с доски"…эх…
Замкнутый круг, чтоб научиться прогать на С++ нужно уметь прогать на С++…
Проверил у себя. Всё работает.
Проверьте внимательно свой код. Ошибки подобного рода часто возникают, когда вместо void OnPlus(); пишут просто OnPlus();
Если не получится исправить, то скиньте сюда ссылку на скриншот с исходниками вашей программы.
Добрый день. Подскажите пожалуйста в примере с курсорами в конструкторе класса Cursors задается стандартное значение указателя parent = 0. Можно ли туда заместо 0 передавать nullPtr?
Немного не понял вопроса… Если вы имеете в виду — можно ли вместо parent = 0 использовать parent = nullPtr, то ответ — да, можно. Вообще говоря, в наших уроках с этим проблем нет, но вот в более серьезных программах предпочтительнее использовать вариант parent = nullPtr, т.к. из-за автоприведения типов в Си++, которое иногда (неочевидным образом) может приводить 0 или NULL (который заменяется на 0) к целому числу и вызывать косяки.
Насчёт первого примера — лучше бы воспользоваться QHBoxLayout, всё-таки он чуть быстрее, да и нужны в группировке таблицей тут нет… А вообще да, спасибо за труд.
Всегда пожалуйста 🙂
Т.е. в QT есть что-то типа garbage collector? А можно поподробнее, как он здесь работает?
В Qt есть есть класс, который является базовым для всех других классов и объектов. Называется он — QObject. Когда вы создаете свой класс или объект при помощи прямого наследования (непосредственно от QObject) или косвенного (через цепочку промежуточных классов, которые были наследованы от QObject), то вам становится доступен механизм связи объектов, а именно: организация в т.н. иерархии (или деревья объектов). Чтобы его задействовать, нужно при создании объекта-потомка передавать в качестве параметра указатель на объект-предок. И тогда, при удалении родительского объекта, Qt автоматически удалит и всех связанных с ним потомков.
Пример для наглядности:
Простите, вот я такой момент недопонял: в конструкторе классов создаются указатели с выделением памяти. Как потом очищается память? Если эти указатели были бы полями класса, инициализировались в конструкторе и в деструкторе delete`лись, я бы понял. А так получается утечка памяти, вроде как. Поясните этот момент, пожалуйста
>>Как потом очищается память?
Обратите внимание на второй параметр ("this") в примере ниже:
В данном случае "this" — это указатель на объект-предок. Если предок будет уничтожен, то Qt автоматически подчистит память за всеми связанными с ним объектами.
P.S.: В данной статье работа этого механизма не особо заметна, т.к. созданные нами объекты (кнопки, курсоры, подсказки и пр.) живут всё время работы программы. А после завершения работы приложения — операционная система сама позаботится об освобождении всех выделенных ресурсов.
Спасибо, тоже интересовал этот вопрос. Весьма удобно реализовано.
Да, очень крутое объяснение, жду с нетерпением следующих уроков.
Спасибо. Новая статья уже передана на корректуру 🙂
Жду недождусь новых уроков. Спасибо огромное за Вашу работу.
Всегда пожалуйста 🙂