47

我有一个充满 QPushButtons 和 QLabels 以及各种其他有趣的 QWidgets 的窗口,所有这些都使用各种QLayout对象动态布局......我想做的是偶尔让其中一些小部件变得不可见。也就是说,不可见的小部件仍会占用窗口布局中的正常空间,但不会被渲染:相反,用户只会在小部件的矩形/区域中看到窗口的背景颜色。

hide()和/或setVisible(false)不会成功,因为它们会导致小部件从布局中完全移除,从而允许其他小部件扩展以占用“新可用”空间;我想避免的影响。

我想我可以将覆盖(等)的每种QWidget类型的子类设置为无操作(在适当的时候),但我更喜欢不需要我创建三打不同子类的解决方案。paintEvent()mousePressEvent()QWidget

4

10 回答 10

82

这个问题在 Qt 5.2 中得到了解决。可爱的解决方案是:

QSizePolicy sp_retain = widget->sizePolicy();
sp_retain.setRetainSizeWhenHidden(true);
widget->setSizePolicy(sp_retain);

http://doc.qt.io/qt-5/qsizepolicy.html#setRetainSizeWhenHidden

于 2016-01-07T18:58:54.973 回答
15

我知道的唯一体面的方法是将事件过滤器附加到小部件,并过滤掉重绘事件。无论小部件多么复杂,它都可以工作 - 它可以有子小部件。

下面是一个完整的独立示例。但是,它带有一些警告,需要进一步开发才能完成。只有绘制事件被覆盖,因此您仍然可以与小部件进行交互,只是看不到任何效果。

鼠标点击、鼠标进入/离开事件、焦点事件等仍然会到达小部件。如果小部件依赖于在重绘时完成的某些事情,可能是由于在这些事件上触发了 update(),则可能会出现问题。

至少您需要一个 case 语句来阻止更多事件——比如鼠标移动和单击事件。处理焦点是一个问题:您需要将焦点移到链中的下一个小部件,如果小部件在获得焦点时被隐藏,并且每当它重新获得焦点时。

鼠标跟踪也引起了一些担忧,如果小部件之前正在跟踪,您可能想假装它丢失了鼠标跟踪。正确地模拟这需要一些研究,我不知道 Qt 呈现给小部件的确切鼠标跟踪事件协议是什么。

//main.cpp
#include <QEvent>
#include <QPaintEvent>
#include <QWidget>
#include <QLabel>
#include <QPushButton>
#include <QGridLayout>
#include <QDialogButtonBox>
#include <QApplication>

class Hider : public QObject
{
    Q_OBJECT
public:
    Hider(QObject * parent = 0) : QObject(parent) {}
    bool eventFilter(QObject *, QEvent * ev) {
        return ev->type() == QEvent::Paint;
    }
    void hide(QWidget * w) {
        w->installEventFilter(this);
        w->update();
    }
    void unhide(QWidget * w) {
        w->removeEventFilter(this);
        w->update();
    }
    Q_SLOT void hideWidget()
    {
        QObject * s = sender();
        if (s->isWidgetType()) { hide(qobject_cast<QWidget*>(s)); }
    }
};

class Window : public QWidget
{
    Q_OBJECT
    Hider m_hider;
    QDialogButtonBox m_buttons;
    QWidget * m_widget;
    Q_SLOT void on_hide_clicked() { m_hider.hide(m_widget); }
    Q_SLOT void on_show_clicked() { m_hider.unhide(m_widget); }
public:
    Window() {
        QGridLayout * lt = new QGridLayout(this);
        lt->addWidget(new QLabel("label1"), 0, 0);
        lt->addWidget(m_widget = new QLabel("hiding label2"), 0, 1);
        lt->addWidget(new QLabel("label3"), 0, 2);
        lt->addWidget(&m_buttons, 1, 0, 1, 3);
        QWidget * b;
        b = m_buttons.addButton("&Hide", QDialogButtonBox::ActionRole);
        b->setObjectName("hide");
        b = m_buttons.addButton("&Show", QDialogButtonBox::ActionRole);
        b->setObjectName("show");
        b = m_buttons.addButton("Hide &Self", QDialogButtonBox::ActionRole);
        connect(b, SIGNAL(clicked()), &m_hider, SLOT(hideWidget()));
        QMetaObject::connectSlotsByName(this);
    }
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Window w;
    w.show();
    return a.exec();
}

#include "main.moc"
于 2012-05-31T20:36:32.533 回答
8

您可以使用 QStackedWidget。将您的按钮放在第一页上,将空白 QWidget 放在第二页上,并更改页面索引以使您的按钮在保留其原始空间的同时消失。

于 2013-01-08T20:26:49.733 回答
4

我有3个解决方案:

1)子类化您的 QWidget 并使用特殊/自己的 setVisible() 替换方法来打开/关闭小部件的绘画(如果小部件不可见,只需使用覆盖的 paintEvent() 方法忽略绘画)。这是一个肮脏的解决方案,如果您可以通过其他方式做到这一点,请不要使用它。

2) 使用QSpacerItem作为占位符,并将其可见性设置为要隐藏的 QWidget 的对面,但在布局中保留它的位置+大小。

3)您可以使用一个特殊的容器小部件(从 QWidget 继承),它根据子/子小部件的大小获取/同步它的大小。

于 2012-05-30T15:18:27.770 回答
3

我遇到了类似的问题,最后我在我关心的尺寸和ExpandingsizeType 的尺寸为 0 的控件旁边放置了一个垫片。然后我用ExpandingsizeType 标记控件本身并将其拉伸设置为 1。这样,当它可见时,它优先于分隔符,但当它不可见时,分隔符扩展以填充控件通常占用的空间。

于 2014-04-14T19:58:54.093 回答
2

可能是 QWidget::setWindowOpacity(0.0) 是你想要的?但这种方法并不适用于任何地方。

于 2012-05-29T07:33:47.307 回答
2

一种选择是实现一个新的 QWidgetItem 子类,它总是返回 false QLayoutItem::isEmpty。我怀疑由于 Qt 的 QLayout 示例子类文档,这将起作用:

我们忽略 QLayoutItem::isEmpty(); 这意味着布局会将隐藏的小部件视为可见的。

但是,您可能会发现在布局中添加项目有点烦人。特别是,如果您这样做,我不确定您是否可以轻松地在 UI 文件中指定布局。

于 2012-05-29T07:55:35.817 回答
2

这是来自Kuba Ober的答案的 C++ Hider 类的 PyQt 版本。

class Hider(QObject):
    """
    Hides a widget by blocking its paint event. This is useful if a
    widget is in a layout that you do not want to change when the
    widget is hidden.
    """
    def __init__(self, parent=None):
        super(Hider, self).__init__(parent)

    def eventFilter(self, obj, ev):
        return ev.type() == QEvent.Paint

    def hide(self, widget):
        widget.installEventFilter(self)
        widget.update()

    def unhide(self, widget):
        widget.removeEventFilter(self)
        widget.update()

    def hideWidget(self, sender):
        if sender.isWidgetType():
            self.hide(sender)
于 2014-07-01T20:32:48.943 回答
1

我相信您可以使用QFrame作为包装器。虽然可能有更好的主意。

于 2012-05-31T20:46:24.747 回答
0

试试void QWidget::erase ()。它适用于 Qt 3。

于 2012-05-29T07:52:16.370 回答