Introdução ao Desenvolvimento de GUIs com PyQt5

PyQt5: Uma Visão Geral

PyQt5 é um poderoso cojnunto de bindings Python para o framework Qt. Ele permite criar interfaces gráficas de usuário (GUIs) ricas e multiplataforma usando Python. Este guia aborda os fundamentos do PyQt5, desde a instalação até a criação de janelas, widgets, layouts e o manejo de sinais e slots.

Instalação

Para começar, você precisa instalar o PyQt5. Utilize o pip para isso:


pip install PyQt5
pip install PyQt5-tools

Módulos Principais do PyQt5

  • PyQt5.QtWidgets: Contém todos os elementos visuais (widgets) para a interface gráfica.
  • PyQt5.QtCore: Fornece funcionalidades não relacionadas à GUI, como manipulação de arquivos, diretórios, threads e eventos.
  • PyQt5.QtGui: Lida com processamento gráfico, tratamento de eventos e formatação de texto e fontes.
  • PyQt5.Network: Oferece classes para programação de rede, suportando protocolos TCP/IP e UDP.
  • PyQt5.QtBluetooth: Permite interagir com dispositivos Blueototh, incluindo escaneamento, conexão e troca de dados.

Tipos de Janelas

PyQt5 oferece três classes principais para a criação de janelas:

1. QWidget

QWidget é a classe base para todos os elementos da interface do usuário. É uma janela genérica com alta flexibilidade para adicionar e estilizar controles.


from PyQt5.QtWidgets import QWidget, QLabel, QApplication

class JanelaWidget(QWidget):
    """
    Uma janela baseada em QWidget, permitindo grande flexibilidade.
    """
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Exemplo QWidget")

        # Adiciona um rótulo à janela
        rotulo = QLabel('Esta é uma janela QWidget', self)
        rotulo.setStyleSheet('color: blue;') # Estilo simples
        rotulo.move(50, 50) # Posição do rótulo

if __name__ == '__main__':
    app = QApplication([])
    janela = JanelaWidget()
    janela.setGeometry(300, 300, 300, 200) # Define posição e tamanho da janela
    janela.show()
    app.exec_()

2. QMainWindow

QMainWindow é uma subclasse de QWidget que fornece uma estrutura para aplicativos com barras de menu, barras de ferramentas e barras de status.


from PyQt5.QtWidgets import QMainWindow, QApplication, QLabel, QMenuBar

class JanelaPrincipal(QMainWindow):
    """
    Uma janela principal com menu, ferramentas e status.
    """
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Exemplo QMainWindow")
        self.setGeometry(300, 300, 400, 300)

        # Central widget
        rotulo_central = QLabel('Conteúdo principal da janela', self)
        rotulo_central.setStyleSheet('color: red;')
        self.setCentralWidget(rotulo_central)

        # Menu Bar
        barra_menu = QMenuBar(self)

        menu_arquivo = barra_menu.addMenu('Arquivo')
        menu_arquivo.addAction('Novo')
        menu_arquivo.addAction('Abrir')
        menu_arquivo.addAction('Salvar')

        barra_menu.addMenu('Editar')
        barra_menu.addMenu('Exibir')

        self.setMenuBar(barra_menu)

if __name__ == '__main__':
    app = QApplication([])
    janela = JanelaPrincipal()
    janela.show()
    app.exec_()

3. QDialog

QDialog é usado para criar janelas de diálogo, geralmente modais, para interações específicas do usuário, como exibir mensagens ou solicitar informações.


import sys
from PyQt5.QtWidgets import QDialog, QLabel, QPushButton, QWidget, QApplication

class DialogoSimples(QDialog):
    """
    Um diálogo simples com uma mensagem.
    """
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("Diálogo")
        self.setGeometry(400, 400, 200, 100)

        rotulo = QLabel('Mensagem do Diálogo!', self)
        rotulo.move(50, 30)

        botao_fechar = QPushButton('Fechar', self)
        botao_fechar.move(70, 60)
        botao_fechar.clicked.connect(self.accept) # Fecha o diálogo

class JanelaPrincipalComDialogo(QWidget):
    """
    Janela principal que abre um diálogo.
    """
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Janela Principal")
        self.setGeometry(300, 300, 300, 150)

        self.dialogo = DialogoSimples(self)

        botao_abrir_dialogo = QPushButton('Abrir Diálogo', self)
        botao_abrir_dialogo.move(100, 60)
        botao_abrir_dialogo.clicked.connect(self.dialogo.show)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    janela = JanelaPrincipalComDialogo()
    janela.show()
    app.exec_()

Widgets Comuns

QWidget é a classe base para todos os widgets. Abaixo estão exemplos de widgets frequentemente utilizados:


from PyQt5.QtWidgets import *

class ExemploWidgets(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Exemplo de Widgets")
        self.setGeometry(300, 300, 400, 400)

        layout_formulario = QFormLayout(self) # Layout para formulários

        # Widgets
        layout_formulario.addRow("Texto Puro:", QLabel("Exibe texto ou links."))
        layout_formulario.addRow("Campo de Texto:", QLineEdit("Texto inicial"))
        layout_formulario.addRow("Área de Texto:", QTextEdit("Texto multilinha."))

        grupo_radio = QGroupBox("Opções Únicas")
        layout_grupo_radio = QVBoxLayout(grupo_radio)
        layout_grupo_radio.addWidget(QRadioButton("Opção A"))
        layout_grupo_radio.addWidget(QRadioButton("Opção B"))
        layout_formulario.addRow(grupo_radio)

        grupo_checkbox = QGroupBox("Opções Múltiplas")
        layout_grupo_checkbox = QVBoxLayout(grupo_checkbox)
        layout_grupo_checkbox.addWidget(QCheckBox("Item 1"))
        layout_grupo_checkbox.addWidget(QCheckBox("Item 2"))
        layout_grupo_checkbox.addWidget(QCheckBox("Item 3"))
        layout_formulario.addRow(grupo_checkbox)

        layout_formulario.addRow(QPushButton("Um Botão"))

if __name__ == '__main__':
    app = QApplication([])
    janela = ExemploWidgets()
    janela.show()
    app.exec_()

Widgets Personalizados com QPainter

É possível criar widgets completamente personalizados desenhando diretamente neles usando QPainter.


from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5.QtGui import QPainter, QColor, QPen, QPolygon, QPoint
from PyQt5.QtCore import QRect, Qt

class RelogioWidget(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Widget Personalizado")
        self.resize(300, 300)

    def paintEvent(self, event):
        """
        Este método é chamado quando o widget precisa ser redesenhado.
        """
        painter = QPainter(self)

        # Exemplo de desenho de um arco (fatia de pizza)
        # QRect(x, y, largura, altura), angulo_inicio, angulo_extensao
        rect_desenho = QRect(20, 20, 260, 260)
        angulo_inicio = 30 * 16 # 30 graus em 1/16 de grau
        angulo_extensao = 150 * 16 # 150 graus em 1/16 de grau

        painter.setBrush(QColor(255, 100, 0)) # Cor de preenchimento
        painter.setPen(QPen(Qt.black, 2))    # Cor e espessura da linha
        painter.drawPie(rect_desenho, angulo_inicio, angulo_extensao)

        # Exemplo de desenho de texto
        painter.setPen(QColor(0, 0, 255)) # Cor do texto
        painter.drawText(150, 150, "Desenho Personalizado")

if __name__ == '__main__':
    app = QApplication([])
    widget = RelogioWidget()
    widget.show()
    app.exec_()

Design de UI com Designer e PyQt-tools

O Qt Designer é uma ferramenta gráfica para criar interfaces. O arquivo .ui gerado pode ser convertido para Python com pyuic5 ou carregado dinamicamente.


# Converter UI para Python
pyuic5 seu_arquivo.ui -o seu_arquivo_convertido.py

Carregamento dinâmico:


from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5 import uic

class JanelaComUI(QWidget):
    def __init__(self):
        super().__init__()
        # Carrega o arquivo UI diretamente
        uic.loadUi("caminho/para/seu_arquivo.ui", self)
        self.setWindowTitle("UI Carregada Dinamicamente")

if __name__ == '__main__':
    app = QApplication([])
    janela = JanelaComUI()
    janela.show()
    app.exec_()

Exibindo Imagens

Imagens podem ser carregadas diretamente ou incorporadas ao código usando o recurso de arquivos de recusros do Qt.


from PyQt5.QtWidgets import QWidget, QLabel, QApplication
from PyQt5.QtGui import QPixmap

class JanelaComImagem(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Exemplo de Imagem")
        self.setGeometry(300, 300, 400, 300)

        label_imagem = QLabel(self)
        pixmap = QPixmap("./icone.png") # Certifique-se que o arquivo existe
        if not pixmap.isNull():
            label_imagem.setPixmap(pixmap)
            label_imagem.resize(pixmap.width(), pixmap.height())
        else:
            label_imagem.setText("Erro ao carregar imagem.")

if __name__ == '__main__':
    app = QApplication([])
    janela = JanelaComImagem()
    janela.show()
    app.exec_()

Para incorporar imagens, crie um arquivo .rcc:


<RCC>
    <qresource prefix="/">
        <file>./icone.png</file>
    </qresource>
</RCC>

Compile-o com pyrcc5:


pyrcc5 seu_recurso.rcc -o recursos_rc.py

E importe no seu código Python:


# from . import recursos_rc # Se no mesmo diretório
from PyQt5.QtWidgets import QLabel, QApplication, QWidget
from PyQt5.QtGui import QPixmap

class JanelaComRecurso(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Imagem de Recurso")
        self.setGeometry(300, 300, 200, 200)

        label = QLabel(self)
        # Carrega a imagem do recurso compilado
        pixmap = QPixmap(":/icone.png")
        label.setPixmap(pixmap)

if __name__ == '__main__':
    app = QApplication([])
    janela = JanelaComRecurso()
    janela.show()
    app.exec_()

Layouts: Organizando Widgets

Layouts gerenciam o posicionamento e o redimensionamento dos widgets dentro de uma janela.

1. Box Layouts (QVBoxLayout, QHBoxLayout)

QVBoxLayout organiza widgets verticalmente e QHBoxLayout horizontalmente.


import sys
from PyQt5.QtWidgets import *

class ExemploBoxLayout(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Box Layouts")
        self.setGeometry(300, 300, 350, 250)

        # Layout principal vertical
        layout_principal = QVBoxLayout(self)

        # Grupo de Checkboxes (Vertical)
        grupo_esportes = QGroupBox("Esportes Favoritos")
        layout_esportes = QVBoxLayout(grupo_esportes)
        layout_esportes.addWidget(QCheckBox("Futebol"))
        layout_esportes.addWidget(QCheckBox("Basquete"))
        layout_esportes.addWidget(QCheckBox("Vôlei"))

        # Grupo de Radio Buttons (Horizontal)
        grupo_genero = QGroupBox("Gênero")
        layout_genero = QHBoxLayout(grupo_genero)
        layout_genero.addWidget(QRadioButton("Masculino"))
        layout_genero.addWidget(QRadioButton("Feminino"))
        layout_genero.addStretch(1) # Espaço flexível

        # Adiciona os grupos ao layout principal
        layout_principal.addWidget(grupo_esportes)
        layout_principal.addWidget(grupo_genero)
        layout_principal.addStretch(1) # Empurra os widgets para cima

if __name__ == '__main__':
    app = QApplication(sys.argv)
    janela = ExemploBoxLayout()
    janela.show()
    app.exec_()

2. Grid Layout (QGridLayout)

QGridLayout organiza widgets em uma grade bidimensional.


import sys
from PyQt5.QtWidgets import *

class ExemploGridLayout(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Grid Layout")
        self.setGeometry(300, 300, 300, 200)

        layout_principal = QVBoxLayout(self)
        campo_texto = QLineEdit("Campo Principal")
        layout_principal.addWidget(campo_texto)

        grid_layout = QGridLayout()
        botoes = [
            '7', '8', '9', '/', 'C',
            '4', '5', '6', '*', '.',
            '1', '2', '3', '-', '=',
            '0', '(', ')', '+', 'CLR'
        ]
        posicoes = [(r, c) for r in range(4) for c in range(5)] # 4 linhas, 5 colunas

        for texto, pos in zip(botoes, posicoes):
            botao = QPushButton(texto)
            # Adiciona o botão na linha 'pos[0]' e coluna 'pos[1]'
            grid_layout.addWidget(botao, pos[0], pos[1])

        layout_principal.addLayout(grid_layout)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    janela = ExemploGridLayout()
    janela.show()
    app.exec_()

3. Form Layout (QFormLayout)

Ideal para formulários, alinha pares de rótulos e campos de entrada.


from PyQt5.QtWidgets import *

class ExemploFormLayout(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Form Layout")
        self.setGeometry(300, 300, 300, 200)

        layout_principal = QVBoxLayout(self)
        form_layout = QFormLayout()

        campo_usuario = QLineEdit()
        campo_usuario.setPlaceholderText("Digite seu nome de usuário")

        campo_senha = QLineEdit()
        campo_senha.setPlaceholderText("Digite sua senha")
        campo_senha.setEchoMode(QLineEdit.Password) # Esconde a senha

        form_layout.addRow("Usuário:", campo_usuario)
        form_layout.addRow("Senha:", campo_senha)

        checkbox_lembrar = QCheckBox("Lembrar-me")

        # Layout horizontal para botões
        layout_botoes = QHBoxLayout()
        botao_login = QPushButton("Login")
        botao_reset = QPushButton("Reset")
        layout_botoes.addWidget(botao_login)
        layout_botoes.addWidget(botao_reset)

        layout_principal.addLayout(form_layout)
        layout_principal.addWidget(checkbox_lembrar)
        layout_principal.addLayout(layout_botoes)
        layout_principal.addStretch(1) # Espaço flexível no final

if __name__ == '__main__':
    app = QApplication([])
    janela = ExemploFormLayout()
    janela.show()
    app.exec_()

4. Stacked Layout (QStackedLayout)

QStackedLayout gerencia uma pilha de widgets, mostrando apenas um por vez.


from PyQt5.QtWidgets import *
from PyQt5.QtCore import QTimer

class ExemploStackedLayout(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Stacked Layout")
        self.setGeometry(300, 300, 300, 200)

        stacked_layout = QStackedLayout(self)

        # Adiciona widgets à pilha
        stacked_layout.addWidget(QLabel("Página 1: Bem-vindo!"))
        stacked_layout.addWidget(QLabel("Página 2: Informações"))
        stacked_layout.addWidget(QLabel("Página 3: Contato"))

        # Timer para alternar páginas a cada segundo
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.proxima_pagina)
        self.timer.start(1000) # Intervalo de 1000ms (1 segundo)

        self.pagina_atual = 0

    def proxima_pagina(self):
        # Avança para a próxima página, voltando ao início se chegar ao fim
        self.pagina_atual = (self.pagina_atual + 1) % stacked_layout.count()
        stacked_layout.setCurrentIndex(self.pagina_atual)

if __name__ == '__main__':
    app = QApplication([])
    janela = ExemploStackedLayout()
    janela.show()
    app.exec_()

Sinais e Slots

Sinais são emitidos quando um evento ocorre (ex: clique de botão), e slots são funções que respondem a esses sinais.

Conectando Sinais e Slots


import sys
from PyQt5.QtWidgets import *

class ExemploSinaisSlots(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Sinais e Slots")
        self.setGeometry(300, 300, 250, 100)

        botao = QPushButton("Clique Aqui", self)
        botao.setGeometry(50, 30, 150, 40)

        # Conecta o sinal 'clicked' do botão ao slot 'on_clique'
        botao.clicked.connect(self.on_clique)

    def on_clique(self):
        """
        Este é o slot que será chamado quando o botão for clicado.
        """
        print("Botão foi clicado!")

if __name__ == '__main__':
    app = QApplication(sys.argv)
    janela = ExemploSinaisSlots()
    janela.show()
    app.exec_()

Criando Sinais Personalizados

Você pode definir seus próprios sinais para comunicação entre objetos.


import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import pyqtSignal

class EmissorSinal(QWidget):
    # Define um sinal que pode emitir uma string
    sinal_personalizado = pyqtSignal(str)

    def __init__(self):
        super().__init__()
        self.setWindowTitle("Sinais Personalizados")
        self.setGeometry(300, 300, 300, 150)

        # Conecta o sinal personalizado ao slot 'processar_dado'
        self.sinal_personalizado.connect(self.processar_dado)

        botao_emitir = QPushButton("Emitir Sinal", self)
        botao_emitir.setGeometry(80, 40, 120, 40)
        botao_emitir.clicked.connect(self.emitir)

    def emitir(self):
        mensagem = "Olá do Sinal!"
        print(f"Emitindo sinal com: '{mensagem}'")
        # Emite o sinal com a mensagem
        self.sinal_personalizado.emit(mensagem)

    @staticmethod
    def processar_dado(dado):
        """
        Slot que recebe os dados do sinal.
        """
        print(f"Slot recebeu: '{dado}'")

if __name__ == '__main__':
    app = QApplication(sys.argv)
    emissor = EmissorSinal()
    emissor.show()
    app.exec_()

Tags: pyqt5 Python gui Desenvolvimento de Interface Widgets

Publicado em 6-9 17:47 por Thomas