问题
I have an ui file with a QProgressBar
in a QWidget
. Moreover, I've created my custom progress bar component that inherits from QProgressBar
. In QT Designer, I can promote the QProgressBar
widget to my custom widget. Is there a way to do this in the widget cpp file instead of using QT Designer?
In other words, is there a way to programmatically promote a QWidget
into an another custom widget of the same type (a sort of morphing)?
Here follows an example:
class MyProgressBar : public QProgressBar
{
Q_OBJECT
public:
explicit CmdProgressWidget(QWidget *parent = 0);
~CmdProgressWidget();
int myCustomFunction();
};
class MyWidgetWithProgress : public QWidget, public Ui::MyWidget
{
Q_OBJECT
public:
MyWidgetWithProgress (QWidget *parent = 0) {setupUi(this);}
~MyWidgetWithProgress() {;}
inline int callMyCustomFunction() {progressBar->myCustomFunction();}
};
The common way to get the code int callMyCustomFunction()
compile is to promote in QT Designer the progress bar in the widget (QProgressBar
) to my custom widget MyProgressBar
.
Back to original question: is there a way to do it programmatically (e.g. in the MyWidgetWithProgress
constructor after setupUi(this);
)?
回答1:
Is there a way to do this in the widget cpp file instead of using QT Designer?
Generally speaking: no. Qt Designer generates a Xyz.ui
file, a simple XML description of the object tree and object properties. The uic
code generator takes that .ui
file and generates ui_Xyz.h
. The types of its members are set: you cannot programmatically change them, just as you can't programmatically change the type of any other member.
So, use the correct type of the object in the Designer. If you promote some base type (say a QProgressBar
) to your own derived type, the setupUi
will create an instance of your type. Thus, the whole problem disappears.
But you don't need to change the .ui
file using Designer. You can trivially change it manually to promote the widgets you need. Suppose we start with a simple widget that has a progress bar in it:
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>256</width>
<height>40</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QProgressBar" name="placeholder">
<property name="value">
<number>24</number>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
To change the type of the progressBar
item, you have to make two changes to the XML file. First, change the type of the item itself:
<widget class="MyProgressBar" name="placeholder">
<property name="value">
<number>24</number>
</property>
</widget>
Then add your type to the <customwidgets>
item:
<customwidgets>
<customwidget>
<class>MyProgressBar</class>
<extends>QProgressBar</extends>
<header>myprogressbar.h</header>
</customwidget>
</customwidgets>
If you intend to, in effect, have an incorrect .ui
file, you can do the widget swap at runtime.
There are two major aspects of this:
Do you actually need a custom type?
In many cases, you can do everything without deriving from the base widget. Whether it makes sense for you is hard to tell: I don't understand why you can't use the proper type (
MyProgressBar
) in your.ui
file.// Would-Be Derived Class class MyProgressBar : public QProgressBar { int m_var; protected: void paintEvent(QPaintEvent * ev) { QProgressBar::paintEvent(event(ev)); // let the base class paint itself QPainter p(this); // do some overpainting, etc. } public: void doSomething() { m_var = 3; } }; // Do the same using the base class instead: void doSomething(QProgressBar * bar) { bar.setProperty("m_var", 3); } void paintEvent(QWidget * w, QPaintEvent * ev) { w->event(ev); // let the base class paint itself QPainter p(w); // do some overpainting, etc. } struct Painter : public QObject { bool eventFilter(QObject * obj, QEvent * ev) { if (obj->isWidgetType() && ev->type() == QEvent::Paint) paintEvent(static_cast<QWidget*>(obj), static_cast<QPaintEvent*>(ev)); return QObject::eventFilter(obj, ev); } } QProgressBar bar; bar.installEventFilter(new Painter(&bar));
Doing the replacement.
You need access to the widget through a pointer/reference/value of a correct type. Ideally, store the new widget directly by value.
class Form : public QWidget, private Ui::Form { MyProgressBar m_bar; ... }
Then, replace the placeholder widget in its layout with an instance of the proper type.
void replace(QWidget * & old, QWidget * replacement) { auto layout = old->parent()->layout(); // name the new widget the same replacement->setObjectName(old->objectName()); // swap the widgets and delete the layout item delete layout->replaceWidget(old, replacement); // delete the old widget delete old; // don't leave a dangling pointer old = nullptr; } Form:: Form(QWidget * parent) : QWidget(parent) { setupUi(this); replace(placeholder, &m_bar); // you have to manually connect slots for the m_bar widget }
回答2:
The "promote" operation in Qt Designer changes the actual type of the widget, and uses the promoted widget class in the .cpp file. The compiler never sees the original, non-promoted class name as being the type of the widget. In C++, you would have to do the same: change the type of the widget to the so-called "promoted" type.
You could, if you had an arbitrary QWidget*
, cast it into your "promoted" type, by using qobject_cast<>()
. This only would work if you knew the QWidget
pointed to was an instantiation of your "promoted" class, otherwise the cast would return NULL
.
回答3:
All you need may look like this:
QProgressBar* temp = new MyProgressBar(m_widget, CALENDAR_DEL, 30);
m_horizontalLayout->replaceWidget(m_prg, temp);//the most important function replaceWidget()
delete m_prg;//
m_prg = temp;//Qt will release children widgets finally, make sure m_prg point to the temp
来源:https://stackoverflow.com/questions/32584320/programmatically-promote-qwidget