Search code examples
c++qtsignals-slotsqlistwidgetqlistwidgetitem

QListWidget with custom widgets - not triggering itemClicked signal


I have listwidgetitem's that have a custom widget (step_widget) which contains 3 QPushButtons and a QLabel. When I press one of the buttons in the widget, it is not triggering itemClicked which i need to see the index of the listwidget that was clicked. If i click the QLabel, the signal is triggered and i am able to obtain the index. How can trigger the signal when one of the buttons is pressed? Why does it not trigger?

QListWidgetItem *item = new QListWidgetItem();
stepWidget *step_widget = new stepWidget();
ui->listWidget->addItem(item);
ui->listWidget->setItemWidget(item,step_widget);

Solution

  • The itemClicked() signal is not emmited because the QPushButton consumes the click event.
    There is the simplest solution I've found.
    First, in your class stepWidget add a signal that will be emitted when any QPushButton is clicked, for example:

    signals:
        void widgetClicked();
    

    Then connect the clicked() signal of every QPushButton with that signal. This code is in the constructor of the widget stepWidget:

    connect(ui->pushButton1, &QPushButton::clicked, this, &stepWidget::widgetClicked);
    connect(ui->pushButton2, &QPushButton::clicked, this, &stepWidget::widgetClicked);
    connect(ui->pushButton3, &QPushButton::clicked, this, &stepWidget::widgetClicked);
    

    To test it I added 10 widgets to a QListWidget that reside in the MainWindow class. You must connect that signal to a slot (in this case I use a C++11 Lambda Function, but you can create a slot instead, it's fine) where you establish the current item as the item under the widget. This code is located in the constructor of MainWindow:

    for (int i = 0; i < 10; ++i) {
        QListWidgetItem *item = new QListWidgetItem;
        stepWidget *step_Widget = new stepWidget;
        ui->listWidget->addItem(item);
        ui->listWidget->setItemWidget(item, step_Widget);
        connect(step_Widget, &stepWidget::widgetClicked, [=]() {
            ui->listWidget->setCurrentItem(item);
        });
    }
    

    Now, this will not make the itemClicked() signal to be emitted because the user not clicked the item. But I advice you to connect instead the signal currentRowChanged() as it will be emitted with this code and also it has the advantage that it allows you to directly obtain the row. Once you have the row you can obtain the item and the widget, evidently.
    However, this procedure has the inconvenient that the currentRowChanged() signal will be emitted even when the user selects a row with the keyboard or when the user press and slide the mouse over the QListWidget without releasing it.
    On workaround could be, for example, using a flag as an attribute for your MainWindow class that is always false except during this connection:

    connect(ste_pWidget, &stepWidget ::widgetClicked, [=]() {
        buttonPressed = true;
        ui->listWidget->setCurrentItem(item);
        buttonPressed = false;
    });
    

    Then you must manipulate two signals from the QListWidget: clicked() and currentRowChanged(). I choose clicked() instead of itemClicked() because it gives almost directly access to the row:

    void MainWindow::on_listWidget_clicked(const QModelIndex &index)
    {
        ui->statusBar->showMessage(tr("Row clicked: %1").arg(index.row()), 1000);
    }
    
    void MainWindow::on_listWidget_currentRowChanged(int currentRow)
    {
        if (buttonPressed) {
            ui->statusBar->showMessage(tr("Row clicked: %1").arg(currentRow), 1000);
        }
    }