Signal/Slot between a form class and the main window class
-
Hi,
I have a QT application that, when the user clicks the new option on the file menu, a form pops up and collects some info. What I would like to happen is for the clicked() signal on the "ok" button to trigger creation of a new
tab containing a specific layout (layout already finished).I've created several basic signal/slot communications within the program, but this is the first situation where I need to connect two objects that aren't "connected".
Basically, I have a MainWindow class and a FormClass (instance is a member of the MainWindow class), and I want to use the form "ok" signal to trigger a slot in the MainWindow class. General reading on the site and around the forums seems to indicate I should be able to use something like this:
Sender: Form Instance
Signal: clicked( QDialogButton::Ok )
Receiver: MainWindow
Slot: MainWindow->SlotFunction()I have the connect statement in the constructor of the form object, but it claims it doesn't know what MainWindow is. Do I need to pass a pointer to MainWindow as a parameter to the constructor, or is there
a better way? Ideally, I'd like to do this outside of the constructor because this form has multiple uses (situations where it wouldn't create the new tab as it is only being used to edit data on a tab that is already there).Note: QMainWindow is not a global variable, the instance is created in main(). The form instance is a private member of the QMainWindow class.
This is slightly more complex than the tutorial (which does make a small reference to it being possible), but far more basic than most of the posts I've been reading on the topic. Hence this post :).
-
You make the connect of the signal and the slot at the place in your code where you have references to both parties involved in the connection. In your case, that could be in two places:
at the place where you create the dialog in response to the QAction that was triggered by the menu. Here, the main window is represented by the this pointer, and the dialog you just created so you also have a pointer to that.
if your dialog takes a parent argument, and you use the main window as the parent, then you might also make the connection in the constructor of the dialog, if you like.
Personally, I prefer method 1), as that shields the dialog from having to know anything about the main window it is used with.
-
I'm in agreement on the technique, but I'm still running into a problem with it.
Here's the function that does the work:
@
void VCMainWindow::CreateNewDataTab( VCWIDGETPTR ParentTabBar ){ /BEGIN FUNCTION CREATENEWDATATAB()/
VCWIDGETPTR NewDataTab = NULL;
NewDataTab = new VCWIDGET; //Creates a widget for the new tab page.
MMNewForm = new VCMPDataInputForm; //Creates an instance of the data input form.VCTabBar->addTab( new VCMPDataTabLayout( this ), "New Tab");
//Warning here that ui was a private member, so I cheated (temporarily) and made VCMainWindow a friend
//class of VCMPDataInputForm.
connect( MMNewForm->ui->buttonBox->button( QDialogButtonBox::Ok ), SIGNAL( clicked() ), this, SLOT( ) );//Shows the new tab.
VCTabBar->show();} /CLOSE FUNCTION CREATENEWDATATAB()/
@There is one header included in this source file, and here are the contents:
@
#ifndef __VCWINDOWS_H
#define __VCWINDOWS_H#include "C:\VCCalculator\VCCalculator\header_files\vcsysteminfo.h"
#include "C:\VCCalculator\VCCalculator\header_files\vctablayouts.h"#include "C:\VCCalculator\VCCalculator\source_files\vcmpdatainputform.h"
#include "C:\VCCalculator\VCCalculator\header_files\ui_vcpopulationinputform.h"#include <QSize>
#include <QtGui>
#include <QTabWidget> //Header file for tab features.typedef QSize VCDIMENSIONS;
typedef QSize* VCDIMENSIONSPTR;typedef QMenu VCMENUCATEGORY;
typedef QMenu* VCMENUCATEGORYPTR;typedef QAction VCMENUITEM;
typedef QAction* VCMENUITEMPTR;typedef QWidget VCWIDGET;
typedef QWidget* VCWIDGETPTR;typedef QStatusBar VCSTATUSBAR;
typedef QStatusBar* VCSTATUSBARPTR;typedef QMessageBox VCMESSAGEBOX;
typedef QMessageBox* VCMESSAGEBOXPTR;typedef QTabWidget VCTABWIDGET;
typedef QTabWidget* VCTABWIDGETPTR;class VCMainWindow : public QMainWindow
{ /BEGIN CLASS VCMAINWINDOW DECLARATION/
Q_OBJECT //Macro that declares the class as an object of type QOBJECT
private:
/*MAIN MENU HEADINGS*/ VCMENUCATEGORYPTR MMFileMenu; //Main Menu: File Menu Pointer. VCMENUCATEGORYPTR MMEditMenu; //Main Menu: Edit Menu Pointer. VCMENUCATEGORYPTR MMHelpMenu; //Main Menu: Help Menu Pointer. /*MAIN MENU DROPDOWN OPTIONS*/ VCMENUITEMPTR MMNewOption; //Main Menu: File Menu: New Option VCMENUITEMPTR MMExitOption; //Main Menu: File Menu: Exit Option VCMENUITEMPTR MMInfoOption; //Main Menu: Help Menu: About Option VCSTATUSBARPTR VCStatusBar; //Main Window: Status Bar Pointer. /*TAB BAR*/ VCTABWIDGETPTR VCTabBar; //Main Window: Tab Bar Pointer. /*TABBED WINDOWS -- THESE WINDOWS APPEAR WHEN THE CORRESPONDING TAB IS CLICKED!*/ VCWIDGETPTR VCTabBarPage1; //Tabbed Window: Content Widget. 1st Tab. VCWIDGETPTR VCTabBarPage2; //Tabbed Window: Content Widget. 2nd Tab. VCWIDGETPTR VCTabBarPage3; //Tabbed Window: Content Widget. 3rd Tab. VCWIDGETPTR VCTabBarPage4; //Tabbed Window: Content Widget. 4th Tab. /*FORMS*/ VCMPDataInputForm* MMNewForm; //Input Form: New Population Data
public slots:
void AboutMessage(); //Slot that generates help->about popup box and displays it. void InvokeMPDDialogWindow(); //Slot that generates new mosquito population dialog window. void CreateNewDataTab( VCWIDGETPTR );
protected:
public:
VCMainWindow(); //Class Constructor void CreateMainMenu(); //Creates the main menu and it's components. void CreateStatusBar(); //Creates the status bar. void CreateMainMenuActions(); //Creates the actions associated with main menu options. void CreateMainPageLayout( ); //Creates the page layout for a widget. void CreateTabbedEnvironment( QWidget* CentralWidget ); //Create the tab bars and pages associated with them.
}; /CLOSE CLASS VCMAINWINDOW DECLARATION/
#endif // VCWINDOWS_H
@I'm receiving these errors:
invalid use of incomplete type 'struct Ui::VCMPDataInputForm'
forward declaration of 'struct Ui::VCMPDataInputForm'Searching around this problem seems to be related to the header inclusions, but I can't seem to locate the cause.
If I need to post anything else, just let me know what.
EDIT: Ok, so I've noticed that in the header declarations for the form, the namespace {} tags hold only a forward declaration of the classes, like this:
@
namespace Ui {
class Dialog: public Ui_Dialog {};
} // namespace Ui
@Similarly...
@
namespace Ui {
class VCMPDataInputForm;
}
@The full class declarations are in the same files, but they don't appear in the namespace. This is designer generated code.
Thanks!
-
The problem you are seeing is really a C++ problem where you run into the fact that you can not access private or protected class members from outside the class. Note that that is a good thing; don't "fix" it by just making everything public.
What you are trying to do, is to connect directly to the OK button on the form. Don't do that. Instead, connect to an ok signal that is exposed by your widget class itself. That way, you can change the internals of your widget without other parts of your program being affected.
You might change your VCMPDataInputForm like this (add to the header):
@
signals: //chosen to match the QDialog interface
void accepted();
void rejected();
@Then, in your VCMPDataInputForm implementation, you make sure these signals get emitted when the buttons are pressed. You can do that like this. I suggest the constructor for this bit of code:
@
connect(ui->buttonBox, SIGNAL(accepted()), this, SIGNAL(accepted()));
connect(ui->buttonBox, SIGNAL(rejected()), this, SIGNAL(rejected()));
@
That is: you forward the accepted and rejected signals from your button box to be emitted from the API of your VCMPDataInputForm class too.Now, you can replace your connect statement from line 14 of your first code piece with this:
@
connect( MMNewForm, SIGNAL( accepted() ), this, SLOT( theSlotToActivate ) );
@Note that you must name a slot; you cannot connect to nothing.
This way, you don't need any cheating.
-
Ahhh...I didn't know I could do this:
@
connect(ui->buttonBox, SIGNAL(accepted()), this, SIGNAL(accepted()));
connect(ui->buttonBox, SIGNAL(rejected()), this, SIGNAL(rejected()));
@I only cheated to try and figure out what the cause of my problem was, I had no intention of keeping it that way. This technique is much, much better.
Thank you!
-
Using this technique has brought me to a (hopefully minor) issue. The tab that is created by the slot triggered by the custom signal accepted() needs data that must be obtained from the form when the user presses the "ok" button (which I assume emits the accepted() signal).
My solution was to put all of the code that loaded the data within the custom form class accepted() signal assuming that the accepted() function contents would be executed before the signal was forwarded and the slot triggered. My assumption was wrong, and because of a lot of NULL checks I used a messagebox to determine that the slot that needs the data is actually executing before the data is placed in memory.
I tried connecting another slot to the accepted signal and putting it before the signal forwarding, but that didn't work. Here's what I did:
@
//This was what I added
// connect( ui->buttonBox, SIGNAL( accepted() ), this, SLOT( GetFormData( ) ) );connect( ui->buttonBox, SIGNAL( accepted() ), this, SLOT( accepted() ) ); connect( ui->buttonBox, SIGNAL( rejected() ), this, SLOT( rejected() ) );
@
Reading around, I see that the slot execution order can never be assumed, so what is the best route to get the data loading before the accepted() signal is forwarded? I was considering loading it with editingFinished() on each form field, but this leaves a lot of work if the user wants to change data or decides to cancel the form entirely.
Thanks!
-
Actually, the slots connected to a signal will be executed in order, that has been relatively recently added to the documentation. You may have found older comments that the order was not guaranteed. It always have been this order, but that was never documented and thus could not be relied on.
I asume your code snippet above comes from the code in your form or tab, right?
If you want to do something before emitting the accept signal, then I suggest you don't create the SIGNAL-SIGNAL connection that I talked about earlier, but create two private slots in your form that you connect to your button box (like you do in your code snippet), do whatever work you need to do there, and after that, from that slot emit the accepted() or rejected() signal to the outside world. That way, program flow is always clear, and does not depend on connect orders. That is much more maintainable.What I don't completely get, is why you need this. After your form emits the accepted() signal, your object receiving that signal can still query your dialog or tab for the data it needs. You just expose your getFromData() publicly. All you need is that you keep a member variable pointer to the dialog. Most (all?) Qt dialogs work like that.
-
@
void VCMainWindow::InvokeMPDDialogWindow(){ /BEGIN FUNCTION INVOKEMPDDIALOGWINDOW/
VCMPDataInputForm* NewForm;
//Allocate memory for the new mosquito population data input form.
NewForm = new VCMPDataInputForm( );//Set the title of the form window.
NewForm->setWindowTitle( "New Mosquito Population");//connect( NewForm, SIGNAL( accepted( ) ), NewForm, SLOT( NewForm->GetFormData( ) ) );
//If the form is successfully submitted, then we need to create a new tab for
//this mosquito population.
connect( NewForm, SIGNAL( accepted( ) ), this, SLOT( CreateNewDataTab( ) ) );//SHOW the new form and don't let the user interact with the program windows
//until it is either submitted or cancelled.
NewForm->exec();} /CLOSE FUNCTION INVOKEMPDDIALOGWINDOW/
@Andre - the reason I need to do it this way currently is because I have the tab creation triggered by the accepted() slot, and the tab text etc... is provided by the data obtained by GetFormData(). If the data doesn't exist (Cancel or not validated), I don't want the tab created.
The code above is the calling method. Would it be more feasible for me to connect the accepted() signal to the GetFormData() method and then after exec() returns do an:
if( DataElement[ i ] )
{
//create tab.
}
Would this work?
-
I am really sorry, but I don't follow it anymore. The code you show above seems fine in terms of operation order, though there is a problem with ownership. Who will delete the NewForm object you created?
How about these modifications to your code:
Make your NewForm pointer a private member of your VCMainWindow class.
Initialize the NewForm pointer to 0 in your VCMainWindow constructor.
Only create a new instance of VCMPDataInputForm if there isn't one already.
In your CreateNewDataTab() slot, use the now available NewForm member pointer to access the VCMPDataInputForm class instance.
Add a member function (GetFromData() will serve, I guess) to VCMPDataInputForm to access the form data, so you can get to it from the CreateNewTab slot.
This way, you only access the form data if the form was accepted. No new tab will be created if the dialog was cancelled.