Evgenii Legotckoi
Evgenii Legotckoi1 сентября 2015 г. 11:00

Qt/C++ - Урок 014. Динамическое создание виджетов в Qt

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

В данном уроке описывается процесс динамического создания кнопок QPushButton, приёма сигналов от этих кнопок, а также последующее удаление этих кнопок из компоновщика Qt.

Программный код был написан в QtCreator 3.3.1 на основе Qt 5.4.1.

Структура проекта

Описание структуры проекта:

  • DynamicButtons.pro - профайл;
  • mainwindow.h - заголовочный файл основного окна приложения;
  • mainwindow.cpp - исходный код окна;
  • main.cpp - основной исходный файл, с которого стартует приложение;
  • mainwindow.ui - формочка основного окна приложения;
  • qdynamicbutton.h - заголовочный файл класса обертки, который упрощает процесс работы с динамическими объектами в данном уроке;
  • qdynamicbutton.cpp - исходный файл класса обертки, который упрощает процесс работы с динамическими объектами в данном уроке.

mainwindow.ui

В данном уроке нам понадобится два слоя layout . В одном будут находиться все динамически создаваемые кнопки, а во втором слое будут находиться кнопки, которые будут отвечать за процесс создания и удаления динамических кнопок, а также поле lineEdit для отображения номера создаваемых кнопок.

Внешний вид приложения Непосредственная работа будет осуществляться со следующими объектами формы окна приложения:

  • addButton - кнопка для добавления динамических кнопок;
  • deleteButton - кнопка для удаления динамических кнопок;
  • verticalLayout - слой для добавления динамических кнопок с вертикальной компоновкой;
  • lineEdit - поле для отображения номеров созданных кнопок.

qdynamicbutton.h

Заголовочный файл класса обёртки, который наследуется от класса QPushButton. В Данном классе объявляется статическая переменная, которая будет общей для всех объектов класса и будет являться счетчиков всех динамических кнопок, которые будут создаваться в данном приложении. Это необходимо для адекватного присвоения номеров каждой кнопки.

#ifndef QDYNAMICBUTTON_H
#define QDYNAMICBUTTON_H

#include <QPushButton>

class QDynamicButton : public QPushButton
{
    Q_OBJECT
public:
    explicit QDynamicButton(QWidget *parent = 0);
    ~QDynamicButton();
    static int ResID;   // Статическая переменная, счетчик номеров кнопок
    int getID();        // Функция для возврата локального номера кнопки


public slots:

private:
    int buttonID = 0;   // Локальная переменная, номер кнопки
};

#endif // QDYNAMICBUTTON_H

qdynamicbutton.cpp

В файле исходного кода класса-обёртки производится инициализация кнопки в её конструкторе, инициализация статической переменной, а также находится метод для возврата номера динамической кнопки.

#include "qdynamicbutton.h"

QDynamicButton::QDynamicButton(QWidget *parent) :
    QPushButton(parent)
{
    ResID++;            // Увеличение счетчика на единицу
    buttonID = ResID;   /* Присвоение кнопке номера, по которому будет производиться
                         * дальнейшая работа с кнопок
                         * */

}

QDynamicButton::~QDynamicButton()
{

}

/* Метод для возврата значения номера кнопки
 * */
int QDynamicButton::getID()
{
    return buttonID;
}

/* Инициализация статической переменной класса.
 * Статическая переменная класса должна инициализироваться в обязательном порядке
 * */
int QDynamicButton::ResID = 0;

mainwindow.h

В заголовочном файле требуется добавить только СЛОТы для обработки нажатий управляющих кнопок и СЛОТ для получения номера нажатой динамической кнопки.

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

/* My Includes */
#include <qdynamicbutton.h>

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

private slots:
    void on_addButton_clicked();    // СЛОТ-обработчик нажатия кнопки добавления
    void on_deleteButton_clicked(); // СЛОТ-обработчик нажатия кнопки удаления
    void slotGetNumber();           // СЛОТ для получения номера нажатой динамической кнопки

private:
    Ui::MainWindow *ui;
};

#endif // MAINWINDOW_H
mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    /* Для удобства работы слои разделены QSplitter
     * */
    ui->splitter->setStretchFactor(0,1);
    ui->splitter->setStretchFactor(1,0);
}

MainWindow::~MainWindow()
{
    delete ui;
}

/* Метод для добавления динамической кнопки
 * */
void MainWindow::on_addButton_clicked()
{
    QDynamicButton *button = new QDynamicButton(this);  // Создаем объект динамической кнопки
    /* Устанавливаем текст с номером этой кнопки
     * */
    button->setText("Кнопочка " + QString::number(button->getID()));
    /* Добавляем кнопку в слой с вертикальной компоновкой
     * */
    ui->verticalLayout->addWidget(button);
    /* Подключаем сигнал нажатия кнопки к СЛОТ получения номера кнопки
     * */
    connect(button, SIGNAL(clicked()), this, SLOT(slotGetNumber()));
}

/* Метод для удаления динамической кнопки по её номеру
 * */
void MainWindow::on_deleteButton_clicked()
{
    /* Выполняем перебор всех элементов слоя, где располагаются динамические кнопки
     * */
    for(int i = 0; i < ui->verticalLayout->count(); i++){
        /* Производим каст элемента слоя в объект динамической кнопки
         * */
        QDynamicButton *button = qobject_cast<QDynamicButton*>(ui->verticalLayout->itemAt(i)->widget());
        /* Если номер кнопки соответствует числу, которое установлено
         * в lineEdit, то производим удаление данной кнопки
         * */
        if(button->getID() == ui->lineEdit->text().toInt()){
            button->hide();
            delete button;
        }
    }
}

/* СЛОТ для получения номера кнопки.
 * */
void MainWindow::slotGetNumber()
{
    /* Определяем объект, который вызвал сигнал
     * */
    QDynamicButton *button = (QDynamicButton*) sender();
    /* После чего устанавливаем номер кнопки в lineEdit,
     * который содержится в данной динамической кнопке
     * */
    ui->lineEdit->setText(QString::number(button->getID()));
    /* То есть номер кнопки устанавливается в поле lineEdit только тогда,
     * когда мы нажимаем одну из динамических кнопок, и этот номер соответствует
     * номеру нажатой кнопки
     * */
}

Итог

В результате у Вас должно получиться приложение, которое осуществляет динамическое создание и удаление кнопок. Работа приложения показана в видео приведённом ниже.

Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.

Вам это нравится? Поделитесь в социальных сетях!

KL
  • 26 января 2017 г. 11:13
 QDynamicButton *button = qobject_cast<QDynamicButton*>(ui->verticalLayout->itemAt(i)->widget());
Поясните пожалуйста смысл этой строки.
Evgenii Legotckoi
  • 26 января 2017 г. 11:22

В Qt элементы интерфейса имеют базовый класс QWidget . QDynamycButton наследован от QPushButton , который в свою очередь наследован в конечном итоге от QWidget . Из Layout`а указатели на объекты забираются в качестве указателей на объекты класса QWidget .

Таким образом, чтобы получить доступ к методам класса QDynamicButton , объектами которого являются в данном случае объекты, которые находятся в данном Layout`е, необходимо скастовать указатель из класса QWidget в QDynamicButton

Такие возможности языка C++ достигаются за счёт парадигмы полиморфизма. Пример полиморфизма с некоторыми пояснениями дан в этой статье .

F
  • 23 мая 2018 г. 20:36

Хм, не знаю как у других, а у меня отсутствует метод ui->splitter . Может быть потому что я на линухе сижу

Evgenii Legotckoi
  • 24 мая 2018 г. 2:12

компонент был создан в графическом дизайнере, о чём говорит название объекта ui , в котором находится объект splitter.

Это не имеет зависимости на платформу
H
  • 1 ноября 2018 г. 4:23

А если я неким неизвестным образом добавил разношерстные элементы в ui: checkbox,radiobutton,textedit и label. Они лежат группами одинаковых (одного типа) объектах в vbox, лежащий в groupbox, который в свою очередь находится в vertical layout. Как бы теперь двойным циклом пройтись по каждому из этих объектов в каждом из этих groupbox?

Evgenii Legotckoi
  • 1 ноября 2018 г. 4:28

Можно поискать нужные элементы через метод findChildren

QList<QWidget *> widgets = parentWidget.findChildren<QWidget *>("widgetname");

или выбрать все групбоксы, а потом каждом использовать метод findChildren.

Метод шаблонный, поэтому можно попытаться найти например только QLineEdit, вместо QWidget, поскольку QWidget - это базовый класс для QLineEdit и остальных виджетов.

H
  • 2 ноября 2018 г. 6:03

Т.е. я могу сделать что-то вроде такого?

QList<QGroupBox*> groups = ui->centralWidget->findChildren<QGroupBox*>();
QList<QWidget*> items;
items = groups.at(0)->findChildren<QCheckBox*>();
Когда я пробую такой вариант, происходит ошибка:
candidate function not viable: no known conversion from 'QList<QCheckBox *>' to 'const QList<QWidget *>' for 1st argument, т.е. получается, лист чекбоксов в лист виджетов я все-таки поместить не могу.
Evgenii Legotckoi
  • 2 ноября 2018 г. 6:23

Вы с ошибкой написали

Надо так

QList<QGroupBox*> groups = ui->centralWidget->findChildren<QGroupBox*>();
QList<QCheckBox*> items;
items = groups.at(0)->findChildren<QCheckBox*>();

H
  • 2 ноября 2018 г. 6:27

Не, я в смысле думал, что могу использовать родительский класс для этого, т.е. я хотел бы сделать что-то вроде:

  1. QList<QGroupBox*> groups = ui->centralWidget->findChildren<QGroupBox*>();
  2. QList<QWidget*> items;
  3. items = groups.at(0)->findChildren<QCheckBox*>();
  4. if(items.length() != 0) // т.е. если там есть хоть один чекбокс
  5. do smthing;
  6. else items = groups.at(0)->findChildren<QRadioButton*>();
  7. ...
Evgenii Legotckoi
  • 2 ноября 2018 г. 6:42
  • (ред.)

То, что вы так хотели сделать, не значит что это работает в С++. Вы не можете присвоить объект QList<QCheckBox*> к объекту QList<QWidget*> - это разные сущности контейнеров, хотя через методы append вы сможете добавить в контейнер другие классы, у которых родительский класс бцдет QWidget, но в данном случае не будет происходит копирование одного контейнера в объект другого.

И вообще, что вам мешает сделать так?

QList<QGroupBox*> groups = ui->centralWidget->findChildren<QGroupBox*>();
if(groups.at(0)->findChildren<QCheckBox*>().length() != 0) // т.е. если там есть хоть один чекбокс
 do smthing;

H
  • 2 ноября 2018 г. 6:45

Ну да, сейчас я уже буду делать тогда так, просто придется создавать листы под каждый тип виджетов. Спасибо!

Михаиллл
  • 15 ноября 2018 г. 7:54

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

Evgenii Legotckoi
  • 15 ноября 2018 г. 8:07

используйте метод insertWidget. В нём можно указать индекс, куда добалвять кнопку. Нужно будет найти индекс VerticalSpacer с помощью метода indexOf(verticalSpacer)

и на этот индекс добавлять кнопку.

Михаиллл
  • 15 ноября 2018 г. 8:30

но у VerticalSpacer нет метода indexOf.

insertWidget использовать вместо addWidget?


Evgenii Legotckoi
  • 15 ноября 2018 г. 8:34
но у VerticalSpacer нет метода indexOf.

У него нет, а вот у verticalLayout есть. Если вы добавите через дизайнер лейаут и спейсер, то можете забрать индекс спейсера так

ui->verticalLayout->indexOf(ui->verticalSpacer);

Да, использовать insertWidget




Михаиллл
  • 15 ноября 2018 г. 8:55

у меня выдает ошибку D:\QTProject\ReaderResume\mainwindow.cpp:811: ошибка: cannot initialize a parameter of type 'QWidget *' with an lvalue of type 'QSpacerItem *'

Evgenii Legotckoi
  • 15 ноября 2018 г. 8:58

А вот про это я забыл... Он же не наследован от QWidget.

Тогда, если вы знаете, что спецсер всегда в конце, то тогда так можете это сделать.

ui->verticalLayout->insertWidget(ui->verticalLayout.count() - 1, widget);


Михаиллл
  • 15 ноября 2018 г. 9:45

но у verticalLayout нет метода insertWidget

Evgenii Legotckoi
  • 15 ноября 2018 г. 9:55

verticalLayout - это, по-моему предположению, должен быть у вас объект класса QVBoxLayout, который наследован от QBoxLayout.

Поэтому открываете документацию на QVBoxLayout .

И ищете там строку List of all members, including inherited members это сразу под краткой анотацией класса. Кликаете по этой надписи и переходите на страницу с полным списком всех методов класса QVBoxLayout.

После этого нужно воспользоваться поиском по странице Ctrl+F, чтобы найти insertWidget. И вы увидите, что там есть этот метод.

Если вы всё-таки использовали QGridLayout, то измените класс QVBoxLayout, если вам нужен имен просто вертикальный тип размещения, а не сетка.

Михаиллл
  • 15 ноября 2018 г. 10:35

Спасибо. Похоже где то описку сделал, поэтому не работало.

Я добавил на verticalLayout много виджитов. А можно ли их как то быстро и просто удалить?


Evgenii Legotckoi
  • 16 ноября 2018 г. 1:48

пройтись циклом по всем виджетам в обратном порядке

for (int i = ui->vertialLayout->count() - 1; i >= 0; --i)
{
    QWidget* w = ui->verticalLyout->itemAt(i)->widget();
    ui->verticalLayout->removeWidget(w);
    w->deleteLater();
}


P.
  • 14 мая 2019 г. 18:33
  • (ред.)

Здравствуйте! А не подскажите, как можно при удалении какой либо кнопки, у щётчика отнять значение? Дабы например четвёртой кнопке соответствовал ID 4, а не 5 скажем

Evgenii Legotckoi
  • 18 мая 2019 г. 5:13

Добрый день!

Отнимать значение общего счётчика можно в деструкторе класса кнопки

QDynamicButton::~QDynamicButton()
{
    ResID--;
}

При этом я бы ещё переустанавливал значения всех ID у всех кнопкок, когда была удалена одна из кнопок, дял этого можно написать метод установки ID, который будет называть setButtonID

class QDynamicButton : public QPushButton
{
    Q_OBJECT
public:
    explicit QDynamicButton(QWidget *parent = 0);
    ~QDynamicButton();
    static int ResID;   // Статическая переменная, счетчик номеров кнопок
    int getID();        // Функция для возврата локального номера кнопки

    void setButtonID(int value);    // Установка id и текста

public slots:

private:
    int buttonID = 0;   // Локальная переменная, номер кнопки
};

Вот его релизация

void QDynamicButton::setButtonID(int value)
{
    buttonID = value;
    setText("Кнопочка " + QString::number(buttonID));
}

После чего модифицировать метод удаления кнопок следующим образом

void Widget::on_deleteButton_clicked()
{
    /* Выполняем перебор всех элементов слоя, где располагаются динамические кнопки
     * */
    for(int i = 0; i < ui->verticalLayout->count(); i++){
        /* Производим каст элемента слоя в объект динамической кнопки
         * */
        QDynamicButton *button = qobject_cast<QDynamicButton*>(ui->verticalLayout->itemAt(i)->widget());
        /* Если номер кнопки соответствует числу, которое установлено
         * в lineEdit, то производим удаление данной кнопки
         * */
        if(button->getID() == ui->lineEdit->text().toInt()){
            button->hide();
            delete button;
        }
    }

    for (int i = 0; i < ui->verticalLayout->count(); ++i)
    {
        QDynamicButton *button = qobject_cast<QDynamicButton*>(ui->verticalLayout->itemAt(i)->widget());
        button->setButtonID(i + 1);
    }
}

P.
  • 18 мая 2019 г. 10:03

Спасибо большое! Вскоре буду разбираться!

b
  • 29 марта 2022 г. 16:16
  • (ред.)

а можно посмотреть финальный проект? Что-то не пойму что я делаю не так

Ruslan Polupan
  • 11 мая 2022 г. 4:38

Добрый день! В какую сторону копать чтобы изменить на кнопке созданной динамически например Иконку основываяь на данных полученных из потока. Ну напрепер функция из потока возвращает true олна иконка, false - другая.

Нужно хранить указатели на кнопки или получать их, после чего работать с этим указателем, например задать иконку как то так: указательКнопки->setIcon(QIcon)

Ruslan Polupan
  • 11 мая 2022 г. 5:15

Хотелось бы по подробнее про сохранение.
Вот создаю QLabel в потоке проверяю есть с хостом связь или нет.
Срабатывает только на посдний созданный QLabel.
Т.е Основной поток только его идентифицирует.

for(uint i = 0;i<rowCount; ++i){

        QGroupBox *groupBoxPos = new QGroupBox("Pos ID "+QString::number(i+1),this);
        QHBoxLayout *grBoxLayout = new QHBoxLayout();
        labelStatus = new QLabel();
        grBoxLayout->addWidget(labelStatus);
        groupBoxPos->setLayout(grBoxLayout);
        ui->gridLayout->addWidget(groupBoxPos);


        TestVNCConnect *testVNC = new TestVNCConnect(connList.at(i).at(0), connList.at(i).at(1).toUInt());
        QThread *thread = new QThread();
        testVNC->moveToThread(thread);

        connect(thread,&QThread::started,this,&TestTemplateDialog::slotStartTestVNC);
        connect(thread,&QThread::started,testVNC,&TestVNCConnect::checkConnectionStatus);
        connect(testVNC,&TestVNCConnect::signalSendStatusConnect,this,&TestTemplateDialog::slotGetStatusVNC);
        connect(testVNC,&TestVNCConnect::finish,thread,&QThread::quit);
        connect(testVNC,&TestVNCConnect::finish,this,&TestTemplateDialog::slotFinishTestVNC);
        connect(testVNC,&TestVNCConnect::finish,testVNC,&TestVNCConnect::deleteLater);
        connect(thread,&QThread::finished,thread,&QThread::deleteLater);

        thread->start();
}

void TestTemplateDialog::slotStartTestVNC()
{
    qInfo(logInfo()) << "Show testing" << labelStatus->indent();
    labelStatus->setPixmap(QPixmap(":/Images/connect_creating_icon.png").scaled(QSize(32,32)));
}

void TestTemplateDialog::slotFinishTestVNC()
{
//    qInfo(logInfo()) << "Show result.";
    if(statusVNC){
        labelStatus->setPixmap(QPixmap(":/Images/connect_established_icon.png").scaled(QSize(32,32)));
    } else {
        labelStatus->setPixmap(QPixmap(":/Images/connect_no_icon.png").scaled(QSize(32,32)));
    }
}

void TestTemplateDialog::slotGetStatusVNC(bool status)
{
    statusVNC = status;
}
Михаил Сермяжко
  • 11 мая 2022 г. 5:18

Думаю упускаете важный момент, в Qt можно создавать виджеты и работать с ними только в главном потоке.

Ruslan Polupan
  • 11 мая 2022 г. 5:23

Я из потока получаю только true\false и на основе этого хочу в главном потоке установить ту или иную иконку.

Михаил Сермяжко
  • 11 мая 2022 г. 5:38

Тогда сделайте массив, запихните в него labelStatus, потом уже обращайтесьь к нему как к члену массива и меняйте как хотите

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь
e
  • ehot
  • 31 марта 2024 г. 14:29

C++ - Тест 003. Условия и циклы

  • Результат:78баллов,
  • Очки рейтинга2
B

C++ - Тест 002. Константы

  • Результат:16баллов,
  • Очки рейтинга-10
B

C++ - Тест 001. Первая программа и типы данных

  • Результат:46баллов,
  • Очки рейтинга-6
Последние комментарии
k
kmssr8 февраля 2024 г. 18:43
Qt Linux - Урок 001. Автозапуск Qt приложения под Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
АК
Анатолий Кононенко5 февраля 2024 г. 1:50
Qt WinAPI - Урок 007. Работаем с ICMP Ping в Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
EVA
EVA25 декабря 2023 г. 10:30
Boost - статическая линковка в CMake проекте под Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
J
JonnyJo25 декабря 2023 г. 8:38
Boost - статическая линковка в CMake проекте под Windows Сделал всё по-как у вас, но выдаёт ошибку [build] LINK : fatal error LNK1104: не удается открыть файл "libboost_locale-vc142-mt-gd-x64-1_74.lib" Хоть убей, не могу понять в чём дел…
G
Gvozdik18 декабря 2023 г. 21:01
Qt/C++ - Урок 056. Подключение библиотеки Boost в Qt для компиляторов MinGW и MSVC Для решения твой проблемы добавь в файл .pro строчку "LIBS += -lws2_32" она решит проблему , лично мне помогло.
Сейчас обсуждают на форуме
a
a_vlasov14 апреля 2024 г. 6:41
Мобильное приложение на C++Qt и бэкенд к нему на Django Rest Framework Евгений, добрый день! Такой вопрос. Верно ли следующее утверждение: Любое Android-приложение, написанное на Java/Kotlin чисто теоретически (пусть и с большими трудностями) можно написать и на C+…
Павел Дорофеев
Павел Дорофеев14 апреля 2024 г. 2:35
QTableWidget с 2 заголовками Вот тут есть кастомный QTableView с многорядностью проект поддерживается, обращайтесь
f
fastrex4 апреля 2024 г. 4:47
Вернуть старое поведение QComboBox, не менять индекс при resetModel Добрый день! У нас много проектов в которых используется QComboBox, в версии 5.5.1, когда модель испускает сигнал resetModel, currentIndex не менялся. В версии 5.15 при resetModel происходит try…
P
Pisych27 февраля 2023 г. 4:04
Как получить в массив значения из связанной модели? Спасибо, разобрался:))
AC
Alexandru Codreanu19 января 2024 г. 11:57
QML Обнулить значения SpinBox Доброго времени суток, не могу разобраться с обнулением значение SpinBox находящего в делегате. import QtQuickimport QtQuick.ControlsWindow { width: 640 height: 480 visible: tr…

Следите за нами в социальных сетях