Passing an argument to a slot. Ask Question Asked 8 years, 8 months ago. How to use this if my context parameter targets a to a class which has no acess to the actions? So eitherway. How to pass a value with a clicked signal from a Qt PushButton?
I have an old codebase I started writing using the Qt 3.x framework—a little while before Qt4 was released. It’s still alive! I still work on it, keeping up-to-date with Qt and C++ as much as possible, and I still ship the product. Over the years I have moved the codebase along through Qt4 to Qt5, and on to compilers supporting C++11. One of the things I’ve sometimes found a little excessive is declaring slots for things that are super short and won’t be reused.
Here’s a simplified example from my older code that changes the title when a project is “dirty” (needs to be saved):
2 4 6 8 10 12 14 16 18 20 22 | classmyMainWindow... privateslots: void_setTitle(constQString&inTitle ); // implementation connect(inProject,SIGNAL(signalDirtyChange(bool)), ... voidmyMainWindow::slotProjectDirtyChanged() QString name=mProject->name(); name+='*'; _setTitle(name); |
(Aside: If you are wondering about my naming convention, I developed a style when using Qt early on (c. 2000) where signals are named signalFoo() and slots are named slotFoo(). This way I know at a glance what the intent is. If I’m about to modify a slot function I might take an extra minute to look around since most IDEs can’t tell syntactically where it’s used in a SLOT()
macro. In this case you have to search for it textually.)
Thanks to C++11 lambdas and Qt’s ongoing evolution, these short slots can be replaced by a more succinct syntax. This avoids having to declare a method in your class declaration and shortens your implementation code. Both desirable goals!
Let’s take a look.
Getting rid of SIGNAL() and SLOT() macros
The first really nice thing about Qt5 is that we can move away from the classic SIGNAL()
and SLOT()
macros to using method pointers.
This does several things for us:
- relieves us of having to lookup and fill in the parameters for the methods
- shortens your code
- IDEs can identify the methods used in a connect call when you search for uses of a method
- allows implicit conversion of arguments
- catches problems at compile time rather than runtime
This last one is the most important. If the two methods you are trying to connect are mismatched it simply won’t compile. I can say with authority that problems found at compile time can save you many, many hours of debugging!
So if we apply this idea to our example, the connect()
call becomes:
A lambda is essentially a fancy name for an anonymous function. These are typically short and can be useful for things like this slot mechanism where the code may not be re-usable, or for callbacks.
The C++11 specification for a lambda function looks like this:
[capture](parameters) -> return_type { function_body }
Looks simple, but when you dig into the details, they can be rather complicated.
The capture
part defines how variables in the current scope are captured for the function_body to use. Put another way, it defines what variables are made available to the lambda function body from the current scope.
The one we’ll use here is [=]
which says to capture all variables from the local scope by value. You can also capture nothing ([]
), capture all variables in the scope by reference ([&]
*note), capture just the members of the class ([this]
), or capture individual variables ([foo, &bar]
) – or some combination.
It also takes optional parameters
which is how we’ll pass signal parameters to the lambda (see below).
return_type
and function_body
are standard C++.
If we apply this to our example, we get something like this:
2 4 6 8 10 12 14 16 | classmyMainWindow... private: ... connect(inProject,&myProject::signalDirtyChange, [=] () { if(mProject->dirty()) }); |
What about the arguments?
As I mentioned earlier, lambdas can also take arguments, so you can pass the parameters from the signal into the lambda function. For example, I recently ran into an issue where I needed to accept a QRectF
, adjust it, and send it on again. I did it like this: