4

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

ABCplus
  • 3,981
  • 3
  • 27
  • 43
  • "into an another custom widget of the same type" - The last part doesn't make much sense. If it were the **same** type, there wouldn't be anything to promote. You mean another custom widget type inheriting the type to be promoted from? Like, promote a `QProgressBar` instance to a `MyProgressBar` which inherits `QProgressBar`? – leemes Sep 15 '15 at 11:05
  • 1
    If I understand you correctly, you only can *replace* the widget to be promoted completely. You can't promote an object instance subsequently to a sub-type, that's not possible by C++ itself and has nothing to do with Qt. So a possible solution would be to programmatically create a new widget of the new type and copy the properties over from the old to the new object (this can be done in a special constructor of your custom widget type) and finally replace it in the widget tree and layout. – leemes Sep 15 '15 at 11:10
  • 1
    This being said, it sometimes is easier to just place a "placeholder" in the designer, in which you add your custom widget during runtime as a child (with a simple layout with no margins etc such that the child fills the placeholder completely). But then, the actual widget isn't present at all in the designer, which makes it impossible to edit some properties of the widget-to-be-added directly in the designer, but usually this is not a problem. This procedure is how I did it most of the time when I was in such a situation. – leemes Sep 15 '15 at 11:12
  • @leemes: yes, what I want to achieve is to programmatically promote the `QProgressBar` instance that comes from the ui to a `MyProgressBar` inheriting `QProgressBar`. – ABCplus Sep 15 '15 at 11:40
  • I'm talking about downcasting of the QProgressBar widget using dynamic_cast in the implementation file – ABCplus Sep 15 '15 at 11:43
  • That is a totally different question... And yes as you say it is possible with `dynamic_cast` (or `qobject_cast`). Now I'm not sure anymore what your actual question is. Some code would help. – leemes Sep 15 '15 at 11:45
  • I've posted an example, maybe my last comment is wrong and confusing – ABCplus Sep 15 '15 at 12:07
  • "Promoting" a widget in Qt Designer just mean that in the generated code `Ui::MyWidget` will have a `MyProgressBar*` member, and setupUi will instantiate a `MyProgressBar` instead of a `QProgressBar`. There is no transformation occuring at runtime. – Leiaz Sep 15 '15 at 12:16
  • The problem is, since you talk about "instead of designer", you probably didn't instantiate a `MyProgressBar` but rather a `QProgressBar`. Casting the pointer to `MyProgressBar*` will not solve this - it is only possible if the object behind the pointer actually **is** a `MyProgressBar`. And if you promote it in the designer, not only the instance is promoted but also the pointer is then of the correct type, so it can be used without casting. So I assume you did not promote it in the designer and thus the instance is of type `QProgressBar`, making a cast undefined behavior. – leemes Sep 15 '15 at 12:32

2 Answers2

2

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
    }
    
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
1

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.

owacoder
  • 4,815
  • 20
  • 47