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.