У складних проектах може бути недостатньо наявності статичних віджетів в інтерфейсі програми, оскільки інформація, що надходить, може змінюватися щомиті. Відтак порушується питання про динамічне створення віджетів, наприклад кнопок у компонувальнику 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 только тогда, * когда мы нажимаем одну из динамических кнопок, и этот номер соответствует * номеру нажатой кнопки * */ }
Підсумок
В результаті у Вас має вийти програма, яка здійснює динамічне створення та видалення кнопок. Робота програми показана у відео, наведеному нижче.
В Qt элементы интерфейса имеют базовый класс QWidget . QDynamycButton наследован от QPushButton , который в свою очередь наследован в конечном итоге от QWidget . Из Layout`а указатели на объекты забираются в качестве указателей на объекты класса QWidget .
Таким образом, чтобы получить доступ к методам класса QDynamicButton , объектами которого являются в данном случае объекты, которые находятся в данном Layout`е, необходимо скастовать указатель из класса QWidget в QDynamicButton
Такие возможности языка C++ достигаются за счёт парадигмы полиморфизма. Пример полиморфизма с некоторыми пояснениями дан в этой статье .
Хм, не знаю как у других, а у меня отсутствует метод ui->splitter . Может быть потому что я на линухе сижу
компонент был создан в графическом дизайнере, о чём говорит название объекта ui , в котором находится объект splitter.
А если я неким неизвестным образом добавил разношерстные элементы в ui: checkbox,radiobutton,textedit и label. Они лежат группами одинаковых (одного типа) объектах в vbox, лежащий в groupbox, который в свою очередь находится в vertical layout. Как бы теперь двойным циклом пройтись по каждому из этих объектов в каждом из этих groupbox?
Можно поискать нужные элементы через метод findChildren
или выбрать все групбоксы, а потом каждом использовать метод findChildren.
Метод шаблонный, поэтому можно попытаться найти например только QLineEdit, вместо QWidget, поскольку QWidget - это базовый класс для QLineEdit и остальных виджетов.
Т.е. я могу сделать что-то вроде такого?
Когда я пробую такой вариант, происходит ошибка:candidate function not viable: no known conversion from 'QList<QCheckBox *>' to 'const QList<QWidget *>' for 1st argument, т.е. получается, лист чекбоксов в лист виджетов я все-таки поместить не могу.
Вы с ошибкой написали
Надо так
Не, я в смысле думал, что могу использовать родительский класс для этого, т.е. я хотел бы сделать что-то вроде:
То, что вы так хотели сделать, не значит что это работает в С++. Вы не можете присвоить объект QList<QCheckBox*> к объекту QList<QWidget*> - это разные сущности контейнеров, хотя через методы append вы сможете добавить в контейнер другие классы, у которых родительский класс бцдет QWidget, но в данном случае не будет происходит копирование одного контейнера в объект другого.
И вообще, что вам мешает сделать так?
Ну да, сейчас я уже буду делать тогда так, просто придется создавать листы под каждый тип виджетов. Спасибо!
Скажите пожалуйста, как в VerticalLayout добавить VerticalSpaser так, чтобы при этом кнопки смещались вверх, а не в низ. Если это сделать в дизайнере, кнопки смещаются вниз, т.к. они добавляются после.
используйте метод insertWidget. В нём можно указать индекс, куда добалвять кнопку. Нужно будет найти индекс VerticalSpacer с помощью метода indexOf(verticalSpacer)
и на этот индекс добавлять кнопку.
но у VerticalSpacer нет метода indexOf.
insertWidget использовать вместо addWidget?
У него нет, а вот у verticalLayout есть. Если вы добавите через дизайнер лейаут и спейсер, то можете забрать индекс спейсера так
Да, использовать insertWidget
у меня выдает ошибку D:\QTProject\ReaderResume\mainwindow.cpp:811: ошибка: cannot initialize a parameter of type 'QWidget *' with an lvalue of type 'QSpacerItem *'
А вот про это я забыл... Он же не наследован от QWidget.
Тогда, если вы знаете, что спецсер всегда в конце, то тогда так можете это сделать.
но у verticalLayout нет метода insertWidget
verticalLayout - это, по-моему предположению, должен быть у вас объект класса QVBoxLayout, который наследован от QBoxLayout.
Поэтому открываете документацию на QVBoxLayout .
И ищете там строку List of all members, including inherited members это сразу под краткой анотацией класса. Кликаете по этой надписи и переходите на страницу с полным списком всех методов класса QVBoxLayout.
После этого нужно воспользоваться поиском по странице Ctrl+F, чтобы найти insertWidget. И вы увидите, что там есть этот метод.
Если вы всё-таки использовали QGridLayout, то измените класс QVBoxLayout, если вам нужен имен просто вертикальный тип размещения, а не сетка.
Спасибо. Похоже где то описку сделал, поэтому не работало.
Я добавил на verticalLayout много виджитов. А можно ли их как то быстро и просто удалить?
пройтись циклом по всем виджетам в обратном порядке
Здравствуйте! А не подскажите, как можно при удалении какой либо кнопки, у щётчика отнять значение? Дабы например четвёртой кнопке соответствовал ID 4, а не 5 скажем
Добрый день!
Отнимать значение общего счётчика можно в деструкторе класса кнопки
При этом я бы ещё переустанавливал значения всех ID у всех кнопкок, когда была удалена одна из кнопок, дял этого можно написать метод установки ID, который будет называть setButtonID
Вот его релизация
После чего модифицировать метод удаления кнопок следующим образом
Спасибо большое! Вскоре буду разбираться!
а можно посмотреть финальный проект? Что-то не пойму что я делаю не так
Добрый день! В какую сторону копать чтобы изменить на кнопке созданной динамически например Иконку основываяь на данных полученных из потока. Ну напрепер функция из потока возвращает true олна иконка, false - другая.
Нужно хранить указатели на кнопки или получать их, после чего работать с этим указателем, например задать иконку как то так: указательКнопки->setIcon(QIcon)
Хотелось бы по подробнее про сохранение.
Вот создаю QLabel в потоке проверяю есть с хостом связь или нет.
Срабатывает только на посдний созданный QLabel.
Т.е Основной поток только его идентифицирует.
Думаю упускаете важный момент, в Qt можно создавать виджеты и работать с ними только в главном потоке.
Я из потока получаю только true\false и на основе этого хочу в главном потоке установить ту или иную иконку.
Тогда сделайте массив, запихните в него labelStatus, потом уже обращайтесьь к нему как к члену массива и меняйте как хотите