1
votes

My application shows a MainWindow ui, which gain new ui sections at run time. The main window's ui is created at compile time, while the new widgets are loaded at run time. (with .ui inside qrc, and uiloader)

My problem: I cannot get the signals and slots of the run-time widgets to trigger, despite Qt reporting that the QObject::connect() all succeeded.

I have read over a lot of troubleshooting advice on signals and slots here and elsewhere (like these 20 tips) but I am starting to suspect this is something unique to using uitools/uiloader to get runtime ui elements. This is because the Main Window's signals and slots all work.

I am using Qt 5.2.1, Qt Creator 3.0.1 on Windows 7 Professional. I also use boost threads and no QThreads.

Pictures tell a thousand words.

MainWindow.ui

main window, contains a button and a text edit

ChildWidget.ui

child widget, which looks a lot like main window, with an extra label

Combined.

2 Child Widgets inside the Main Window's Vertical Layout

combined, two child widgets sit inside the main window's Vertical Layout

In this instance of the program, I hard coded a spawn of two child windows inside main window.

I connected the Main Window's PushButton click signal to a test() slot, which writes "OH PUSH" on the Main Window's text box. The same signal is connected to all the child window's test() slots, which should do the same on all of their text boxes. Nothing shows up.

Hidden inside the program is a Boost::Thread with a custom QObject. Its signal is triggered every 2 seconds and is likewise connected to all test() slots. Only the Main Window shows "OH PUSH".

I also tried connecting the Toggle Pause Button's click with all slots. In this case, not even a signal is fired when clicked.

No code for now, because I am hoping it is a simple problem a pro can identify quickly on sight. I will show code if that is not the case.


EDIT: While writing up an edit that includes code, I inadvertently discovered the solution. I will show the write up here and post the explanation as to why my program did not work.

The main players are MainController, MainWindow, ChildController, ChildWidget

/*
main spawns a mainController.
mainController::ctor spawns a QApplication.
mainController::ctor spawns a MainWindow.
The MainWindow spawns its ui.  ( `ui->setupUI( this );` )

// back in main, after spawning MainWindow
main calls mainController's init()
mainController::init() spawns a ChildController.
ChildController::ctor spawns a boost::thread that idles.
mainController::init() spawns a ChildWidget.* // see below for code
mainController::init() connects MainWindow with all ChildWidgets, as well as all signals/slots**. // see below for the code
mainController::init() loops to spawn another pair of ChildController/ChildWidget, if required.

// back in main, after calling mainController's init()
main calls mainController's run()
mainController::run() calls MainWindow's show()
mainController::run() calls (every) ChildController's unpause(), so the boost::threads stop idling and starts emitting signals every 2 seconds.
mainController::run() calls QApplication's exec() and blocks till the end of the program.
*/

*ChildWidget is spawned.

class ChildWidget : public QWidget
{
    Q_OBJECT

public:
    ChildWidget( QWidget* parent = 0 );

    QWidget* get_pointer_to_ui();

public slots: 
    void slot_push_text( std::string text );
    void slot_push_image( const std::vector< unsigned char > image );
    void slot_test();

private slots:
    void on_buttonPause_clicked();

private:
    QPlainTextEdit* uiText;
    QLabel* uiImage;
    QPushButton* uiPauseButton;

    QWidget* ui;

private:
    QWidget* load_ui(); // called on construct
};

ChildWidget::ChildWidget( QWidget* parent )
    : QWidget( parent )
{
    QWidget* widg = load_ui();

    uiImage = widg->findChild< QLabel* >( "labelImage" );
    uiText = widg->findChild< QPlainTextEdit* >( "textConsole" );
    uiPauseButton = widg->findChild< QPushButton* >( "buttonPause" );

    ui = widg;

    QMetaObject::connectSlotsByName( this );
}

QWidget* ChildWidget::load_ui()
{
    QUiLoader loader;

    QFile file(":/widgets/ChildWidget.ui");
    file.open(QFile::ReadOnly);

    QWidget *widg = loader.load(&file, this);
    file.close();

    return widg;
}

**The main controller connects all the signals and slots.

void MainController::init()
{
    //FIXME. hard code to spawn two for now.
    for( int i = 0; i < 2; ++i )
    { // routine spawning the child controllers

        /* **** construct the child controllers, which internally contains a small module that emits sigs **** */
        boost::shared_ptr< ChildController > v1 = boost::shared_ptr< ChildController >( new ChildController );

        /* **** each child controller is to have 1-1 corresponding ui element. **** */
        /* We shall spawn ONE ChildWidget for each ChildController.
         * Each ChildWidget itself contains pointer to a widget generated via runtime uic.
         * This widget needs to be passed to The Main Controller so it can be added to its layout.
        */
        boost::shared_ptr< ChildWidget > newWidget = boost::shared_ptr< ChildWidget >( new ChildWidget( w.get() ) );
        // a new ChildWidget is created, whose parent is defined to be the MainWindow, w

        // the main window will now attempt to incorporate the new widget in its layout
        QWidget* newWidgUi = newWidget->get_pointer_to_ui(); // dangerous? returns a pointer to the ui loaded with `loader.load(&file, this)` above
        this->w->add_view( newWidgUi ); // calls MainWindow's VerticalLayout addWidget()


        // all signals and slots are to be connected next.
        if( newWidget != 0 )
        {
            // if main window button pressed, send text to this ChildWidget's text window
            bool isTest = QObject::connect( w.get(), &MainWindow::sig_test,
                                          newWidget.get(), &ChildWidget::slot_push_text );

            // if main window button pressed, send text to the main window's text window.
            QObject::connect( w.get(), &MainWindow::sig_test,
                                          w.get(), &MainWindow::slot_test );

            // if the boost::thread signals, deliver some text to this ChildWidget
            QObject::connect( v1->widget.get(), &ChildController::sig_push_text,
                              newWidget.get(), &ChildWidget::slot_push_text );

            // if this child window's button is pressed, show some text on the main window.
            QObject::connect( v1->widget.get(), &ChildController::sig_push_text,
                         this->w.get(), &MainWindow::slot_test );

            this->vControllers.push_back( v1 );
        }

    }

    // all the connections above succeed.
    // but this is where my problem was.
    // newWidget was not stored beyond this function, so boost::shared_ptr destroys it
}

Misc notes: I have done the following in MainController.cpp

Q_DECLARE_METATYPE( std::string )
Q_DECLARE_METATYPE( std::vector< unsigned char > )

and added

qRegisterMetaType< std::string >( "std::string" );
qRegisterMetaType< std::string >( "std::vector< unsigned char >" );

in MainController's ctor.

I put this as the first line in main()

Q_INIT_RESOURCE( dynwidgets ); // dynwidgets.qrc contains the ChildWindow's .ui file

At first, I put code here but it got really messy so now it is just a snippet at an area of interest, and descriptive text for the rest.

1
Perhaps would be nice if you showed the creation of the child widgets and the signal/slot connections.thuga

1 Answers

2
votes

It turned out I made a basic mistake that had nothing to do with Qt.

When spawning the ui element via QUiLoader, I accompanied it with a spawn of a companion widget. This widget is used in emitting signals from the ui and to make changes to the ui when its slots are triggered.

All the connections were correctly made, but will not fire simply because the companion widget was accidentally deleted, invalidating all the connections.

  • it is perfectly valid to mix compile time ui with run time loaded ui
  • the signals and slots generated in run time behave the same way as any other
  • manage the lifetime of the widgets carefully