Evgenii Legotckoi
Evgenii Legotckoi01 вересня 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 р. 02:12

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

Это не имеет зависимости на платформу
H
  • 01 листопада 2018 р. 04:23

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

Evgenii Legotckoi
  • 01 листопада 2018 р. 04:28

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

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

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

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

H
  • 02 листопада 2018 р. 06: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
  • 02 листопада 2018 р. 06:23

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

Надо так

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

H
  • 02 листопада 2018 р. 06: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
  • 02 листопада 2018 р. 06: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
  • 02 листопада 2018 р. 06:45

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

Михаиллл
  • 15 листопада 2018 р. 07:54

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

Evgenii Legotckoi
  • 15 листопада 2018 р. 08:07

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

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

Михаиллл
  • 15 листопада 2018 р. 08:30

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

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


Evgenii Legotckoi
  • 15 листопада 2018 р. 08:34
но у VerticalSpacer нет метода indexOf.

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

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

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




Михаиллл
  • 15 листопада 2018 р. 08:55

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

Evgenii Legotckoi
  • 15 листопада 2018 р. 08:58

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

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

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


Михаиллл
  • 15 листопада 2018 р. 09:45

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

Evgenii Legotckoi
  • 15 листопада 2018 р. 09: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 р. 01: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 р. 05: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 р. 04:38

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

Михаил Сермяжко
  • 11 травня 2022 р. 04:57

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

Ruslan Polupan
  • 11 травня 2022 р. 05: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 р. 05:18

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

Ruslan Polupan
  • 11 травня 2022 р. 05:23

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

Михаил Сермяжко
  • 11 травня 2022 р. 05:38

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

Коментарі

Only authorized users can post comments.
Please, Log in or Sign up
e
  • ehot
  • 31 березня 2024 р. 21:29

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

  • Результат:78бали,
  • Рейтинг балів2
B
  • Bogdannn
  • 28 березня 2024 р. 02:21

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

  • Результат:16бали,
  • Рейтинг балів-10
B
  • Bogdannn
  • 28 березня 2024 р. 02:15

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

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

Слідкуйте за нами в соціальних мережах