web-dev-qa-db-fra.com

Comment rendre un widget Qt invisible sans changer la position des autres widgets Qt?

J'ai une fenêtre pleine de QPushButtons et QLabels et de divers autres QWidgets amusants, tous disposés dynamiquement en utilisant divers objets QLayout ... et ce que j'aimerais faire, c'est que de temps en temps certains de ces widgets deviennent invisibles . Autrement dit, les widgets invisibles occuperaient toujours leur espace normal dans la disposition de la fenêtre, mais ils ne seraient pas rendus: à la place, l'utilisateur verrait simplement la couleur d'arrière-plan de la fenêtre dans le rectangle/la zone du widget.

hide() et/ou setVisible(false) ne fera pas l'affaire car ils entraînent la suppression complète du widget de la mise en page, permettant à d'autres widgets de s'étendre pour occuper l'espace "nouvellement disponible" ; un effet que je veux éviter.

Je suppose que je pourrais faire une sous-classe de chaque QWidget type qui remplace paintEvent() (et mousePressEvent() et etc) pour être un no-op (le cas échéant), mais je Je préférerais une solution qui ne nécessite pas que je crée trois douzaines de sous-classes QWidget différentes.

37
Jeremy Friesner

La seule manière décente que je connaisse est d'attacher un filtre d'événements au widget et de filtrer les événements de repeinture. Cela fonctionnera quelle que soit la complexité du widget - il peut avoir des widgets enfants.

Vous trouverez ci-dessous un exemple autonome complet. Il est livré avec quelques mises en garde, cependant, et aurait besoin d'être développé pour être complet. Seul l'événement Paint est remplacé, vous pouvez donc toujours interagir avec le widget, vous ne verrez aucun effet.

Les clics de souris, les événements d'entrée/sortie de souris, les événements de focus, etc. continueront à accéder au widget. Si le widget dépend de certaines choses qui sont effectuées lors d'un repeint, peut-être en raison d'une mise à jour () déclenchée sur ces événements, il peut y avoir des problèmes.

Au minimum, vous auriez besoin d'une déclaration de cas pour bloquer plus d'événements - par exemple, déplacer la souris et cliquer sur des événements. La gestion du focus est une préoccupation: vous devez déplacer le focus vers le widget suivant de la chaîne si le widget est masqué pendant qu'il est focalisé et chaque fois qu'il réacquiert le focus.

Le suivi de la souris pose également quelques problèmes, vous voudrez peut-être prétendre que le widget a perdu le suivi de la souris s'il le faisait auparavant. Émuler correctement cela nécessiterait des recherches, je ne sais pas du haut de ma tête quel est le protocole d'événement de suivi de souris exact que Qt présente aux widgets.

//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"
14
Kuba Ober

Ce problème a été résolu dans Qt 5.2. La solution mignonne est:

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

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

53
Thorbjørn Martsum

Vous pouvez utiliser un QStackedWidget. Placez votre bouton sur la première page, un QWidget vierge sur la seconde et modifiez l'index de page pour faire disparaître votre bouton tout en conservant son espace d'origine.

7
goertzenator

J'ai 3 solutions en tête:

1) Sous-classe votre QWidget et utilisez une méthode de remplacement spéciale/propre setVisible () qui active/désactive la peinture du widget (si le widget doit être invisible, ignorez simplement la peinture avec une méthode paintEvent () remplacée). C'est une sale solution, ne l'utilisez pas si vous pouvez le faire autrement.

2) Utilisez un QSpacerItem comme espace réservé et définissez sa visibilité à l'opposé du QWidget que vous souhaitez masquer mais conservez sa position + sa taille dans la mise en page.

3) Vous pouvez utiliser un widget conteneur spécial (hérité de QWidget) qui obtient/synchronise sa taille en fonction de la taille de ses widgets enfant/enfants.

4
Viktor Benei

J'ai eu un problème similaire et j'ai fini par mettre un espaceur à côté de mon contrôle avec une taille de 0 dans la dimension qui m'intéressait et un Expanding sizeType. Ensuite, j'ai marqué le contrôle lui-même avec un Expanding sizeType et défini son étirement sur 1. De cette façon, lorsqu'il est visible, il a priorité sur l'espaceur, mais lorsqu'il est invisible, l'espaceur se développe pour remplir l'espace normalement occupé par le contrôle.

3
BKewl

Une option consiste à implémenter une nouvelle sous-classe de QWidgetItem qui renvoie toujours false pour QLayoutItem::isEmpty. Je soupçonne que cela fonctionnera en raison de l'exemple de sous-classe QLayout de Qt documentation :

Nous ignorons QLayoutItem :: isEmpty (); cela signifie que la mise en page traitera les widgets cachés comme visibles.

Cependant, vous trouverez peut-être que l'ajout d'éléments à votre mise en page est un peu ennuyeux de cette façon. En particulier, je ne suis pas sûr que vous puissiez facilement spécifier des dispositions dans les fichiers d'interface utilisateur si vous le faisiez de cette façon.

2
cgmb

Voici une version PyQt de la classe C++ Hider de la réponse de Kuba Ober .

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)
1
glennr

Je pense que vous pouvez utiliser un QFrame comme wrapper. Bien qu'il puisse y avoir une meilleure idée.

1
Yassir Ennazk

Essayez void QWidget::erase (). Cela fonctionne sur Qt 3.

0
shan

Peut-être que QWidget :: setWindowOpacity (0.0) est ce que vous voulez? Mais cette méthode ne fonctionne pas partout.

0
dmr