I am trying to create a custom slot that can receive an int and pass it as a double to QDoubleSpinBox.SetValue(double);
But I have a bunch of spinboxes in my UI so I would like to have a single slot that can have a DoubleSpinBox pointer as a parameter like so:
void MainWindow::ConvertSetSpinBoxValue(int value, QDoubleSpinBox * spinbox)
{
double ValToDouble = value;
spinbox->setValue(ValToDouble);
}
and then I can connect a widget that emits a Int returning signal i.e. a QSlider:
connect(ui->horizontalSlider, SIGNAL(valueChanged(int)), this, SLOT(ConvertSetSpinBoxValue(int, ui->doubleSpinBox)));
This connect could be repeated and I would simply change the spinbox pointer. But this doesn't work and the connect is discarded at compilation.
Any idea? thanks!
Using the new Qt5 signal/slot syntax allows you to connect to a lambda that can capture the QDoubleSpinBox * (untested)...
auto *sb = ui->doubleSpinBox;
connect(ui->horizontalSlider, &QAbstractSlider::valueChanged, this,
[this, sb](int value)
{
this->ConvertSetSpinBoxValue(value, sb);
});
connect(ui->horizontalSlider, &QSlider::valueChanged,
[this] (int value) {
ConvertSetSpinBoxValue(value, ui->doubleSpinBox);
});
Please use modern syntax for connections
A lambda should help you
As the others pointed out using the (new) Qt5 signals and slots (using function pointers and lambda functions) should solve your problem.
Different methods exist:
Using lambda function:
connect(ui->horizontalSlider, &QSlider::valueChanged, this /*context*/, [this] (int value) {ui->doubleSpinBox->setValue(value);});
Note that the context is useful as it automatically destructs the connection when the context is destructed, but it is probably not needed here as the ui->horizontalSlider will probably destructed first.
Using std::bind:
connect(ui->horizontalSlider, &QSlider::valueChanged, this /*context*/, std::bind(&MainWindow::ConvertSetSpinBoxValue, this, value, ui->doubleSpinBox));
Using implicit conversion between signals/slots:
connect(ui->horizontalSlider, &QSlider::valueChanged, ui->doubleSpinBox, &QDoubleSpinBox::SetValue);
Note that the new signal and slot mechanism allows implicit conversion between the signals and slots.
References
Differences between String-Based and Functor-Based Connections
Related
I already have a working connection from SomeClass::somSignal using the SIGNAL() macro but I don't have a way of connecting it to a lambda using &SomeClass::someSignal mostly because someClass is inaccessible. I have the QObject *, though.
I tried to look into QMetaObject; it should have some sort of look-up structure where these details are kept.
We can have:
QMetaMethod::fromSignal(PointerToMemberFunction)
returning QMetaMethod
My question is, is it possible to have something like this?
fromMetaMethod(QMetaMethod)
returning PointerToMemberFunction
??
Thanks.
One way to create the connection you want does not require you to find any methods address.Simply:
Create a signal-to-signal connection, from SomeClass::someSignal to your own signal in your own class using the SIGNAL() connection style.
Connect your signal to the lambda.
You only have to make sure the parameters of the original signal are forwarded to your lambda.
Example:
QObject::connect(
pointerToSomeClass, SIGNAL(someSignal(int)),
pointerToMyClass , SIGNAL(mySignal(int)),
Qt::DirectConnection
);
QObject::connect(
pointerToMyClass, &MyClass::mySignal,
[](int i) { ... }
);
If you happen to need the sender of the original signal, then you will need a slot.
Example:
void myClass::mySlot(int i)
{
emit mySignal(sender(), i);
}
and
QObject::connect(
pointerToSomeClass, SIGNAL(someSignal(int)),
pointerToMyClass , SIGNAL(mySlot(int)),
Qt::DirectConnection
);
QObject::connect(
pointerToMyClass, &MyClass::mySignal,
[](QObject* sender, int i) { ... }
);
can someone tell me how exactly signals with parameters work? I mean... if i have declared signal f.e.:
void sign1(int)
how should i specify what integer i want to send with that signal? Also, can i declare signal with multiple arguments? Like:
void sign2(int, int)
And again... i want to send with sign2 two out of four variables that i have. Is that possible, and how it should be done? To specify my question below is a little more detailed example:
class Board
{
signals:
void clicked(int, int);
private:
int x1{4}; int x2{4}; int x3{5}; int x4{8};
}
and there is board.ui file with pushbutton. After pushbutton is clicked i want to send to the slot for example x1 and x3. Example:
connect(ui->button, SIGNAL(clicked(int, int)), obj2, slot2);
I hope that it's somehow clear. I will really appreciate your help.
QObject::connect() works like this (in the general case, not using lambda):
connect(obj1, obj1_signal, obj2, ob2_slot)
Since there is no signal clicked(int, int) in the class QPushButton (which I assume you are using), it cannot be use for the connection.
If you want to have the signal clicked(int, int) in a button, you can subclass QPushButton, add the signal, and using emit to send the signal where the click event is handled.
However, that is not a good design, since you will have to store a Board object (or at least a reference to it) in the button class, which is irrelevant to the class.
Instead, you can have a slot Board::buttonClicked(), connected to QPushButton::clicked(bool). Then in that slot, you can do emit Board::clicked(int, int).
The rule for signal/slot connection may be formulated as the following:
You can ignore signal arguments, and you cannot create slot arguments from nothing
What does it mean?
If your signal has n arguments, your slot shall have at most n arguments as well (beware of the types). See tabular below.
On first line, you have a signal with two arguments, thus, your slot can have two arguments (using all the signal arguments), or one argument (ignoring one argument of the signal) or no argument (ignoring both signal arguments)
On the second line, you have a signal valueChanged(int) with one argument. Your slot may have one or no argument (ignoring the signal argument) but may not have two
or more arguments as you cannot create values.
On the third line the signal textChanged(QString) cannot be connected with setValue(int) because we cannot create an int value from from QString.
The fourth line follow these rules. If the signal has no argument, the connected signal cannot create new arguments, thus update() is correct, setValue(int) isn't.
Another point that shall be looked at is overloading of signal/slots. It is the case where many signals/slots with the same name but with different numers or different types of arguments.
You may have the class QLCDNumber, where the slot display has many overload. In this cas, you have to explicitly defined which pair of signals slots, you would like to use as explained here
You can try out the following example:
Example :
#include <QtWidgets>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QWidget *window = new QWidget();
window->setAttribute(Qt::WA_DeleteOnClose);
QVBoxLayout *topLayout = new QVBoxLayout(window);
//Set up of GUI
QSlider *slider = new QSlider(Qt::Horizontal);
slider->setRange(0, 100);
QSpinBox *spin = new QSpinBox;
spin->setReadOnly( true );
QHBoxLayout *horizontalLayout = new QHBoxLayout;
horizontalLayout->addWidget(slider);
horizontalLayout->addWidget(spin);
topLayout->addLayout(horizontalLayout);
// using pointer to member function
QObject::connect(slider, &QSlider::valueChanged,
spin, &QSpinBox::setValue);
// set the slider position and hence the QSpinBox value too
slider->setValue(40);
// Uncommenting the following connect will result in a compile time error.
// The signal passes no arguments whereas the slot is expecting a single
// argument.
// By using function pointers we get compile time parameter list checking.
// Using the old-style SIGNAL/SLOT macros this would have been detected
// as a run time warning only.
//QObject::connect(slider, &QSlider::sliderPressed,
// spin, &QSpinBox::setValue);
QTextEdit *textEdit = new QTextEdit();
textEdit->setAttribute(Qt::WA_DeleteOnClose);
// Uncommenting the following connect will result in a compile time error.
// The signal is passing an incompatible parameter to the slot.
// By using function pointers we get compile time parameter type conversion.
// Using the old-style SIGNAL/SLOT macros this would have been detected
// as a run time warning only.
//QObject::connect(slider, &QSlider::sliderMoved,
// textEdit, &QTextEdit::setFontFamily);
window->show();
return app.exec();
}
To make a function of a class to be a slot, the class has to inherit from QObject. However, QObject takes up a quite large amount of memory. I am not sure how much it is and if the memory is for each class or each object. My code has many small data whose functions can be a slot sometime. I am wonder if there is a way to make a function of class to be a slot temporally when using it. After using it, the memory for the slot cost will be deleted. The following code illustrates the requirement.
class SmallData // size of 2 or 3 integers.
{
public:
virtual void F(); // use it as a slot.
virtual QMenu* createMenu(); // use this to create the context menu with
// an action connected to F()
...
};
// use the small data
vector<SmallData> vec(1000000); // the vector is put at a tree view. When an
// item in the tree view is selected, a context
// menu pop up with an action to run F().
SmallData* data = treeView.selectedItem();
connect(action, SIGNAL(triggered()), data, SLOT(F())); // How to make F() to be
// a slot just here.
// The action is from
// data->createMenu().
If you can use Qt5, you can connect signals to plain functions and static methods (which essentially are funnily named plain functions):
connect(action, &QAction::triggered,
&SmallData::statF);
Where action is a QAction instance, and SmallData::statF is a static method of SmallData.
Edit per Christian Rau's comment, to call a particular instance, you can also connect to lambda:
connect(action, &QAction::triggered,
[data]() { data->F(); });
Already with Qt4, you can use QSignalMapper to achieve much the same effect, with a few more objects. It allows you to add add a parameter (in this case, probably an integer index to your vec) to signal, based on which object emitted it. But in Qt4, receiver must still always be a QObject.
For using the signal slot mechanism, you won't get around QObject, but what you can do is create a temporary object that has a slot calling your function. You just have to care for properly releasing the object. Something like:
class Callback : public QObject
{
Q_OBJECT
public:
typedef std::function<void()> FunctionType;
Callback(FunctionType fn, bool oneShot = true, QObject *parent = nullptr)
: QObject(parent), fn_(std::move(fn)), oneShot_(oneShot) {}
public slots:
void call()
{
fn_(); //delegate to callback
if(oneShot_)
deleteLater(); //not needed anymore
}
private:
FunctionType fn_;
bool oneShot_;
};
Callback* makeCallback(FunctionType fn, bool oneShot = true, QObject *parent = nullptr)
{
return new Callback(std::move(fn), oneShot, parent);
}
You then just create a (more or less temporary) Callback object each time needed:
SmallData* data = treeView.selectedItem();
connect(action, SIGNAL(triggered()),
makeCallback(std::bind(&SmallData::F, data)), SLOT(call()));
With the oneShot parameter you can control if the slot should dissolve automatically once triggered.
The only problem is, if this slot is never called, you have a leaking Callback hanging around. To accomodate this, you can pass something meaningful into the parent argument, so that Qt cares for proper deletion at least at some later point in time:
SmallData* data = treeView.selectedItem();
connect(action, SIGNAL(triggered()),
makeCallback(std::bind(&SmallData::F, data), true, this), SLOT(call()));
This way you can also bind the lifetime of the callback object (and thus the signal-slot connection) to some other object (e.g. the action itself and deleting the action when no item is selected, or something the like).
Alternatively, you can also remember the Callback object for the currently selected item and care for proper deletion yourself, once it's delesected.
disclaimer: Beware that the above example contains plenty of C++11, but I'm not in the mood to rewrite this for C++03. Likewise can this solution be imporved further, maybe using a templated functor instead of a std::function (but if I remember correctly the Qt meta object system doesn't like templates that much).
EDIT: In the end the solution proposed by Frank Osterfeld in his comment might be a much simpler approach for your situation than my overly generic object lifetime madness above: Just connect the action to a single slot of a higher level object (your main widget or maybe the item model containing the data vector) and call F on the currently selected item:
connect(action, SIGNAL(triggered()), this, SLOT(callF()));
...
void MyController::callF()
{
treeView.selectedItem()->F();
}
I don't think that what you try to do is possible in Qt.
If you really don't want to inherit QObject, then I suggest you have a look at the boost signals and slots mechanism.
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.
I basically have multiple events signals which I want to connect to the same slot. What I want to know is how can I pass string based parameters to that same slot so that the slot knows which is this signal coming from. One alternative is to make as many slots as there are signals and then connect them in a 1:1 manner, but this is efficient, considering that the code for all the processing is very similar. I tried doing this but I'm getting some errors:
connect(selecter1,SIGNAL(selected(QString)),this,SLOT(backgroundTypeChoiceMade(QString)));
connect(button1,SIGNAL(clicked()),this,SLOT(backgroundTypeChoiceMade("button1")));
connect(button2,SIGNAL(clicked()),this,SLOT(backgroundTypeChoiceMade("button2")));
The error is related to the parameters I'm passing in the last 2 commands .. And backgroundTypeChoiceMade is declared like this:
void backgroundTypeChoiceMade(QString);
Can someone tell me what the error is in the above code ?
You can use QSignalMapper. Although the QSignalMapper is the answer to your question, I think jon hanson's answer is the way you should take. You get much more cleaner code that way.
Four methods. One doesn't suck.
QSignalMapper. Works, but makes for messy code.
Named slots. Messy for any significant number of senders, and doesn't work for dynamically-generated senders (e.g., buttons in a list).
sender()-compare. Can handle dynamic senders, but is still kinda ugly.
Subclass the sender. Doesn't suck. Gives you what you really wanted all along: parameterized signals.
Especially when you're using a small number of signals and sender types and when the senders are dynamically generated, subclassing the sender is the cleanest way. This lets you overload the existing signals to contain whatever parameters you need.
And now, wiring up the signals and slots just works:
Keypad::Keypad(QWidget *parent) : QWidget(parent)
{
for (int i = 0; i < 10; ++i)
{
// KeypadButton keeps track of the identifier you give it
buttons[i] = new KeypadButton(i, this);
// And passes it as a signal parameter. Booyah.
connect(buttons[i], SIGNAL(clicked(int)), this, SIGNAL(digitClicked(int)));
}
createLayout();
}
void Keypad::digitClicked(int digit)
{
// The slot can find the clicked button with ease:
dial(button[i]); // or whatever
//...
}
and the extra code is out-of-sight in a subclass you'll never have to touch again.
See http://doc.qt.digia.com/qq/qq10-signalmapper.html#thesubclassapproach for an example implementation of subclassing QPushButton to emit clicked(int) signals. Also discusses all four methods - named slots ("the trivial solution"), sender(), subclassing, and signal mapper.
Caveat: Obviously works best for small numbers of sender types. But that's usually the case. And in that case, it's worth it.
What is inefficient about using separate slots? If there's commonality in the slot handlers then move that into a function, e.g. extending ereOn's example:
void YourClass::YourClass() :
m_button1(new QPushButton()),
m_button2(new QPushButton())
{
connect(m_button1, SIGNAL(clicked()), this, SLOT(yourSlot1()));
connect(m_button2, SIGNAL(clicked()), this, SLOT(yourSlot2()));
}
void YourClass::common(int n)
{
}
void YourClass::yourSlot1()
{
common (1);
}
void YourClass::yourSlot2()
{
common (2);
}
You can't pass constants to connect() because the effective parameters are deduced at execution time, not compile time.
However, while this is against the OO principle, you can use QObject::sender() which gives a pointer to the emitter QObject.
Example below:
void YourClass::YourClass() :
m_button1(new QPushButton()),
m_button2(new QPushButton())
{
connect(m_button1, SIGNAL(clicked()), this, SLOT(yourSlot()));
connect(m_button2, SIGNAL(clicked()), this, SLOT(yourSlot()));
}
void YourClass::yourSlot()
{
if ((QPushButton* button = dynamic_cast<QPushButton*>(sender()))
{
// Now button points to a QPushButton* that you can compare with the pointers you already have
if (button == m_button1)
{
// Whatever
} else
if (button == m_button2)
{
// Whatever
}
}
}
If you have many buttons, you may also use a QSignalMapper by providing an identifier for each button.
You can now really bind a value when connecting. Qt5 added support for that.
Example:
connect(sender, &Sender::valueChanged,
tr1::bind(receiver, &Receiver::updateValue, "senderValue", tr1::placeholder::_1));
See more info.
NB: you can of course use std::bind or boost::bind instead of tr1::bind.
If you really don't want to use QSignalMapper, you could do something like this:
class SignalForwarderWithString: public QObject
{
Q_OBJECT
public:
SignalForwarderWithString(QString data = "", QObject *parent = 0) : QObject(parent), _data(data) {}
QString _data;
signals:
void forward(QString);
public slots:
void receive() { emit forward(_data); }
};
...
connect(selecter1,SIGNAL(selected(QString)),this,SLOT(backgroundTypeChoiceMade(QString)));
SignalForwarderWithString *sfws;
sfws = new SignalForwarderWithString("button1", this);
connect(button1,SIGNAL(clicked()), sfws, SLOT(receive(QString)));
connect(sfws, SIGNAL(forward(QString)), this,SLOT(backgroundTypeChoiceMade(QString)));
sfws = new SignalForwarderWithString("button2", this);
connect(button2,SIGNAL(clicked()), sfws, SLOT(receive(QString)));
connect(sfws, SIGNAL(forward(QString)), this,SLOT(backgroundTypeChoiceMade(QString)));
but QSignalMapper is just as easy...
QSignalMapper *mapper = new QSignalMapper(this);
connect(button1, SIGNAL(clicked()), mapper, SLOT(map()));
mapper->setMapping(button1, "button 1");
connect(button2, SIGNAL(clicked()), mapper, SLOT(map()));
mapper->setMapping(button2, "button 2");
// you might have to tweak the argument type for your slot...
connect(mapper, SIGNAL(mapped(const QString &), this, SLOT(backgroundTypeChoiceMade(QString)));