Fundamentos de Design de UI para Aplicações Python com PyQt5

Este guia aborda os conceitos básicos para criar interfaces de usuário (UI) em aplicações Python utilizando a bibliotcea PyQt5.

Instalação do PyQt5

Para começar, instale o PyQt5 seguindo as instruções disponíveis em tutoriais online (como o recomendado anteriormente).

Qt Designer

Após a instalação, utilize a ferramenta Qt Designer para criar visualmente suas interfaces. Acesse-a através do prompt de comando ou do menu de ferramentas externas no seu ambiente de desenvolvimento. Clique em "Criar" para iniciar um novo projeto.

Widgets Comuns

O painel esquerdo do Qt Designer oferece uma variedade de widgets. Os mais utilizados incluem:

1. QLabel (Etiqueta)

  • Propósito: Exibição de conteúdo estático. Não permite edição ou interação direta (a menos que eventos sejam associados).
  • Conteúdo Suportado: Texto (uma ou múltiplas linhas), imagens (útil para exibir resultados de OCR, por exemplo), e animações.
  • Atributos Comuns: text (conteúdo textual), pixmap (para imagens), alignment (alinhamento do texto), font (configuração da fonte).

2. QPushButton (Botão de Pressão)

  • Propósito: Interação principal do usuário. Clicar em um botão dispara funções pré-definidas através do mecanismo de Sinais e Slots.
  • Estilo: Altamente personalizável com texto, ícones e diferentes aparências para estados de ativação (pressionado, hover).
  • Atributos Comuns: text (rótulo do botão), icon (ícone exibido), enabled (controla se o botão está ativo ou desativado, útil para indicar estados como "disponível apenas após conexão serial").

3. QLineEdit (Campo de Entrada de Linha Única)

  • Propósito: Entrada ou exibição de texto em uma única linha. Não suporta quebras de linha.
  • Interatividade: Permite que o usuário edite o texto ou pode ser configurado como somente leitura.
  • Funcionalidades Úteis: Ocultação de senhas (echoMode configurado para modo senha), validação de entrada (ex: permitir apenas números), texto de placeholder (placeholderText).

4. QTextEdit (Caixa de Texto Múltiplas Linhas)

  • Propósito: Entrada ou exibição de texto em múltiplas linhas, suportando quebras de linha e Enter. Adequado para volumes maiores de texto.
  • Recursos Avançados: Suporte a texto rico (formatação de fonte, cor), funcionalidades de desfazer/refazer, e quebra automática de linha.
  • Atributos Comuns: plainText (conteúdo como texto puro), html (conteúdo como HTML para formatação rica), readOnly (define se o campo é somente leitura).

Estes quatro widgets formam a base para a maioria das necessidades de design de UI.

Folhas de Estilo (Style Sheets)

No Qt Designer, clique com o botão direito em um widget para acessar suas propriedades de estilo. As folhas de estilo permitem customizar a aparência dos widgets:

  • Sintaxe: Similar ao CSS (Cascading Style Sheets), facilitando o aprendizado para quem tem experiência com desenvolvimento web. A estrutura é baseada em "seletor + propriedade".
  • Flexibilidade: Podem ser aplicadas a widgets individuais, a um tipo específico de widget, à janela inteira ou a toda a aplicação.
  • Aplicação em Tempo Real: Modificações na folha de estilo são refletidas imediatamente na interface durante a execução.
  • Compatibilidade: Funciona com todos os widgets do Qt, incluindo QPushButton, QLineEdit, QLabel e QTextEdit.

Implementação de Funcionalidades com Sinais e Slots

A interação entre widgets e a lógica da aplicação é gerenciada pelo mecanismo de Sinais e Slots. Isso permite que ações do usuário (como clicar em um botão ou digitar texto) acionem funções específicas da aplicação (como executar um processamento ou enviar um comando).

Para conectar um sinal a um slot no Qt Designer: clique no botão desejado, mantenha pressionado o botão esquerdo do mouse e arraste a seta que aparece para o widget de destino (por exemplo, um QTextEdit). Na janela de edição de sinais e slots que surge, selecione o sinal (ex: clicked() para um botão) e o slot correspondente (ex: clear() para limpar o texto do QTextEdit).

Exemplo Prático: Previsão de Preço de Carne Suína

Este exemplo demonstra como usar widgets para entrada de dados, um modelo de machine learning para previsão e exibição de resultados.


import os
import sys
import PyQt5
from PyQt5 import QtCore, QtGui, QtWidgets
import warnings
warnings.filterwarnings("ignore", category=DeprecationWarning, module="PyQt5")
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import QFileDialog, QMainWindow, QMessageBox
# Configuração do path para os plugins Qt
dirname = os.path.dirname(PyQt5.__file__)
plugin_path = os.path.join(dirname, 'Qt5', 'plugins', 'platforms')
os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = plugin_path

from ui_main_window import Ui_MainWindow  # Importa a classe gerada a partir do .ui
import joblib # Para carregar o modelo treinado
import numpy as np # Para manipulação de arrays

class MainWindow(QMainWindow, Ui_MainWindow):
    def __init__(self):
        super().__init__()
        self.setupUi(self)
        # Carrega o modelo de machine learning pré-treinado
        self.prediction_model = joblib.load('model_pig_price.pkl')
        self.connect_signals()

    def connect_signals(self):
        # Conecta o sinal de clique do botão "Prever" ao método predict_price
        self.button_predict.clicked.connect(self.predict_price)
        # Conecta o sinal de clique do botão "Mostrar Tabela" ao método show_price_chart
        self.button_show_chart.clicked.connect(self.show_price_chart)
        # Conecta o sinal de clique do botão "Limpar" ao método clear_inputs
        self.button_clear.clicked.connect(self.clear_inputs)

    def predict_price(self):
        try:
            # Obtém os valores dos campos de entrada e converte para float
            corn_price = float(self.input_corn_price.text())
            soybean_price = float(self.input_soybean_price.text())
            
            # Realiza a previsão usando o modelo carregado
            predicted_price = self.prediction_model.predict(np.array([[corn_price, soybean_price]]))
            
            # Exibe o preço previsto no campo de saída
            self.output_predicted_price.setText(str(round(predicted_price[0], 2)))
        except ValueError:
            # Exibe uma mensagem de erro se a entrada não for um número válido
            QMessageBox.warning(self, 'Erro de Entrada', 'Por favor, insira valores numéricos válidos para os preços.')
        except Exception as e:
            QMessageBox.critical(self, 'Erro de Previsão', f'Ocorreu um erro durante a previsão: {e}')

    def show_price_chart(self):
        try:
            # Carrega e exibe uma imagem de gráfico de preços em um QLabel
            chart_pixmap = QPixmap("price_comparison_chart.png")
            self.label_chart_display.setPixmap(chart_pixmap)
            self.label_chart_display.setScaledContents(True) # Ajusta a imagem ao tamanho do QLabel
        except Exception as e:
            QMessageBox.warning(self, 'Erro ao Carregar Imagem', f'Não foi possível carregar o gráfico: {e}')
            self.label_chart_display.setText("Erro ao carregar gráfico.")

    def clear_inputs(self):
        # Limpa os campos de entrada e saída
        self.input_corn_price.clear()
        self.input_soybean_price.clear()
        self.output_predicted_price.clear()

if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    main_window = MainWindow()
    main_window.show()
    sys.exit(app.exec_())

Neste exemplo:

  • Os valores de preço de milho e soja são lidos dos campos QLineEdit.
  • Um modelo treinado (.pkl) realiza a previsão.
  • O resultado é exibido em outro QLineEdit.
  • Um botão "Limpar" apaga os campos de entrada e saída.
  • Outro botão, ao ser clicado, carrega e exibe uma imagem de gráfico em um QLabel.
  • Tratamento de erros é incluído para entradas inválidas.

A conversão do arquivo .ui gerado pelo Qt Designer para um arquivo Python (.py) é feita usando a ferramenta pyuic5. Funções personalizadas, como predict_price, precisam ser adicionadas manualmente à classe principal e conectadas aos sinais apropriados.

Exemplo Prático 2: Câmera com Pausa

Este exemplo mostra como integrar uma câmera ao vivo em uma aplicação PyQt5, com funcionalidade de pausa/retomada.


import sys
import cv2 # Biblioteca OpenCV para processamento de imagem
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QMainWindow, QPushButton, QLabel, QVBoxLayout, QWidget

# Assumindo que você tem um arquivo UI gerado com um QLabel (self.label_camera_feed)
# e um QPushButton (self.button_toggle_camera)
# from your_ui_file import Ui_MainWindow

class CameraApp(QMainWindow): # Ou herde de QMainWindow, Ui_MainWindow se tiver um .ui
    def __init__(self):
        super().__init__()
        # Configuração básica da janela (substitua pela sua estrutura UI se usar Qt Designer)
        self.setWindowTitle("Visualizador de Câmera")
        self.setGeometry(100, 100, 800, 600)

        self.central_widget = QWidget()
        self.setCentralWidget(self.central_widget)
        self.layout = QVBoxLayout(self.central_widget)

        self.label_camera_feed = QLabel("Câmera Offline", self)
        self.label_camera_feed.setAlignment(QtCore.Qt.AlignCenter)
        self.layout.addWidget(self.label_camera_feed)

        self.button_toggle_camera = QPushButton("Iniciar Câmera", self)
        self.button_toggle_camera.clicked.connect(self.toggle_camera)
        self.layout.addWidget(self.button_toggle_camera)

        # Inicialização da câmera e timer
        self.camera_capture = None
        self.is_capturing = False
        self.update_timer = QtCore.QTimer(self)
        self.update_timer.timeout.connect(self.update_frame)
        self.update_timer.setInterval(30) # Atualiza a cada 30ms (aprox. 33 FPS)

    def toggle_camera(self):
        if not self.is_capturing:
            try:
                # Tenta inicializar a câmera padrão (índice 0)
                self.camera_capture = cv2.VideoCapture(0)
                if not self.camera_capture.isOpened():
                    raise IOError("Não foi possível abrir a câmera.")
                
                self.is_capturing = True
                self.button_toggle_camera.setText("Parar Câmera")
                self.update_timer.start()
                self.label_camera_feed.setText("") # Limpa o texto "Câmera Offline"
            except Exception as e:
                self.label_camera_feed.setText(f"Erro: {e}")
                self.button_toggle_camera.setText("Iniciar Câmera")
        else:
            self.is_capturing = False
            self.update_timer.stop()
            if self.camera_capture:
                self.camera_capture.release() # Libera o recurso da câmera
                self.camera_capture = None
            self.button_toggle_camera.setText("Iniciar Câmera")
            self.label_camera_feed.setText("Câmera Offline")

    def update_frame(self):
        if self.camera_capture and self.is_capturing:
            ret, frame = self.camera_capture.read()
            if ret:
                # Converte o frame BGR (OpenCV) para RGB (Qt)
                rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                
                # Redimensiona o frame para caber no QLabel (opcional)
                h, w, ch = rgb_frame.shape
                bytes_per_line = ch * w
                convert_to_qt_format = QtGui.QImage(rgb_frame.data, w, h, bytes_per_line, QtGui.QImage.Format_RGB888)
                
                # Cria um QPixmap e o exibe no QLabel
                p = QtGui.QPixmap.fromImage(convert_to_qt_format)
                self.label_camera_feed.setPixmap(p)
                self.label_camera_feed.setScaledContents(True) # Ajusta a imagem ao tamanho do QLabel

if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    camera_window = CameraApp()
    camera_window.show()
    sys.exit(app.exec_())

O efeito de vídeo dinâmico é obtido pela atualização contínua do frame da câmera. O timer QTimer dispara periodicamente a função update_frame, que lê um novo frame, o converte para um formato compatível com Qt e o exibe no widget QLabel. O botão alterna entre iniciar e parar a captura, atualizando o texto do botão e o estado do timer.

Tags: pyqt5 Qt Designer Python UI Widgets

Publicado em 6-21 00:23