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.