Entendendo isHidden e isVisible em QWidget do Qt

Aálise de isHidden e isVisible em QWidget

Problema Encontrado

Ao desenvolver uma aplicação Qt, encontrei uma situação peculiar onde um widget não estava sendo exibido corretamente. Criei duas classes, SequenciaCenario e Capitulo2, com a seguinte estrutura:

// SequenciaCenario.cpp
SequenciaCenario::SequenciaCenario(QWidget *parent)
    :QWidget(parent)
{
    numero = 1;
    this->setFixedSize(1920, 1080);
}

void SequenciaCenario::iniciar()
{
    if (listaCenarios.isEmpty()) {
        qDebug() << "Lista de exibição está vazia";
        return;
    }

    cenarioAtual = new Cenario(listaCenarios.at(0), this);
    connect(cenarioAtual, &Cenario::sinal_fimPagina,
            this, &SequenciaCenario::slot_proximaPagina, Qt::UniqueConnection);
    cenarioAtual->iniciar();
}

// Capitulo2.cpp
Capitulo2::Capitulo2(QWidget *parent)
    :QWidget(parent)
{
    meusCenarios = new SequenciaCenario(this);
    this->iniciar();
}

void Capitulo2::slot_Cenarios1()
{
    meuTelefone->close();

    QStringList listaCenarios;
    listaCenarios.push_back("capitulo2_4.png");
    listaCenarios.push_back("capitulo2_5.png");
    listaCenarios.push_back("capitulo2_6.png");
    listaCenarios.push_back("capuito2_7.png");

    meusCenarios->setListaCenarios(listaCenarios);

    connect(meusCenarios, &SequenciaCenario::sinal_fimSequenciaCenarios,
            this, &Capitulo2::slot_Telefone2);

    meusCenarios->iniciar();
    meusCenarios->show();
}

Após criar uma instância da classe SequenciaCenario e chamar show() em Capitulo2, nada era exibido na tela.

Apensivei sobre o problema e descobri duas soluções funcionais:

Primeira solução:

SequenciaCenario::SequenciaCenario(QWidget *parent)
    :QWidget(parent)
{
    numero = 1;
    this->setFixedSize(1920, 1080);
    this->hide();
}

Chamar this->hide() no construtor permitiu que os elementos fossem exibidos corretamente.

Segunda solução:

void SequenciaCenario::iniciar()
{
    if (listaCenarios.isEmpty()) {
        qDebug() << "Lista de exibição está vazia";
        return;
    }

    cenarioAtual = new Cenario(listaCenarios.at(0), this);
    connect(cenarioAtual, &Cenario::sinal_fimPagina,
            this, &SequenciaCenario::slot_proximaPagina, Qt::UniqueConnection);
    cenarioAtual->show();
    cenarioAtual->iniciar();
}

Chamar cenarioAtual->show() no método iniciar() também resolveu o problema.

A primeira solução era particularmente confusa, pois chamar hide() parecia contraintuitivo para fazer elementos aparecerem.

Função show() de QWidget

Na documentação do Qt, show() deveria exibir tanto o widget quanto seus filhos. Portanto, ao chamar meusCenarios->show() em Capitulo2, esperava-se que o cenarioAtual dentro de SequenciaCenario também fosse exibido.

Investigando o código-fonte do QWidget, descbori que todos os métodos show() internamente chamam setVisible():

void QWidgetPrivate::setVisible(bool visible)
{
    Q_Q(QWidget);
    if (visible) { // show
        // Designer usa uma técnica para fazer o grabWidget funcionar sem exibir
        if (!q->isWindow() && q->parentWidget() && q->parentWidget()->isVisible()
            && !q->parentWidget()->testAttribute(Qt::WA_WState_Created))
            q->parentWidget()->window()->d_func()->createRecursively();

        //cria janelas principais, mas não filhos de pais invisíveis
        QWidget *pw = q->parentWidget();
        if (!q->testAttribute(Qt::WA_WState_Created)
            && (q->isWindow() || pw->testAttribute(Qt::WA_WState_Created))) {
            q->create();
        }

O método createRecursively é responsável por exibir recursivamente widgets filhos. A decisão de criar um widget filho depende se ele está oculto ou não:

void QWidgetPrivate::createRecursively()
{
    Q_Q(QWidget);
    q->create(0, true, true);
    for (int i = 0; i < children.size(); ++i) {
        QWidget *child = qobject_cast<QWidget *>(children.at(i));
        if (child && !child->isHidden() && !child->isWindow() && !child->testAttribute(Qt::WA_WState_Created))
            child->d_func()->createRecursively();
    }
}

Métodos isVisible e isHidden

Para entender melhor o comportamento, adicionei testes com os métodos isVisible() e isHidden():

void Capitulo2::slot_Cenarios1()
{
    meuTelefone->close();

    qDebug() << meusCenarios->isVisible();

    QStringList listaCenarios;
    listaCenarios.push_back("capitulo2_4.png");
    listaCenarios.push_back("capitulo2_5.png");
    listaCenarios.push_back("capitulo2_6.png");
    listaCenarios.push_back("capuito2_7.png");

    meusCenarios->setListaCenarios(listaCenarios);

    connect(meusCenarios, &SequenciaCenario::sinal_fimSequenciaCenarios,
            this, &Capitulo2::slot_Telefone2);

    meusCenarios->iniciar();
    meusCenarios->show();

    qDebug() << meusCenarios->isVisible();
}

void SequenciaCenario::iniciar()
{
    if (listaCenarios.isEmpty()) {
        qDebug() << "Lista de exibição está vazia";
        return;
    }

    cenarioAtual = new Cenario(listaCenarios.at(0), this);
    connect(cenarioAtual, &Cenario::sinal_fimPagina,
            this, &SequenciaCenario::slot_proximaPagina, Qt::UniqueConnection);
    qDebug() << "cenarioAtual visible :" << cenarioAtual->isVisible();
    qDebug() << "cenarioAtual hidden :" << cenarioAtual->isHidden();
    cenarioAtual->iniciar();
}

Sem chamar hide(), a saída era:

true
cenarioAtual visible : false
cenarioAtual hidden : true
true

Após chamar hide(), a saída era:

false
cenarioAtual visible : false
cenarioAtual hidden : false
true

Consultando a documentação do método isHidden, encontrei a explicação crucial:

"Um widget pode ser visível (isVisible() retorna true) e ainda assim ser considerado oculto (isHidden() retorna true) se seu pai não estiver visível ou se o widget tiver o atributo WA_WState_Hidden."

Investigação no Código-Fonte

Ao investigar o código-fonte do Qt, descobri que no construtor de QWidget:

QWidget::QWidget(QWidget *parent, Qt::WindowFlags f)
    : QObject(), QPaintDevice(), 
#ifndef QT_NO_TOOLTIP
    toolTip(nullptr),
#endif
#ifndef QT_NO_ACCESSIBILITY
    accessibleRoot(nullptr),
#endif
    d_ptr(new QWidgetPrivate(this))
{
    Q_D(QWidget);
    d->init(parent, f);
}

No método init():

void QWidgetPrivate::init(QWidget *parent, Qt::WindowFlags flags)
{
    // ...
    q->setAttribute(Qt::WA_WState_Hidden);
    // ...
}

Isso define o atributo WA_WState_Hidden como padrão para novos widgets.

A investigação continuou no método setParent():

void QWidget::setParent(QWidget *parent, Qt::WindowFlags f)
{
    // ...
    if (parent && parent->isVisible())
        setAttribute(Qt::WA_WState_Hidden);
    // ...
}

Finalmente, no método setParent_sys:

void QWidgetPrivate::setParent_sys(QWidget *parent, bool create, bool visible)
{
    // ...
    if (parent && parent->isVisible() && !testAttribute(Qt::WA_WState_Hidden))
        setAttribute(Qt::WA_WState_Hidden);
    // ...
}

Isso explica o comportamento observado: quando um novo widget é definido como filho de um widget pai visível, ele automaticamente recebe o atributo WA_WState_Hidden, tornando-o oculto mesmo que o pai seja exibido.

A primeira solução funcionou porque ao chamar hide() explicitamente no construtor, o widget já estava oculto antes de ser adicionado como filho, evitando que o sistema definisse WA_WState_Hidden novamente.

Tags: Qt QWidget C++ gui desenvolvimento

Publicado em 7-15 04:54