QT how to emit a signal in this case - c++

I need to develop a Sudoku game. After reading a text file containing the number values, I create 9*9 widgets.
If (the value is already set then I instantiate to a qlabel containing the number,
else I instantiate a combobox containing the possible values of each case).
Until here everything is OK.
The problem is that when a value is chosen from the combobox, I need to draw it in a square in my view (MVC). The problem is how can I know which one was chosen?
The only signal can I use from combobox signals is currenttextchanged(QString), but I'll not know which combo made that signal.
The ideal for me would be something like this SIGNAL(curretextchanged(QString, int, int)), but I don't know if I can define a new signal?
Here is some code:
QWidget *tab[9][9];
SudModel *modele = ???;
QComboBox *combobox = new QComboBox();
combobox->setStyleSheet("border: 1px solid red");
int tmp = modele->valuesof(i, j).size();
for (int s = 0; s < tmp; s++) {
combobox->addItem(QString::number(modele->valuesof(i, j)[s]));
}
connect(combobox, SIGNAL(currentTextChanged(QString)), this, SLOT(update()));
tab[i][j] = combobox;

One solution would be to attach that extra information when you connect to the signal for a specific combobox.
For example, say you have your function update(QString text, int x, int y) then you could attach the signal to a lambda that calls the function with the extra arguments, captured at connect time. Something like this:
connect(combobox, &QComboBox::currentTextChanged, [x, y, this](const QString& text){ this->update(text, x, y); });
That would then call the update functions with the x and y values captured when the connection was made along with the text argument that originated from the signal.

You can ask the slot for a sender()
QObject *QObject::sender() const
Returns a pointer to the object that sent the signal, if called in a
slot activated by a signal; otherwise it returns 0. The pointer is
valid only during the execution of the slot that calls this
function from this object's thread context.

You can use QSignalMapper.
In this you map every item with mapper with any specific string. Now when any mapped item emits signal you can know which item emitted it by the string.
You can use the same with minor changes like using Signal 'currentTextChanged' as mentioned. But the same result can be obtained by using 'currentIndexChanged' signal with some modifications.
signalMapper = new QSignalMapper(this);
for (int i = 0; i < 2; ++i) {
QComboBox *combo = new QComboBox();
connect(combo, SIGNAL(currentIndexChanged()), signalMapper, SLOT(map()));
signalMapper->setMapping(combo, "combo" + i);
}
connect(signalMapper, SIGNAL(mapped(QString)),
this, SIGNAL(indexChanged(QString)));
NOTE : (source Qt Assistant)
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

Related

How to pass a QString to a Qt slot from a QMenu via QSignalMapper or otherwise

I have a QMenu with many submenus. These are dynamically created i.e. the names menus come from a db and created in a loop. Now i wanted to fire the same slot triggered() or similar when a menu is clicked, but i needed the QString menu name to be passed to slot so i could perform menu specific actions. I have tried this i.e. passing a QAction * to the triggered event and used setData but i am getting the run time error.
object::connect: No such signal QAction::triggered(QAction *)
for(int j=0; j<channelTypes[i].getNumChannels() ; j++){
QAction *subMenuAct = subMenu->addAction(tr(c_name)); // c_name the menu name
subMenuAct->setData(ch_name);
connect(subMenuAct, SIGNAL(triggered(QAction *)), this, SLOT(playChannel(QAction *))); // playChannel is the slot
}
void <ClassName>::playChannel(QAction *channelAction)
{
QString str = channelAction->data().toString();
qDebug() << "Selected - " << str;
}
Alternately, i have also tried QSignalMapper where signalMapper is a data member initialized in the constructor
signalMapper = new QSignalMapper(this);
and
for(int j=0; j<channelTypes[i].getNumChannels() ; j++){
QAction *subMenuAct = subMenu->addAction(tr(c_name));
connect(subMenuAct, SIGNAL(triggered()), signalMapper, SLOT(map()));
signalMapper->setMapping(subMenu, ch_name);
connect(signalMapper, SIGNAL(mapped(QString)), this, SLOT(playChannel(QString)));
}
In the second case, i don't get any error, however the slot function playChannel is not being called. Would really appreciate if some one could help resolving it.
Update 1: The only difference that i see from other examples i have seen is that usually people are connecting signals from several widgets to a single slot (say different buttons). In my case i am connecting several sub menus (with different names) to a single slot. Should this make any difference?
Update 2: It worked after the correction suggested in the solution below for QSignalMapper. Also the fact that i was using SubMenu as argument to setMapping , where as MenuAction item should have been used instead. But now i am getting event fired multiple times i.e. as many times as there are entries in the main menu for the selected sub menu category. If channel type is English (main menu) with four entries), HBO, star movies etc. (sub menu), and i choose HBO, then event is fired four times with string HBO. It works fine if i create a separate signal mapper for each submenu. But i was hoping a single mapper should be used and i am doing something incorrectly here. Some more details in the comments to the answer.
After adding the QAction to the menu, you only have to connect QMenu to the slot. You don't connect each action individually to the slot:
for(int j=0; j<channelTypes[i].getNumChannels() ; j++){
ch_name = <name from the database for the channel j>;
QAction *subMenuAct = subMenu->addAction(tr(ch_name));
subMenuAct->setData(ch_name);
}
connect(subMenu, SIGNAL(triggered(QAction *)),
this, SLOT(playChannel(QAction *)), Qt::UniqueConnection);
As I don't know how you if you delete subMenu each time the dynamic menu is filled, the Qt::UniqueConnection ensure that the slot won't be reconnected multiple times.
For the signal mapper version, you should only connect the actions to the mapper in the loop. The connection from the mapper to the slot should only be done once.
for(int j=0; j<channelTypes[i].getNumChannels() ; j++){
ch_name = <name from the database for the channel j>;
QAction *subMenuAct = subMenu->addAction(tr(ch_name));
connect(subMenuAct, SIGNAL(triggered()), signalMapper, SLOT(map()));
signalMapper->setMapping(subMenuAct, ch_name);
}
connect(signalMapper, SIGNAL(mapped(QString)), this, SLOT(playChannel(QString)));
And for that case, the slot playChannel should accept a QString instead of a QAction*.

Qt QAction Dynamic Array Connect

I want to create a QMenu that contains QAction objects that are checkable. Once an action is checked, it will fire and enable drawing of some 3D object. However, the number of 3D objects depends on what files to be loaded. Thus, this QMenu have dynamic number of QAction objects. Suppose we have 10 3D objects with names "1", "2", ... "10" and thus the QAction objects in the QMenu would be displayed as "1", "2", ... "10". When one of these are checked, 3D object of that name will be enabled to display.
Code to generate dynamic QAction objects:
QStringList labels = defaultScene->getLabels();
for(int i=0; i<labels.size(); i++){
QAction* labelAction = new QAction(labels[i], this);
labelAction->setToolTip("Trace Marker " + labels[i]);
labelAction->setStatusTip("Trace Marker " + labels[i]);
labelAction->setCheckable(true);
traceMenu->addAction(labelAction);
}
My question is, how do I connect these QAction objects? Specifically, I have an array of bool in defaultScene that will be toggled as the QAction are toggled. How am I to know which QAction is firing? The SIGNAL of QAction on toggle only passes through a bool. Ideally, I would have a single function in defaultScene:
void toggleObject3D(int index){
if(index >= 0 && index < visibleSize){
visible[index] = !visible[index];
}
}
So to make this work, I would need some sort of SIGNAL from traceMenu that would fire an int variable. I am unaware of such SIGNAL.
You can use QSignalMapper (Link in the documentation)
The idea is to associate each QAction with an index, and then use the mapped(int) signal from QSignalMapper. Of course, we need to map the toggled signal.
So first, define your method toggleObject3D as a slot.
Then, when generating the instances of QAction, create the QSignalMapper and associate each action with it:
QStringList labels = defaultScene->getLabels();
QSignalMapper *mapper = new QSignalMapper(this);
for(int i=0; i<labels.size(); i++){
QAction* labelAction = new QAction(labels[i], this);
labelAction->setToolTip("Trace Marker " + labels[i]);
labelAction->setStatusTip("Trace Marker " + labels[i]);
labelAction->setCheckable(true);
traceMenu->addAction(labelAction);
// Map this action to index i
mapper->setMapping(labelAction, i);
// Associate the toggled signal to map slot from the mapper
// (it does not matter if we don't use the bool parameter from the signal)
connect(action, SIGNAL(toggled(bool)), mapper, SLOT(map()));
}
// Connect the QSignalMapper map() signal to your method
connect(mapper, SIGNAL(mapped(int)), this, SLOT(toggleObject3D(int)));
And it should work :)

Qt, PushButton, id attribute? Any way to know which button was clicked

void MainWindow::addRadioToUI()
{ int button_cunter=4;
while(!database.isEmpty())
{ button_cunter++;
QPushButton *one = new QPushButton("Play: "+name(get_r.getTrackId()));
one->setIcon(QIcon(":/images/play_button.png"));
one->setMaximumWidth(140);
one->setFlat(true);
QGroupBox* get_rGB = new QGroupBox("somethink");
QFormLayout* layout = new QFormLayout;
if(button_cunter%5 == 0){
layout->addWidget(one);
}
get_rGB->setLayout(layout);
scrollAreaWidgetContents->layout()->addWidget(get_rGB);
}
}
I have a few QPushButtons which are added automaticlly.
Is there a way to add "id attribute or sth else" to button and next know which button was clicked? I have different action for each button.
QApplication offers sender() which contains which object sent the signal. So you can do:
//slot, this could also be done in a switch
if(button[X] == QApplication::sender()){
doX();
}else if(button[Y] == QApplication::sender()){
doY();
}
http://doc.qt.io/qt-4.8/qobject.html#sender
QSignalMapper is pretty good for this type of thing.
You would define your slot like this for instance:
public slots:
void clicked(int buttonId); // or maybe trackId
Then add a QSignalMapper* member to your class and connect it to that slot:
signalMapper = new QSignalMapper(this);
connect(signalMapper, SIGNAL(mapped(int)),
this, SLOT(clicked(int)));
In the addRadioToUI, after creating your push button, do:
signalMapper.setMapping(one, button_cunter);
// or trackId if that's more practical
If all you need is a pointer to the object that triggered the signal though, you can use the static QOjbect::sender function in your slot to get a handle to that.
Use QButtonGroup. It takes id as a parameter when a button is added and provides the id to a slot when a button in the group is pressed.

Problem with QSignalMapper and QAction never triger the Slot

Hi i try to bind slot with argument to QAction triggered SIGNAL
i have this code ,the context menu working great . BUT the OpenPublishWin never triggered .
void MyApp::ShowContextMenu(const QPoint& pos) // this is a slot
{
QString groupID;
QPoint globalPos = ui.treeView_mainwindow->mapToGlobal(pos);
QModelIndex modelIndx = ui.treeView_mainwindow->indexAt(pos);
groupID = modelIndx.model()->index(modelIndx.row(),0,modelIndx.parent()).data(Qt::UserRole).toString();
QMenu myMenu;
OpenPublishAction = new QAction(tr("Send"), this);
myMenu.addAction(OpenPublishAction);
connect(OpenPublishAction, SIGNAL(triggered()),m_SignalMapper, SLOT(map()) );
m_SignalMapper->setMapping(OpenPublishAction,groupID);
connect(m_SignalMapper, SIGNAL(mapped(QString)), this, SLOT(OpenPublishWin(QString)));
QAction* selectedItem = myMenu.exec(globalPos);
}
void MyApp::OpenPublishWin(QString gid)
{
WRITELOG(gid)
}
A quick look at the Qt docs for QSignalMapper (assuming that is what you're using based on the question title) states that the parameter for the mapped signal is const QString&. I can't recall if the parameter needs to be exact in this case for the connection but it may be a factor.
Additionally, double check that your connects are being made by wrapping them in an assert or some form of verify. Qt will also print out to the console if a connection cannot be made.

QSignalMapper and original Sender()

I have a bunch of QComboBoxes in a table. So that I know which one was triggered I remap the signal to encode the table cell location (as described in Selecting QComboBox in QTableWidget)
(Why Qt doesn't just send the cell activated signal first so you can use the same current row/column mechanism as any other cell edit I don't know.)
But this removes all knowledge of the original sender widget. Calling QComboBox* combo = (QComboBox* )sender() in the slot fails, presumably because sender() is now the QSignalMapper.
I can use the encoded row/column to lookup the QComboBox in the table widget but that seems wrong. Is there a more correct way to do it?
e.g.:
// in table creator
_signalMapper = new QSignalMapper(this);
// for each cell
QComboBox* combo = new QComboBox();
connect(combo, SIGNAL(currentIndexChanged(int)), _signalMapper, SLOT(map()));
_signalMapper->setMapping(combo, row);
// and finally
connect(_signalMapper, SIGNAL(mapped(int)),this, SLOT(changedType(int)));
// slot
void myDlg::changedType(int row)
{
QComboBox* combo = (QComboBox* )sender(); // this doesn't work !!
}
EDIT: Added for future search: there is a new book "Advanced Qt Programming" by Mark Summerfield that explains how to do this sort of thing.
Why not connect the QComboBox's signal straight to your slot?
QComboBox *combo = ...
connect(combo, SIGNAL(currentIndexChanged(int)), this, SLOT(changedType(int)));
And then in your slot you can use the sender() method to retrieve the QComboBox that was changed.
void myDlg::changedType(int row)
{
QComboBox *combo = qobject_cast<QComboBox *> sender();
if(combo != 0){
// rest of code
}
}
Alternatively, to use the QSignalMapper method you would just need to change your slot to use the mapping you set up:
void myDlg::changedType(int row)
{
QComboBox *combo = qobject_cast<QComboBox *>(_signalMapper->mapping(row));
if(combo != 0){
// rest of code
}
}
I don't know exact answer, but maybe you should use: QComboBox* combo = qobject_cast(sender()) instead of QComboBox* combo = (QComboBox* )sender(). Someting like this:
QObject* obj = sender();
QComboBox* combo = qobject_cast<QComboBox*>(obj);
if(combo)
{
doSomethingWithCombo(combo);
}
else
{
// obj is not QComboBox instance
}
But maybe QSignalMapper really substitutes itself instead of real sender...