ABCplus ABCplus - 3 months ago 10
C++ Question

Programmatically promote QWidget

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);
)?

Answer

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:

  1. 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));
    
  2. 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
    }
    
Comments