Evgenii Legotckoi
Evgenii LegotckoiSept. 1, 2015, 11 a.m.

Qt/C++ - Lesson 014. Dynamic creation of widgets in Qt

In complex projects may not be enough to have a static widget in the interface as the incoming information can change every second. Therefore the question on creation of dynamic widgets, such as the layout Qt buttons.

This tutorial describes how to dynamically create QPushButton buttons, receiving signals from these buttons, and the subsequent removal of these buttons from the layout Qt.

Project Structure

Description of the Project:

  • DynamicButtons.pro - profile;
  • mainwindow.h - header file of the main application window;
  • mainwindow.cpp - source of window;
  • main.cpp - the main source file from which the application starts;
  • mainwindow.ui - form of the main application window;
  • qdynamicbutton.h - header file of the wrapper class, which simplifies the process of working with dynamic objects in this lesson;
  • qdynamicbutton.cpp - source file wrapper class that simplifies the process of working with dynamic objects in this lesson.

mainwindow.ui

In this tutorial, we will need two layers layout. One will be all dynamically created buttons, and the second layer will be a button that will be responsible for the creation and deletion of dynamic buttons, as well as field lineEdit to display the number of buttons created.

Direct work will be carried out with the following entities form the application window:

  • addButton - button to add dynamic buttons;
  • deleteButton - button to remove the dynamic buttons;
  • verticalLayout - layer to add dynamic buttons with vertical layout;
  • lineEdit - field to display the number of the created buttons.

qdynamicbutton.h

Header file wrapper class that inherits from QPushButton class. In this class, we declare a static variable, which will be common to all objects of the class and will be counter all dynamic buttons that are created in this application. It is necessary for an adequate numbering each button.

#ifndef QDYNAMICBUTTON_H
#define QDYNAMICBUTTON_H

#include <QPushButton>

class QDynamicButton : public QPushButton
{
    Q_OBJECT
public:
    explicit QDynamicButton(QWidget *parent = 0);
    ~QDynamicButton();
    static int ResID;   // A static variable counter buttons rooms
    int getID();        // Function to return a local number buttons


public slots:

private:
    int buttonID = 0;   // Local variable number of the button
};

#endif // QDYNAMICBUTTON_H

qdynamicbutton.cpp

The file source code wrapper class initialization key in its constructor to initialize static variable, and there is a method to return a dynamic number buttons.

#include "qdynamicbutton.h"

QDynamicButton::QDynamicButton(QWidget *parent) :
    QPushButton(parent)
{
    ResID++;            // Increment of counter by one
    buttonID = ResID;   /* Assigning a button number which will be further work with buttons
                         * */

}

QDynamicButton::~QDynamicButton()
{

}

/* Method to return the value of the button numbers
 * */
int QDynamicButton::getID()
{
    return buttonID;
}

/* Initialize static class variable. 
 * Static class variable must be initialized without fail
 * */
int QDynamicButton::ResID = 0;

mainwindow.h

In the header you want to add only a slot for processing pressing the control buttons and a slot for dynamic numbers down buttons.

#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();    // SLOT-handler pressing add button
    void on_deleteButton_clicked(); // SLOT-handler pressing the delete button
    void slotGetNumber();           // SLOT for getting number of the dynamic buttons

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);

    /* For convenience, the layers are separated QSplitter
     * */
    ui->splitter->setStretchFactor(0,1);
    ui->splitter->setStretchFactor(1,0);
}

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

/* The method for adding dynamic buttons
 * */
void MainWindow::on_addButton_clicked()
{
    QDynamicButton *button = new QDynamicButton(this);  // Create a dynamic button object
    /* Set the text with number of button
     * */
    button->setText("Кнопочка " + QString::number(button->getID()));
    /* Adding a button to the bed with a vertical layout
     * */
    ui->verticalLayout->addWidget(button);
    /* Connect the signal to the slot pressing buttons produce numbers
     * */
    connect(button, SIGNAL(clicked()), this, SLOT(slotGetNumber()));
}

/* Method for the removal of the dynamic on its number buttons
 * */
void MainWindow::on_deleteButton_clicked()
{
    /* Iterates through all elements of the layer, where are dynamic buttons
     * */
    for(int i = 0; i < ui->verticalLayout->count(); i++){
        /* We produce cast layer element in the dynamic button object
         * */
        QDynamicButton *button = qobject_cast<QDynamicButton*>(ui->verticalLayout->itemAt(i)->widget());
        /* If the button number corresponds to the number that is set to lineEdit, 
         * then deletes the button
         * */
        if(button->getID() == ui->lineEdit->text().toInt()){
            button->hide();
            delete button;
        }
    }
}

/* SLOT for getting number of buttons.
 * */
void MainWindow::slotGetNumber()
{
    /* To determine the object that caused the signal
     * */
    QDynamicButton *button = (QDynamicButton*) sender();
    /* Then set the number of buttons in lineEdit, 
     * which is contained in the dynamic button
     * */
    ui->lineEdit->setText(QString::number(button->getID()));
    /* That is the key number is set to lineEdit field only 
     * when we press one of the dynamic keys, 
     * and this number corresponds to the button you pressed
     * */
}

Result

As a result, you should have an application that performs dynamic creation and deletion of keys. The work of applications shown in the video below.

We recommend hosting TIMEWEB
We recommend hosting TIMEWEB
Stable hosting, on which the social network EVILEG is located. For projects on Django we recommend VDS hosting.

Do you like it? Share on social networks!

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

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

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

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

F
  • May 23, 2018, 8:36 p.m.

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

Evgenii Legotckoi
  • May 24, 2018, 2:12 a.m.

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

Это не имеет зависимости на платформу
H
  • Nov. 1, 2018, 4:23 a.m.

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

Evgenii Legotckoi
  • Nov. 1, 2018, 4:28 a.m.

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

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

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

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

H
  • Nov. 2, 2018, 6:03 a.m.

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

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
  • Nov. 2, 2018, 6:23 a.m.

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

Надо так

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

H
  • Nov. 2, 2018, 6:27 a.m.

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

  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
  • Nov. 2, 2018, 6:42 a.m.
  • (edited)

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

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

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

H
  • Nov. 2, 2018, 6:45 a.m.

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

Михаиллл
  • Nov. 15, 2018, 7:54 a.m.

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

Evgenii Legotckoi
  • Nov. 15, 2018, 8:07 a.m.

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

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

Михаиллл
  • Nov. 15, 2018, 8:30 a.m.

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

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


Evgenii Legotckoi
  • Nov. 15, 2018, 8:34 a.m.
но у VerticalSpacer нет метода indexOf.

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

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

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




Михаиллл
  • Nov. 15, 2018, 8:55 a.m.

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

Evgenii Legotckoi
  • Nov. 15, 2018, 8:58 a.m.

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

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

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


Михаиллл
  • Nov. 15, 2018, 9:45 a.m.

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

Evgenii Legotckoi
  • Nov. 15, 2018, 9:55 a.m.

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

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

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

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

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

Михаиллл
  • Nov. 15, 2018, 10:35 a.m.

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

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


Evgenii Legotckoi
  • Nov. 16, 2018, 1:48 a.m.

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

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


P.
  • May 14, 2019, 6:33 p.m.
  • (edited)

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

Evgenii Legotckoi
  • May 18, 2019, 5:13 a.m.

Добрый день!

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

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.
  • May 18, 2019, 10:03 a.m.

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

b
  • March 29, 2022, 4:16 p.m.
  • (edited)

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

Ruslan Polupan
  • May 11, 2022, 4:38 a.m.

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

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

Ruslan Polupan
  • May 11, 2022, 5:15 a.m.

Хотелось бы по подробнее про сохранение.
Вот создаю 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;
}
Михаил Сермяжко
  • May 11, 2022, 5:18 a.m.

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

Ruslan Polupan
  • May 11, 2022, 5:23 a.m.

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

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

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

Comments

Only authorized users can post comments.
Please, Log in or Sign up
OI

C++ - Test 001. The first program and data types

  • Result:40points,
  • Rating points-8
AD

C ++ - Test 004. Pointers, Arrays and Loops

  • Result:50points,
  • Rating points-4
m

C ++ - Test 004. Pointers, Arrays and Loops

  • Result:80points,
  • Rating points4
Last comments
ИМ
Игорь МаксимовNov. 22, 2024, 10:51 p.m.
Django - Tutorial 017. Customize the login page to Django Добрый вечер Евгений! Я сделал себе авторизацию аналогичную вашей, все работает, кроме возврата к предидущей странице. Редеректит всегда на главную, хотя в логах сервера вижу запросы на правильн…
Evgenii Legotckoi
Evgenii LegotckoiNov. 1, 2024, 12:37 a.m.
Django - Lesson 064. How to write a Python Markdown extension Добрый день. Да, можно. Либо через такие же плагины, либо с постобработкой через python библиотеку Beautiful Soup
A
ALO1ZEOct. 19, 2024, 6:19 p.m.
Fb3 file reader on Qt Creator Подскажите как это запустить? Я не шарю в программировании и кодинге. Скачал и установаил Qt, но куча ошибок выдается и не запустить. А очень надо fb3 переконвертировать в html
ИМ
Игорь МаксимовOct. 5, 2024, 5:51 p.m.
Django - Lesson 064. How to write a Python Markdown extension Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
d
dblas5July 5, 2024, 9:02 p.m.
QML - Lesson 016. SQLite database and the working with it in QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
Now discuss on the forum
Evgenii Legotckoi
Evgenii LegotckoiJune 25, 2024, 1:11 a.m.
добавить qlineseries в функции Я тут. Работы оень много. Отправил его в бан.
t
tonypeachey1Nov. 15, 2024, 5:04 p.m.
google domain [url=https://google.com/]domain[/url] domain [http://www.example.com link title]
NSProject
NSProjectJune 4, 2022, 1:49 p.m.
Всё ещё разбираюсь с кешем. В следствии прочтения данной статьи. Я принял для себя решение сделать кеширование свойств менеджера модели LikeDislike. И так как установка evileg_core для меня не была возможна, ибо он писался…
9
9AnonimOct. 25, 2024, 7:10 p.m.
Машина тьюринга // Начальное состояние 0 0, ,<,1 // Переход в состояние 1 при пустом символе 0,0,>,0 // Остаемся в состоянии 0, двигаясь вправо при встрече 0 0,1,>…

Follow us in social networks