Eventos Qt e sinal / slots

97

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

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

Rafael
fonte

Respostas:

30

A documentação do Qt provavelmente explica melhor:

No Qt, eventos são objetos, derivados da QEventclasse abstrata , que representam coisas que aconteceram dentro de um aplicativo ou como resultado de atividade externa que o aplicativo precisa saber. Os eventos podem ser recebidos e tratados por qualquer instância de uma QObjectsubclasse, mas são especialmente relevantes para widgets. Este documento descreve como os eventos são entregues e tratados em um aplicativo típico.

Portanto, eventos 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, teclado ou roda do mouse) e será entregue por meio do loop de eventos QApplication. Em geral, a menos que você configure o código, você não estará gerando eventos. Você pode filtrá-los QObject::installEventFilter()ou manipular eventos em um objeto de subclasse, substituindo as funções apropriadas.

Sinais e slots são muito mais fáceis de gerar e receber e você pode conectar quaisquer duas QObjectsubclasses. Eles são controlados por meio da Metaclasse (dê uma olhada em seu arquivo moc_classname.cpp para mais informações), mas a maior parte da comunicação entre classes que você produzirá provavelmente usará sinais e slots. Os sinais podem ser entregues imediatamente ou adiados por meio de uma fila (se você estiver usando threads).

Um sinal pode ser gerado.

Harald Scheirich
fonte
a fila adiada é igual ao loop de eventos da fila ou existem duas filas? um para sinais adiados e ligado para evento?
Guillaume07
29
@Raphael que vergonha por aceitar essa resposta. - O parágrafo citado nem mesmo contém as palavras “sinal” ou “slot”!
Robert Siemer
1
@neuronet: o parágrafo é citado com “[isso] explica melhor”, o que não é verdade. De modo nenhum. Nem um pouco.
Robert Siemer
@Robert, então, se aquela pequena linha de introdução fosse alterada para "Primeiro, vamos considerar como os documentos explicam os eventos, antes de prosseguirmos para considerar como eles estão relacionados aos sinais", você aceitaria a resposta? Em caso afirmativo, podemos apenas editar a resposta para melhorá-la, pois é um ponto menor e concordo que poderia ser melhor formulado. [Editar esta é uma pergunta genuína: como não tenho certeza se o resto é muito melhor ... mas só porque a parte citada não menciona sinais parece uma preocupação superficial se o resto é realmente bom, o que eu sou não supondo .]
Eric
4
@neuronet, se você remover a citação por completo, a resposta aos meus olhos melhorará. - Se você remover a resposta inteira, isso melhorará todo o controle de qualidade.
Robert Siemer
152

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

Em primeiro lugar, vamos definir o que queremos dizer com 'evento Qt' exatamente: uma função virtual em uma classe Qt, que você deve reimplementar em uma classe base sua se quiser lidar com o evento. Está relacionado ao padrão Template Method .

Observe como usei a palavra " identificador ". Na verdade, aqui está uma diferença básica entre a intenção de sinais e eventos:

  • Você " lida " com eventos
  • Você " é notificado sobre " as emissões de sinal

A diferença é que, ao "lidar" com o evento, você assume a responsabilidade de "responder" com um comportamento útil fora da classe. Por exemplo, considere um aplicativo que tem um botão com um número. O aplicativo precisa permitir que o usuário focalize o botão e altere o número pressionando as teclas "para cima" e "para baixo" do teclado. Caso contrário, o botão deve funcionar normalmente QPushButton(pode ser clicado, etc). No Qt, isso é feito criando seu próprio pequeno "componente" reutilizável (subclasse de QPushButton), que é reimplementado 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? Este código apresenta uma nova abstração: um widget que atua como um botão, mas com alguma funcionalidade extra. Adicionamos esta funcionalidade de forma muito conveniente:

  • Como reimplementamos um virtual, nossa implementação tornou-se automaticamente encapsulada em nossa classe. Se os designers do Qt tivessem feito keyPressEventum sinal, precisaríamos decidir se herdaríamos QPushButtonou apenas se conectaríamos externamente ao sinal. Mas isso seria estúpido, já que no Qt sempre se espera que você herde ao escrever um widget com um comportamento personalizado (por uma boa razão - capacidade de reutilização / modularidade). Assim, ao criar keyPressEventum evento, eles transmitem sua intenção que keyPressEventé apenas um bloco de construção básico da funcionalidade. Se fosse um sinal, pareceria algo voltado para o usuário, quando não era para ser.
  • Como a implementação da classe base da função está disponível, implementamos facilmente o padrão Cadeia de responsabilidade tratando de nossos casos especiais (teclas para cima e para baixo) e deixando o resto para a classe base. Você pode ver que isso seria quase impossível se keyPressEventfosse 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 de QPushButton- apenas instanciá-lo e ser notificado quando for clicado :

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

Isso claramente deve ser feito pelo usuário da classe:

  • se tivéssemos que criar uma subclasse QPushButtontoda vez que quisermos que algum botão nos notifique de um clique, isso exigiria muitas subclasses sem um bom motivo! Um widget que sempre mostra um "Hello world" messageboxquando clicado é útil apenas em um único caso - portanto, não é totalmente reutilizável. Novamente, não temos escolha a não ser fazer a coisa certa - conectando-nos a ela externamente.
  • podemos querer conectar vários slots para clicked()- ou conectar vários sinais para sayHello(). Com sinais, não há confusão. Com a criação de subclasses, você teria que se sentar e refletir sobre alguns diagramas de classes até decidir sobre um design apropriado.

Observe que um dos locais QPushButtonemite clicked()está em sua mousePressEvent()implementação. Isso não significa clicked()e mousePressEvent()são intercambiáveis ​​- apenas que estão relacionados.

Portanto, sinais e eventos têm finalidades diferentes (mas estão relacionados porque ambos permitem que você "assine" uma notificação de que algo está acontecendo).

Stefan Monov
fonte
Eu entendi a ideia. O evento é por classe, todas as instâncias de uma classe irão reagir da mesma forma, todas elas irão chamar o mesmo QClassName :: event. Mas o sinal é por objeto, cada objeto pode ter sua conexão única de slot de sinal.
炸鱼 薯条 德里克
39

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

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

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

Um loop principal ocioso (Qt's, por exemplo) está 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 dono do mundo, sabe quando isso acontece.

O resultado dessa chamada select () poderia ser: novos dados no soquete conectar ao X11, um pacote para uma porta UDP que ouvimos entrou, etc. - Esse material não é 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 os ignora.

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

Uma diferença entre esses dois conceitos é visível aqui: um slot não tem voto sobre se outros slots registrados para aquele sinal serão chamados ou não. - Os eventos são mais como uma cadeia, e o manipulador de eventos decide se interrompe essa cadeia ou não. Os sinais se parecem com uma estrela ou árvore nesse aspecto.

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

O que abstrai o que depende do caso: o sinal clicked () - abstrai os eventos do mouse (um botão desce e sobe novamente sem se mover muito). Os eventos de teclado são abstrações de níveis inferiores (coisas como 果 ou é são vários toques de tecla em meu sistema).

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

Robert Siemer
fonte
4
Acho esta parte: "um slot não tem voto sobre se outros slots registrados para aquele sinal serão chamados ou não" muito esclarecedora para a diferença entre sinais e eventos. No entanto, tenho uma pergunta relacionada: o que são mensagens e como as mensagens estão relacionadas a sinais e eventos? (se estiverem relacionados). As mensagens são uma abstração de sinais e eventos? Eles podem ser usados ​​para descrever tais interações entre QObjects (ou outros objetos?)
user1284631
@axeoth: No Qt, não existem "mensagens". Bem, há uma caixa de mensagem, mas é só isso.
Reintegrar Monica de
@KubaOber: Obrigado. Eu queria dizer "eventos".
user1284631 de
3
@axeoth: Então sua pergunta é um absurdo. Ele diz: "o que são eventos e como os eventos estão relacionados a sinais e eventos".
Reintegrar Monica de
@KubaOber: Não me lembro do contexto um ano depois. Enfim, parece que eu estava perguntando quase a mesma coisa que o OP: qual é a diferença entre eventes e eventos e como os eventos estão relacionados aos sinais e slots. O que era realmente o caso naquela época, IIRC, eu estava procurando uma maneira de agrupar classes controladas por sinais / slots Qt em algum tipo de classes não-Qt, então eu estava procurando uma maneira de comandar a primeira de dentro da última. Eu imagino que estava pensando em enviar eventos para eles, já que isso significava que eu só tinha que incluir cabeçalhos Qt, e não forçar minhas classes a implementar o sinal / slots.
user1284631 de
16

Os eventos são despachados pelo loop de eventos. Cada programa GUI precisa de um loop de eventos, seja o que for que você escreva no Windows ou Linux, usando Qt, Win32 ou qualquer outra biblioteca GUI. Além disso, cada thread tem seu próprio loop de eventos. No Qt, "GUI Event Loop" (que é o loop principal de todos os aplicativos Qt) está oculto, mas você o inicia chamando:

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

As mensagens que o SO e outros aplicativos enviam ao seu programa são despachadas como eventos.

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

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

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

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

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

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

firescreamer
fonte
Esta frase me dá uma compreensão concisa da diferença entre o intervalo de sinal do Qt e os eventos:When you send an event, it must wait for time when event loop dispatch all events that came earlier. Because of this, execution of the cod after sending event or signal is different
swdev
7

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

Ele discute a diferença entre eventos 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 eventos 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 no pressionamento do mouse de baixo nível ou eventos de pressionamento de tecla que causaram a emissão do sinal. Mas se estivermos implementando a classe QPushButton, estaremos mais interessados ​​na implementação de código para eventos de mouse e tecla. Além disso, normalmente lidamos com eventos, mas somos notificados por emissões de sinal.

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


Observação, por favor, veja os comentários úteis abaixo sobre esta resposta de Kuba Ober, que me fazem pensar se ela pode ser um pouco simplista.

Eric
fonte
Não consigo ver como seriam dois mecanismos usados ​​para realizar a mesma coisa - acho que é uma generalização inútil que não ajuda ninguém.
Reintegrar Monica em
@KubaOber não, ajuda a evitar os detalhes de nível inferior. Você diria que Python não é útil porque você pode fazer a mesma coisa escrevendo em código de máquina? :)
Eric
2
Estou dizendo que a primeira frase da citação é generalizante a ponto de ser inútil. Tecnicamente, não é incorreto, mas apenas porque você poderia, se desejasse, usar a passagem de eventos para realizar o que o mecanismo de slot de sinal faz e vice-versa. Seria muito complicado. Então, não, slots de sinal não são o mesmo que eventos, eles não realizam a mesma coisa, e o fato de que ambos existem é crítico para o sucesso do Qt. O exemplo de botão citado ignora completamente a funcionalidade geral do sistema de slot de sinal.
Reintegrar Monica em
1
"Eventos e sinais são dois mecanismos paralelos usados ​​para realizar a mesma coisa dentro do escopo estreito de alguns widgets / controles de IU ." A parte em negrito está criticamente ausente e torna a citação inútil. Mesmo assim, pode-se questionar o quanto da "mesma coisa" é realmente realizado: sinais permitem que você reaja a eventos de clique sem instalar um filtro de eventos no widget (ou derivar do widget). Fazer isso sem os sinais é muito mais complicado e acopla o código do cliente ao controle. Isso é um design ruim.
Reintegrar Monica em
1
Os eventos são um conceito onipresente . A implementação particular em Qt os define concretamente como estruturas de dados passadas para event. Os sinais e slots são, concretamente , métodos, enquanto o mecanismo de conexão é uma estrutura de dados que permite ao sinal invocar um ou mais slots listados como conectados a ele. Espero que você veja que falar de sinal / slots como algum "subconjunto" ou "variante" de eventos é um absurdo e vice-versa. Eles realmente são coisas diferentes que acontecem a ser usado para fins semelhantes no contexto de alguns widgets. É isso aí. Quanto mais você generaliza, menos útil você se torna IMHO.
Reintegrar Monica em
5

TL; DR: Sinais e slots são chamadas de método indireto. Os eventos são estruturas de dados. Então, eles são animais bem diferentes.

O único momento em que eles se unem é quando as chamadas de slot são feitas através dos limites do thread. Os argumentos de chamada de slot são empacotados em uma estrutura de dados e enviados como um evento para a fila de eventos do segmento de recebimento. Na thread de recebimento, o QObject::eventmétodo descompacta 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, podemos pensar nos eventos como uma forma de invocar o eventmétodo do objeto-alvo . 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.

Reintegrar Monica
fonte
2

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

QEvent e suas subclasses são basicamente apenas pequenos pacotes de dados padronizados para a estrutura se comunicar com seu código. Se você quiser prestar atenção ao mouse de alguma forma, você só precisa 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 do a API Qt.

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

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

jkerian
fonte
2
"Os eventos são normalmente tratados no Qt com sinais / slots" - na verdade, não ... Objetos QEvent são sempre passados ​​por meio de virtuais sobrecarregados! Por exemplo, não há sinal de "keyDown" em QWidget - em vez disso, keyDown é uma função virtual.
Stefan Monov
Concordo com stefan, na verdade uma parte bem confusa do Qt
Harald Scheirich
1
@Stefan: Eu diria que poucos aplicativos Qt substituem keyDown (e em vez disso usam sinais como QAbstractButton :: clicked e QLineEdit :: editionFinished) para justificar "tipicamente". Eu certamente já peguei a entrada do teclado antes, mas não é a maneira usual de lidar com eventos.
jkerian
após a sua edição (esclarecendo o que você quer dizer com "evento"), sua postagem agora está correta. Eu me abstenho de reutilizar palavras como essa, no entanto. As coisas estão turvas o suficiente sem chamar os sinais de "um tipo de eventos".
Stefan Monov,
2

Eu encontrei essa pergunta ao ler 'Processamento de eventos' por Leow Wee Kheng. Também diz:

insira a descrição da imagem aqui

Jasmine Blanchette diz:

A principal razão pela qual você usaria eventos em vez de chamadas de função padrão, ou sinais e slots, é que os eventos podem ser usados ​​de forma síncrona e assíncrona (dependendo se você chama sendEvent () ou postEvents ()), enquanto chama uma função ou invoca um slot é sempre síncrono. Outra vantagem dos eventos é que eles podem ser filtrados.

França
fonte
1

Outra consideração pragmática menor: emitir ou receber sinais requer herança, QObjectenquanto um objeto de qualquer herança pode postar ou enviar um evento (desde que você invoque QCoreApplication.sendEvent()ou postEvent()). Isso geralmente não é um problema, mas: para usar sinais PyQt estranhamente requer QObjectpara ser a primeira superclasse, e você pode não querer reorganizar sua ordem de herança apenas para poder enviar sinais.)

bootchk
fonte
-1

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

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

Substituiria a mouseMoveEvent()função de conveniência encontrada em QWidget(mas não QQuickItemmais) e lidaria com os mouseMovesinais que um gerenciador de cena emitiria para o item. O fato de o sinal ser emitido em nome do item por alguma entidade externa não é importante e acontece com bastante frequência no mundo dos componentes Qt, embora supostamente não seja permitido (componentes Qt freqüentemente contornam essa regra). Mas Qt é um conglomerado de muitas decisões de design diferentes e praticamente definido por medo de quebrar o código antigo (o que acontece com bastante frequência de qualquer maneira).

user1095108
fonte
Os eventos têm a vantagem de se propagar na QObjecthierarquia pai-filho, quando necessário. As conexões de sinal / slot são apenas uma promessa de chamar uma função, direta ou indiretamente, quando uma determinada condição for atendida. Não há uma hierarquia de manuseio associada com sinais e slots.
anônimo de
Você pode implementar regras de propagação semelhantes com sinais. Não existe regra. que um determinado sinal não pode ser reemitido para outro objeto (filho). Você pode adicionar um acceptedparâmetro de referência a um sinal e os slots que o manipulam ou pode ter uma estrutura como parâmetro de referência com um acceptedcampo. Mas o Qt fez as escolhas de design que fez e agora elas estão imutáveis.
user1095108
1
Você está basicamente reimplementando eventos se fizer da maneira que sugere.
Fabio A.
@FabioA. Esse é o ponto da pergunta do OP. Eventos e sinais podem / podem substituir uns aos outros.
user1095108
Se você reimplementa eventos, não está substituindo eventos. Mais do que isso: os sinais são implementados em cima dos eventos. Você precisa de um tipo de "sistema de eventos" para informar a alguns outros loops de eventos, possivelmente em um thread que não seja o seu, que em algum momento no futuro ele terá que executar uma determinada função em algum lugar.
Fabio A.