QT - connecting QPushButtons with QCheckBoxes - c++

I am new to QT. I started creating a TODO app and I want to somehow connect my PushButtons that are placed in vector with CheckBoxes that are also placed in a different vector.
std::vector <QPushButton*> buttons;
std::vector <QCheckBox*> checks;
I thought that the best way to do that will be to make a for loop connecting every element of mentioned vectors
Something like:
for(int i=0; i<buttons.size(); ++i){
connect(buttons[i], SIGNAL(???), checks[i], SLOT(???));
}
But idea is the only thing that I have. I tried putting different things into SIGNAL() and SLOT() but none of them worked. By "none of them worked" I mean the fact that when button is clicked nothing happens. Program is normally compiled without any error.

What about just clicked(bool) for SIGNAL and toggle() for SLOT?
Something like that:
connect(pushButton, SIGNAL(clicked(bool)), checkBox, SLOT(toggle()));

Works for me - and you can store the widgets directly in a std::list: that avoids the need to mess with manual memory management. Let the libraries do it for you.
#include <QtWidgets>
#include <list>
int main(int argc, char **argv) {
QApplication app{argc, argv};
QWidget win;
QGridLayout layout{&win};
std::list<QPushButton> buttons;
std::list<QCheckBox> checkboxes;
QPushButton addButton{"Add"};
layout.addWidget(&addButton, 0, 0, 2, 1);
auto const clicked = &QAbstractButton::clicked;
auto const toggle = &QAbstractButton::toggle;
auto const add = [&,clicked,toggle]{
int const col = layout.columnCount();
auto const text = QString::number(col);
auto *button = &(buttons.emplace_back(text), buttons.back()); //C++11, not 14
auto *checkbox = &(checkboxes.emplace_back(text), checkboxes.back());
layout.addWidget(button, 0, col);
layout.addWidget(checkbox, 1, col);
QObject::connect(button, clicked, checkbox, toggle);
};
add();
QObject::connect(&addButton, clicked, add);
win.show();
return app.exec();
}

With Qt-5 you can now use lambda functions as slots (see connect version 5)
You can also do away with the need for the SIGNAL macro, and instead use member function pointers.
QObject::connect(buttons[i], &QPushButton::clicked, [=]
{
// toggle the check state
checks[i]->setChecked(!checks[i]->isChecked());
});
The first two parameters are a pointer to an object, and a member function pointer
buttons[i] is of type QPushButton*
&QPushButton::clicked is a member function pointer of the signal you want to connect to
The second parameter is a C++11 lambda, which captures checked and i by value, and then sets the QCheckBox checked state to the inverse of its previous value

Related

Not able to connect signals and slots

I wrote a basic C++ program for simulating the signal and slot process. I made a push button "button", a QVBoxLayout "layout". I added the button in the layout. Everything is fine till now. But when push button is made to connect by signal and slot, there's are two problem/errors.
"no instance of overloaded function "QObject::connect matches the argument list"."
'QObject::connect': none of the 3 overloads could convert all the argument types.
Question:
I am sure that there's something missing, which I am not able to decipher. Here, the push button "button" has to invoke the function "connectFunc". Instead it gives me above two errors. How to make the button invoke the function?
This is my code.
#include "signalsslots.h"
#include <QtWidgets/QApplication>
#include<qpushbutton.h>
#include<qboxlayout.h>
#include<iostream>
using std::cout;
using std::endl;
void connectFunc()
{
cout << endl << "connected " << endl;
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QPushButton* button = new QPushButton("Press Here");
QAction* bAction = new QAction;
button->addAction(bAction);
QVBoxLayout* layout = new QVBoxLayout;
layout->addWidget(button);
//layout->SetFixedSize(200, 150);
QObject::connect(bAction, SIGNAL(QPushButton::clicked()), &a,(connectFunc()));
QWidget w;
w.setLayout(layout);
w.setWindowTitle("Signal and Slot Example !!!");
w.show();
return a.exec();
}
FYI, I am using VS 2019 for writing Qt widget applications.
The QObject::connect() function that you use, connects a signal (i.e. a method) of an object to a slot (i.e. a method) of another object.
What you are telling QT is to connect the clicked() method of your QAction object, to the connectFunc() of your QApplication object. Both methods do not exist on those objects!
What you probably want is to connect the clicked() method of your QButton button to the free function connectFunc(). Like this:
QObject::connect(button, &QPushButton::clicked, &connectFunc);
Note that this requires QT5. (See Is it possible to connect a signal to a static slot without a receiver instance?)

Do you need to delete widget after removeItemWidget from QTreeWidget?

I have a QTreeWidget with two columns: one for property name and one for property value. The value can be edited via a widget. For example one property is Animal. When you double click the property value column I make a (custom) combobox with different animal types via this code:
QTreeWidgetItemComboBox* comboBox = new QTreeWidgetItemComboBox(treeItem, 1);
// treeitem is a pointer to the row that is double clicked
comboBox->addItems(QStringList() << "Bird" << "Fish" << "Ape");
ui.treeWidget->setItemWidget(treeItem, 1, comboBox);
When the row loses focus I remove the widget again (and the value is put as text of the QTreeWidgetItem). For removing I use
ui.treeWidget->removeItemWidget(treeItem, 1);
Now I'm wondering, since I've used new, do I neww to also delete the widget. I know this is the case if you use takeChild(i) for example. But I didn't see something similar for an itemWidget.
Do I need to delete it what would be the right order?
QTreeWidgetItemComboBox* comboBox = ui.treeWidget->itemWidget(treeItem,1);
// Do I need a cast here since the return type is QWidget*
ui.treeWidget->removeItemWidget(treeItem, 1);
delete comboBox;
or
QTreeWidgetItemComboBox* comboBox = ui.treeWidget->itemWidget(treeItem,1);
// Do I need a cast here since the return type is QWidget*
delete comboBox;
ui.treeWidget->removeItemWidget(treeItem, 1);
When the widget is added ot the QTreeWidget, it indeed takes ownership of the widget. But it only implies that the widget will be deleted when the parent is destroyed.
So if you just want to remove the widget while keeping the parent QTreeWidget alive, you indeed have to delete it manually.
The correct solution is the first one, remove the widget from the QTreeWidget first, and then delete it with one of the following ways:
delete comboBox;
comboBox = nullptr;
or:
comboBox.deleteLater();
The second one is preferred.
EDIT:
I don't change the answer since it could be a dishonest to change what was already accepted, ...
But as #Scopchanov mentioned, by reading the source code, the QTreeWidget::removeItemWidget() already calls the deleteLater() method on the old widget. We don't have to do it manually.
Anyway, the documentation says it is safe to call deleteLater() more than once:
Note: It is safe to call this function more than once; when the first deferred deletion event is delivered, any pending events for the object are removed from the event queue.
Therefore, manually deleting the widget after calling QTreeWidget::removeItemWidget() becomes useless.
You are not allowed to delete the item widget as the tree is the owner of the widget once it has been passed to the tree with setItemWidget().
From the documentation of setItemWidget():
Note: The tree takes ownership of the widget.
EDIT: In case you want a new widget, simply call setItemWidget() once more or call removeItemWidget() in case you do not need the widget anymore. The tree will ensure that no memory gets lost.
Explaination
You should not manually delete a widget, added to a QTreeWidget, since it is automatically deleted either by
destructing its parent tree widget
This is a direct consequence of the Qt's parent-child mechanism.
calling QTreeWidget::removeItemWidget anytime the tree widget still lives.
This one is not so obvious, since the documentation simply sais:
Removes the widget set in the given item in the given column.
However, looking at the source code it becomes pretty clear what is indeed happening, i.e.
QTreeWidget::removeItemWidget calls QTreeWidget::setItemWidget with a null pointer (no widget)
inline void QTreeWidget::removeItemWidget(QTreeWidgetItem *item, int column)
{ setItemWidget(item, column, nullptr); }
QTreeWidget::setItemWidget in turn calls QAbstractItemView::setIndexWidget
void QTreeWidget::setItemWidget(QTreeWidgetItem *item, int column, QWidget *widget)
{
Q_D(QTreeWidget);
QAbstractItemView::setIndexWidget(d->index(item, column), widget);
}
Finally QAbstractItemView::setIndexWidget checks if there is already a widget at this index, and if there is one, calls its deleteLater method
if (QWidget *oldWidget = indexWidget(index)) {
d->persistent.remove(oldWidget);
d->removeEditor(oldWidget);
oldWidget->removeEventFilter(this);
oldWidget->deleteLater();
}
Simply put (and this should be made clear in the documentation of both methods of QTreeWidget), any call to QTreeWidget::setItemWidget or QTreeWidget::removeItemWidget deletes the widget (if any) already set for the item.
Example
Here is a simple example I have prepared for you in order to demonstrate the described behaviour:
#include <QApplication>
#include <QBoxLayout>
#include <QTreeWidget>
#include <QComboBox>
#include <QPushButton>
struct MainWindow : public QWidget
{
MainWindow(QWidget *parent = nullptr) : QWidget(parent) {
auto *l = new QVBoxLayout(this);
auto *treeWidget = new QTreeWidget(this);
auto *item = new QTreeWidgetItem(treeWidget);
auto *button = new QPushButton(tr("Remove combo box"), this);
auto *comboBox = new QComboBox();
comboBox->addItems(QStringList() << "Bird" << "Fish" << "Ape");
treeWidget->setItemWidget(item, 0, comboBox);
l->addWidget(button);
l->addWidget(treeWidget);
connect(comboBox, &QComboBox::destroyed, [](){
qDebug("The combo box is gone.");
});
connect(button, &QPushButton::clicked, [treeWidget, item](){
treeWidget->removeItemWidget(item, 0);
});
resize(400, 300);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
Result
The described ways of destroyng the widget could be tested with the application
Simply closing the window destroys the tree widget together with its child combo box, hence the combo box's destroyed signal is emitted and the lambda prints
The combo box is gone.
After pressing the button the lambda function connected to its clicked signal is called, which removes the combo box from the tree widget. Because the combo box is deleted (automatically) as well, the lambda from the second connect statement is called, which also prints
The combo box is gone.

How do we pass the value to all the elements of GUI in this QT code

I'm reading a book on QT4 and here's an example from the book:
QApplication a(argc, argv);
QWidget window;
QVBoxLayout* mainLayout = new QVBoxLayout(&window);
QLabel* label = new QLabel("0");
QSpinBox* spinBox = new QSpinBox;
QSlider* slider = new QSlider(Qt::Horizontal);
mainLayout->addWidget(label);
mainLayout->addWidget(spinBox);
mainLayout->addWidget(slider);
QObject::connect(spinBox, SIGNAL(valueChanged(int)), label, SLOT(setNum(int)));
QObject::connect(spinBox, SIGNAL(valueChanged(int)), slider, SLOT(setValue(int)));
QObject::connect(slider, SIGNAL(valueChanged(int)), label, SLOT(setNum(int)));
QObject::connect(slider, SIGNAL(valueChanged(int)), spinBox, SLOT(setValue(int)));
window.show();
As the book and compilation show, changing the value of one of the widgets leads to changing the values of the other elements.
What I don't understand is how it happens. How do we pass that value from one widget to the rest? There's no variable that gets changed by means of one widget and gets passed to the others.
PS Conceptually, I do understand the idea of slots and signals and 'connect'. It's how the value is passed from one widget to the rest is the problem.
A signal is a C++ method with code generated by a utility called moc (meta object compiler). A slot is regular C++ method, with code under your control. A connection is a way of letting the signal know what slots to call. When the signal gets emitted, it really means that you call the machine-generated method that iterates the connection list.
Conceptually, the valueChanged signal implementation looks like this:
void valueChanged(int value) {
for (slot : this->slots)
(slot.object->*slot.method)(value);
}
Thus, when the slider "emits" its signal, it calls each slot with a given value. After the connections are made, you should think of the spinbox's valueChanged signal as doing the following:
void SpinBox::valueChanged(int value) {
// 1st connection
label->setNum(value);
// 2nd connection
slider->setVale(value);
}
There's no "variable" that gets changed because the signal-slot mechanism is, at its core, an easier-to-use way of doing indirect method calls (via method pointers and instance pointers).
In modern code (Qt5/C++11), that example would be (this is complete code):
#include <QtWidgets>
int main(int argc, char** argv) {
QApplication a{argc, argv};
QWidget window;
QVBoxLayout mainLayout{&window};
QLabel label{"0"};
QSpinBox spinBox;
QSlider slider{Qt::Horizontal};
mainLayout.addWidget(&label);
mainLayout.addWidget(&spinBox);
mainLayout.addWidget(&slider);
QObject::connect(&spinBox, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged),
&label, static_cast<void(QLabel::*)(int)>(&QLabel::setNum));
QObject::connect(&spinBox, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged),
&slider, static_cast<void(QSlider::*)(int)>(&QSlider::setValue));
QObject::connect(&slider, static_cast<void(QSlider::*)(int)>(&QSlider::valueChanged),
&label, static_cast<void(QLabel::*)(int)>(&QLabel::setNum));
QObject::connect(&slider, &QSlider::valueChanged, &spinBox, &QSpinBox::setValue);
window.show();
return a.exec();
}

Qt C++ GUI QSpinBox Storing Input?

How would I take the user input from the a spinbox and use that as a value? In other words if I wanted to store the input from the QSpinBox into a variable how would I go about doing this. Im really new at Qt GUI so any input would be greatly appreciated.
To react to GUI elements in Qt, you connect to the signals that those elements give off. Also if you have a pointer to the instance of it, you can query and change its states and properties.
Here is a quick example of what you are looking for
#include <QApplication>
#include <QVBoxLayout>
#include <QLabel>
#include <QSpinBox>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// The widget, contains a layout
QWidget * w;
w = new QWidget;
// The layout arranges and holds
// all the children of the widget
QVBoxLayout * vbox;
vbox = new QVBoxLayout;
// The user input element, the spinbox!
QSpinBox * spinbox;
spinbox = new QSpinBox();
spinbox->setValue(5);// example of using a pointer to edit its states
// now add it to the layout
vbox->addWidget(spinbox);
// add in an element to connect to,
// the infamous QLabel
QLabel * label;
label = new QLabel("spinbox output");
// add it also to the layout
vbox->addWidget(label);
// connection can happen anytime as long as the two elements
// are not null!
// This connection takes advantage of signals and slots
// and making their connection at runtime.
// if a connect call fails you should be able to see why in the
// application output.
QObject::connect(spinbox, SIGNAL(valueChanged(QString)),
label, SLOT(setText(QString)));
// associate the layout to the widget
w->setLayout(vbox);
// make the widget appear!
w->show();
return a.exec();
}
I usually put most of the initializing and connecting of GUI elements into the constructor or a method of the main QWidget or the QMainWindow. I often take the signal from a GUI input element, like a spinbox and I connect it to a custom slot defined on my subclassed QWidget. Then if I want to display it with the input value differently or increase the output by 2, I can do so easily.
// in the constructor of my Widget class
// after spinbox has been initialized
QObject(m_spinbox, SIGNAL(valueChanged(int)),
this, SLOT(on_spinboxValueChanged(int)));
void Widget::on_spinboxValueChanged(int i)
{
// here m_label is a member variable of the Widget class
m_label->setText(QString::number(i + 2));
// if accessing the value in this function is inconvenient, you can always
// use a member variable pointer to it to get its stored value.
// for example:
int j = m_spinbox->value();
qDebug() << "Spinbox value" << j;
}
Identical things can be done in QML and Qt Quick, and for many people it is easier and more intuitive because of how close it is to javascript and css.
Also Qt Creator has a tool for generating forms, and it provides another way to create your widgets with layouts and then when you access your elements you do it through a ui variable.
Also the Qt docs and examples are awesome. Taking time to learn them and read up on them is well worth the effort.
Hope that helps.

Multiple buttons on click same function

This is a follow up question of Efficient way to make an array of labels.
I have an array of buttons made by code (not designer) which are all added to a gridlayout. What I want is to be able to click any button on that gridlayout and call one same function with the row and column as parameters. Why I want this is because I do not feel like writing 15x15 functions which all do the same thing.
Is there a way or should I try to find another solution?
Ps. All my other input is made in the qt designer via "go to slot" so if it has to happen otherwise, I'll be clueless about how to.
Edit: The array of labels is now an array of buttons.
You could connect all of your buttons to a slot with no parameters and then get the position of the sender in this steps:
Cast the sender QObject to a QWidget via qobject_cast
Retrieve the index of that QWidget using QLayout::indexOf(QWidget *widget)
Then get the row, column, column span and row span with the QGridLayout::getItemPosition(int index, int *row, int *column, int *rowSpan, int *columnSpan)
The example code would look like this:
void MyWidgetWithAllLabels::commonSlot()
{
QWidget *buttonWidget = qobject_cast<QWidget*>(sender());
if (!buttonWidget)
return;
int indexOfButton = ui->gridLayout->indexOf(buttonWidget);
int rowOfButton, columnOfButton, rowSpanOfButton, columnSpanOfButton;
ui->gridLayout->getItemPosition(indexOfButton,
&rowOfButton, &columnOfButton, &rowSpanOfButton, &columnSpanOfLabel);
// Now you can get a reference to that specific QPushButton
QLayoutItem *item = ui->gridLayout->itemAtPosition(rowOfButton, columnOfButton);
QPushButton *clickedButton = qobject_cast<QPushButton*>(item->widget());
if (!clickedButton)
return;
// ... do something with that clickedButton
}
Referring to the code in your related post, you can connect your buttons to that slot like this:
connect( ui->tile_0_0, SIGNAL(clicked()),
this, SLOT(commonSlot()));
connect( ui->tile_0_1, SIGNAL(clicked()),
this, SLOT(commonSlot()));
// ...
By default, a QLabel has no "clicked" signal.
But you can do your own QLabel with 2 integers (row, col) and when you've got a mouseReleaseEvent (or mousePressEvent), you send a custom signal that looks like this: clicked(int row, int col).
You can also use a QSignalMapper:
http://qt-project.org/doc/qt-4.8/qsignalmapper.html#details