Olá programadores e programadoras! Tudo bem com vocês? Espero que sim! Nessa aula veremos como criar e tratar sinais e slots enviados a partir da GUI.
Até agora criamos uma janela e adicionamos um widget de botão simples a ela, mas o botão não faz nada. Isso não é nada útil – quando você cria aplicativos GUI, normalmente você quer que eles façam alguma coisa! O que precisamos é de uma maneira de conectar a ação de pressionar o botão a fazer algo acontecer. No Qt, isso é fornecido por sinais e slots.
Sinais são notificações emitidas por widgets quando algo acontece. Esse algo pode ser uma série de coisas, desde pressionar um botão, até a alteração do texto de uma caixa de entrada, até a alteração do texto da janela. Muitos sinais são iniciados pela ação do usuário, mas isso não é uma regra.
Além de notificar sobre algo que está acontecendo, os sinais também podem enviar dados para fornecer contexto adicional sobre o que aconteceu.
Slots é o nome que Qt usa para os receptores de sinais. Em Python, qualquer função (ou método) em sua aplicação pode ser usada como slot – simplesmente conectando o sinal a ela. Se o sinal enviar dados, a função de recepção também receberá esses dados. Muitos widgets Qt também possuem seus próprios slots integrados, o que significa que você pode conectar widgets Qt diretamente.
Vamos dar uma olhada nos fundamentos dos sinais Qt e como você pode usá-los para conectar widgets e fazer as coisas acontecerem em seus aplicativos.
Nosso aplicativo simples atualmente possui um QMainWindow com um QPushButton definido como widget central. Vamos começar conectando este botão a um método Python personalizado. Aqui criamos um slot personalizado simples chamado botao_foi_clicado que aceita o sinal clicado do QPushButton:
# --- Importar as bibliotecas --- #
import sys
from PySide2.QtWidgets import QApplication, QMainWindow, QPushButton
# --- Criar uma classe que herda QMainWindow para personalizar a janela principal do app --- #
class TelaPrincipal(QMainWindow):
"""Classe que cria a tela principal do app."""
def __init__(self):
"""Função responsável por inicializar a classe."""
# --- Herdar a classe QMainWindow --- #
super().__init__()
# --- Colocar o nome na janela --- #
self.setWindowTitle('Meu App')
# --- Criar um botão --- #
botao = QPushButton('Me aperte!')
# --- Deixar o botão clicável --- #
botao.setCheckable(True)
# --- Conectar a função ao botão --- #
botao.clicked.connect(self.botao_foi_clicado)
# --- Deixar o widget centralizado --- #
self.setCentralWidget(botao)
def botao_foi_clicado(self):
"""Função responsável por enviar o sinal para a GUI."""
print('Clicado!')
# --- Criar a instância do app --- #
app = QApplication(sys.argv)
# --- Criar a janela do app --- #
janela = TelaPrincipal()
janela.show()
# --- Executar o loop de eventos --- #
app.exec_()
Se você clicar no botão, verá o texto "Clicado!" no console.
É um bom começo! Já ouvimos que os sinais também podem enviar dados para fornecer mais informações sobre o que acabou de acontecer. O sinal .clicked não é exceção, também fornecendo um estado verificado (ou alternado) para o botão. Para botões normais isso é sempre False, então nosso primeiro slot ignorou esses dados. No entanto, podemos tornar nosso botão verificável e ver o efeito.
No exemplo a seguir, adicionamos um segundo slot que gera o checkstate:
# --- Importar as bibliotecas --- #
import sys
from PySide2.QtWidgets import QApplication, QMainWindow, QPushButton
# --- Criar uma classe que herda QMainWindow para personalizar a janela principal do app --- #
class TelaPrincipal(QMainWindow):
"""Classe que cria a tela principal do app."""
def __init__(self):
"""Função responsável por inicializar a classe."""
# --- Herdar a classe QMainWindow --- #
super().__init__()
# --- Colocar o nome na janela --- #
self.setWindowTitle('Meu App')
# --- Criar um botão --- #
botao = QPushButton('Me aperte!')
# --- Deixar o botão clicável --- #
botao.setCheckable(True)
# --- Conectar a função ao botão --- #
botao.clicked.connect(self.botao_foi_clicado)
botao.clicked.connect(self.botao_foi_verificado)
# --- Deixar o widget centralizado --- #
self.setCentralWidget(botao)
def botao_foi_clicado(self):
"""Função responsável por enviar o sinal para a GUI."""
print('Clicado!')
def botao_foi_verificado(self, verificado):
"""
Função responsável por retornar o valor do estado do botão.
:param verificado: Valor bool do botão quando verificado.
"""
print('Clicado?', verificado)
# --- Criar a instância do app --- #
app = QApplication(sys.argv)
# --- Criar a janela do app --- #
janela = TelaPrincipal()
janela.show()
# --- Executar o loop de eventos --- #
app.exec_()
Se você pressionar o botão, você o verá destacado como marcado. Pressione-o novamente para liberá-lo. Procure o estado de verificação no console.
Você pode conectar quantos slots desejar a um sinal e responder a diferentes versões de sinais ao mesmo tempo em seus slots.
Muitas vezes é útil armazenar o estado atual de um widget em uma variável Python. Isso permite que você trabalhe com os valores como qualquer outra variável Python e sem acessar o widget original. Você pode armazenar esses valores como variáveis individuais ou usar um dicionário, se preferir. No próximo exemplo, armazenamos o valor verificado do nosso botão em uma variável chamada botao_foi_checado em self:
# --- Criar uma classe que herda QMainWindow para personalizar a janela principal do app --- #
class TelaPrincipal(QMainWindow):
"""Classe que cria a tela principal do app."""
def __init__(self):
"""Função responsável por inicializar a classe."""
# --- Herdar a classe QMainWindow --- #
super().__init__()
# --- Variável de checagem do botão --- #
self.botao_foi_checado = True # 1
# --- Colocar o nome na janela --- #
self.setWindowTitle('Meu App')
# --- Criar um botão --- #
botao = QPushButton('Me aperte!')
# --- Deixar o botão clicável --- #
botao.setCheckable(True)
# --- Conectar a função ao botão --- #
botao.clicked.connect(self.botao_foi_clicado)
sellf.setChecked(self.botao_foi_checado) # 2
# --- Deixar o widget centralizado --- #
self.setCentralWidget(botao)
def botao_foi_verificado(self, verificado):
"""
Função responsável por retornar o valor do estado do botão.
:param verificado: Valor bool do botão quando verificado.
"""
self.botao_foi_checado = verificado # 3
print(self.botao_foi_checado)
Defina o valor padrão para nossa variável;
Use o valor padrão para definir o estado inicial do widget e;
Quando o estado do widget mudar, atualize a variável para corresponder.
Você pode usar esse mesmo padrão com qualquer widget PySide2. Se um widget não fornecer um sinal que envie o estado atual, você precisará recuperar o valor do widget diretamente em seu manipulador. Por exemplo, aqui estamos verificando o estado verificado em um manipulador pressionado:
class TelaPrincipal(QMainWindow):
"""Classe que cria a tela principal do app."""
def __init__(self):
"""Função responsável por inicializar a classe."""
# --- Herdar a classe QMainWindow --- #
super().__init__()
# --- Variável de checagem do botão --- #
self.botao_foi_checado = True
# --- Colocar o nome na janela --- #
self.setWindowTitle('Meu App')
# --- Criar um botão --- #
self.botao = QPushButton('Me aperte!') # 1
# --- Deixar o botão clicável --- #
botao.setCheckable(True)
# --- Conectar a função ao botão --- #
botao.clicked.connect(self.botao_foi_solto) # 2
sellf.setChecked(self.botao_foi_checado)
# --- Deixar o widget centralizado --- #
self.setCentralWidget(botao)
def botao_foi_solto(self):
"""Função responsável por retornar se o botão foi solto ou não."""
self.botao_foi_checado = self.botao.isChecked() # 3
print(self.botao_foi_checado)
Precisamos manter uma referência ao botão (self) para que possamos acessá-lo em nosso slot;
O sinal liberado é acionado quando o botão é liberado, mas não envia o estado de verificação;
.isChecked() retorna o estado de verificação do botão.
Até agora vimos como aceitar sinais e imprimir a saída no console. Mas que tal fazer algo acontecer na interface quando clicamos no botão? Vamos atualizar nosso método de slot para modificar o botão, alterando o texto e desabilitando o botão para que não seja mais clicável. Também desativaremos o estado verificável por enquanto:
# --- Importar as bibliotecas --- #
import sys
from PySide2.QtWidgets import QApplication, QMainWindow, QPushButton
# --- Criar uma classe que herda QMainWindow para personalizar a janela principal do app --- #
class TelaPrincipal(QMainWindow):
"""Classe que cria a tela principal do app."""
def __init__(self):
"""Função responsável por inicializar a classe."""
# --- Herdar a classe QMainWindow --- #
super().__init__()
# --- Colocar o nome na janela --- #
self.setWindowTitle('Meu App')
# --- Criar um botão --- #
self.botao = QPushButton('Me aperte!') # 1
# --- Conectar a função ao botão --- #
botao.clicked.connect(self.botao_foi_clicado)
# --- Deixar o widget centralizado --- #
self.setCentralWidget(botao)
def botao_foi_clicado(self):
"""Função responsável por enviar o sinal para a GUI."""
self.botao.setText('Você já me clicou') # 2
self.botao.setEnable(False) # 3
# --- Criar a instância do app --- #
app = QApplication(sys.argv)
# --- Criar a janela do app --- #
janela = TelaPrincipal()
janela.show()
# --- Executar o loop de eventos --- #
app.exec_()
Precisamos conseguir acessar o botão no nosso método botao_foi_clicado, então mantemos uma referência a ele em self;
Você pode alterar o texto de um botão passando uma string para .setText() e;
Para desabilitar um botão, chame .setEnabled() com False.
Se você clicar no botão, o texto mudará e o botão se tornará não clicável.
Você não está restrito a alterar o botão que aciona o sinal, você pode fazer o que quiser em seus métodos de slot. Por exemplo, tente adicionar a seguinte linha ao método botao_foi_clicado para alterar também o título da janela:
self.setWindowTitle('Um novo título do app')
A maioria dos widgets tem seus próprios sinais – e o QMainWindow que estamos usando para nossa janela não é exceção. No exemplo mais complexo a seguir, conectamos o sinal .windowTitleChanged no QMainWindow a uma customização.
No exemplo a seguir, conectamos o sinal .windowTitleChanged no QMainWindow a um slot de método mudar_titulo_app. Este slot também recebe o novo título da janela:
# --- Importar as bibliotecas --- #
import sys
from random import choice
from PySide2.QtWidgets import QApplication, QMainWindow, QPushButton
# --- Criar a lista com os títulos do app --- #
lista_titulos = [ # 1
'Meu app',
'Meu app',
'Ainda meu app',
'Ainda meu app',
'Será que mudou o nome?',
'Será que mudou o nome?',
'Isso é uma surpresa!',
'Isso é uma surpresa!',
'Algo deu errado'
]
# --- Criar uma classe que herda QMainWindow para personalizar a janela principal do app --- #
class TelaPrincipal(QMainWindow):
"""Classe que cria a tela principal do app."""
def __init__(self):
"""Função responsável por inicializar a classe."""
# --- Herdar a classe QMainWindow --- #
super().__init__()
# --- Colocar o nome na janela --- #
self.setWindowTitle('Meu App')
# --- Criar um botão --- #
self.botao = QPushButton('Me aperte!')
# --- Conectar a função ao botão --- #
botao.clicked.connect(self.botao_foi_clicado)
# --- Conectar a função ao título do app --- #
self.windowTitleChanged.connect(self.mudar_titulo_app) # 2
# --- Deixar o widget centralizado --- #
self.setCentralWidget(botao)
def botao_foi_clicado(self):
"""Função responsável por enviar o sinal para a GUI."""
print('Clicado')
novo_titulo = choice(lista_titulos)
print(f'Título escolhido: {novo_titulo}')
self.setWindowTitle(novo_titulo) # 3
def mudar_titulo_app(self, titulo_app):
"""
Função responsável por travar o botão dependendo o título do app.
:param titulo_app: Título escolhido aleatoriamente para o app.
"""
print(f'O título mudado foi para: {titulo_app}') # 4
if titulo_app == 'Algo deu errado':
self.botao.setDisabled(True)
# --- Criar a instância do app --- #
app = QApplication(sys.argv)
# --- Criar a janela do app --- #
janela = TelaPrincipal()
janela.show()
# --- Executar o loop de eventos --- #
app.exec_()
Uma lista de títulos de janelas que selecionaremos usando random.choice();
Conecte nosso método de slot personalizado mudar_titulo_app ao sinal .windowTitleChanged;
Defina o título da janela para o novo título e;
Se o título da nova janela for igual a 'Algo deu errado', desative o botão.
Clique no botão repetidamente até que o título mude para 'Algo deu errado' e o botão seja desativado.
Há algumas coisas a serem observadas neste exemplo.
Primeiramente, o sinal windowTitleChanged não é sempre emitido ao definir o título da janela. O sinal só dispara se o novo título for uma alteração do anterior. É importante saber exatamente em que condições os sinais disparam, para evitar ser surpreendido!
Em segundo lugar, observe como somos capazes de encadear coisas usando sinais. Uma coisa que acontece – o pressionar de um botão – pode fazer com que várias outras coisas aconteçam. Esses efeitos subsequentes não precisam ser conhecidos o que os causou, mas simplesmente seguem como consequência de regras simples. Essa dissociação de efeitos de seus gatilhos é um dos principais conceitos a serem entendidos ao construir aplicativos GUI. Continuaremos voltando a isso ao longo das nossas aula!
Nesta aula cobrimos sinais e slots. Demonstramos alguns sinais simples e como usá-los para transmitir dados e estados em seu aplicativo. Na próxima aula veremos os widgets que o Qt fornece para uso em suas aplicações – juntamente com os sinais que eles fornecem.