I'm new to Qt and GUI programming and am unsure of the best way to connect a signal to to a slot when the parameter list doesn't match. I have a settings dialog box made with Qt Designer and it contains a series of QCheckBoxes and QLineEdits, with the QLineEdits disabled by default. I want to enable a QLineEdit when the QCheckBox next to it is checked.
At first I thought to connect the QCheckBox::stateChanged signal to the QLineEdits::setEnabled slot, but when I looked I found they had different parameter types so this obviously won't work:
connect(checkBox1, SIGNAL(stateChanged(int)), lineEdit1, SLOT(setEnabled(bool)));
connect(checkBox2, SIGNAL(stateChanged(int)), lineEdit2, SLOT(setEnabled(bool)));
connect(checkBox3, SIGNAL(stateChanged(int)), lineEdit3, SLOT(setEnabled(bool)));
Next I thought to create setLineEditEnabled(int) function in the dialog box class to enable the appropriate QLineEdit when a QCheckBox is checked:
connect(checkBox1, SIGNAL(stateChanged(int)), settingsDialog, SLOT(setLineEditEnabled(int)));
connect(checkBox2, SIGNAL(stateChanged(int)), settingsDialog, SLOT(setLineEditEnabled(int)));
connect(checkBox3, SIGNAL(stateChanged(int)), settingsDialog, SLOT(setLineEditEnabled(int)));
When I came to write the setLineEditEnabled() function I realised there's no way to know which QCheckBox sent the signal, so I don't know which QLineEdit should be enabled:
void SettingsDialog::setLineEditEnabled(int checkState)
{
????->setEnabled(checkState == Qt::Checked);
}
The only solution I can think is to think of is to have a a series of functions in the dialog class, with one for each checkbox:
connect(checkBox1, SIGNAL(stateChanged(int)), settingsDialog, SLOT(setLineEdit1Enabled(int)));
connect(checkBox2, SIGNAL(stateChanged(int)), settingsDialog, SLOT(setLineEdit2Enabled(int)));
connect(checkBox3, SIGNAL(stateChanged(int)), settingsDialog, SLOT(setLineEdit3Enabled(int)));
void SettingsDialog::setLineEdit1Enabled(int checkState)
{
lineEdit1->setEnabled(checkState == Qt::Checked);
}
void SettingsDialog::setLineEdit2Enabled(int checkState)
{
lineEdit2->setEnabled(checkState == Qt::Checked);
}
void SettingsDialog::setLineEdit3Enabled(int checkState)
{
lineEdit3->setEnabled(checkState == Qt::Checked);
}
However, that seems a bit messy (there are actually seven QCheckBox-QLineEdit pairs so I'd need seven functions), and I feel I'm missing something that would make this easier. If I knew which object sent the signal I could do it with a single function, which would be tidier.
Is there a way get the object that sent the signal from the slot function?
If there's no way to get the signalling object, is there a better solution to this that doesn't involve having multiple functions in the dialog class for enabling the QLineEdits?
Thanks for your help.
In this case you can use the QCheckBox::toggled(bool) signal instead of stateChanged(int).
connect(checkBox1, SIGNAL(toggled(bool)), lineEdit1, SLOT(setEnabled(bool)));
connect(checkBox2, SIGNAL(toggled(bool)), lineEdit2, SLOT(setEnabled(bool)));
connect(checkBox3, SIGNAL(toggled(bool)), lineEdit3, SLOT(setEnabled(bool)));
However, inside a slot, you can get the QObject that sent the signal calling the sender() method. See QObject::sender()
Another option would be using a QSignalMapper:
// Set up a map, just for convenience.
std::map<QCheckBox*, QLineEdit*> widgetMap;
widgetMap.emplace(checkBox1, lineEdit1);
widgetMap.emplace(checkBox2, lineEdit2);
widgetMap.emplace(checkBox3, lineEdit3);
QSignalMapper signalMapper;
connect(signalMapper, SIGNAL(mapped(QWidget*)), SLOT(singleSlotHandlingThemAll(QWidget*)));
connect(checkBox1, SIGNAL(statusChanged(int)), signalMapper, SLOT(map()));
connect(checkBox2, SIGNAL(statusChanged(int)), signalMapper, SLOT(map()));
connect(checkBox3, SIGNAL(statusChanged(int)), signalMapper, SLOT(map()));
signalMapper->setMapping(checkBox1, checkBox1);
signalMapper->setMapping(checkBox2, checkBox2);
signalMapper->setMapping(checkBox3, checkBox3);
And here's the singleSlotHandlingThemAll() implementation:
void singleSlotHandlingThemAll(QWidget* widget)
{
// Provided widget is one of the check-boxes.
QCheckBox* checkBox = static_cast<QCheckBox8>(widget);
QLineEdit* relatedLineEdit = widgetMap[checkBox];
relatedLineEdit->setEnabled(checkBox->isChecked());
}
Related
Can i pass in any QWidget, in my Case a Subclass of a QTreeWidget to a Qt Slot?
I need to get text from the QTreeWidget in a member function of parent. Just passing in a QString as extra argument with connect(signalMapper, SIGNAL(mapped(QString)), parent, SLOT(changePicture(QString))); works fine, but now i want to pass in a subclassed QTreeWidget ifxTreeWidget *tree = new ifxTreeWidget(); to changePicture. I changed the Signature of changePicture to take an ifxTreeWidget as argument and the mapping like:
QSignalMapper * signalMapper = new QSignalMapper(parent);
signalMapper->setMapping(tree, tree)
and tried to connect it like:
connect(signalMapper, SIGNAL(mapped(QWidget)), parent, SLOT(changePicture(ifxTreeWidget)));
connect( tree, SIGNAL(clicked(QModelIndex)), signalMapper, SLOT(map()) );
but this leaves me with:
QObject::connect: No such signal QSignalMapper::mapped(QWidget) in...
Do i need to declare a Signal? How and where (if so)?
your connect statement is wrong. Try this
connect(signalMapper, SIGNAL(mapped(QWidget*)), parent, SLOT(changePicture(QWidget*)));
and then in the parent class
void ParentClass::changePicture(QWidget* widget)
{
ifxTreeWidget* tree = qobject_cast<ifxTreeWidget*>(widget);
if (tree) {
// do something with the tree now
}
}
I have got problem in Qt. I have to make two windows:
In the first one you can click on 10 buttons and each button have to add an item(name of the button) in comboBox in the second window. But I can't refer to this comboBox. I am out of any ideas :(
I tried to make the variable protected and public, but it doesn't work. I had included window2.h to window1 and I'm trying to do something like this:
//this is in window1
void window1::on_button1_clicked() {
window2::combo->addItem("button1");
}
You can connect the button's click signals to a slot in the second window. This slot will add the information to the combobox.
To do that, you will need to distinguish the signals from each other. The best way to do this is to use a QSignalMapper.
class window1 {
Q_OBJECT
... // your other definitions...
QSignalMapper* signalMapper;
};
window1::window1 (/*your constructor's parameters*/) {
signalMapper = new QSignalMapper(this); // Will map each buttons' signals to a signal with a QString parameter.
// You can do an iteration instead of this if your buttons are on a container.
signalMapper->setMapping(button1, QString("button1"));
signalMapper->setMapping(button2, QString("button2"));
// ...
signalMapper->setMapping(button10, QString("button10"));
// Same comment as above applies here.
connect(button1, SIGNAL(clicked()),
signalMapper, SLOT(map());
connect(button2, SIGNAL(clicked()),
signalMapper, SLOT(map());
// ...
connect(button10, SIGNAL(clicked()),
signalMapper, SLOT(map());
connect(signalMapper, SIGNAL(mapped(QString)),
window2, SLOT(updateCombo(QString)));
}
class window2 {
Q_OBJECT
... // your other definitions...
public slots:
void updateCombo(QString);
};
void window2::updateCombo(QString str) {
combo->addItem(str);
}
Alternatively to the QSignalMapper approach you could name the button objects in window1 (setName("buttonXYZ")), connect the clicked signals to a slot in window2 and ask for the object name of the sender (sender()->name()).
So in the receiving slot you could do :
m_combo->addItem(sender()->name());
or
if(sender()->name() == "Button1") {
m_combo->addItem("Foo");
}
After my post here : Associate signal and slot to a qcheckbox create dynamically I need to associate :
• The signal clicked() when I click on a qCheckBox to my function cliqueCheckBox(QTableWidget *monTab, int ligne, QCheckBox *pCheckBox)
To do so, I have to use QSignalMapper, after two hours of trying to understand how it works, I can't have a good result, here's the code I make, this is obviously wrong :
QSignalMapper *m_sigmapper = new QSignalMapper(this);
QObject::connect(pCheckBox, SIGNAL(mapped(QTableWidget*,int, QCheckBox*)), pCheckBox, SIGNAL(clicked()));
QObject::connect(this, SIGNAL(clicked()), this, SLOT(cliqueCheckBox(QTableWidget *monTab, int ligne, QCheckBox *pCheckBox)));
m_sigmapper->setMapping(pCheckBox, (monTab,ligne, pCheckBox));
QObject::connect(m_sigmapper, SIGNAL(clicked()),this, SLOT(cliqueCheckBox(QTableWidget *monTab, int ligne, QCheckBox *pCheckBox)));
Can you explain to me, how QSignalMapper works ? I don't really understand what to associate with :(
QSignalMapper class collects a set of parameterless signals, and re-emits them with integer, string or widget parameters corresponding to the object that sent the signal. So you can have one like:
QSignalMapper * mapper = new QSignalMapper(this);
QObject::connect(mapper,SIGNAL(mapped(QWidget *)),this,SLOT(mySlot(QWidget *)));
For each of your buttons you can connect the clicked() signal to the map() slot of QSignalMapper and add a mapping using setMapping so that when clicked() is signaled from a button, the signal mapped(QWidget *) is emitted:
QPushButton * but = new QPushButton(this);
QObject::connect(but, SIGNAL(clicked()),mapper,SLOT(map()));
mapper->setMapping(but, but);
This way whenever you click a button, the mapped(QWidget *) signal of the mapper is emitted containing the widget as a parameter.
First I will explain you how QSignalMapper works. Then I will explain you why you don't need it.
How QSignalMapper works:
Create s QSignalMapper. Lets assume that you want to assign an integer value to each checkbox, so every time you click on any checkbox, you will get a signal with the integer value assigned to it.
Connect the mapper signal to your SLOT, that you will implement:
connect(mapper, SIGNAL(mapped(int)), this, SLOT(yourSlot(int)));
Now you can write slot, that will take integer argument. The argument will be different for each checkbox you have.
While you create checkboxes, for each checkbox you need to do following:
mapper->setMapping(checkBox, integerValueForThisCheckbox);
connect(checkBox, SIGNAL(clicked()), mapper, SLOT(map()));
From now on, every time you click on a checkbox, it will emit clicked() signal to the QSignalMapper, which will then map it to the assigned integer value and will emit mapped() signal. You connected to that mapped() signal, so yourSlot(int) will be called with the proper integer value.
Instead of integers, you can assign QString, QWidget* or QObject* (see Qt documentation).
This is how QSignalMapper work.
You don't need it:
The QTableWidget *monTab is the single object, it doesn't change. Keep it as a class member field and use it from your slot function.
The QCheckBox *pCheckBox - you can get it by casting sender() to QCheckBox*.
Like this:
void supervision::yourSlot()
{
QCheckBox* pCheckBox = qobject_cast<QCheckBox*>(sender());
if (!pCheckBox) // this is just a safety check
return;
}
The sender() function is from QObject, which you do inherit from, so you have access to it.
The int linge (it's a line number, right?) - when you create checkboxes, you can store pointers to that checkboxes in QList class field and use it from your slot function find out which line is it, like this:
In class declaration:
private:
QList<QCheckBox*> checkboxes;
When creating checkboxes:
QCheckBox* cb = new QCheckBox();
checkboxes << cb;
In your slot function:
void supervision::yourSlot()
{
QCheckBox* pCheckBox = qobject_cast<QCheckBox*>(sender());
if (!pCheckBox) // this is just a safety check
return;
int linge = checkboxes.indexOf(pCheckBox);
}
If you want, you can skip that QList and use QSignalMapper and assign lines to checkboxes using mapper. That's just a matter of what you prefer.
First of all my apologies for big looking question but indeed it's not. I’m reading Foundation of qt development book and while reading fourth chapter author tells the basics of MDI window by showing this example :
MdiWindow::MdiWindow( QWidget *parent ) : QMainWindow( parent ) {
setWindowTitle( tr( "MDI" ) );
QWorkspace* workspace = new QWorkspace;
setCentralWidget( workspace );
connect( workspace, SIGNAL(windowActivated(QWidget *)), this, SLOT(enableActions()));
QSignalMapper* mapper = new QSignalMapper( this );
//my problem is in this line
connect( mapper, SIGNAL(mapped(QWidget*)), workspace, SLOT(setActiveWindow(QWidget*)) );
createActions();
createMenus();
createToolbars();
statusBar()->showMessage( tr("Done") );
enableActions();
}
His this para of explanation completely eluded me (is it me or others having problem understanding it too?) :
Next, a signal mapping object called QSignalMapper is created and
connected. A signal mapper is used to tie the source of the signal to
an argument of another signal. In this example, the action of the menu
item corresponding to each window in the Window menu is tied to the
actual document window. The actions are in turn connected to mapper.
When the triggered signal is emitted by the action, the sending action
has been associated with the QWidget* of the corresponding document
window. This pointer is used as the argument in the mapped(QWidget*)
signal emitted by the signal mapping object.
My question : I still don’t get what is signal mapper class, how it’s used and what's functionality it's doing in the example above?. Can anyone please explain the above para using easy terms? also It’d be awesome if you could please teach me about mapper class’s basics with simple example? possibly in layman’s term?
P.S : A confusion is when we have MDI window, do menu changes (though actions are disabled/enabled) e.g suppose for one particular document we have menu “File/close” and for other document we have “File/remaper” ?
The QSignalMapper is used to re-emit signals with optional parameters. In other words (from the documentation):
This class collects a set of parameterless signals, and re-emits them
with integer, string or widget parameters corresponding to the object
that sent the signal.
A good example (also from the doc - take a look at it) is set as follows:
Suppose we want to create a custom widget that contains a
group of buttons (like a tool palette). One approach is to connect
each button's clicked() signal to its own custom slot; but in this
example we want to connect all the buttons to a single slot and
parameterize the slot by the button that was clicked.
So imagine you have a number of buttons encapsulated in a class, say ButtonWidget, with a custom signal void clicked(const QString &text). Here is the definition:
class ButtonWidget : public QWidget {
Q_OBJECT
public:
ButtonWidget(QStringList texts, QWidget *parent = 0);
signals:
void clicked(const QString &text);
private:
QSignalMapper *signalMapper;
};
The constructor could then be defined like the following:
ButtonWidget::ButtonWidget(QStringList texts, QWidget *parent)
: QWidget(parent)
{
signalMapper = new QSignalMapper(this);
QGridLayout *gridLayout = new QGridLayout;
for (int i = 0; i < texts.size(); ++i) {
QPushButton *button = new QPushButton(texts[i]);
connect(button, SIGNAL(clicked()), signalMapper, SLOT(map()));
signalMapper->setMapping(button, texts[i]);
gridLayout->addWidget(button, i / 3, i % 3);
}
connect(signalMapper, SIGNAL(mapped(const QString &)),
this, SIGNAL(clicked(const QString &)));
setLayout(gridLayout);
}
So what happens here? We construct a grid layout and our buttons of type QPushButton. The clicked() signal of each of these is connected to the signal mapper.
One of the forces using QSignalMapper is that you can pass arguments to the re-emitted signals. In our example each of the buttons should emit a different text (due to the definition of our signal), so we set this using the setMapping() method.
Now all that's left to do is map the signal mapper to the signal of our class:
connect(signalMapper, SIGNAL(mapped(const QString &)),
this, SIGNAL(clicked(const QString &)));
Assume we have a testing class called TestClass then ButtonWidget can be used thusly:
TestClass::TestClass() {
widget = new ButtonWidget(QStringList() << "Foo" << "Bar");
connect(widget, SIGNAL(clicked(const QString &)),
this, SLOT(onButtonClicked(const QString &)));
}
void TestClass::onButtonClicked(const QString &btnText) {
if (btnText == "Foo") {
// Do stuff.
}
else {
// Or something else.
}
}
By using the signal mapper this way you don't have to declare and manage all the buttons and their clicked signals, just one signal pr. ButtonWidget.
The buttom line is that the signal mapper is great for bundling multiple signals and you can even set parameters when it re-emits them. I hope that gave some intuition about the usage of QSignalMapper.
Your example code
The explanation (your "para") states that all the actions are each individually mapped to a specific QWidget*. When triggering an action its respective QWidget* will be passed to the slot QWorkspace::setActiveWindow(QWidget*) of workspace, which in turn activates the widget.
Also note that the mapping from action to widget has to happen somewhere in your code. I assume it is done in createActions() or enableActions() perhaps.
A QSignalMapper allows you to add some information to a signal, when you need it. This object internally have a map like QMap<QObject*,QVariant>. Then you connect an object to it, and when the slot is called, it re-emit the signal with the associated value.
Workflow:
mySignalMapper:
[ obj1 -> 42 ]
[ obj2 -> "obiwan" ]
[ obj3 -> myWidget ]
connect(obj1,mySignal,mySignalMapper,SLOT(map())); // idem for obj2 and obj3
(obj2 emits "mySignal")
-> (mySignalMapper::map slot is called)
-> (sender() == obj2, associated data = "obiwan")
-> (mySignalMapper emits mapped("obiwan"))
I was going to add a more detailed example, but Morten Kristensen was faster than me ;)
I'm making a little chat messenger program, which needs a list of chat channels the user has joined. To represent this list graphically, I have made a list of QPushButtons, which all represent a different channel. These buttons are made with the following method, and that's where my problem kicks in:
void Messenger::addToActivePanels(std::string& channel)
{
activePanelsContents = this->findChild<QWidget *>(QString("activePanelsContents"));
pushButton = new QPushButton(activePanelsContents);
pushButton->setObjectName("pushButton");
pushButton->setGeometry(QRect(0, 0, 60, 60));
pushButton->setText("");
pushButton->setToolTip(QString(channel.c_str()));
pushButton->setCheckable(true);
pushButton->setChecked(false);
connect(pushButton, SIGNAL(clicked()), this, SLOT(switchTab(channel)));
}
(activePanelContents is a QWidget that holds the list.)
The point is that each button should call the switchTab(string& tabname) method when clicked, including the specific channel's name as variable. This implementation doesn't work though, and I haven't been able to find out how to properly do this.
For strings and integers, you can use QSignalMapper. In your Messenger class, you would add a QSignalMapper mapper object, and your function would look like:
void Messenger::addToActivePanels(std::string& channel)
{
activePanelsContents = this->findChild<QWidget *>(QString("activePanelsContents"));
pushButton = new QPushButton(activePanelsContents);
// ...
connect(pushButton, SIGNAL(clicked()), &mapper, SLOT(map()));
mapper.setMapping(pushButton, QString(channel.c_str()));
}
and after you have added all channels to your active panels, you call
connect(&mapper, SIGNAL(mapped(const QString &)), this, SLOT(switchTab(const QString &)));
Use QSignalMapper to pass variables;
QSignalMapper* signalMapper = new QSignalMapper (this) ;
QPushButton *button = new QPushButton();
signalMapper -> setMapping (button, <data>) ;
connect (signalMapper, SIGNAL(mapped(QString)), this,
SLOT(buttonClicked(QString))) ;
in slot i.e
void class::buttonClicked(QString data){
//use data
// to get sender
QSignalMapper *temp = (QSignalMapper *)this->sender();
QPushButton *btn = (QPushButton *)temp->mapping(data);
// use btn
}
Hope my ans may help you
Don't use the sender method unless you absolutely have to. It ties the function directly to being used only as a slot (can't be called directly). Retain the behavior of having the function accept a string and simply make a mechanism by which you can call it.
One method, among others you might find, is to leverage use of QSignalMapper. It will map objects to values and regenerate signals of the appropriate signature.
I would do it with "relay" objects:
Create TabSwitchRelay which is a sub-class of QObject with this constructor:
TabSwitchRelay::TabSwitchRelay(QObject *parent, Messanger * m, const QString & c)
: QObject(parent), m_messanger(m), m_channel(c)
{
}
It also has a slot clicked():
void TabSwitchRelay::clicked()
{
m_messager->switchTab(m_channel);
}
Now replace the line in your code that does connect with this:
TabSwitchRelay * tabRelay = new TabSwitchRelay(pushButton, this, channel);
connect(pushButton, SIGNAL(clicked()), tabRelay, SLOT(clicked()));
It's not tested but you get teh basic idea.
You could try having your switchTab slot take no argument and use QObject::sender to get the object that sent the signal.
Messenger::switchTab()
{
QObject* sender = this->sender();
QPushButton* button = qobject_cast<QPushButton*>(sender);
if(button)
{
// Do stuff...
}
}
if you're using Qt5, you can do it through lambda:
connect( sender, &Sender::valueChanged, [=](){ myMethod(5); } );
void myMethod(int value)
{
// do stuff
}