Qt - Any guideline on how to implement navigation between UI Forms? - c++

I'm learning Qt right now, trying to make a simple application. What I'm trying to implement is a simple 'Welcome' screen, with two buttons ('Register' and 'Login'). The problem is on the page redirection to the other two pages.
Also, I have the screens already setup using the QtCreator (in .ui Forms format).
The only solution I could come up with, until now, was based on an example from Qt itself, which uses a QStackedWidget, adding QWidgets as pages. The problem is that those pages in the example are mounted programmatically (and I want to use the Forms that I have).
If I try this:
MainWindow::MainWindow() :
ui_home(new Ui::HomeView),
ui_register(new Ui::RegisterView) {
ui_home->setupUi(this);
ui_register->setupUi(this);
pagesWidget = new QStackedWidget;
pagesWidget->addWidget(ui_home->centralWidget);
pagesWidget->addWidget(ui_register->centralWidget);
...
}
It 'kind of works', but the result is horrible. The 'centralWidget' from my Forms is added to the 'pagesWidget', but the 'setupUi' before that really renders the 'Home' and 'Register' pages all at once, messing everything up.
So, real question is:
Is there any guideline on how to implement navigation between UI Forms?
Secondly:
How can I retrieve the QWidget from my UI Form and add to a QStackedWidget, without rendering it?
I am using Ubuntu 12.04, with QtCreator 3.2.1.
Thanks in advance.

You do it wrong. You should create a "MainWidget" with 2 items: a) QStackedWidget, b) navigation panel (your buttons). Then you should set "MainWidget" as a central widget.
After it you can connect signals from navigation panel (clicked signals of "Registed" or "Login" buttons) to corresponding slots, that will select necessary widget on QStackedWidget
So, in your case, you need next 3 ui forms:
MainWindow (QStackWidget + 2 buttons)
LoginWidget
RegisterWidget
Pseudocode:
// RegisterWidget.cpp + you should have RegisterWidget.ui
RegisterWidget::RegisterWidget()
: public QWidget(NULL)
, ui( new Ui::RegisterWidget() )
{}
// LoginWidget.cpp + you should have LoginWidget.ui
LoginWidget::LoginWidget()
: public QWidget(NULL)
, ui( new Ui::LoginWidget() )
{}
// MainWindow.cpp + you should have MainWindow.ui
MainWindow::MainWindow()
: public QMainWindow()
, ui( new Ui::MainWindow() )
{
ui->setupUi(this);
// !!!!!!!!!!!!!!!!!!!
// Creating widgets here.
// Possible - setup communication between widgets with signals / slots
// !!!!!!!!!!!!!!!!!!!
m_loginForm = new LoginWidget();
m_regForm = new RegisterWidget();
ui->stackedWidget->addWidget( m_loginForm );
ui->stackedWidget->addWidget( m_regForm );
connect( ui->loginBtn, &QPushButton::clicked, this, &MainWindow::onLogin );
connect( ui->regBtn, &QPushButton::clicked, this, &MainWindow::onReg );
}
// private slots:
void MainWindow::onLogin()
{
ui->stackedWidget->setCurrentWidget( m_loginForm );
}
void MainWindow::onReg()
{
ui->stackedWidget->setCurrentWidget( m_regForm );
}
And don't forget about layouts. Because you may see nothing if your widgets will have 1x1px size.
How can I retrieve the QWidget from my UI Form and add to a QStackedWidget, without rendering it?
You may use QWidget::hide() method.

Related

QObject::findChild returns 0 for QLabels added to statusbar

I created a application running in a QMainWindow using qtcreator, so the typical way.
I added two 'manually' (meaning: not with the Form editor) created qlabels to the statusbar:
in the header:
QLabel *label_timestamp;
QLabel *contentLabel_timestamp;
in the constructor:
MainWin::MainWin(const CmdLineOptions &opts, QWidget *parent)
: QMainWindow(parent),
ui(new Ui::MainWin),
m_connectionStatusLabel(new QLabel),
m_client(new QMqttClient),
m_mqttmanager(new MQTTManager(m_client)),
m_mqttServerName("localhost")
{
ui->setupUi(this);
label_timestamp = new QLabel(this);
contentLabel_timestamp = new QLabel(this);
label_timestamp->setText("system time");
contentLabel_timestamp->setText("dd.mm.yyyy, hh:mm:ss:zzz"); /* just testing output */
statusBar()->addPermanentWidget(label_timestamp);
statusBar()->addPermanentWidget(contentLabel_timestamp);
}
If I do a
Label *label = findChild<QLabel *>(QString("contentLabel_")+objName);
elsewhere in this class implementation with objName being 'timestamp', of course, findChild() returns 0. It's working fine with other QLabels created using QtCreator in the form editor, findChild() finds them all. Isn't the statusbar widget and its content also a child of ui? Does somebody eventually know a way out of there?
I want to use findChild to generically fill my labels following a naming scheme with content I receive over MQTT, this is the background. Would be great if the statusbar content would need a special handling but could also be handled in this dynamic approach.
Thanks a lot
findChild uses the objectName, in the case of Qt Creator this establishes it in the MOC, but in your case you must establish it:
label_timestamp = new QLabel(this);
contentLabel_timestamp->setObjectName("label_timestamp");
contentLabel_timestamp = new QLabel(this);
contentLabel_timestamp->setObjectName("contentLabel_timestamp");
And then you can recover it with:
QLabel *label_1 = findChild<QLabel *>("label_timestamp");
if(label_1){
// some code
}
QLabel *label_2 = findChild<QLabel *>("contentLabel_timestamp");
if(label_2){
// some code
}

Which UI item allows dynamic expansion on the go in Qt?

Suppose I have a single text box and a radio button placed below the text box .
What I want is that if the user enters some text and ALSO clicks the radio button a similar text box and a radio button should appear just below. And similar thing should happen if the user does that for new UI items.
Any suggestions what classes I should be looking for?
The relevant class you need to handle the inserting of new widgets is one of the QLayout subclasses, most likely QVBoxLayout it sounds like. This will allow you to insert a new textbox and radio button at the bottom of the layout, which will then automatically expand to fit these new widgets.
Inserting the new objects is pretty straightforward. Connect a slot to a signal of the QRadioButton that's at the bottom of the layout, like the QRadioButton::toggled or QRadioButton::clicked signals. This slot will check if the above textbox has any text in it, and, if so, insert a new textbox/radio button pair below them.
To be clear, this slot should be a method of the widget that uses the layout to arrange its sub-widgets. For example, if you're putting all these objects in a QGroupBox, then that's the object to which you'd add the slot I'm describing.
Here is an (untested) example:
class Group : public QGroupBox {
Q_OBJECT
public:
Group(QWidget* parent = nullptr) : QGroupBox(parent)
{
layout = new QVBoxLayout(this);
insertNewRow();
}
private slots:
void insertNewRow(bool checked = true) {
/* You might want to make this remove the row if the button is unchecked */
if (!checked)
return;
/* Only add row if text box is non-empty */
if (textBoxes.isEmpty() || textBoxes.last()->text().isEmpty())
return;
/* Disconnect slot for previous radio button */
QObject::disconnect(radioButtons.last(), 0, 0, 0);
/* Add new text box and button, labeled Button 1, Button 2, etc. */
textBoxes.append(new QLineEdit("", this));
radioButtons.append(new QRadioButton(QString("Button %1").arg(textBoxes.size())), this));
/* Connect signal/slot to add new row when clicked */
QObject::connect(radioButtons.last(), &QRadioButton::toggled, this, &Group::insertNewRow);
}
private:
QVBoxLayout* layout;
QList<QLineEdit*> textBoxes;
QList<QRadioButton*> radioButtons;
};

QAbstractItemView Tab Focus While Editing Item

I have a QTreeView populated with items from a model. When a call to edit() is made on an index, a custom editor displays. The editor consists of two QLineEdit widgets.
I want the focus to switch between the two QLineEdit widgets when Tab is pressed. However, pressing Tab cycles through everything else on my program. All my QPushButton and QTabWidget objects are included in the Tab order even though they are completely different widgets than my editor.
I've tried setting the tab order using setTabOrder() to loop it between the two QLineEdit widgets, however this still doesn't isolate the editor widget from the surrounding widgets. Why is this happening?
NOTE: I'm not trying to disable tab ordering anywhere else, just isolate it to my editor for the time being.
Thanks for your time!
This can be easily implemented using QWidget::focusNextPrevChild as follows:
class EditWidget : public QWidget
{
public:
EditWidget(QWidget *pParent) : QWidget(pParent)
{
QHBoxLayout *pLayout = new QHBoxLayout(this);
setLayout(pLayout);
pLayout->addWidget(m_pEdit1 = new QLineEdit ());
pLayout->addWidget(m_pEdit2 = new QLineEdit ());
}
bool focusNextPrevChild(bool next)
{
if (m_pEdit2->hasFocus())
m_pEdit1->setFocus();
else
m_pEdit2->setFocus();
return true; // prevent further actions (i.e. consume the (tab) event)
}
protected:
QLineEdit *m_pEdit1;
QLineEdit *m_pEdit2;
};

Add widget with editable properties through code

I've created a custom widget plugin that is a container( it overloads the qframe). In this container i would like to add children through the code. I've succeeded in doing this. However the children aren't editable or clickable in the designer. I know for children to be editable through the designer, they should be added as code to the XML, but i don't know how to do this.
Would anybody know if it's at all possible to do this?
The code I have, with arrowedFrame as my custom widget plugin class:
arrowedFrame::arrowedFrame(QWidget *parent, Qt::WindowFlags f) : (parent, f)
{
QLabel* testLabel = new QLabel(this);
}
this adds a new Label, as member (sorry i can't yet post pictures, but imagine a box with a label in it). But as i said this label isn't at all editable through the designer.
The solution I found to this is by taking this guide. And adding some things in the initialize function:
void PlotContainerPlugin::initialize(QDesignerFormEditorInterface *formEditor)
{
if (initialized)
return;
QExtensionManager *manager = formEditor->extensionManager();
myFormEditor = formEditor;
Q_ASSERT(manager != 0);
manager->registerExtensions(factory, Q_TYPEID(QDesignerContainerExtension));
initialized = true;
}
The first part just gets a manager:
QExtensionManager *manager = formEditor->extensionManager();
And then we use this manager to,
manager->registerExtensions(factory, Q_TYPEID(QDesignerContainerExtension));
register the plugin(a plotter I my case) with the designer.
Hope this helps:)

How to change contents of QMainWindow dynamically

I have a QMainWindow that starts out with nothing but a menubar with a menu that has two options. When the first is clicked the window should be populated with QLabels and various input widgets to recieve data. When the second option is clicked the window should be populated with a QTextEdit(obviously removing whatever was on the window at the time)
The following is code I have tried :
void OrderWindow::displayAddOrder(){
QVBoxLayout* tlayout = new QVBoxLayout();
QHBoxLayout* row = new QHBoxLayout();
row->addWidget(nameLbl);
tlayout->addLayout(row);
qDeleteAll(children());
delete layout();
setLayout(tlayout);
}
It's a bit messy since I've been trying various things. When I click on a menu option with this code it simply says the application has stopped working.
Any help would be appreciated.
You have at least the following options:
Always show the actual widget, and hide the rest. This is simple in case of two widgets like in your example. You could use this technique with the observer design pattern for any number of widgets.
Use the QStackedWidget class which basically behaves the way as your custom observer pattern implementation would be, although you will need to use an extra class for this.
Therefore, I would suggest to write the following code:
orderwindow.h
...
class QStackedWidget;
class OrderWindow
{
...
public:
explicit OrderedWindow(QWidget *parent);
...
private:
QStackedWidget m_stackedWidget;
...
}
...
orderwindow.cpp
#include "orderwindow.h"
#include <QStackedWidget>
...
OrderWindow::OrderWindow(QWidget *parent)
: QWidget(parent)
, m_stackedWidget(new QStackedWidget(this))
{
QWidget *firstPageWidget = new QWidget;
QWidget *secondPageWidget = new QWidget;
m_stackedWidget->addWidget(firstPageWidget);
m_stackedWidget->addWidget(secondPageWidget);
QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget(stackedWidget);
setLayout(layout);
}
...
void OrderWindow::displayAddOrder() {
m_stackedWidget->setCurrentWidget(nameLbl);
}
...
you can use a QStackedWidget
start with showing an empty page and then show the correct page as needed:
there is then no need to mess with adding or removing widgets
Yes, you can use a QStakedWidget if your input options are fixed. If it's not, I suggest you to use an abstract factory pattern to create the stacked widget content. This woluld make your code more readable.