projeto

Projeto Guiado: Conversor de Moedas/Unidades

Aprenda sobre projeto guiado: conversor de moedas/unidades

70 min
Aula 3 de 5

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, DoubleVar para 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 tk e from tkinter import ttk: Importamos o módulo principal e o módulo ttk para 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 widgets ttk.
  • 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ário self.moedas, que será usada para popular os ttk.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 um Frame para agrupar e organizar melhor os widgets. O padding adiciona um espaço interno.
  • main_frame.grid(...) e self.root.columnconfigure(0, weight=1): Configuram o main_frame para ocupar todo o espaço disponível na janela principal e permitir que a coluna 0 do root se 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 ao ttk.Entry.
    • self.moeda_origem_selecionada, self.moeda_destino_selecionada: Vinculadas aos ttk.Combobox.
    • self.resultado_conversao: Vinculado ao ttk.Label de resultado.
  • state="readonly" no Combobox: 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étodo perform_conversion da nossa classe.
  • grid(): O gerenciador de layout grid é 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, W para 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 do main_frame (onde estão os Entry e Combobox) 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. Usamos messagebox.showerror para exibir uma mensagem de erro amigável.
  • self.valor_origem.get(): Obtém o valor numérico do DoubleVar vinculado ao Entry.
  • self.moeda_origem_selecionada.get(): Obtém a string selecionada no Combobox.
  • 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ário self.taxas_conversao.
  • taxa = self.taxas_conversao.get(chave_conversao): Busca a taxa. get() é seguro pois retorna None se 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 o StringVar do 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 atualizar self.nomes_moedas també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_moedas para self.nomes_unidades.
    • A lógica de extração do código da unidade pode ser similar.
  • Validação de Entrada Aprimorada:
    • Garanta que o campo de entrada de valor só aceite números (inteiros ou decimais). Pesquise sobre validatecommand no 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).
  • Melhorias na Interface (UI):
    • Adicione um ícone à janela (self.root.iconbitmap()).
    • Experimente diferentes temas ttk.Style().theme_use().
    • Adicione um ttk.Separator entre o botão e o resultado para melhor clareza visual.
    • Centralize a janela na tela ao iniciar (pesquise sobre update_idletasks e winfo_width/winfo_height).
  • 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.ttk como Entry, Label, Combobox e Button.
  • A gerenciar o layout de widgets de forma eficaz com grid().
  • A vincular dados da aplicação aos widgets usando StringVar e DoubleVar.
  • 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:

  1. Explore outros gerenciadores de layout: Experimente pack() e place() para entender suas diferenças e quando usá-los.
  2. Widgets mais avançados: Pesquise sobre ScrolledText, Treeview, Notebook para criar interfaces mais ricas.
  3. Tratamento de eventos: Entenda melhor o sistema de eventos do Tkinter, incluindo bind() para eventos de teclado e mouse.
  4. 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?
  5. Aprofunde em APIs: Se interessou pela integração de APIs? Estude mais sobre o módulo requests e como consumir dados de serviços web.

Continue praticando e construindo! A melhor forma de aprender é fazendo. 🚀

© 2025 Escola All Dev. Todos os direitos reservados.

Projeto Guiado: Conversor de Moedas/Unidades - Curso de Python com Tkinter para Criação de Interfaces | escola.all.dev.br