QT - Detecting which button was pressed - c++

I have a problem with the usage of Application::sender. I have a few QPushButtons and in one function, I want to detect which button was pressed.
I got to know that using Application::sender might be the solution, however I have troubles with it. Namely I get two errors:
call to non-static member function without an object argument
'sender' is a protected member of 'QObject'
And here is my code:
void MainWindow::on_button_click()
{
unsigned long i=0;
for(; i<buttons.size(); ++i)
{
if(buttons[i] == QApplication::sender())
break;
}
if(checks[i]->checkState() == false)
buttons[i]->setText("Undone");
else
buttons[i]->setText("Done!");
}
Where variable buttons is a vector of QPushButton *

Call the method sender() of the object where your slot is, not the static member of QApplication.
In other words, remove QApplication:: and your code should work as expected.

sender() returns QObject. You need QPushButton so your have to use casting. This code will work:
QPushButton *button = qobject_cast<QPushButton*>(sender());
button->setText("New Text");

Related

QT - connecting QPushButtons with QCheckBoxes

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

Put arguments in slots in Qt

I've made a class named MyWindow which inherits from QWidget to create a window. Here is the content of mywindow.h:
class MyWindow: public QWidget{
public:
MyWindow(QString title,QString icon,int w = 600,int h = 400);
int getWidth() const;
int getHeight() const;
public slots:
void openDialogBox(QString title,QString message);
private:
int m_width;
int m_height;
};
There is a openDialogBox slot which takes the title and the message of the dialog box as arguments.
I've made a menu bar which basically looks like this:
MyWindow myWindow("Example window",QCoreApplication::applicationDirPath() + "/icon.png");
QMenuBar menuBar(&myWindow);
menuBar.setGeometry(0,0,myWindow.getWidth(),menuBar.geometry().height());
QMenu *fileMenu = new QMenu("&File");
QAction *fileMenu_open = fileMenu->addAction("&Open");
MyWindow::connect(fileMenu_open,&QAction::triggered,&myWindow,&MyWindow::openDialogBox);
In the last line, I would like to send arguments to the slot &MyWindow::openDialogBox. I tried to do:
MyWindow::connect(fileMenu_open,&QAction::triggered,&myWindow,&MyWindow::openDialogBox("Title","Hello, this is a message"));
but it didn't work (I don't need you to explain why it didn't work, I already know why). How to do this properly so that it works?
Since you are using the New Signal Slot Syntax, I would suggest using a c++11 lambda instead of a slot, and call the desired function inside your slot, here is how your connect call would look like:
QObject::connect(fileMenu_open, &QAction::triggered, &myWindow, [&myWindow](){
myWindow.openDialogBox("Title","Hello, this is a message");
});
Note that openDialogBox is not required to be a slot this way, it can be any normal function.
If your compiler does not support C++11 lambda expression, you might have to declare a slot that does not take any argument, and connect to that slot. And inside that slot call your function with the desired arguments. . .
Use lambdas
QObject::connect(fileMenu_open, &QAction::triggered, &myWindow, [QWeakPointer<MyWindow> weakWindow = myWindow]()
{
weakWindow->openDialogBox("Title","Hello, this is a message");
});
QWeakPointer is used in case your class is moved, so the "old" myWindow is a dangling pointer
If your class won't be moved, just capture myWindow.
Note that my code needs C++14 to declare a variable in the lambda capture

Call method on subclass given pointer to superclass

I have a set of buttons in a group:
buttons = new QButtonGroup();
button_0 = new MyButton("A", this);
button_1 = new MyButton("B", this);
button_2 = new MyButton("C", this);
MyButton is a QPushButton, which inherits QAbstractButton. One MyButton in the group can be selected at a time.
MyButton::MyButton(const QString &text, QWidget *parent)
: QPushButton(text, parent)
{
this->setCheckable(true);
this->setChecked(false);
}
MyButton has an increment method.
void MyButton::increment()
{
this->j++;
}
I want to call the increment method on whichever MyButton instance is currently selected. There's a handy checkedButton() method that I can call on buttons, but that returns a pointer to a QAbstractButton.
I can't call increment on the pointer or the QAbstractButton. buttons->checkedButton()->increment(); yields error: no member named 'increment' in 'QAbstractButton'. How can I increment the selected MyButton?
You just need to let the compiler know that you are using a MyButton* rather than a QAbstractButton*, and that means a cast.
If you know that the pointer is a MyButton* then you can just do a static_cast<MyButton*>(buttons->checkedButton())->increment(); If you don't know if its a MyButton* then you'll need to check, and that'll mean something like this:
MyButton* temp = dynamic_cast<MyButton*>(buttons->checkedButton());
if(temp != nullptr) temp->increment();
EDIT:
SaZ has pointed out that qobject_cast is preferable to dynamic_cast because:
It doesn't require RTTI support and it works across dynamic library boundaries
MyButton already inherits from QObject, but there is one more qualification, that it is declared with Q_OBJECT. If both of these qualifications are met then you can simply replace the dynamic_cast with a qobject_cast:
MyButton* temp = qobject_cast<MyButton*>(buttons->checkedButton());
if(temp != nullptr) temp->increment();
Note that in the case of QObject derived classes, you can and should use qobject_cast() instead of dynamic cast. It is more efficient by employing the Qt meta system.
Also, unlike the dynamic cast, it will work when compiling without RTTI, and with dynamic libraries as well.
Dynamic cast QAbstractButton to MyButton and call increment().
QAbstractButton* abstractButton = buttons->checkedButton();
MyButton* myButton = dynamic_cast<MyButton*>(abstractButton);
if (myButton)
{
myButton->increment();
}
For the sake of completeness, in the case of Qt program you may also make use of signal-slot system: if increment was declared to be a slot then this would work:
QMetaObject::invokeMethod(abstractButton, "increment");

Qt issue passing arguments to slot

I can't seem to pass an argument to a slot. If I don't pass an argument, the function rolls through fine. If I pass an argument (integer), I get the errors "No such name type" and "No such slot" when I compile.
In my header, I declare:
private slots:
void addButton(int);
signals:
void clicked(int)
in my Main.cpp, I do:
int count;
int count = 0;
QPushButton* button = new QPushButton("Button");
_layout->addWidget(button);
connect(button, SIGNAL(clicked(count), this, SLOT(addButton(count)));
....
void Main::addButton(int count) {
//do stuff with count
}
Sebastian is correct that you cannot do this in the way you're trying, however Qt does provide a class that gives you the functionality you want.
Check out the QSignalMapper. It allows you to associate an integer with an object/signal pair. You then connect to its signals instead of directly to the button.
The signal and the slot must have the same number and type(s) of argument(s), and you can only pass the argument(s) of the signal to the slot, not any variable or value that you want.
I can see three problems with this.
Firstly, the clicked() signal is emitted by QPushButton (with no parameters), but you're trying to redefine it in your own class (with an int parameter). If you want to do this:
SignalClass* objectWithSignals = new SignalClass;
SlotClass* objectWithSlots = new SlotClass;
connect(objectWithSignals, SIGNAL(a()), objectWithSlots, SLOT(b()));
then you can only connect to the signals already defined in SignalClass. In other words, the signal a() must belong to SignalClass, not SlotClass.
(In fact, clicked() is defined in QPushButton's base class QAbstractButton.)
Secondly, inside the connect() function, you need to specify the signal and slot signatures with their parameter types. Where you have count inside the connect() function, it should be int.
And thirdly, there's a bracket missing in your call to connect: SIGNAL(clicked(count)).
Hope that helps.

Qt Drag&Drop with own widgets?

I created a little widget on my own, including a QProgressBar and a QLabel in a QVBoxLayout. It has also a function which returns the text of the label (self-created).
Now in my MainWindow I have two other QHBoxLayouts and I want to drag and drop my widget from one to another. It also works when I click on the little free space between the QLabel and the QProgressBar. But when I click on one of them directly, the application crashed and burned painfully.
I also know where it fails. My mousePressEvent looks like this:
void DragDrop::mousePressEvent(QMouseEvent *event) {
// !!!!---- make sure ONLY MyWidgets are here, else: CRASH ----!!!!
MyWidget *child = static_cast<MyWidget*>(childAt(event->pos()));
if (!child)
return;
qDebug() << child->returnLabelText();
...
}
So when I click on the ProgressBar, it will cast the ProgressBar, not my own widget. And because the QProgressBar doesn't have a function like returnLabelText() (but my widget does) it fails.
What is a better method to get my widget?
QWidget::childAt(int,int) returns the child widget, not the parent widget. In your case, it returns the QProgressBar. You then try to cast into a MyWidget, which it is not. What you are looking for is for the parent of the QProgressBar (or QLabel).
static_cast does not verify the type of the object you are trying to cast, and will always yield a non-null pointer even if the cast is invalid. What you are looking for here is dynamic_cast, which will return NULL if the object is not of the type you are looking for. Since you are looking for the parent (or an ancestor) of the widget being clicked, you could use a loop to iterate through the clicked widget's ancestry to find the instance of MyWidget you are looking for.
void DragDrop::mousePressEvent(QMouseEvent *event) {
QWidget *widget = childAt(event->pos());
do {
MyWidget *myWidget = dynamic_cast<MyWidget*>(widget);
widget = widget->parentWidget();
} while (myWidget == NULL && widget != NULL)
if (myWidget == NULL)
return;
qDebug() << myWidget->returnLabelText();
// ...
}