【问题标题】:Qt add a widget inside another widget?Qt 在另一个小部件中添加一个小部件?
【发布时间】:2012-08-05 12:37:32
【问题描述】:

我想制作一个结合了两个现有小部件行为的小部件。例如,一个小部件是一个按钮,它还包含一个复选框。用户可以点击较大的按钮,也可以点击其中的复选框。

例如gmail 引入了一个“全选”按钮,单击时会显示一个菜单,但它还集成了一个选择所有电子邮件的复选框。有关示例,请参见此屏幕截图:


如何将现有小部件的行为组合到此效果?我希望有一个可以组合小部件行为的解决方案,我只需要覆盖它们冲突的地方。

此问题与Combine multiple widgets into one in qt 问题相似,但不同之处在于我希望将两个小部件放在彼此的顶部并显示为一个小部件。

为了响应 cbamber85 的回复,我添加了以下代码,以显示我已成功整合回复的程度。与 cbamber85 的回复一样,这不包含真正的功能,它只是他的代码的 PyQT 版本。

from PyQt4 import QtCore, QtGui

class CheckBoxButton(QtGui.QWidget):
    checked = QtCore.pyqtSignal(bool)

    def __init__(self, *args):
        QtGui.QWidget.__init__(self, *args)
        self._isdown = False
        self._ischecked = False

    def isChecked(self):
        return self._ischecked

    def setChecked(self, checked):
        if self._ischecked != checked:
            self._ischecked = checked
            self.checked.emit(checked)

    def sizeHint(self, *args, **kwargs):
        QtGui.QWidget.sizeHint(self, *args, **kwargs)
        return QtCore.QSize(128,128)

    def paintEvent(self, event):
        p = QtGui.QPainter(self)
        butOpt = QtGui.QStyleOptionButton()
        butOpt.initFrom(self)
        butOpt.state = QtGui.QStyle.State_Enabled
        butOpt.state |= QtGui.QStyle.State_Sunken if self._isdown else QtGui.QStyle.State_Raised

        self.style().drawControl(QtGui.QStyle.CE_PushButton, butOpt, p, self)

        chkBoxWidth = self.style().pixelMetric(QtGui.QStyle.PM_CheckListButtonSize,
                                               butOpt, self) / 2
        butOpt.rect.moveTo((self.rect().width() / 2) - chkBoxWidth, 0)

        butOpt.state |= QtGui.QStyle.State_On if self._ischecked else QtGui.QStyle.State_Off
        self.style().drawControl(QtGui.QStyle.CE_CheckBox, butOpt, p, self)

    def mousePressEvent(self, event):
        self._isdown = True
        self.update()

    def mouseReleaseEvent(self, event):
        self.setChecked(not self.isChecked())
        self._isdown = False
        self.update()


class Dialog(QtGui.QMainWindow):
    def __init__(self):
        QtGui.QWidget.__init__(self)
        widg = CheckBoxButton(self)
        self.setCentralWidget(widg)


if __name__ == '__main__':
    app = QtGui.QApplication([])
    window = Dialog()
    window.show()
    app.exec_()

【问题讨论】:

    标签: qt user-interface widget pyqt


    【解决方案1】:

    是的,当然可以,但它的简单程度取决于如何组合这些小部件。

    例如,如果您想创建一个旁边有标签而不是内部的按钮,您可以从QWidget 派生,添加您的QLabelQPushButton 作为成员,然后简单地将它们附加到小部件的内部构造函数中的布局。 (我应该指出,有很多不同的方法可以做到这一点)。

    但是,对于将 行为 组合成一个新的、视觉上不同的小部件的小部件 - 您需要告诉 Qt 如何绘制它。所以继承paintEvent(..) 并手动将您的小部件绘制到QPainter(this) 上。并且要将小部件与当前样式匹配,您必须让 QStyle 为您绘制组件 - 这实际上让生活变得更加轻松。

    然后您需要覆盖mouse###Event(..) 并手动处理鼠标交互(可能还需要键盘交互)。

    这很费力,但您的小部件越接近现有小部件,工作量自然就越少。在您的情况下,我将从 QToolButton 派生,因为它已经支持下拉菜单。然后重新实现paintEvent(..),调用QStyle::drawControl(CE_PushButtonBevel, ...绘制按钮,然后QStyle::drawControl(CE_CheckBox, ...绘制正确状态的复选框。然后重新实现mousePressEvent(..),判断是否在复选框命中区域内,并相应地改变状态。

    一个荒谬的例子

    这个毫无意义的小部件演示了上述一些技术。

    class WeirdCheckBoxButton : public QWidget
    {
        Q_OBJECT
    
    public:
        explicit WeirdCheckBoxButton( QWidget* parent = nullptr )
                 : QWidget( parent ), checked_( false ), down_( false ) {}
        virtual ~WeirdCheckBoxButton() {}
    
        bool isChecked() const { return checked_; }
        QSize sizeHint () const { return QSize( 128, 128 ); }
    
    public slots:
        virtual void setChecked( bool checked )
        {
            if ( checked_ != checked ) {
                checked_ = checked;
                emit clicked( checked );
            }
        }
    
    signals:
        void clicked( bool checked );
    
    protected:
        virtual void paintEvent( QPaintEvent* event )
        {
            Q_UNUSED( event )
            QPainter p( this );
    
            //  QStyleOption, and derived classes, describe the state of the widget.
            QStyleOptionButton butOpt;
            butOpt.initFrom( this ); // A convenience function that steals state
                                     // from this widget.
            butOpt.state = QStyle::State_Enabled;
            butOpt.state |= down_ ? QStyle::State_Sunken : QStyle::State_Raised;
    
            //  Renders the widget onto the QPainter.
            style()->drawControl( QStyle::CE_PushButton, &butOpt, &p, this );
    
            //  pixelMetric() gives you style relevant dimensional data.
            int chkBoxWidth = style()->pixelMetric( QStyle::PM_CheckListButtonSize,
                                                    &butOpt, this ) / 2;
            butOpt.rect.moveTo( ( rect().width() / 2 ) - chkBoxWidth, 0 );
            butOpt.state |= checked_ ? QStyle::State_On : QStyle::State_Off;
            style()->drawControl( QStyle::CE_CheckBox, &butOpt, &p, this );
        }
    
        virtual void mousePressEvent( QMouseEvent* event )
        {
            Q_UNUSED( event )
    
            down_ = true;
            update();
        }
    
        virtual void mouseReleaseEvent( QMouseEvent* event )
        {
            Q_UNUSED( event )
    
            setChecked( !isChecked() );
            down_ = false;
            update();
        }
    
    private:
        bool checked_;
        bool down_;
    };
    

    这是一个简单的例子,但复制和试验的时机已经成熟。例如,通过访问butOpt.palette,您可以根据其状态调整呈现的小部件的颜色。

    【讨论】:

    • 感谢您的回复,它很有见地,但我仍然太无知,无法连接所有点。您如何成功覆盖paintEvent?我已经尝试将您的回复与文章和 Qt 文档结合起来,但我无法让事情顺利进行,而且我目前还没有从我的实验中学到更多东西。我用包含可覆盖的paintEvent 方法的自定义小部件的基本示例更新了我的帖子。我可以谦虚地要求一个澄清的 C++ 或 Python 示例,说明您如何进行简单的覆盖,将您所说的“调用QStyle.drawControl”的意思放入上下文中
    • @JonLauridsen 我添加了一个示例,希望能澄清一些事情。
    • 谢谢,这非常有帮助。我已使用您的示例的 PyQT 版本更新了我的回复并接受了您的回答。
    【解决方案2】:

    是的,有可能。 您可以在 QtDesigner 中构建一个单独的小部件,其中包含您的所有功能和添加小部件交互的一切。您可以编写后端(python 或 c++ 等)代码来处理复杂的交互性。一旦你完成了所有。您可以将其打包为 QtDesignerPluginWidget。 这将在您的 QtDesigner 小部件列表框中可用。您可以拖放它来构建您的主应用程序。

    检查: http://doc.qt.nokia.com/qq/qq26-pyqtdesigner.html(基于 C++) http://diotavelli.net/PyQtWiki/Using_Python_Custom_Widgets_in_Qt_Designer(基于 Python)

    【讨论】:

    • OP 没有询问如何创建 Designer 插件。