How to use signal to execute any function with arguments? - c++

Suppose I have a QPropertyAnimator animating (moving), say, a button - slightly to the left over the course of 10 seconds.
When the button reaches its destination on the other side of the window it should change its text to "banana" using the QLineEdit::setText() function.
If the QLineEdit::setText() function is issued directly after the animation start;
QPropertyAnimator *animator = new QPropertyAnimator(someButton, "pos");
animator->setDuration(10000);
animator->setStartValue(*current position of the button*);
animator->setEndValue(*current position of the button with x-100*);
animator->start();
QLineEdit::setText(QString("Banana"));
The text changes before it has the chance to start moving. Luckily, QPropertyAnimator emits a signal when the animation is completed - aptly titled finished().
One would like to just be able to:
connect(animator, SIGNAL(finished()), someButton, SLOT(setText("Banana")));
But since you can't pass an argument to a slot that won't work.
How do I change the text after the animation is complete without creating different "proxy" functions (slots) to do it without arguments?

As already others told, you should use lambda, in your case,
connect( animator, &QPropertyAnimator::finished, [&]()
{
m_lineEdit->setText( QString("Banana") );
} );

Related

C++ QT Creator create slot function for QTextEdit::verticalSlideBar?

I'm using QT Creator to create a simple Application that has two textEdit fields next to each oteher. I want both fields to be linked when it comes to scrolling so that when one field scrolls up or down, the other one will as well automatically and vice versa. For this, I need a callback function that is triggered whenever the user moves the slideBar of one of the fields. Unfortunately, when I right click the textEdit fields and press "Go to slots" I can not find an event for the movement of the slideBar.
How can I achieve this?
QTextEdit does not have a signal for when the sliderbar in it changes, since it is not a scrollbar. However QScrollBar has the sliderMoved(int value) signal which is emitted when the slider moves. QScrollBar also has a way to set its scroll value via slots (with setValue(int value))
We can therefore tie two scrollbars together using signals and slots very easily.
For example:
...
// Get easy pointers to the scrollbars
QScrollBar* textbx_slider_1 = ui->textbx1->verticalScrollBar();
QScrollBar* textbx_slider_2 = ui->textbx2->verticalScrollBar();
// Connect them too each other
connect(textbx_slider_1, &QScrollBar::sliderMoved, textbx_slider_2, &QScrollBar::setValue); // Connect the first scrollbar to the second
connect(textbx_slider_2, &QScrollBar::sliderMoved, textbx_slider_1, &QScrollBar::setValue); // Connect the second scrollbar to the first
...
(This assumes that your QTextEdit widgets have ids' of textbx1 and textbx2)
Edit:
It is worth mentioning that sliderMoved will not be emitted when using the scroll wheel on the text box. To detect those inputs you must use something like QScrollBar::valueChanged. You have to be careful with this however since setValue emits valueChanged, meaning you will get an infinite feedback loop if you simply modify the above code.
To prevent this you could use a lambda, something like this:
...
int old_pos = textbx_slider_1->value()
std::function<void(int, QScrollBar*)> f = [old_pos](int new_pos, QScrollBar* slider){
if (new_pos != old_pos) {
// Only trigger if the value has changed
slider->setValue(new_pos);
old_pos = new_pos;
};
connect(textbx_slider_1, &QScrollBar::sliderMoved, std::bind(f, std::placeholders::_1, textbx_slider_2)); // Connect the first scrollbar to the second
connect(textbx_slider_2, &QScrollBar::sliderMoved, std::bind(f, std::placeholders::_1, textbx_slider_1)); // Connect the second scrollbar to the first
...
(The weirdness with std::bind() is simply so we don't repeat virtually the same lambda twice)

C++ Qt: is it possible to create a sort of template slot?

I'm new to Qt and not a C++ expert so please bear with me with this one.
I'm working on an application that has 12 different QPushButtons, but all of them perform very similar actions: they grab some value from a map and then use it to set the style for that button:
void MainWindow::on_btn01_clicked(){
QString img=images["btn01"];
ui->btn01->setStyleSheet("#btn01{ background-image: url(://" + img + ") }");
}
For each one of the 12 buttons I have to create a slot that only differs in the button being used. So it looks a bit weird to create 12 functions that are almost identical.
Is there a better way to do this?
Generally, there are several approaches I've seen used:
The preferred method: lambda expressions.
If you're using modern C++ (C++11 or newer), you can use a lambda function to get the exact effect you described.
I'd expect the resulting code to look something like this:
foreach( QPushButton * button : buttons ) {
connect( button, &QPushButton::clicked, [button, &images]() {
button->setStyleSheet( QString( "{ background-image: url(://%1) }" )
.arg( images[button->objectName()] ) );
});
}
See "Qt Slots and C++11 lambda" for more guidance on how to write lambda functions with Qt.
A dirtier way: QObject::sender().
Qt lets you use QObject::sender() within a slot to get a pointer to the object that invoked it. This has been a widely used approach for many years, but there are limitations, as described in the Qt documentation:
Warning: As mentioned above, the return value of this function is not valid when the slot is called via a Qt::DirectConnection from a thread different from this object's thread. Do not use this function in this type of scenario.
See "How to get sender widget with a signal/slot mechanism?" for more information.
Obsolete code: QSignalMapper.
There's a QSignalMapper class that will let you associate a signal with a bit of data, like a string, to then connect to a slot that uses this parameter. This used to exist as a convenience class in Qt but is being phased out because the lambda methodology makes it pointless.
If you need to update code that uses QSignalMapper to remove it, this tutorial is a reasonable starting point.
You can try one more simple method, that is set the QPushButton's object name respectively, and check the Object name in your slot and use that string. This saves you a lot of code.
Ex:
QPushButton 1 object name is set as button->setObjectName("btn01"); and respectively you can set the other names of the buttons and in your slot you could do some thing like this
void MainWindow::on_button_clicked(){
QPushButton* btn=qobject_cast<QPushButton*>(sender());
QString name=btn->objectName();
QString img=images[name]; btn->setStyleSheet("#" + name + "{ background-image: url(://" + img + ");
}
and then connect all your QPushButtons to this slot

connecting a basic signal mousepress

I am using QCustomPlot where I am trying to write a code that will rescale my axes once the user press the mouse and drags. I did:
connect(ui->plot, SIGNAL(mousePress(QMouseEvent *event)), this, SLOT(mousedrag(QMouseEvent*)));
and I keep getting:
QObject::connect: No such signal QCustomPlot::mousePress(QMouseEvent
*event)
But mouseWheel(QWheelEvent*) and both mouseWheel and mousePress have signals declared in the QCustomPlot library.
Where am I going wrong? Also if someone has a better signal to trigger my function mousedrag(QMouseEvent*) which rescales the the y2 axis according to y1 axis I am open for suggestions.
The signal signature passed to connect is invalid. The parameter names are not a part of the signature. You should also remove any whitespace so that connect doesn't have to normalize the signatures. A normalized signature has no unnecessary whitespace and outermost const and reference must be removed, e.g. SIGNAL(textChanged(QString)), not SIGNAL(textChanged(const QString &)).
remove
vvvvv
connect(ui->plot, SIGNAL(mousePress(QMouseEvent *event)), this,
SLOT(mousedrag(QMouseEvent*)));
Do the below instead:
// Qt 5
connect(ui->plot, &QCustomPlot::mousePress, this, &MyClass::mousedrag);
// Qt 4
connect(ui->plot, SIGNAL(mousePress(QMouseEvent*)), SLOT(mousedrag(QMouseEvent*));
Sidebar
TL;DR: This sort of API design is essentially a bug.
Events and signal/slot mechanism are different paradigms that the QCustomPlot's design mangles together. The slots connected to these signals can be used in very specific and limited ways only. You have to use them exactly as if they were overloads in a derived class. This means:
Each signal must have either 0 or 1 slots connected to it.
The connections must be direct or automatic to an object in the same thread.
You cannot use queued connections: by the time the control returns to the event loop, the event has been destroyed and the slot/functor will be using a dangling pointer.
When using the "old" signals/slot connection syntax, i.e. the one using the SIGNAL and SLOT macros in the connect() statement, you shall not provide the names of the parameters, only their types.
In other words:
SIGNAL(mousePress(QMouseEvent *event)) // WRONG, parameter name in there!
SIGNAL(mousePress(QMouseEvent *)) // GOOD
SIGNAL(mousePress(QMouseEvent*)) // BETTER: already normalized
So simply change your statement to
connect( ui->plot, SIGNAL(mousePress(QMouseEvent*)),
this, SLOT(mousedrag(QMouseEvent*)) );

QT Signals & slots in diffrent class

My program has 2 classes. One of them is MainWindow and another is Calc.
In main window I use automatic generated function on_PushButton_clicked. This function should send two values: double & char to function in Calc.
first:
void MainWindow::on_OneButton_clicked(){
QObject::connect(ui->ZeroButton , SIGNAL(clicked()), this, SLOT(...)) );
ui->TextEdit->insertPlainText("1");
}
second :
void Calc::Add(double val, char oper){
//compute something
}
It's my first app with Qt and I do not know how can I connect them. I've searched similar question on this forum, but can't found.
Sorry if i'm wrong.
First of all, you have to well understand what signal/slot mecanism is, and what you are doing.
Signal/slot mecanism is a Qt concept to link a function (signal) to another function (slot). To "make a link" between a signal and a slot, you have to connect them using QObject::connect(...).
When you use automatic generated function on_PushButton_Clicked() with Qt designer, you, in fact, "make a link" between the signal clicked() emitted when the pushButton is clicked, with a slot on_PushButton_Clicked(). However, the connection between this signal and this slot doesn't appear in your code so it may be confusing and that's why I'm pointing it out.
When you write this:
void MainWindow::on_OneButton_clicked(){
QObject::connect(ui->ZeroButton , SIGNAL(clicked()), this, SLOT(...)) );
ui->TextEdit->insertPlainText("1");
}
You create a connection with zeroButton when clicked and a slot, each time you clic on your button. As a connection is valid till an object is destructed, if you clic again on your pushButton, you'll have a second connection between zeroButton when clicked and your slot.
A better way to create connection is to use connect(...) function when you create your object (mainWindow in your case).
To make it simple for your calculator, you can create 9 buttons for digits, 4 buttons for operators, and 1 button to compute everything.
In your mainwindow constructor, you could have something like:
connect(ui->pushButton1, SIGNAL(clicked()), this, SLOT(onPushButton1Clicked()));
.... // Every other signal for each button
connect(ui->pushButtonEqual, SIGNAL(clicked(), this, SLOT(onPushButtonEqualClicked());
And in your body
void MainWindow::onPushButton1Clicked()
{
// concatenate current value + 1
ui->textEdit->insertPlainText(ui->textEdit->toPlainText() + "1");
}
void MainWindow::onPushButtonEqualClicked()
{
// check textedit content (need a digit + operator + digit)
...
// compute result
...
// write result in TextEdit
...
}
I hope it will help a little bit ;)

QLineEdit editingFinished signal twice when changing focus?

I've found a few similar questions on this but these appear to refer to cases where a message box is used in the slot handler. In my case I am a bit stuck as I am getting the editFinished signal twice even when my slot handler is doing nothing.
For a test, I have an array of QLineEdit which use a signalMapper to connect the editingFinished() signals to a single slot. The signalMapper passes the array index so I can see where the signal came from.
eg:
testenter::testenter(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::testenter)
{
// setup the UI according to the .h file
ui->setupUi(this);
signalMapper = new QSignalMapper(this);
// init the labels and edit boxes
for (int i = 0; i < 10; i++)
{
pm_label[i] = new QLabel(ui->scrollArea);
QString text = QString("Number %1").arg(i);
pm_label[i]->setText(text);
pm_label[i]->setGeometry(10,20+i*30, 50, 20);
pm_label[i]->show();
pm_editBox[i] = new QLineEdit(ui->scrollArea);
pm_editBox[i]->setGeometry(80,20+i*30, 50, 20);
pm_editBox[i]->show();
signalMapper->setMapping(pm_editBox[i], int(i));
connect(pm_editBox[i], SIGNAL(editingFinished()), signalMapper, SLOT(map()));
}
connect(signalMapper, SIGNAL(mapped(int)), this, SLOT(data_entry(int)));
}
void testenter::data_entry(int entry)
{
//dummy
}
When run in the debugger, if I enter data into one box then either hit return or select another box with the mouse (ie change focus) , then it calls data_entry twice, the first time with index of the box that is losing focus and the 2nd time with the box which gets the focus.
So my question: Am I missing something? Is this expected behaviour or a bug?
If a bug, anyone know a way round it as I wanted to use this signal to do custom validation on data when it is entered (by either return, tab or mouse click to change focus).
First off, no this isn't expected behavior, i.e. selecting a QLineEdit should not cause it's editingFinished signal to be emitted.
There are a couple of possible things that may cause this problem:
You've accidentally connected a signal twice to a slot
The slot map() is causing the newly selected box to lose focus
In the same vain, if you're debugging and using a break point to detect when the slots are getting called you may be causing the QLineEdit to lose focus when the active application changes from your QWidget to your debugger, again causing the signal to be sent again.
If you're having problems because of a doubly connected slot, which doesn't seem to be the case because you're specifically getting a signal from two different QLineEdits, you can make sure that this isn't happening by specifying the connection type, the connect method actually has an additional optional argument at the end which allows you to change the type from a DefaultConnection to a UniqueConnection.
That being said, data validation is something that Qt has an established mechanism for, and I suggest that you use it if possible, look into extending the QValidator abstract base class Ref Doc. You then tell each of your QLineEdit's to use the same validator.
I have run into the same issue. It really does emit the signal twice, which is a known bug: https://bugreports.qt.io/browse/QTBUG-40 which however has not been addressed for a very long time.
Finally I found that the best solution in my case is to change the signal from editingFinished to returnPressed. As a side effect this behaves much more predictably from the user perspective. See also here: http://www.qtforum.org/article/33631/qlineedit-the-signal-editingfinished-is-emitted-twice.html?s=35f85b5f8ea45c828c73b2619f5750ba9c686190#post109943
The OP "found a few similar questions on this but these appear to refer to cases where a message box is used in the slot handler." Well, that is my situation also, and here is where I ended up. So, at the risk of going off topic...
In my situation, when my slot receives the editingFinished signal sent from the QLineEdit, I launch a modal QMessageBox to ask the user something. The appearance of that message box is what triggers the QLineEdit to send the second, undesirable editingFinished signal.
A post in the bug report (https://bugreports.qt.io/browse/QTBUG-40) mentioned by #V.K. offers a workaround which helped me. The following is my implementation of the workaround. I let Qt magic mojo automatically connect the QLineEdit signal to my MainWindow slot.
void MainWindow::on_textbox_editingFinished( void )
{
QLineEdit * pTextbox = qobject_cast<QLineEdit *>( QObject::sender() );
if ( !pTextbox->isModified() )
{
// Ignore undesirable signals.
return;
}
pTextbox->setModified( false );
// Do something with the text.
doSomething( pTextbox->text() );
}
void MainWindow::doSomething( QString const & text )
{
QMessageBox box( this );
box.setStandardButtons( QMessageBox::Yes | QMessageBox::No );
box.setText( "Are you sure you want to change that text value?" );
if ( box.exec() == QMessageBox::Yes )
{
// Store the text.
m_text = text;
}
}