【问题标题】:Programmatically promote QWidget以编程方式提升 QWidget
【发布时间】:2015-12-11 14:34:00
【问题描述】:

我在QWidget 中有一个带有QProgressBar 的ui 文件。此外,我创建了继承自QProgressBar 的自定义进度条组件。在 QT Designer 中,我可以将 QProgressBar 小部件提升为我的自定义小部件。有没有办法在小部件 cpp 文件中执行此操作而不是使用 QT Designer? 换句话说,有没有办法以编程方式将QWidget 提升为另一个相同类型的自定义小部件(一种变形)?

下面是一个例子:

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();}
};

获取代码int callMyCustomFunction() 编译的常用方法是在QT Designer 中提升小部件(QProgressBar) 中的进度条到我的自定义小部件MyProgressBar

回到最初的问题:有没有办法以编程方式进行(例如在setupUi(this); 之后的MyWidgetWithProgress 构造函数中)?

【问题讨论】:

  • “进入另一个相同类型的自定义小部件” - 最后一部分没有多大意义。如果是 same 类型,就没有什么可推广的了。你的意思是另一个自定义小部件类型继承了要提升的类型?比如,将QProgressBar 实例提升为继承QProgressBarMyProgressBar
  • 如果我理解正确,您只能替换要完全升级的小部件。您不能将对象实例随后提升为子类型,这对于 C++ 本身来说是不可能的,并且与 Qt 无关。因此,一种可能的解决方案是以编程方式创建新类型的新小部件并将属性从旧对象复制到新对象(这可以在自定义小部件类型的特殊构造函数中完成),最后在小部件中替换它树和布局。
  • 话虽如此,有时在设计器中放置一个“占位符”会更容易,您可以在运行时添加自定义小部件作为孩子(使用没有边距的简单布局等,例如孩子完全填充占位符)。但是,实际的小部件在设计器中根本不存在,这使得无法直接在设计器中编辑要添加的小部件的某些属性,但通常这不是问题。当我遇到这种情况时,我大部分时间都是这样做的。
  • @leemes:是的,我想要实现的是以编程方式将来自ui的QProgressBar实例提升为继承QProgressBarMyProgressBar
  • 我说的是在实现文件中使用 dynamic_cast 向下转换 QProgressBar 小部件

标签: c++ qt casting qt5 qwidget


【解决方案1】:

Qt Designer 中的“提升”操作改变了小部件的实际类型,并使用了.cpp 文件中提升的小部件类。编译器永远不会将原始的、未提升的类名视为小部件的类型。在 C++ 中,您必须这样做:将小部件的类型更改为所谓的“提升”类型。

如果您有任意的QWidget*,您可以使用qobject_cast<>() 将其转换为您的“升级”类型。仅当您知道指向的 QWidget 是您的“提升”类的实例化时,这才有效,否则强制转换将返回 NULL

【讨论】:

    【解决方案2】:

    有没有办法在小部件 cpp 文件中执行此操作,而不是使用 QT Designer?

    一般来说:没有。 Qt Designer 生成一个Xyz.ui 文件,一个简单的对象树和对象属性的XML 描述。 uic 代码生成器获取 .ui 文件并生成 ui_Xyz.h。其成员的类型已设置:您不能以编程方式更改它们,就像您不能以编程方式更改任何其他成员的类型一样。

    因此,请在设计器中使用正确类型的对象。如果您将某些基本类型(例如QProgressBar)提升为您自己的派生类型,setupUi 将创建您类型的实例。这样,整个问题就消失了。

    但您不需要使用 Designer 更改 .ui 文件。您可以手动更改它以推广您需要的小部件。假设我们从一个简单的小部件开始,其中有一个进度条:

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

    要更改progressBar 项目的类型,您必须对 XML 文件进行两次更改。首先,改变item本身的类型:

    <widget class="MyProgressBar" name="placeholder">
     <property name="value">
      <number>24</number>
     </property>
    </widget>
    

    然后将您的类型添加到&lt;customwidgets&gt; 项:

    <customwidgets>
     <customwidget>
      <class>MyProgressBar</class>
      <extends>QProgressBar</extends>
      <header>myprogressbar.h</header>
     </customwidget>
    </customwidgets>
    

    如果您打算拥有一个不正确的.ui 文件,您可以在运行时进行小部件交换。

    这有两个主要方面:

    1. 您真的需要自定义类型吗?

      在许多情况下,您无需从基本小部件派生即可完成所有操作。很难说这对您是否有意义:我不明白为什么您不能在您的 .ui 文件中使用正确的类型 (MyProgressBar)。

      // 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. 进行替换。

      您需要通过正确类型的指针/引用/值访问小部件。理想情况下,直接按值存储新小部件。

      class Form : public QWidget, private Ui::Form {
        MyProgressBar m_bar;
        ...
      }
      

      然后,用适当类型的实例替换其布局中的占位符小部件。

      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
      }
      

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-02-13
      • 2011-03-09
      • 2011-04-13
      • 2016-10-30
      • 2012-10-04
      • 1970-01-01
      • 2011-01-17
      相关资源
      最近更新 更多