Replace a widget in Qt - c++

I have a base class which has some gui items that i have set positions of using the designer in Qt creator. Those items are:
QWidget* w1;
QWidget* w2;
QWidget* w3;
Now in a class that inherits that base class, I would like to "transform" those widgets into lineEdit items, that would keep all the geometrical parameters of that widgets. So I do something like this:
QLineEdit* leAmplitude;
leAmplitude = new QLineEdit(ui->w1);
leAmplitude->setGeometry(ui->w1->geometry());
ui->glControls->addWidget(leAmplitude);
But the added QLineEdit item doesn't appear in the exact same place as w1 item. Its just added at the bottom of other controls in the QGridLayout glControls. How to make the lineEdit to take all geometric parameters from w1?

Layout takes care of the widgets placed in the layout, according to the hints given by the widget, so calling setGeometry, then doing addLayout is not useful. Also, adding widget to layout resets it parent, so you setting new widget's parent to ui->w1 is not useful either.
Fortunately, there is QLayout::replaceWidget method! Just use that. Example:
QLineEdit* leAmplitude;
leAmplitude = new QLineEdit;
QLayoutItem *previous = ui->glControls->replaceWidget(ui->w1, leAmplitude);
// possibly assert that previous is ui->w1, or just delete it, or whatever
This method was added as late as in Qt 5.2 it seems, so if you need to support older versions, I can expand this answer to cover how to (try to) do the same manually. But in short, you have to use the right QGridLayout::addWidget overload and make sure relevant properties (including at least sizeHint and sizePolicy) match.

try this, it is works:
QLineEdit* leAmplitude;
leAmplitude = new QLineEdit(ui->w1->parentWidget());
ui->w1->parentWidget()->layout()->replaceWidget(ui->w1, leAmplitude);
ui->w1 = leAmplitude;

Related

How can I create a QVector or other container which can contain different widgets?

I am developing a GUI with Qt5, and in the main window of this GUI, it contains at least 4 tab widgets, and each tab widget will contain serval different child QWidgets, such as QLineEdit, QSpinBox, QLCDnumber, etc. Then when I open the tab, all its child widgets will appear.
Thus, for each tab, I decided to create a QVector(or another container type) to contain its all child Widgets,e.g.:
QVector<QWidget*> wids;
for the first tab widget, if it has the following children:
QLineEdit* line=new QLineEdit(this);
QLCDNumber* lcd=new QLCDNumber(this);
QSpinBox* spn=new QSpinBox(this);
then somehow I would like to do
wids.append(line);
wids.append(lcd);
wids.append(spn);
and next,I want to operate each widget inside the tab,e.g.:
wids[0]->setText("good");
wids[1]->display(1);
wids[2]->setRange(0,10);
I know I need to use dynamic_cast<...>, but I DO NOT KNOW how to do that,is ther anyone could give me some advice?
many thanks!
QLineEdit, QLCDNumber and QSpinBox already inherit from QWidget. So if you put those in a QVector (or any container for that matter, could be a std::vector<QWidget*>) you have to deal with QWidget pointers.
As you correctly stated:
wids.push_back(line);
wids.push_back(lcd);
wids.push_back(spn);
To get your respective classes back, for example QLineEdit back, you need to downcast from QWidget to QLineEdit, QLCDNumber or whatever else inherits from QWidget.
QLineEdit * line = dynamic_cast<QLineEdit*>(wids[0]);
This of course assumes that you know exactly at which position the object is located. You can of course test if your cast was successful:
if( line == nullptr )
{
// cast failed, either wids[0] is invalid or does not derive from QWidget
}
else
{
// cast was successful, use 'line' as regular QLineEdit.
}
A one-liner, although being quite unsafe, would be:
dynamic_cast<QLineEdit*>(wids[0])->setText("myText");

Accessing widgets inside QStackedWidget

I am developing a Qt app with QtDesigner.
Previously it was quite easy to access specific widgets to do something with them like connecting signals. After I added QStackedWidget I can no longer access specific widgets with something like ui->stack->page1->widget.
Is there a way to do it somehow? Or should I always call findChild method? Or maybe it is possible to at least assign some of the nested widgets in stack widget to properties of the main windwo class?
QStackedWidget provides a method to get child widgets by index, as well as the current widget.
A quick example is as follows:
MOCed Header
class MyWidget: QWidget
{
Q_OBJECT
public:
using QWidget::QWidget
QWidget *ptr;
};
Source File
QStackedWidget *stackedWidget = new QStackedWidget;
stackedWidget->addWidget(new MyWidget); // index 0
stackedWidget->addWidget(new QWidget); // index 1
stackedWidget->addWidget(new MyWidget); // index 2
QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget(stackedWidget);
setLayout(layout);
// do something specific with the first widget's ptr element
auto* widget = stackedWidget->widget(0);
auto* mywidget = qobject_cast<MyWidget*>(widget);
if (mywidget) {
mywidget->ptr->setObjectName("FirstPage");
}
Now, Qt uses virtual interfaces by default, so if you have a custom subwidget you need to extract, you can use qobject_cast. qobject_cast is basically a fast dynamic_cast, and works even without RTTI. In template-driven code, dynamic_cast is a bit of a code-smell: it means you lost useful type information too early. With virtual interfaces, the exact opposite is true: you should use qobject_cast as needed.
Why you get the widget layer by layer, if your widgets are added in Qt designer, you can get it by ui->widget directly.

How to logically group widgets in QT for easy show/hide?

I'm grouping a set of widgets in a parent and then I control the visibility/flow of these widgets by hiding/showing the parent. Is this a good way to achieve what I'm trying to do? Here is the code:
QVBoxLayout* l = new QVBoxLayout(this);
// .....
QWidget* toolset_frame = new QWidget(this);
{
QVBoxLayout* l = new QVBoxLayout(toolset_frame);
l->addWidget(new QLabel(tr("Stuff")));
this->Toolset = new QLineEdit(toolset_frame);
l->addWidget(this->Toolset);
}
l->addWidget(toolset_frame);
// Call toolset_frame->hide() and this hides everything inside the parent
The problem with this solution is that the children shrink in size slightly, I think this is due to some padding or border in the parent. Ideally the children should appear as if they are not contained in an intermediate object, but rather flow with the parent. In this case the horizontal size of the children should not be affected.
http://doc.qt.io/qt-5/qtwidgets-dialogs-extension-example.html
This example shows that your approach is correct. Using a widget to contain the elements you want to hide, and so on.
If you want the margins/content margins/padding to be less, then change it.
// in finddialog.cpp
extensionLayout->setMargin(0);
To quickly prototype what properties to change to get it to look right, try laying it out in the Qt Designer, and modify the property editor to get the look and feel you want.
Hope that helps.

Qt - QPushButtons in place of QTreeView Items

Is it possible to add QPushButtons for every item in a QTreeView? For instance, when you click on a TreeItem (that is a button), it's children get displayed as buttons as well? I just have a standard QTreeView.
_layout = new QVBoxLayout(this);
treeView = new QTreeView(this);
QStandardItemModel* standardModel = new QStandardItemModel();
QStandardItem* rootMenu = standardModel->invisibleRootItem();
//populate TreeView
treeView->setModel(standardModel);
treeView->setWordWrap(true);
treeView->setHeaderHidden(true);
//treeView->expandAll();
_layout->addWidget(treeView);
this->setLayout(_layout);
I have not personally done this (yet), but you could try using QAbstractItemView::setIndexWidget(). The widgets won't aren't connected in any way to the data model, so it is up to your code to update them if necessary. Also, you need to call it for each QModelIndex separately.
Here is the answer. You must create your own delegate and applay it for your QTreeView.
To create delegate you must subclass QStyledItemDelegate and re-implement its QStyledItemDelegate::paint(...) method in that way what you want, also, don't forget about re-implementing QStyledItemDelegate::sizeHint(...) method if needed, of course.
Also, you may need to re-implement QStyledItemDelegate::createEditor(...) method.
To apply created delegate to your view (QTreeView) you must create delegate and call QTreeView's method setItemDelegate (or setItemDelegateForColumn, or setItemDelegateForRow).
Good luck!

How to specialize a widget according to a file type?

I'm looking for a way to specialize a widget at runtime. I have a form created with Qt Designer. In this form there is a widget that displays user data, like name, age and so on. Then the user chooses a file and according to the type the widget shall display additional information (like to OO example person -> student, teacher).
I tried to put an empty QWidget on my form, assigned a person widget to it and if the user clicks a button I call deleteLater() on person and assign a new Student(). This works but the new widget then doesn't follow the layout.
I also found QStackedWidget. This seems to work but because all possible widgets are created when the form is shown, even if they are never used, this way doesn't feel right.
What would be the way to accomplish this?
Edit: I feel that my question is a bit unclear. What Qt mechanism should be utilized if one wants to replace a QWidget with a specialized (inherited) version of the original widget? I'm looking for the Qt way to do this.
You need to add a widget dynamically to the widget you have drawn in the designer.
// in UI file
QWidget *wdgFromForm;
// in cpp file
QHBoxLayout *const layout(new QHBoxLayout(wdgFromForm));
SpecializedWidget * specializedWidget( new SpecializedWidget(wdgFromForm));
layout->addWidget(specializedWidget);
Maybe the problem is not that the widget is not suitable, but simply that you're not giving time for the widget to update.
You could take a look at processEvents:
http://web.mit.edu/qt-dynamic/www/qapplication.html#details
This looks like a use case for the factory pattern.
#include <map>
#include <string>
struct Whatever;
struct QWidget;
typedef QWidget*(*WidgetCtor)(Whatever*);
typedef std::map<std::string, WidgetCtor> FileFactory;
QWidget* createFoo(Whatever*);
QWidget* createBar(Whatever*);
QWidget* createDefault(Whatever*);
void factory_init(FileFactory& ff)
{
ff["foo"] = createFoo;
ff["bar"] = createBar;
}
QWidget* create_by_factory(const FileFactory& ff, const std::string t, Whatever* w)
{
FileFactory::const_iterator it(ff.find(t));
if(it != ff.end())
{
return it->second(w);
}
else
{
return createDefault(w);
}
}
Adding widgets dynamically to a layout is not a problem.
You might need to call updateGeometry on the containing widget though.