Вопрос по layout – Как сделать виджет Qt невидимым без изменения положения других виджетов Qt?

37

У меня есть окно, полное QPushButtons и QLabels и различных других забавных QWidgets, все выложены динамически с использованием различныхQLayout объекты ... и то, что я хотел бы сделать, - иногда сделать некоторые из этих виджетов невидимыми. То есть невидимые виджеты по-прежнему занимают свое обычное пространство в макете окна, но они не будут отображаться: вместо этого пользователь просто увидит цвет фона окна в прямоугольнике / области виджета.

hide() и / илиsetVisible(false) не справится с задачей, потому что они приводят к тому, что виджет полностью удаляется из макета, что позволяет другим виджетам расширяться, чтобы занять «вновь доступный» виджет; пространство; эффект, которого я хочу избежать.

Я полагаю, я мог бы сделать подкласс каждогоQWidget введите, что переопределитьpaintEvent() (а такжеmousePressEvent() и т. д.) быть неоперативным (когда это уместно), но я бы предпочел решение, которое не требует от меня создания трех десятков различныхQWidget подклассы.

Что такое версия Qt? shan
Версия Qt 4.8.1 Jeremy Friesner
Вы можете использовать QGraphicsOpacityEffect. stdout
Возможно, вместо того, чтобы скрывать виджеты, вы просто хотите их деактивировать? Troyseph
Графические эффекты на виджетах, к сожалению, не кроссплатформенные :( Kuba Ober

Ваш Ответ

10   ответов
14

Единственный достойный способ, которым я знаю, - это прикрепить фильтр событий к виджету и отфильтровать события перерисовки. Он будет работать независимо от того, насколько сложен виджет - он может иметь дочерние виджеты.

Ниже приведен полный автономный пример. Это идет с некоторыми предостережениями, и потребуется дальнейшее развитие, чтобы завершить его. Только событие рисования переопределяется, поэтому вы все равно можете взаимодействовать с виджетом, вы просто не увидите никаких эффектов.

Щелчки мыши, события входа / выхода мыши, события фокуса и т. Д. Все равно попадут в виджет. Если виджет зависит от определенных действий, выполняемых при перерисовке, возможно, из-за обновления (), инициированного этими событиями, могут возникнуть проблемы.

Как минимум, вам нужен оператор 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"
Это работает для сложных виджетов? Я имею в виду, как ведьма QWidget содержит QPushButton и QLabel.
Хайдер, вероятно, должен быть синглтоном, как QApplication.
Должна ли подпись Q_SLOT void Hider :: hideWidget () быть Q_SLOT void hideWidget (Qbject * sender)?
Там нет простого пути. Код является полным примером, класс Hider - это что, 15 строк? Было бы одинаково просто заблокировать другие события от достижения виджета. Большинство виджетов не заботятся. Сделать его полноценным, производственным решением, которое будет работать для любого виджета, - таким образом, сохраняя все протоколы, от которых виджеты зависят неявно, и это будет сложнее.
+1 за полный код - хотя он немного сложен (слишком много кода для его решения + делает его защищенным от ошибок), но, возможно, он все еще один из самых чистых / простых.
0

Пытатьсяvoid QWidget::erase (), Работает на Qt 3.

1

Я считаю, что вы могли бы использоватьQFrame как обертка. Хотя может быть и лучшая идея.

52

Эта проблема была решена в 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

Это действительно полезно! И это будет еще более полезно, если это будет в виде опции в Qt Designer.
Кстати спасибо за ваш отзыв. Я сделал / добавил код для Qt, так что приятно знать, что он полезен :).
2

Одним из вариантов является реализация нового подкласса QWidgetItem, который всегда возвращает false дляQLayoutItem::isEmpty, Я подозреваю, что это будет работать из-за подкласса примера Qt QLayoutдокументация:

We ignore QLayoutItem::isEmpty(); this means that the layout will treat hidden widgets as visible.

Тем не менее, вы можете обнаружить, что добавление элементов в ваш макет немного раздражает. В частности, я не уверен, что вы могли бы легко указать макеты в файлах пользовательского интерфейса, если бы вы делали это таким образом.

3

У меня была похожая проблема, и я закончил тем, что поместил распорку рядом с моим контролем с размером 0 в измерении, о котором я заботился, иExpanding sizeType. Затем я пометил сам элемент управленияExpanding sizeType и установите его растяжение равным 1. Таким образом, когда он виден, он имеет приоритет над разделителем, но когда он невидим, разделитель расширяется, заполняя пространство, обычно занимаемое элементом управления.

7

Вы можете использовать QStackedWidget. Поместите свою кнопку на первую страницу, пустой QWidget на вторую и измените индекс страницы, чтобы ваша кнопка исчезла, сохранив при этом свое первоначальное пространство.

QStackedWidget может быть вставлен динамически :)
Это выглядит как самое простое решение для реализации. Издержки (как наличие большого количества дополнительных QStackedWidgets, так и кода для создания и размещения QStackedWidget везде, где это необходимо) могут сделать это менее желательным. Но это все еще вероятно решение, которое будет иметь меньше всего сюрпризов позже.
1

Вот PyQt-версия класса C ++ Hider изКуба Оберответ.

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)
4

Я имею в виду 3 решения:

1) Создайте подкласс вашего QWidget и используйте специальный / собственный метод замены setVisible (), который включает / выключает рисование виджета (если виджет должен быть невидимым, просто игнорируйте рисование переопределенным методом paintEvent ()). Это грязное решение, не используйте его, если вы можете сделать это другими способами.

2) ИспользуйтеQSpacerItem в качестве заполнителя и установите его видимость на противоположность QWidget, который вы хотите скрыть, но сохраните его положение + размер в макете.

3) Вы можете использовать специальный контейнерный виджет (унаследованный от QWidget), который получает / синхронизирует его размер на основе его дочерних / дочерних виджетов. размер.

Ваше второе решение не является плохой идеей; это может быть даже реализовано как оболочка QWidget.
Это может быть сделано без разделения на подклассы, замены виджетов и т. Д. Фильтры событий для спасения.
0

Может быть QWidget :: setWindowOpacity (0.0) это то, что вы хотите? Но этот метод работает не везде.

AFAIK setWindowOpacity не работает со встроенными QWidgets, только с высокоуровневыми

Похожие вопросы