Search code examples
c++qtsignals-slots

How to pass coordinates of button in matrix to slot on click?


I'm beginning in C++ and Qt. I have a matrix of QPushButtons and I want to handle the click event for them. Problem is I'm not being able to tell the slot the coordinates os the button in the array so that I can play with them. I've succeed in passing a integer to my slot, but not both coordinates. I'm not sure if I'm making myself clear enough... That's my problematic code:

for (int i = 0; i < mapSize_x; i++) {
    for (int j = 0; j < mapSize_y; j++) {
        buttonsArray[i][j] = new QPushButton();
        ui->mainLayout->addWidget(buttonsArray[i][j], i, j);

        connect(buttonsArray[i][j], SIGNAL(clicked()),
                signalMapper, SLOT(map()));
        signalMapper->setMapping(buttonsArray[i][j], i, j); // here
    }
}
connect(signalMapper, SIGNAL(mapped(int, int)),
        this, SLOT(buttonClick(int, int)));

setMapping only accepts two parameters, and I want to pass three. Is there a workaround? I've googled a lot and still can't find an answer. I've also tried to pass the QPushButton object instead of the coordinates, but unsuccessfully as well. Thanks in advance.


Solution

  • Consider using a QHash to store your pushbuttons, keyed by the buttons themselves and pointing to a pair containing their row and column. You should then be able to set your mapping based on the widget pointer and then look up the associated row and column when the signal mapper emits its signal. For example, declare a class data member as follows:

    QHash<QPushButton*, QPair<int, int> > buttonHash;
    

    Then your code above could become

    for (int i = 0; i < mapSize_x; i++) {
        for (int j = 0; j < mapSize_y; j++) {
            QPair<int, int> gridPair(i, j);
            QPushButton* button = new QPushButton();
            buttonHash.insert(button, gridPair);
            ui->mainLayout->addWidget(button, i, j);
    
            connect(button, SIGNAL(clicked()),
                    signalMapper, SLOT(map()));
            signalMapper->setMapping(button, qobject_cast<QWidget*>(button));
        }
    }
    connect(signalMapper, SIGNAL(mapped(QWidget*)),
            this, SLOT(buttonClick(QWidget*)));
    

    Finally, your buttonClick slot would become the following:

    void SomeClass::buttonClick(QWidget* widget) {
       QPair<int, int> pair = buttonHash.value(qobject_cast<QPushButton*>(widget));
       int myRow = pair.first;
       int myColumn = pair.second;
    
       ...
    }
    

    There are also at least 2 other ways of tackling this problem:

    1. You could try to combine the row and column into a string and use QSignalMapper::setMapping(QObject*, const QString&). This would then require some logic to pull the row and column out of the string in the buttonClick slot.
    2. You could try to combine the row and column into a single integer by using bit shifting and a bitwise OR. You could then rely on QSignalMapper::setMapping(QObject*, int). This would require some logic to pull the row and column out of the integer in the buttonClick slot.

    I won't go into detail about these other solutions because they're somewhat ugly. The solution I provided above remains the most intuitive.