Iniciando no QT, parte II – Sinais, Slots e Timers

February 1, 2008 · Posted in C/C++, Qt 

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

    Muito bom o texto, ja estou acessando o blog periodicamente so para saber mais sobre QT4 e é claro que estou pesquisando e lendo a documentacao.

    Att

  • Walison

    Muito bom o texto, ja estou acessando o blog periodicamente so para saber mais sobre QT4 e é claro que estou pesquisando e lendo a documentacao.

    Att

  • Blabos

    Agradeço sinceramente pela audiência e espero poder ajudar cada vez mais.
    Meu muito obrigado e um forte abraço.

  • Blabos

    Agradeço sinceramente pela audiência e espero poder ajudar cada vez mais.
    Meu muito obrigado e um forte abraço.

  • Rodrigo

    Cara, você é fera, estou adorando seus tutorias, abraços e muito obrigado!!!

  • Rodrigo

    Cara, você é fera, estou adorando seus tutorias, abraços e muito obrigado!!!

  • Lucas

    Muito boa suas explicaçoes, muito bem detalhada tambem!
    Voce ja fez algum programa para parte grafica como exemplo de funçoes matematicas?
    Porque nao estou conseguindo!
    Agradeço pela atençao

  • Lucas

    Muito boa suas explicaçoes, muito bem detalhada tambem!
    Voce ja fez algum programa para parte grafica como exemplo de funçoes matematicas?
    Porque nao estou conseguindo!
    Agradeço pela atençao

  • http://blabos.pip.verisignlabs.com/ blabos

    Oi Lucas,

    Eu não entendi muito bem a sua dúvida.

    Você está procurando por soluções gráficas relacionadas a funções matemáticas? Será que o QWT te ajuda?

    Qualquer coisa entre em contado detalhando mais o que você precisa.

    Abraços

  • http://blabos.pip.verisignlabs.com/ blabos

    Oi Lucas,

    Eu não entendi muito bem a sua dúvida.

    Você está procurando por soluções gráficas relacionadas a funções matemáticas? Será que o QWT te ajuda?

    Qualquer coisa entre em contado detalhando mais o que você precisa.

    Abraços

  • Lucas

    Sim, estou procurando por soluções gráficas relacionadas a funções matemáticas como seno e cosseno por exemplo.

    Eu estou com problema com o QWT acho que o problema é de instalação mas nao tenho certeza, queria saber que versão você usa de QWT e da QT?

    Se ja você já fez algo parecido com isso que estou querendo fazer e puder me dar uma ajuda serei grato.

    Agradeço pela atenção

    Saudações

  • Lucas

    Sim, estou procurando por soluções gráficas relacionadas a funções matemáticas como seno e cosseno por exemplo.

    Eu estou com problema com o QWT acho que o problema é de instalação mas nao tenho certeza, queria saber que versão você usa de QWT e da QT?

    Se ja você já fez algo parecido com isso que estou querendo fazer e puder me dar uma ajuda serei grato.

    Agradeço pela atenção

    Saudações

blog comments powered by Disqus