Curso de Python com Tkinter para Criação de Interfaces
Projeto Guiado: Conversor de Moedas/Unidades
Aprenda sobre projeto guiado: conversor de moedas/unidades
Projeto Guiado: Conversor de Moedas/Unidades 💰🔄
Bem-vindo(a) à nossa aula de projeto guiado! Nesta sessão, você colocará em prática seus conhecimentos de Tkinter para construir uma aplicação funcional e interativa: um Conversor de Moedas ou Unidades. Este projeto é excelente para solidificar o uso de widgets, gerenciamento de layout e lógica de aplicação em uma interface gráfica.
Vamos construir um conversor de moedas, mas os princípios se aplicam perfeitamente a um conversor de unidades!
1. Introdução: O Que Vamos Construir? 🚀
Nosso objetivo é criar uma interface gráfica simples que permita ao usuário inserir um valor em uma moeda (ou unidade) e convertê-lo para outra moeda (ou unidade) selecionada, exibindo o resultado.
O que você aprenderá/reforçará:
- Estrutura de Aplicações Tkinter Orientadas a Objeto: Uma prática recomendada para projetos maiores.
- Uso de Widgets
ttk: Para interfaces mais modernas e consistentes. - Gerenciamento de Layout com
grid(): Posicionamento flexível de widgets. - Variáveis de Controle Tkinter:
StringVar,DoubleVarpara vincular dados aos widgets. - Manipulação de Eventos: Como responder a cliques de botão e seleções de combobox.
- Lógica de Conversão: Implementando o cálculo por trás da conversão.
- Tratamento Básico de Erros: Garantindo que a aplicação seja robusta a entradas inválidas.
2. Explicação Detalhada com Exemplos 🧑🏫
Vamos construir nosso conversor passo a passo, utilizando as melhores práticas para organização do código.
2.1. Estrutura Básica da Aplicação (Orientada a Objetos) 🏗️
Para aplicações Tkinter mais complexas, é altamente recomendável usar uma abordagem orientada a objetos. Isso torna o código mais modular, legível e fácil de manter.
import tkinter as tk
from tkinter import ttk
from tkinter import messagebox
class ConversorApp:
def __init__(self, root):
self.root = root
self.root.title("Conversor de Moedas 💰")
self.root.geometry("400x250") # Define um tamanho inicial para a janela
self.root.resizable(False, False) # Impede redimensionamento
# Configurações de estilo para ttk (opcional, mas bom para consistência)
self.style = ttk.Style()
self.style.theme_use('clam') # 'clam', 'alt', 'default', 'classic'
self.create_widgets()
def create_widgets(self):
# Aqui vamos adicionar nossos widgets
pass
def perform_conversion(self):
# Aqui vai a lógica de conversão
pass
if __name__ == "__main__":
root = tk.Tk()
app = ConversorApp(root)
root.mainloop()
Explicação:
import tkinter as tkefrom tkinter import ttk: Importamos o módulo principal e o módulottkpara widgets mais modernos.class ConversorApp:: Nossa aplicação será uma classe, recebendo a janela principal (root) como argumento em seu construtor__init__.self.root.title(...),self.root.geometry(...),self.root.resizable(...): Configurações básicas da janela.self.style = ttk.Style(): Permite personalizar o estilo dos widgetsttk.self.create_widgets(): Um método dedicado para a criação e organização de todos os componentes da interface.if __name__ == "__main__":: Garante que o código de inicialização da aplicação só seja executado quando o script for rodado diretamente.
2.2. Definindo as Moedas e Taxas de Conversão 📊
Para simplificar, vamos usar taxas de conversão fixas. Em um projeto real, você buscaria essas taxas de uma API online.
# ... dentro da classe ConversorApp, no método __init__
def __init__(self, root):
# ... (código anterior)
self.moedas = {
"Real (BRL)": {"USD": 0.20, "EUR": 0.18, "BRL": 1.0},
"Dólar (USD)": {"BRL": 5.0, "EUR": 0.90, "USD": 1.0},
"Euro (EUR)": {"BRL": 5.5, "USD": 1.10, "EUR": 1.0}
}
self.taxas_conversao = {
"BRL_USD": 0.20, "BRL_EUR": 0.18,
"USD_BRL": 5.0, "USD_EUR": 0.90,
"EUR_BRL": 5.5, "EUR_USD": 1.10
}
# Para o Combobox, queremos apenas os nomes das moedas
self.nomes_moedas = list(self.moedas.keys())
self.create_widgets()Explicação:
self.moedas: Um dicionário aninhado que armazena as taxas de conversão de cada moeda para as outras. Isso facilita a busca.self.taxas_conversao: Uma alternativa mais simples que mapeia diretamente pares de moedas para suas taxas. Usaremos esta para a lógica principal.self.nomes_moedas: Uma lista das chaves do dicionárioself.moedas, que será usada para popular osttk.Combobox.
2.3. Criando os Widgets da Interface 🖥️
Agora, vamos preencher o método create_widgets com os componentes da nossa interface:
- Labels: Para instruções.
- Entry: Para o usuário digitar o valor a ser convertido.
- Combobox: Para selecionar a moeda de origem e a moeda de destino.
- Button: Para disparar a conversão.
- Label: Para exibir o resultado.
# ... dentro da classe ConversorApp
def create_widgets(self):
# Frame principal para organizar tudo
main_frame = ttk.Frame(self.root, padding="20")
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
self.root.columnconfigure(0, weight=1)
self.root.rowconfigure(0, weight=1)
# Variáveis de controle para os widgets
self.valor_origem = tk.DoubleVar(value=0.0)
self.moeda_origem_selecionada = tk.StringVar(value=self.nomes_moedas[0])
self.moeda_destino_selecionada = tk.StringVar(value=self.nomes_moedas[1])
self.resultado_conversao = tk.StringVar(value="0.00")
# 1. Label para o valor
ttk.Label(main_frame, text="Valor a Converter:").grid(row=0, column=0, sticky=tk.W, pady=5)
# 2. Entry para o valor
self.entry_valor = ttk.Entry(main_frame, textvariable=self.valor_origem, width=20)
self.entry_valor.grid(row=0, column=1, sticky=(tk.W, tk.E), pady=5)
# 3. Combobox para moeda de origem
ttk.Label(main_frame, text="De:").grid(row=1, column=0, sticky=tk.W, pady=5)
self.combo_origem = ttk.Combobox(main_frame, textvariable=self.moeda_origem_selecionada,
values=self.nomes_moedas, state="readonly")
self.combo_origem.grid(row=1, column=1, sticky=(tk.W, tk.E), pady=5)
self.combo_origem.set(self.nomes_moedas[0]) # Define um valor padrão
# 4. Combobox para moeda de destino
ttk.Label(main_frame, text="Para:").grid(row=2, column=0, sticky=tk.W, pady=5)
self.combo_destino = ttk.Combobox(main_frame, textvariable=self.moeda_destino_selecionada,
values=self.nomes_moedas, state="readonly")
self.combo_destino.grid(row=2, column=1, sticky=(tk.W, tk.E), pady=5)
self.combo_destino.set(self.nomes_moedas[1]) # Define um valor padrão
# 5. Botão de Conversão
self.btn_converter = ttk.Button(main_frame, text="Converter", command=self.perform_conversion)
self.btn_converter.grid(row=3, column=0, columnspan=2, pady=10)
# 6. Label para o Resultado
ttk.Label(main_frame, text="Resultado:").grid(row=4, column=0, sticky=tk.W, pady=5)
self.label_resultado = ttk.Label(main_frame, textvariable=self.resultado_conversao, font=('Arial', 12, 'bold'))
self.label_resultado.grid(row=4, column=1, sticky=(tk.W, tk.E), pady=5)
# Configurações de expansão para as colunas do frame principal
main_frame.columnconfigure(1, weight=1)Explicação:
main_frame = ttk.Frame(...): Usamos umFramepara agrupar e organizar melhor os widgets. Opaddingadiciona um espaço interno.main_frame.grid(...)eself.root.columnconfigure(0, weight=1): Configuram omain_framepara ocupar todo o espaço disponível na janela principal e permitir que a coluna 0 dorootse expanda.tk.DoubleVar(),tk.StringVar(): Variáveis especiais do Tkinter que se vinculam aos widgets. Quando o valor da variável muda, o widget é atualizado automaticamente, e vice-versa.self.valor_origem: Vinculado aottk.Entry.self.moeda_origem_selecionada,self.moeda_destino_selecionada: Vinculadas aosttk.Combobox.self.resultado_conversao: Vinculado aottk.Labelde resultado.
state="readonly"noCombobox: Impede que o usuário digite no combobox, forçando a seleção de uma das opções.command=self.perform_conversion: Vincula o clique do botão ao métodoperform_conversionda nossa classe.grid(): O gerenciador de layoutgridé usado para posicionar os widgets em linhas (row) e colunas (column).sticky: Define como o widget se "cola" às bordas da célula (N,S,E,Wpara Norte, Sul, Leste, Oeste).pady: Adiciona preenchimento vertical.columnspan: Faz um widget ocupar múltiplas colunas (útil para o botão).
main_frame.columnconfigure(1, weight=1): Permite que a segunda coluna domain_frame(onde estão osEntryeCombobox) se expanda quando a janela é redimensionada.
2.4. Implementando a Lógica de Conversão 🧠
Agora, vamos preencher o método perform_conversion para realizar o cálculo e exibir o resultado.
# ... dentro da classe ConversorApp
def perform_conversion(self):
try:
valor = self.valor_origem.get()
moeda_origem = self.moeda_origem_selecionada.get()
moeda_destino = self.moeda_destino_selecionada.get()
# Extrai os códigos das moedas (ex: "Real (BRL)" -> "BRL")
codigo_origem = moeda_origem.split('(')[1][:-1]
codigo_destino = moeda_destino.split('(')[1][:-1]
if codigo_origem == codigo_destino:
resultado = valor
else:
# Constrói a chave para buscar a taxa de conversão
chave_conversao = f"{codigo_origem}_{codigo_destino}"
taxa = self.taxas_conversao.get(chave_conversao)
if taxa is None:
messagebox.showerror("Erro de Conversão",
f"Taxa de conversão não encontrada para {codigo_origem} para {codigo_destino}.")
self.resultado_conversao.set("Erro!")
return
resultado = valor * taxa
self.resultado_conversao.set(f"{resultado:.2f} {codigo_destino}")
except ValueError:
messagebox.showerror("Erro de Entrada", "Por favor, insira um valor numérico válido.")
self.resultado_conversao.set("Erro!")
except Exception as e:
messagebox.showerror("Erro Inesperado", f"Ocorreu um erro: {e}")
self.resultado_conversao.set("Erro!")
Explicação:
try...except ValueError: Bloco de tratamento de erros para capturar casos onde o usuário insere texto não numérico no campo de valor. Usamosmessagebox.showerrorpara exibir uma mensagem de erro amigável.self.valor_origem.get(): Obtém o valor numérico doDoubleVarvinculado aoEntry.self.moeda_origem_selecionada.get(): Obtém a string selecionada noCombobox.codigo_origem = moeda_origem.split('(')[1][:-1]: Um pequeno truque para extrair o código da moeda (ex: "BRL") da string completa (ex: "Real (BRL)").if codigo_origem == codigo_destino:: Caso as moedas sejam as mesmas, o valor não muda.chave_conversao = f"{codigo_origem}_{codigo_destino}": Cria a chave (ex: "BRL_USD") para buscar no dicionárioself.taxas_conversao.taxa = self.taxas_conversao.get(chave_conversao): Busca a taxa.get()é seguro pois retornaNonese a chave não existir.if taxa is None:: Verifica se a taxa foi encontrada.resultado = valor * taxa: Realiza o cálculo da conversão.self.resultado_conversao.set(f"{resultado:.2f} {codigo_destino}"): Atualiza oStringVardo resultado, formatando o número para duas casas decimais e adicionando o código da moeda de destino.messagebox.showerror: Uma função útil do Tkinter para exibir caixas de diálogo de erro.
3. Código Completo de Exemplo Oficial (Melhores Práticas Tkinter) 🧑💻
Aqui está o código completo do nosso Conversor de Moedas, seguindo as melhores práticas de organização e uso de ttk para uma interface moderna.
import tkinter as tk
from tkinter import ttk
from tkinter import messagebox
class ConversorApp:
def __init__(self, root):
self.root = root
self.root.title("Conversor de Moedas 💰")
self.root.geometry("400x250")
self.root.resizable(False, False)
# Configurações de estilo para ttk
self.style = ttk.Style()
self.style.theme_use('clam') # 'clam', 'alt', 'default', 'classic'
# Definição das moedas e taxas de conversão
self.taxas_conversao = {
"BRL_USD": 0.20, "BRL_EUR": 0.18,
"USD_BRL": 5.0, "USD_EUR": 0.90,
"EUR_BRL": 5.5, "EUR_USD": 1.10
}
self.nomes_moedas = ["Real (BRL)", "Dólar (USD)", "Euro (EUR)"]
self.create_widgets()
def create_widgets(self):
# Frame principal para organizar tudo
main_frame = ttk.Frame(self.root, padding="20")
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
self.root.columnconfigure(0, weight=1)
self.root.rowconfigure(0, weight=1)
# Variáveis de controle para os widgets
self.valor_origem = tk.DoubleVar(value=0.0)
self.moeda_origem_selecionada = tk.StringVar(value=self.nomes_moedas[0])
self.moeda_destino_selecionada = tk.StringVar(value=self.nomes_moedas[1])
self.resultado_conversao = tk.StringVar(value="0.00")
# Label para o valor
ttk.Label(main_frame, text="Valor a Converter:").grid(row=0, column=0, sticky=tk.W, pady=5)
# Entry para o valor
self.entry_valor = ttk.Entry(main_frame, textvariable=self.valor_origem, width=20)
self.entry_valor.grid(row=0, column=1, sticky=(tk.W, tk.E), pady=5)
# Combobox para moeda de origem
ttk.Label(main_frame, text="De:").grid(row=1, column=0, sticky=tk.W, pady=5)
self.combo_origem = ttk.Combobox(main_frame, textvariable=self.moeda_origem_selecionada,
values=self.nomes_moedas, state="readonly")
self.combo_origem.grid(row=1, column=1, sticky=(tk.W, tk.E), pady=5)
self.combo_origem.set(self.nomes_moedas[0]) # Define um valor padrão
# Combobox para moeda de destino
ttk.Label(main_frame, text="Para:").grid(row=2, column=0, sticky=tk.W, pady=5)
self.combo_destino = ttk.Combobox(main_frame, textvariable=self.moeda_destino_selecionada,
values=self.nomes_moedas, state="readonly")
self.combo_destino.grid(row=2, column=1, sticky=(tk.W, tk.E), pady=5)
self.combo_destino.set(self.nomes_moedas[1]) # Define um valor padrão
# Botão de Conversão
self.btn_converter = ttk.Button(main_frame, text="Converter", command=self.perform_conversion)
self.btn_converter.grid(row=3, column=0, columnspan=2, pady=10)
# Label para o Resultado
ttk.Label(main_frame, text="Resultado:").grid(row=4, column=0, sticky=tk.W, pady=5)
self.label_resultado = ttk.Label(main_frame, textvariable=self.resultado_conversao, font=('Arial', 12, 'bold'))
self.label_resultado.grid(row=4, column=1, sticky=(tk.W, tk.E), pady=5)
# Configurações de expansão para as colunas do frame principal
main_frame.columnconfigure(1, weight=1)
def perform_conversion(self):
try:
valor = self.valor_origem.get()
moeda_origem = self.moeda_origem_selecionada.get()
moeda_destino = self.moeda_destino_selecionada.get()
# Extrai os códigos das moedas (ex: "Real (BRL)" -> "BRL")
codigo_origem = moeda_origem.split('(')[1][:-1]
codigo_destino = moeda_destino.split('(')[1][:-1]
if codigo_origem == codigo_destino:
resultado = valor
else:
# Constrói a chave para buscar a taxa de conversão
chave_conversao = f"{codigo_origem}_{codigo_destino}"
taxa = self.taxas_conversao.get(chave_conversao)
if taxa is None:
messagebox.showerror("Erro de Conversão",
f"Taxa de conversão não encontrada para {codigo_origem} para {codigo_destino}.")
self.resultado_conversao.set("Erro!")
return
resultado = valor * taxa
self.resultado_conversao.set(f"{resultado:.2f} {codigo_destino}")
except ValueError:
messagebox.showerror("Erro de Entrada", "Por favor, insira um valor numérico válido.")
self.resultado_conversao.set("Erro!")
except Exception as e:
messagebox.showerror("Erro Inesperado", f"Ocorreu um erro: {e}")
self.resultado_conversao.set("Erro!")
if __name__ == "__main__":
root = tk.Tk()
app = ConversorApp(root)
root.mainloop()4. Integração de Múltiplas Tecnologias (Conceitual) 🤝
Neste projeto, focamos puramente em Tkinter e Python básico. No entanto, para um conversor de moedas real, a integração de tecnologias externas seria crucial.
Exemplo:
Para obter taxas de câmbio atualizadas, você precisaria integrar uma API de taxas de câmbio (ex: ExchangeRate-API, Open Exchange Rates).
import requests # Seria necessário instalar: pip install requests
# ... dentro da classe ConversorApp
class ConversorApp:
def __init__(self, root):
# ... (código anterior)
self.api_key = "SUA_API_KEY_AQUI" # Obtenha uma chave gratuita de um serviço de API
self.base_url = "https://api.exchangerate-api.com/v4/latest/"
# ... (código anterior)
def fetch_exchange_rates(self, base_currency):
try:
response = requests.get(f"{self.base_url}{base_currency}")
response.raise_for_status() # Lança um erro para códigos de status HTTP ruins (4xx ou 5xx)
data = response.json()
return data['rates']
except requests.exceptions.RequestException as e:
messagebox.showerror("Erro de Rede", f"Não foi possível buscar as taxas de câmbio: {e}")
return None
def perform_conversion(self):
try:
valor = self.valor_origem.get()
moeda_origem_str = self.moeda_origem_selecionada.get()
moeda_destino_str = self.moeda_destino_selecionada.get()
codigo_origem = moeda_origem_str.split('(')[1][:-1]
codigo_destino = moeda_destino_str.split('(')[1][:-1]
if codigo_origem == codigo_destino:
resultado = valor
else:
# Busca as taxas de câmbio mais recentes
rates = self.fetch_exchange_rates(codigo_origem)
if rates is None:
self.resultado_conversao.set("Erro!")
return
taxa = rates.get(codigo_destino)
if taxa is None:
messagebox.showerror("Erro de Conversão",
f"Taxa de conversão não encontrada para {codigo_origem} para {codigo_destino}.")
self.resultado_conversao.set("Erro!")
return
resultado = valor * taxa
self.resultado_conversao.set(f"{resultado:.2f} {codigo_destino}")
except ValueError:
messagebox.showerror("Erro de Entrada", "Por favor, insira um valor numérico válido.")
self.resultado_conversao.set("Erro!")
except Exception as e:
messagebox.showerror("Erro Inesperado", f"Ocorreu um erro: {e}")
self.resultado_conversao.set("Erro!")
Observação: A integração de APIs requer conhecimentos de requisições HTTP e tratamento de dados JSON, o que pode ser um próximo passo interessante para aprimorar este projeto.
5. Exercícios e Desafios 🧠💪
Agora que você tem um conversor funcional, é hora de aprimorá-lo e adaptá-lo!
Task List:
- Adicionar mais moedas: Inclua pelo menos mais duas moedas (ex: Yen Japonês - JPY, Libra Esterlina - GBP) e suas respectivas taxas de conversão no dicionário
self.taxas_conversao. Lembre-se de atualizarself.nomes_moedastambém! - Criar um Conversor de Unidades: Adapte o código para converter unidades de medida (ex: Celsius para Fahrenheit, Metros para Pés, Quilogramas para Libras).
- Você precisará de um novo dicionário de taxas de conversão (ex:
{"C_F": 33.8, "M_FT": 3.28084}). - Atualize
self.nomes_moedasparaself.nomes_unidades. - A lógica de extração do código da unidade pode ser similar.
- Você precisará de um novo dicionário de taxas de conversão (ex:
- Validação de Entrada Aprimorada:
- Garanta que o campo de entrada de valor só aceite números (inteiros ou decimais). Pesquise sobre
validatecommandno Tkinter. - Exiba uma mensagem de erro mais específica se o usuário tentar converter a mesma moeda para ela mesma (embora o código atual já lide com isso, você pode querer uma mensagem diferente).
- Garanta que o campo de entrada de valor só aceite números (inteiros ou decimais). Pesquise sobre
- Melhorias na Interface (UI):
- Adicione um ícone à janela (
self.root.iconbitmap()). - Experimente diferentes temas
ttk.Style().theme_use(). - Adicione um
ttk.Separatorentre o botão e o resultado para melhor clareza visual. - Centralize a janela na tela ao iniciar (pesquise sobre
update_idletasksewinfo_width/winfo_height).
- Adicione um ícone à janela (
- Limpar Campos: Adicione um botão "Limpar" que redefina o valor de entrada para 0 e o resultado para "0.00".
- (Desafio Avançado) Integração com API: Se você se sentir confortável, tente integrar uma API de taxas de câmbio para obter valores em tempo real, como sugerido na seção 4.
6. Resumo e Próximos Passos ✨
Parabéns! Você construiu seu primeiro conversor GUI com Tkinter, aplicando conceitos importantes de desenvolvimento de interfaces.
Nesta aula, você aprendeu:
- A estruturar uma aplicação Tkinter usando classes para organização.
- A utilizar widgets modernos do
tkinter.ttkcomoEntry,Label,ComboboxeButton. - A gerenciar o layout de widgets de forma eficaz com
grid(). - A vincular dados da aplicação aos widgets usando
StringVareDoubleVar. - A implementar a lógica de negócios (conversão) e lidar com eventos de usuário.
- A adicionar tratamento básico de erros para uma experiência de usuário mais robusta.
Próximos passos para aprofundar seus conhecimentos:
- Explore outros gerenciadores de layout: Experimente
pack()eplace()para entender suas diferenças e quando usá-los. - Widgets mais avançados: Pesquise sobre
ScrolledText,Treeview,Notebookpara criar interfaces mais ricas. - Tratamento de eventos: Entenda melhor o sistema de eventos do Tkinter, incluindo
bind()para eventos de teclado e mouse. - Persistência de dados: Como você salvaria e carregaria as taxas de conversão de um arquivo (ex: JSON, CSV) em vez de tê-las fixas no código?
- Aprofunde em APIs: Se interessou pela integração de APIs? Estude mais sobre o módulo
requestse como consumir dados de serviços web.
Continue praticando e construindo! A melhor forma de aprender é fazendo. 🚀