Iniciando no QT, parte II – Sinais, Slots e Timers
Neste post vou seguindo com a brincadeira de fazer pequenos tutoriais sobre o QT para iniciantes. Desta vez vou mostrar como criar nossos próprios sinais e slots e como manipular um timer.
O código fonte pode ser encontrado neste link.
O programa de exemplo será um cronômetro que vai utilizar a classe QTimer, uma das principais classes do QT, e vai mostrar o tempo utilizando um display estilo LCD de calculadora com a classe QLCDNumber.
QT e os Timers
O suporte mais básico a timers no QT é disponibilizado pela classe QObject, que fornece os métodos QObject::startTimer() e QObject::killTimer(). O primeiro método retorna um ID único de timer e o segundo finaliza o timer através desse ID.
Para que isso funcione, no entanto, o trecho de código que utiliza esse mecanismo precisa estar dentro de um “event loop”. A partir do momento que o timer for iniciado ele de tempos em tempos (timeout) faz com que a aplicação dispate um QTimerEvent, que interrompe o fluxo normal do programa até que o evento seja processado.
O tempo máximo de timeout não é delimitado, sendo possível criar timers com timeout de anos, porém, o tempo mínimo pode variar de sistema para sistema. No windows Vista o timer mínimo é de 10 ms enquanto no Linux 2.6.x isso é configurável (o default é 4 ms). O QT vai tentar entregar todos os eventos, conforme pedidos, mas caso o sistema não permita ele vai descartar os “excedentes”.
Timers também podem ser utilizados em threads, porém deve-se respeitar a condição de estar dentro de um event loop. Threads merecem um artigo à parte, como sempre…
A classe QTimer implementa timers de mais alto nível, possibilitando algumas funcionalidades. Uma delas é o QTimer::singleShot(), que dispara um evento uma única vez.
Leia a extensa e repetitiva documentação, é chato, mas acredite, vai te poupar de muita QDorDeCabeca…
Mais sobre Sinais e Slots
Sinais e slots são utilizados para comunicação entre objetos, sendo uma das peças fundamentais do QT. Nesse sistema ao invés de se implementar callbacks para tratar eventos, utiliza-se o conceito de conectar sinais a slots, tornando a programação mais intuitiva.
Os sinais e slots são métodos de classe que são tratados pelo Meta-Object Compiler (moc) antes do código fonte ser compilado. Eles têm declarações especiais que não são parte do padrão C++, por isso o moc fazum parsing e gera o código fonte compilável.
Ao executar o comando moc sobre qualquer classe que implemente sinais e slots, uma certa quantidade de código fonte é gerada. Com o uso do programa qmake, a chamada ao programa moc fica a cargo do Makefile, tornando-a transparente para o programador.
Para que uma classe possa implementar sinais e slots, ela precisa ter acesso ao Meta-Object System. Por isso ela deve herdar da classe QObject ou suas subclasses, e precisa ter a macro Q_OBJECT na sua área de declaração privada. Terminados os preparativos, sinais são declarados em uma seção “signals:” e slots em seções “[public|protected|private] slots:“.
Um slot, depois de declarado, é então definido como qualquer outro método comum, podendo ser explicitamente chamado como qualquer outro. Já os sinais, são um pouco mais delicados, e são apenas declarados e jamais definidos pelo programador. A razão? Esta mensagem no final da tentativa de compilação:
tmp/moc_mydisplay.o: In function `MyDisplay::signalPlay()': ~/stopwatch/tmp/moc_mydisplay.cpp:89: multiple definition of `MyDisplay::signalPlay()' tmp/mydisplay.o:~/stopwatch/mydisplay.cpp:169: first defined here collect2: ld returned 1 exit status make: ** [stopwatch] Erro 1
Dentro do arquivo moc_mydisplay.cpp é gerado o seguinte código:
// SIGNAL 0 void MyDisplay::signalPlay() { QMetaObject::activate(this, &staticMetaObject, 0, 0); }
Sim, o moc gera a definição dos sinais com chamadas a meta-métodos do QT. Portanto, definir o corpo de um sinal é ilegal no QT e vai gerar um erro de compilação por redefinição de método. Deixe que o moc cuida da implementação do sinal pra você.
Conecte-se
Não adianta apenas definir sinais e slots. É preciso definir como eles vão interagir. A maneira de fazer isso é conectando-os através do método connect() presente em todo herdeiro de QObject. Isso fará o moc implementar o sinal de forma que uma chamada a ele resulte numa chamada ao(s) slot(s) conectado(s), com os mesmos parâmetros.
Dependendo da forma de conexão, um sinal pode até retornar o mesmo valor que o último slot chamado retornar. Note que isso não é muito seguro para sinais conectados a vários slots, pois, nada garante a ordem de chamada.
Um dado sinal pode ser conectado a um segundo sinal diretamente. Um emit() no primeiro sinal é então equivalente a um emit() no segundo sinal, visto que após a etapa de criação dos mocs, a chamada ao primeiro resulta na chamada ao segundo, que resulta na chamada de outro método conectado e assim sucessivamente.
Formas de conexão
Existem basicamente três formas de conectar sinais e slots e se não usadas adequadamente podem ser fonte de bugs terrivelmente difíceis de se descobrir. Essas formas de conexão são passadas como parâmetros extras para o método connect(). São elas:
Conexão Direta: O slot é chamado imediatamente ao sinal ter sido emitido, na thread onde o sinal foi emitido. Isso funciona como uma chamada direta ao slot.
Conexão Enfileirada: O sinal é emitido e a chamada ao slot vai para uma lista interna do QT, e o sinal retorna imediatamente, independentemente do slot ter sido chamado ou não. O evente loop vai então processando essa lista e só mais tarde o slot será invocado na thread onde o objeto do slot reside.
Conexão Automática: Este é o tipo default, utilizado quando não se especifica o tipo de conexão. É uma das “Sementes do Mal”, pois apresenta dois comportamentos distintos: se o sinal e o slot residirem em uma mesma thread, funciona como conexão direta, mas caso contrário, funciona como conexão enfileirada.
Conexão enfileirada Bloqueante: Ué!? não eram só três? Well, essa é uma das vantagens de ler esse artigo: te salva de documentação inconsitente! Essa conexão é semelhante à conexão enfileirada, exceto pelo fato de que a thread do sinal fica bloqueada até o sinal ser devidamente executado. Note que só deve se utilizada com muito cuidado, e para sinais e slots em threads diferentes. O mal uso disso poderá causar deadlocks. Você vai saber quando vir algo assim:
user@host:~/stopwatch$ ./stopwatch Qt: Dead lock detected while activating a BlockingQueuedConnection: Sender is QPushButton(0x807e2d8), receiver is MyDisplay(0x8076ac0)
Para sanar as dúvidas a respeito do que significa a expressão “thread onde um objeto reside” consulte a documentação do QT arespeito de threads.
Tome sempre muito cuidado com sinais, slots, timers e threads. Esses são recursos fundamentais do QT, mas o seu mal uso pode acarretar em bugs extremamente difíceis de serem localizados. Consulte toda a documentação à respeito.
Descendo à fonte
O nosso exemplo se compões de duas classes MyDisplay e StopWatch e um arquivo main comum. O main apenas cria uma QApplication e um objeto StopWatch. A classe MyDisplay cuida da parte visual do programa, consistindo em um diálogo com botões e um display no estilo LCD. A classe StopWatch vai criar um widget de display e implementar o timer. Note que essa arquitetura não é a mais elegente, sendo mais interessante fazer a classe StopWatch herdar de MyDisplay. Porém, isso estragaria a brincadeira praticamente eliminando a necessidade do uso de sinais e slots.
O uso de sinais e slots fica mais claro quando queremos que objetos se comuniquem com o seu exterior. Em geral, as classes não conhecem o que há fora delas. Passar ponteiros de objetos externos para dentro delas, fere o encapsulamento e torna o código menos genérico. Em nosso exemplo, o uso mais interessante para sinais e slots é para o objeto myDisplay (interno a StopWatch) comunicar eventos de/para o objeto stopwatch (externo a Mydisplay), sem que para isso se perca em generalidade ou encapsulamento.
Com a ajuda do QT Designer (num outro artigo eu falarei dele), criei o diálogo, botões e LCD. Depois simplifiquei o código para se adequar aos nossos propósitos. Como myDisplay herda de QDialog e este herda indiretamente de QObject, para usarmos sinais e slots bastou acrescentar a macro Q_OBJECT na seção privada da declaração da classe. Após isso, acrescentei entre outros auxiliares, os métodos:
public slots: void slotDisplayValue( long ); private slots: void slotPlay(); void slotStop(); signals: void signalPlay(); void signalPause(); void signalStop(); void signalReset();
Os sinais comunicarão ao exterior os eventos relacionados aos cliques nos botões. Já os slots privados farão um pré-processamento interno, enquanto o público receberá o valor de ticks vindo do timer externo. Dessa forma a classe envia informações ao exterior e recebe informações de fora, sem conhecer o que está do outro lado. A boa e velha interface por contrato.
public slots: void slotPlay(); void slotPause(); void slotStop(); void slotReset(); private slots: void slotTick(); signals: void signalTicks( long );
Análoga e inversamente a classe StopWatch implementa os slots que receberão os sinais da classe MyDisplay, e declara o sinal que enviará informações para o seu slot público.
Ela também implementa um QTimer que vai disparar a cada 10 milissegundos (para que os nosso amigos lerdinhos do Vista possam acompanhar!), incrementando um contador. Esse contador é enviado de volta ao display que o formata e exibe.
O cronômetro pode ser parado, pausado e reiniciado a qualquer momento, clicando nos botões correspondentes.
Os Finalmentes
O exemplo em si não tem muito código, mas ilustra o uso mais básico de timers e a construção de sinais e slots customizados. Uma atenção toda especial, no entanto, deve ser dada aos detalhes que envolvem o uso desses recursos, pois eles podem se tornar armadilhas letais.
Links importantes
Sinais e Slots
QTimer
Threads no QT
Documentação on-line do QT
Comments
-
Walison
-
Walison
-
Blabos
-
Blabos
-
Rodrigo
-
Rodrigo
-
Lucas
-
Lucas
-
http://blabos.pip.verisignlabs.com/ blabos
-
http://blabos.pip.verisignlabs.com/ blabos
-
Lucas
-
Lucas

