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

QPushButton, Dynamic widget, Динамический виджет, 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 только тогда,
     * когда мы нажимаем одну из динамических кнопок, и этот номер соответствует
     * номеру нажатой кнопки
     * */
}

Итог

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

Виртуальный хостинг со скидкой 10 процентов
Виртуальный хостинг со скидкой 10 процентов
EVILEG предлагает надёжный хостинг со скидкой 10% на виртуальный хостинг и 5% на VPS
Поддержать автора Donate
KL
 QDynamicButton *button = qobject_cast<QDynamicButton*>(ui->verticalLayout->itemAt(i)->widget());
Поясните пожалуйста смысл этой строки.

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

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

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

F

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

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

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

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

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

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

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

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

H

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

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, т.е. получается, лист чекбоксов в лист виджетов я все-таки поместить не могу.

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

Надо так

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

H

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

  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. ...

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

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

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

H

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

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

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

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

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

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


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

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

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

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




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

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

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

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


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

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

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

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

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

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

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

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


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

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


P.

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

Добрый день!

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

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.

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

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь
m
19 мая 2019 г. 1:49
mahhaki

Qt - Тест 001. Сигналы и слоты

  • Результат:78баллов,
  • Очки рейтинга2
S
17 мая 2019 г. 13:14
SunBro

Qt - Тест 001. Сигналы и слоты

  • Результат:42баллов,
  • Очки рейтинга-8
b
17 мая 2019 г. 4:18
banana

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

  • Результат:57баллов,
  • Очки рейтинга-2
Последние комментарии
P.
18 мая 2019 г. 14:03
PELMYACH .

Спасибо большое! Вскоре буду разбираться!
18 мая 2019 г. 9:13
Евгений Легоцкой

Добрый день! Отнимать значение общего счётчика можно в деструкторе класса кнопки QDynamicButton::~QDynamicButton(){ ResID--;} При этом я бы ещё переустанавливал значения вс...
P.
14 мая 2019 г. 22:33
PELMYACH .

Здравствуйте!А не подскажите, как можно при удалении какой либо кнопки, у щётчика отнять значение?Дабы например четвёртой кнопке соответствовал ID 4, а не 5 скажем
6 мая 2019 г. 6:39
Евгений Легоцкой

Добрый день. Этот урок для Qt Quick Control версии 1, Вы используете вторую версию. Здесь style уже нет, кастомизацию можно делать уже или черещ соответствующие property или через ...
U
4 мая 2019 г. 3:14
Unreal_man

Делаю вроде правильно, а ничего не получается. Что упустил? После button1. в выпадающем списке нет style.Да, и откуда в уроке взялся файл .pri и зачем он нужен?
Сейчас обсуждают на форуме
19 мая 2019 г. 12:45
Михаиллл

Скачал openssl-1.1.1 от сюда , но не понимаю что делать с этой папкой
19 мая 2019 г. 10:52
Евгений Легоцкой

Если честно, то мне нужно самому время потратить, чтобы глянуть это дело. Я использовал Flutter для разработки, а не Qt. Просто исходя из опыта, могу сказать, что по большей части всё на эмуля...
16 мая 2019 г. 23:08
BlinCT

Решил через indexOf сделать. Возвращает или номер позиции где нашел символ или строку или -1 если не найдено.
15 мая 2019 г. 15:06
Михаиллл

Спасибо , заработало.Получаю ответный сигнал.Но теоретически, в ответ на запрос должен прийти json файл. Скажите пожалуйста, как можно открыть ответные данные, прочитать их, и потом удалить...
14 мая 2019 г. 11:07
Евгений Легоцкой

Из той задачи, которую вы привели, у вас есть база данных и таблица в ней с текстами. Для представления данных из базы данных обычно используют QTableView, а text browser здесь не к мест...

Для зарегистрированных пользователей на сайте присутствует минимальное количество рекламы

EVILEG
О нас
Услуги
Присоединяйтесь к нам
© EVILEG 2015-2019
Рекомендует хостинг TIMEWEB