Construindo um gerenciador de interface simples para sistemas multi-janelas com QT

January 24, 2008 · Posted in C/C++, Qt 

Em diversas ocasiões acabei tendo que dar manutenção em interfaces de sistemas com muitas telas, sejam elas gráficas ou em modo texto. Muitas vezes, a quantidade de telas foi crescendo junto com o sistema sem um planejamento prévio, o que sempre levava a um emaranhado de chamadas obscuras, #ifndefs, switches, entre outros monstrinhos.

A primeira vez que eu me deparei com um problema do gênero, foi quando eu estava fazendo a primeira versão de um jogo parecido com o saudoso Elifoot para uma feira no CEFETES. Na época eu me deparei com um conjunto de funções onde elas chamavam umas às outras e só retornavam quando acontecia o gol. Resultado: haja pilha!!! (Não se preocupem, na nova versão isso foi corrigido!)

No caso das interfaces, com várias janelas, e em sistemas onde o planejamento ocorre on-the-fly, é comum termos janelas que chamam outras janelas que fecham as janelas anteriores e assim por diante, deixando pra trás um rastro de destruição e terror. Acredite, você não vai querer nunca dar manutenção num treco desses.

Do pó ao pó…

No meu caso, resovi o problema criando um gerenciador de interface. Um objeto centralizador que controla a criação e destruição dos objetos de interface. Cada objeto, ao terminar suas tarefas, sinaliza para o gerenciador e ele cuida de limpar a sujeira. Assim, um objeto sempre nasce, realiza suas tarefas e morre sem passar o controle do programa para frente, ele sempre volta para o gerenciador.

Note que eu não disse que o gerenciador vai destruir o objeto, mas sim que ele vai fazer a limpeza. Cada objeto tem que ser construído de forma a avisar ao gerenciador toda vez que ele terminar, seja com sucesso ou falha. Muitas vezes o próprio objeto é capaz de “se matar” sozinho e isso é muito útil, tirando um pouco do trabalho do gerenciador, mas não o controle.

Before starting

Eu não tenho a pretensão que esse simples post cubra todos os recursos do QT ou todos os aspectos do gerenciamento de interfaces. Eu apenas vou falar de como EU resolvi um problema pelo qual eu passei. Esse post também é uma homenagem a um amigo paulista-carioca que já teve que descascar um abacaxi desses!

Mão na massa

Eu sou, na maioria das vezes, um programador bottom-up, porém um analista top-down. Isso pode parecer confuso, mas não há razão para pânico, ou quase…

Os objetos precisam trocar mensagens. Para isso o sistema de sinal-slot do QT vai ser de grande ajuda. Os objetos vão mandar sinais ( emit(signal) ) e o gerenciador vai escutá-los ( slots() ).

Por incrível que pareça, o construtor do gerenciador, daqui pra frente chamado de UIM, não possui uma linha de código sequer. Ele apenas inicializa os ponteiro com 0, apenas por paranóia (ou não)…

O UIM implementa slots para criação dos objetos e limpeza da bagunça. Ao criar um objeto, o UIM conecta esse objeto ao seu respectivo slot de limpeza. Quando o objeto morre, ele ativa o slot de limpeza no UIM. Os slots de criação serão ligados aos menus do objeto gráfico principal, neste exemplo, uma janela.

Os outros objetos são janelas menores, dialogs, sendo um para configuração do aplicativo, e dois pra falar de si mesmo (about), afinal, marketing é importante…

Todos os objetos gráficos eu cliquei e arrastei no Qt Designer e depois ajustei pra ficar do meu jeito. Nada de ficar alinhando pixel na mão, afinal não é uma obra de arte, é só uma prova de conceito.

Os Objetos Gráficos

A tela de configuração é um diálogo comum com um combo-box onde está uma lista de parâmetros a serem passados para o widget principal. Os detalhes que eu acrescentei ao código gerado pelo Qt Designer foram um destrutor, e um par sinal-slot para enviar os dados selecionados de volta para o gerenciador. Eu não me preocupei em guardar o estado da configuração atual. Isso é papo pra outro post.

Os abouts ficaram basicamente com o mesmo código, acrescidos de destrutores.

A janela principal, possui um menu que chama as outras janelas e um widget maluco cheio de quadrados que ficam piscando. A quantidade de quadrados é a informação que será alterada pela janela de configuração.

The Magic QT

Todos o objetos gráficos utilizados, são também QWidgets, pois suas classes herdam da classe QWidget, e eu utilizo em cada construtor o seguinte método herdado:

this->setAttribute( Qt::WA_DeleteOnClose , true );

De acordo com a documentação on-line, isto diz para o QT que ele deve deletar o widget quando ele for fechado. Do contrário, ele será apenas escondido.

“…When a widget accepts the close event, it is hidden…”

“…If you want the widget to be deleted when it is closed, create it with the Qt::WA_DeleteOnClose flag. This is very useful for independent top-level windows in a multi-window application…”

As rotinas que fazem os objetos terminarem com falha, como clicar em Cancelar ou Fechar, estão diretamente conectadas aos seus próprios slots close(). Isso dispara uma série de eventos que culminam com a destruição do objeto.

As rotinas que fazem o objeto terminar com sucesso, como clicar em ok e enviar os dados, foram direcionadas a voltar para o UIM, para que ele possa roteá-las. Depois de pegar os dados, o UIM chama diretamente o slot close do objeto().

Quando um objeto é destruído, ele emite seu último suspiro, digo, sinal que é o destroyed(). Literalmente ele grita “FUI!!!”.

Do “lado de fora” do objeto, o UIM está ouvindo, conectando este sinal à rotina de limpeza, e TADÃM!!!! prontinho.

No exemplo que eu usei, a quantidade de quadrados na tela chega de um objeto MyConf, passa pelo UIM e é roteada para a MyApplication. Esta por sua vez, repassa para o widget maluco interno.

Pra ficar ainda mais interessante, enquanto uma janela menor está aberta, o widget pára de piscar, retornando quando a janelinha for fechada. Parece bobo, mas isso foi só pra mostrar que o UIM tem controle total sobreo o que acontece em cada janela, estando ela em foco, ou não.

Conclusão

O framework QT é imenso e fornece uma gama enorme funcionalidades. Uma delas é poder controlar como os objetos são destruídos. Utilizando isso junto com um pouco de planejamento e aliado ao sistema de sinal-slot podemos construir uma interface bastante complexa, sem transformá-la num Tarrasque na hora de dar manutenção.

Fonte:

http://doc.trolltech.com

P.S.: O Código fonte completo do exemplo encontra-se sob a GPL e pode ser baixado por este link. Um dia eu ponho comentários nele…

BUGFIX

O infeliz do inseto se escondia na falta de inicialização de propriedades no construtor da classe MySquares. O bichinho, apesar de pequeno era venenoso, visto que causava uma ‘Segmentation Fault’. Ao iniciar o programa, se eu fosse em qualquer about e o fechasse, isso disparava uma sequência de eventos que partia do pressuposto que a tela já estava devidamente configurada. O código corrigido encontra-se no link acima, e lembre-se:

“Sempre inicialize suas variáveis”

Comments

  • Fernando

    Muito bom… mas não sei pq, eu tenho a impressão de já ter visto isso antes…
    Mas me diz uma coisa, como vc faria se em uma das janelas tivesse q ter uma bolinha vermelha girando em 3 velocidades diferentes?!

  • Fernando

    Muito bom… mas não sei pq, eu tenho a impressão de já ter visto isso antes…
    Mas me diz uma coisa, como vc faria se em uma das janelas tivesse q ter uma bolinha vermelha girando em 3 velocidades diferentes?!

  • Blabos

    Bolinha vermelha? Três velocidades?
    Nem faço idéia do que é isso…
    Mas provavelmente eu usaria TPM:
    Telepatia
    Premonição
    Milagre

  • Blabos

    Bolinha vermelha? Três velocidades?
    Nem faço idéia do que é isso…
    Mas provavelmente eu usaria TPM:

    Telepatia
    Premonição
    Milagre

  • Fernando

    Se tem q usar TPM, então estamos aptos p/ fazer isso… já usamos muito esses 3 pré-requisitos…

  • Fernando

    Se tem q usar TPM, então estamos aptos p/ fazer isso… já usamos muito esses 3 pré-requisitos…

  • Marcelo Ossamu Honda

    Olá
    Para fazer as três bolas girar em diferentes velocidades você precisará de três diferentes timers e controlar e/ou sincronizar o paint event ou outra maneira que queira para mostrá-las (exemplo OpenGL).
    Abraços

  • Marcelo Ossamu Honda

    Olá
    Para fazer as três bolas girar em diferentes velocidades você precisará de três diferentes timers e controlar e/ou sincronizar o paint event ou outra maneira que queira para mostrá-las (exemplo OpenGL).
    Abraços

  • Blabos

    ;)

  • Blabos

    ;)

  • flavio

    Acho q o Marcelo não entendeu…tudo bem, só quem viveu esse pesade…digo projeto sabe do que estamos falando, se nem o gerente do projeto entendeu o que ele mesmo queria está tudo bem o pessoal de fora não entender também…KKKKKKKK
    Ficou show de bola o texto, mas a minha sugestão seria agora fazer um texto para os Qt newbies, anota aí !

  • flavio

    Acho q o Marcelo não entendeu…tudo bem, só quem viveu esse pesade…digo projeto sabe do que estamos falando, se nem o gerente do projeto entendeu o que ele mesmo queria está tudo bem o pessoal de fora não entender também…KKKKKKKK
    Ficou show de bola o texto, mas a minha sugestão seria agora fazer um texto para os Qt newbies, anota aí !

blog comments powered by Disqus