Eventos Qt e sinal / slots

No mundo do Qt, qual é a diferença de events e sinal / slots?

Um substitui o outro? Os events são uma abstração de sinal / slots?

A documentação do Qt provavelmente explica melhor:

Em Qt, events são objects, derivados da class abstrata QEvent, que representam coisas que aconteceram em um aplicativo ou como resultado de atividades externas que o aplicativo precisa conhecer. Os events podem ser recebidos e manipulados por qualquer instância de uma subclass QObject, mas são especialmente relevantes para widgets. Este documento descreve como os events são entregues e tratados em um aplicativo típico.

Portanto, events e sinais / slots são dois mecanismos paralelos que realizam as mesmas coisas. Em geral, um evento será gerado por uma entidade externa (por exemplo, roda de teclado ou mouse) e será entregue através do loop de events no QApplication. Em geral, a menos que você configure o código, você não estará gerando events. Você pode filtrá-los através de QObject::installEventFilter() ou manipular events no object com subclass, sobrescrevendo as funções apropriadas.

Sinais e Slots são muito mais fáceis de gerar e receber e você pode conectar quaisquer duas subclasss QObject. Eles são tratados através do Metaclass (dê uma olhada no seu arquivo moc_classname.cpp para mais), mas a maior parte da comunicação interclass que você produzirá provavelmente usará sinais e slots. Os sinais podem ser entregues imediatamente ou adiados por uma fila (se você estiver usando threads).

Um sinal pode ser gerado.

Em Qt, sinais e events são implementações do padrão Observer . Eles são usados ​​em diferentes situações, porque eles têm diferentes pontos fortes e fracos.

Primeiro de tudo, vamos definir exatamente o que entendemos por ‘evento Qt’: uma function virtual em uma class Qt, que você deve reimplementar em uma class base sua se quiser manipular o evento. Está relacionado ao padrão do Template Method .

Note como eu usei a palavra ” manipular “. De fato, aqui está uma diferença básica entre a intenção de sinais e events:

  • Você ” manipula ” events
  • Você ” é notificado de ” emissões de sinal

A diferença é que quando você “manipula” o evento, assume a responsabilidade de “responder” com um comportamento útil fora da class. Por exemplo, considere um aplicativo que tenha um botão com um número nele. O aplicativo precisa deixar o usuário focar o botão e alterar o número pressionando as teclas “para cima” e “para baixo” do teclado. Caso contrário, o botão deve funcionar como um QPushButton normal (pode ser clicado, etc). No Qt isso é feito criando seu próprio “componente” reutilizável (subclass de QPushButton), que reimplementa QWidget :: keyPressEvent. Pseudo-código:

 class NumericButton extends QPushButton private void addToNumber(int value): // ... reimplement base.keyPressEvent(QKeyEvent event): if(event.key == up) this.addToNumber(1) else if(event.key == down) this.addToNumber(-1) else base.keyPressEvent(event) 

Vejo? Esse código apresenta uma nova abstração: um widget que age como um botão, mas com alguma funcionalidade extra. Adicionamos essa funcionalidade de maneira muito conveniente:

  • Desde que reimplementamos um virtual, nossa implementação foi automaticamente encapsulada em nossa class. Se os projetistas da Qt tivessem feito um sinal keyPressEvent, precisaríamos decidir se herdamos QPushButton ou se apenas conectamos externamente ao sinal. Mas isso seria estúpido, já que no Qt você sempre espera herdar ao escrever um widget com um comportamento personalizado (por um bom motivo – reusabilidade / modularidade). Assim, ao tornar keyPressEvent um evento, eles informam que o keyPressEvent é apenas um bloco básico de funcionalidade. Se fosse um sinal, pareceria uma coisa voltada para o usuário, quando não é para ser.
  • Como a implementação da class base da function está disponível, implementamos facilmente o padrão de Cadeia de Responsabilidade manipulando nossos casos especiais (teclas para cima e para baixo) e deixando o restante para a class base. Você pode ver que isso seria quase impossível se keyPressEvent fosse um sinal.

O design do Qt é bem pensado – eles nos fizeram cair no poço do sucesso , tornando mais fácil fazer a coisa certa e difícil fazer a coisa errada (tornando keyPressEvent um evento).

Por outro lado, considere o uso mais simples do QPushButton – apenas instanciá-lo e ser notificado quando for clicado :

 button = new QPushButton(this) connect(button, SIGNAL(clicked()), SLOT(sayHello()) 

Isto é claramente feito para ser feito pelo usuário da class:

  • se tivéssemos que subclassificar QPushButton toda vez que quisermos que algum botão nos avise de um clique, isso exigiria muitas subclasss sem um bom motivo! Um widget que sempre mostra uma checkbox de mensagem “Hello world” quando clicado é útil apenas em um único caso – portanto, não é totalmente reutilizável. Mais uma vez, não temos escolha a não ser fazer a coisa certa – conectando-nos externamente.
  • Podemos querer conectar vários slots para clicked() – ou conectar vários sinais para sayHello() . Com sinais não há barulho. Com a subclass, você teria que sentar e refletir sobre alguns diagramas de classs até decidir sobre um design apropriado.

Note que um dos lugares que o QPushButton emite clicked() está em sua implementação mousePressEvent() . Isso não significa que clicked() e mousePressEvent() são intercambiáveis ​​- apenas que estão relacionados.

Assim, os sinais e events têm propósitos diferentes (mas estão relacionados em que ambos permitem que você “assine” uma notificação de que algo está acontecendo).

Eu não gosto das respostas até agora. – Deixe-me concentrar nessa parte da questão:

Os events são uma abstração de sinal / slots?

Resposta curta: não. A resposta longa levanta uma questão “melhor”: como os sinais e events estão relacionados?

Um loop principal inativo (Qt’s, por exemplo) é geralmente “preso” em uma chamada select () do sistema operacional. Essa chamada faz o aplicativo “dormir”, enquanto ele passa um monte de sockets ou arquivos ou qualquer coisa para o kernel pedindo: se algo mudar neles, deixe a chamada select () retornar. – E o kernel, como o mestre do mundo, sabe quando isso acontece.

O resultado dessa chamada select () poderia ser: novos dados no socket se conectam ao X11, um pacote para uma porta UDP que nós escutamos entrou, etc. – Esse material não é nem um sinal Qt, nem um evento Qt, e o O loop principal do Qt decide se transforma os dados novos em um, o outro, ou o ignora.

Qt poderia chamar um método (ou vários) como keyPressEvent (), transformando-o efetivamente em um evento Qt. Ou Qt emite um sinal, que na verdade procura todas as funções registradas para aquele sinal, e as chama uma após a outra.

Uma diferença desses dois conceitos é visível aqui: um slot não tem votação sobre se outros slots registrados para esse sinal serão chamados ou não. – Os events são mais como uma cadeia, e o manipulador de events decide se interrompe essa cadeia ou não. Os sinais parecem uma estrela ou uma tree neste aspecto.

Um evento pode triggersr ou ser transformado em um sinal (apenas emita um e não chame “super ()”). Um sinal pode ser transformado em um evento (chame um manipulador de events).

O que abstrai o que depende do caso: o sinal clicked () abstrai os events do mouse (um botão desce e sobe novamente sem muito movimento). Eventos de teclado são abstrações de níveis mais baixos (coisas como 果 ou é vários toques no meu sistema).

Talvez o focusInEvent () seja um exemplo do oposto: ele poderia usar (e, portanto, abstrair) o sinal clicked (), mas não sei se ele realmente funciona.

Eventos são despachados pelo loop de events. Cada programa GUI precisa de um loop de events, seja ele qual for o Windows ou o Linux, usando Qt, Win32 ou qualquer outra biblioteca GUI. Além disso, cada thread tem seu próprio loop de events. Em Qt, o “Event Loop da GUI” (que é o loop principal de todas as aplicações do Qt) está oculto, mas você começa a chamar:

 QApplication a(argc, argv); return a.exec(); 

Mensagens OS e outros aplicativos enviados para o seu programa são despachados como events.

Sinais e slots são mecanismos Qt. No processo de compilações usando moc (compilador meta-object), elas são alteradas para funções de retorno de chamada.

O evento deve ter um receptor, que deve despachá-lo. Ninguém mais deveria receber esse evento.

Todos os slots conectados ao sinal emitido serão executados.

Você não deve pensar em Signals como events, porque como você pode ler na documentação do Qt:

Quando um sinal é emitido, os slots conectados a ele são geralmente executados imediatamente, assim como uma chamada de function normal. Quando isso acontece, o mecanismo de sinais e slots é totalmente independente de qualquer loop de events da GUI.

Quando você envia um evento, ele deve aguardar por algum tempo até que o loop de events despache todos os events que vieram anteriormente. Por causa disso, a execução do código após o envio do evento ou sinal é diferente. O código seguinte ao enviar um evento será executado imediatamente. Com os mecanismos de sinais e slots, depende do tipo de conexão. Normalmente, ele será executado após todos os slots. Usando Qt :: QueuedConnection, ele será executado imediatamente, assim como os events. Verifique todos os tipos de conexão na documentação do Qt .

Há um artigo que discute o processamento de events com algum detalhe: http://www.packtpub.com/article/events-and-signals

Ele discute a diferença entre events e sinais aqui:

Eventos e sinais são dois mecanismos paralelos usados ​​para realizar a mesma coisa. Como uma diferença geral, os sinais são úteis ao usar um widget, enquanto os events são úteis ao implementar o widget. Por exemplo, quando estamos usando um widget como QPushButton, estamos mais interessados ​​em seu sinal clicked () do que na pressão do mouse de baixo nível ou events de pressionamento de tecla que fizeram com que o sinal fosse emitido. Mas se estamos implementando a class QPushButton, estamos mais interessados ​​na implementação de código para events de mouse e chave. Além disso, geralmente lidamos com events, mas somos notificados por emissões de sinais.

Essa parece ser uma maneira comum de falar sobre isso, já que a resposta aceita usa algumas das mesmas frases.


Note, por favor, veja comentários úteis abaixo sobre esta resposta de Kuba Ober, que me fazem pensar se pode ser um pouco simplista.

TL; DR: Sinais e slots são chamadas indiretas de método. Eventos são estruturas de dados. Então eles são animais bem diferentes.

A única vez em que eles se juntam é quando as chamadas de slots são feitas através dos limites dos threads. Os argumentos da chamada de slot são empacotados em uma estrutura de dados e são enviados como um evento para a fila de events do encadeamento de recebimento. No segmento de recebimento, o método QObject::event desempacota os argumentos, executa a chamada e possivelmente retorna o resultado se for uma conexão de bloqueio.

Se estivermos dispostos a generalizar para o esquecimento, poderíamos pensar em events como uma forma de invocar o método de event do object de destino. Esta é uma chamada de método indireta, de certa forma – mas não acho que seja uma maneira útil de pensar sobre isso, mesmo que seja uma afirmação verdadeira.

Os events (em um sentido geral de interação usuário / rede) são normalmente tratados em Qt com sinais / slots, mas os sinais / slots podem fazer muitas outras coisas.

O QEvent e suas subclasss são basicamente apenas pacotes de dados padronizados para o framework se comunicar com o seu código. Se você quiser prestar atenção ao mouse de alguma forma, basta olhar para a API QMouseEvent, e os designers da biblioteca não precisam reinventar a roda toda vez que você precisa descobrir o que o mouse fez em algum canto a API do Qt.

É verdade que se você estiver esperando por events (novamente no caso geral) de algum tipo, seu slot quase certamente aceitará uma subclass QEvent como argumento.

Com isso dito, sinais e slots podem certamente ser usados ​​sem o QEvents, embora você descubra que o ímpeto original para ativar um sinal geralmente será algum tipo de interação do usuário ou outra atividade assíncrona. Às vezes, no entanto, seu código chegará a um ponto em que triggersr um certo sinal será a coisa certa a fazer. Por exemplo, triggersr um sinal conectado a uma barra de progresso durante um longo processo não envolve um QEvent até esse ponto.

Outra pequena consideração pragmática: emitir ou receber sinais requer herdar QObject enquanto um object de qualquer inheritance pode postar ou enviar um evento (já que você invoca QCoreApplication.sendEvent () ou postEvent ().) Isso geralmente não é um problema, mas: usar sinais PyQt estranhamente requer que o QObject seja a primeira superclass, e você pode não querer reorganizar sua ordem de inheritance apenas para poder enviar sinais.)

Na minha opinião, os events são completamente redundantes e podem ser descartados. Não há razão para que os sinais não possam ser substituídos por events ou events por sinais, exceto que o Qt já está configurado como está. Os sinais enfileirados são envolvidos por events e os events podem ser envolvidos por sinais, por exemplo:

 connect(this, &MyItem::mouseMove, [this](QMouseEvent*){}); 

Substituiria a function mouseMoveEvent () de conveniência encontrada em QWidget (mas não mais em QQuickItem) e lidaria com sinais mouseMove que um gerente de cena emitia para o item. O fato de que o sinal é emitido em nome do item por alguma entidade externa não é importante e acontece com bastante frequência no mundo dos componentes do Qt, mesmo que não seja permitido. Mas o Qt é um conglomerado de muitas decisões de design diferentes e praticamente fundido na pedra por medo de quebrar o código antigo (o que acontece com bastante frequência de qualquer maneira).