3

Edit #2: Figured out the lambda call syntax I need!

[=](int value) { (this->*callback_function)(i, value); }

Still looking for suggestions on how to better orchestrate this task... This seems way too messy.


Edit: Updated code, almost there but still hit a snag.

template <typename F>
void Dialog::create_spinbox_column(const int &NUM_ROWS, QFrame *&frame,
                                   QLabel *label_arr[],
                                   QSpinBox *spinbox_arr[],
                                   F callback_function) {
    frame = new QFrame();
    QGridLayout *layout = new QGridLayout;
    for(int i = 0; i < NUM_ROWS; i++) {
        int row = i+1;
        QString label_text = QObject::tr("Line %1:").arg(row);
        label_arr[i] = new QLabel(label_text);
        spinbox_arr[i] = new QSpinBox;
        layout->addWidget(label_arr[i], row, 1);
        layout->addWidget(spinbox_arr[i], row, 2);
        QObject::connect(spinbox_arr[i], qOverload<int>(&QSpinBox::valueChanged), this,
                        [=](int value) { callback_function(i, value); } );
    }
    frame->setLayout(layout);
}

I'm getting an error

error: must use '.*' or '->*' to call pointer-to-member function in 'callback_function (...)', e.g. '(... ->* callback_function) (...)'
                         [=](int value) { callback_function(i, value); } );
                                                       ^

I've tried a few things like this->callback_function(i, value), but nothing I've tried yet has worked. I definitely do NOT want the callback to be static, that makes no sense, it should be called within the context of the current Dialog class which is calling the create_spinbox_column function.


Below is the original post:


I recently started using Qt to convert one of my programs from a text-based input to a GUI input, but I'm hitting a few snags. I could probably use both high-level design feedback of how to do this "properly" in Qt, and specific feedback for how to debug my current problem.

First, a quick description of my current task (if you don't care, skip straight to my code below): I will have a large number of spinboxes which I want to be neatly organized and connected to a callback function of the form void callback_with_id(int id, int value), where id would be a unique number identifying which spinbox's value was changed, and value of course would be the new value of that spinbox.

I've managed to create a visual layout that I like, but trying to connect the spinboxes to a callback function is causing me troubles. Here's my code, which won't compile at the moment. Feel free to pretend ACCOUNT_DATA_COLS = 1 for now.

void Dialog::create_spinbox_column(const int &NUM_ROWS, QFrame *&frame,
                                   QLabel **label_arr,
                                   QSpinBox **spinbox_arr,
                                   void (*callback_function)(int, int)) {
    frame = new QFrame();
    QGridLayout *layout = new QGridLayout;
    for(int i = 0; i < NUM_ROWS; i++) {
        int row = i+1;
        QString label_text = QObject::tr("Line %1:").arg(row);
        label_arr[i] = new QLabel(label_text);
        spinbox_arr[i] = new QSpinBox;
        layout->addWidget(label_arr[i], row, 1);
        layout->addWidget(spinbox_arr[i], row, 2);
        //QObject::connect(spinbox_arr[i], SIGNAL(valueChanged(int)), this,
        //                [=](int value) { callback_function(i, value); };
    }
    frame->setLayout(layout);
}

(I commented out the connect line because I was getting a compiler error.)

void Dialog::create_account_data_grid() {
    account_data_box = new QGroupBox(tr("Account Data"));
    QHBoxLayout *layout = new QHBoxLayout;
    for (int i = 0; i < ACCOUNT_DATA_COLS; i++) {
        QFrame *frame;
        QLabel *label_arr[ACCOUNT_DATA_ROWS];
        QLabel *spinbox_arr[ACCOUNT_DATA_ROWS];
        create_spinbox_column(ACCOUNT_DATA_ROWS, frame, label_arr, spinbox_arr,
                              callback_with_id);
        // int col = i + 1;
        layout->addWidget(frame);
    }
    account_data_box->setLayout(layout);
}

and my callback function:

void Dialog::callback_with_id(int id, int value) {
    std::cout << "callback_with_id(" << value << ", " << id << ")" << std::endl;
}

I'm getting the error

error: no matching function for call to 'Dialog::create_spinbox_column(const int&, QFrame*&, QLabel* [8], QLabel* [8], <unresolved overloaded function type>)'
                               callback_with_id);
                                               ^

I'm sure I'm doing more than one thing wrong here, would be grateful for any feedback.

Apollys supports Monica
  • 2,938
  • 1
  • 23
  • 33
  • The way you are doing this seems unusual for Qt. – drescherjm Dec 25 '17 at 00:23
  • Your function `void Dialog::callback_with_id` either must be a class `static`, or you need to pass in a [pointer to a member function](https://stackoverflow.com/questions/2402579/function-pointer-to-member-function). – Ken Y-N Dec 25 '17 at 00:33

1 Answers1

4

The problem with the commented line is you're mixing two different styles of connections: "runtime" (using SIGNAL and SLOT macros) and "compile-time" (the one that allows you to just pass a callable directly).

Usually the syntax for the latter is

QObject::connect(spinbox_arr[i], &QSpinBox::someSignal, this,
                [=](int value) { callback_function(i, value); };

but you cannot do this for the valueChanged signal directly, as it has two overloads, hence you have to use qOverload, so the resulting code would look like this:

QObject::connect(spinbox_arr[i], qOverload<int>(&QSpinBox::valueChanged), this,
                [=](int value) { callback_function(i, value); };

This requires some C++14 support. If you don't have it available, then you'd have to use QOverload<int>::of(&QSpinBox::valueChanged) instead.

Also, as pointed out in the comments, make sure that either:

  1. callback_with_id is a static function,
  2. or make create_spinbox_column templated on the functor type,
  3. or just change the last parameter of create_spinbox_column to std::function<void(int, int)> and pass a lambda capturing this at call site.

Please let me know if you'd like me to elaborate on any of these points!

0xd34df00d
  • 1,496
  • 1
  • 8
  • 17