Curso de Python com Tkinter para Criação de Interfaces
Projeto Guiado: Aplicativo de Lista de Tarefas (To-Do List)
Aprenda sobre projeto guiado: aplicativo de lista de tarefas (to-do list)
Projeto Guiado: Aplicativo de Lista de Tarefas (To-Do List) 📝
Olá, futuro desenvolvedor de interfaces! Nesta aula prática, vamos colocar a mão na massa e construir um aplicativo completo de lista de tarefas (To-Do List) usando o Tkinter. Este projeto é uma excelente maneira de consolidar os conceitos aprendidos sobre design de UI, manipulação de widgets, tratamento de eventos e, o mais importante, como estruturar uma aplicação Tkinter de forma organizada e eficiente.
Ao final desta aula, você terá um aplicativo funcional que permite adicionar, remover e marcar tarefas como concluídas, além de persistir os dados em um arquivo, para que suas tarefas não sejam perdidas ao fechar o programa! 🚀
1. Introdução ao Projeto: To-Do List com Tkinter ✨
Um aplicativo de lista de tarefas é um clássico para iniciantes e um projeto fundamental para entender como diferentes componentes de uma interface gráfica interagem para criar uma experiência de usuário útil. Com o Tkinter, podemos construir rapidamente uma interface intuitiva para gerenciar nossas atividades diárias.
Nossos objetivos com este projeto são:
- Aplicar conceitos de Tkinter: Widgets (Entry, Button, Listbox, Scrollbar, Frame), gerenciadores de layout (Grid).
- Entender a arquitetura de uma aplicação Tkinter: Usando uma abordagem orientada a objetos (OOP) para melhor organização do código.
- Implementar manipulação de eventos: Responder a cliques de botão e seleções na lista.
- Gerenciar dados: Adicionar, remover e atualizar itens em uma lista.
- Persistência de dados: Salvar e carregar tarefas de um arquivo para que o estado do aplicativo seja mantido entre as sessões.
Vamos construir nosso aplicativo passo a passo, garantindo que cada componente seja explicado e integrado de forma clara.
2. Estrutura do Projeto e Configuração Inicial 🏗️
Para manter nosso código organizado e escalável, utilizaremos uma abordagem orientada a objetos (OOP). Nosso aplicativo será encapsulado em uma classe TodoApp, que conterá a lógica da interface do usuário e as funcionalidades do aplicativo.
Vamos começar com a estrutura básica da nossa janela principal.
# todo_app.py
import tkinter as tk
from tkinter import ttk, messagebox
import json
import os
class TodoApp:
def __init__(self, root):
self.root = root
self.root.title("Lista de Tarefas 📝")
self.root.geometry("400x550")
self.root.resizable(False, False) # Impede o redimensionamento da janela
# Configurações de estilo para uma aparência mais moderna
self.style = ttk.Style()
self.style.theme_use("clam") # Ou "alt", "default", "vista", "xpnative"
self.style.configure("TFrame", background="#f0f0f0")
self.style.configure("TButton", font=("Arial", 10), padding=5)
self.style.configure("TLabel", font=("Arial", 12), background="#f0f0f0")
self.style.configure("TEntry", font=("Arial", 12), padding=5)
self.style.configure("TListbox", font=("Arial", 12), padding=5)
self.tasks = [] # Lista interna para armazenar as tarefas
self._create_widgets()
self._load_tasks() # Carrega as tarefas ao iniciar o app
# Salva as tarefas ao fechar a janela
self.root.protocol("WM_DELETE_WINDOW", self._on_closing)
def _create_widgets(self):
# Frame principal para organizar os elementos
main_frame = ttk.Frame(self.root, padding="10")
main_frame.pack(fill=tk.BOTH, expand=True)
# Título
title_label = ttk.Label(main_frame, text="Minhas Tarefas", font=("Arial", 16, "bold"))
title_label.grid(row=0, column=0, columnspan=2, pady=10)
# Campo de entrada para novas tarefas
self.task_entry = ttk.Entry(main_frame, width=30)
self.task_entry.grid(row=1, column=0, padx=5, pady=5, sticky="ew")
self.task_entry.bind("<Return>", lambda event: self._add_task()) # Adiciona tarefa ao pressionar Enter
# Botão para adicionar tarefa
add_button = ttk.Button(main_frame, text="Adicionar Tarefa", command=self._add_task)
add_button.grid(row=1, column=1, padx=5, pady=5, sticky="ew")
# Listbox para exibir as tarefas
self.task_listbox = tk.Listbox(main_frame, height=15, selectmode=tk.SINGLE,
bg="#ffffff", fg="#333333", selectbackground="#a6d5fa",
selectforeground="#000000", borderwidth=1, relief="solid")
self.task_listbox.grid(row=2, column=0, columnspan=2, padx=5, pady=10, sticky="nsew")
# Scrollbar para a Listbox
scrollbar = ttk.Scrollbar(main_frame, orient=tk.VERTICAL, command=self.task_listbox.yview)
scrollbar.grid(row=2, column=2, sticky="ns")
self.task_listbox.config(yscrollcommand=scrollbar.set)
# Frame para os botões de ação
button_frame = ttk.Frame(main_frame)
button_frame.grid(row=3, column=0, columnspan=2, pady=10, sticky="ew")
button_frame.columnconfigure(0, weight=1)
button_frame.columnconfigure(1, weight=1)
# Botões de ação
mark_completed_button = ttk.Button(button_frame, text="Marcar Concluída", command=self._mark_task_completed)
mark_completed_button.grid(row=0, column=0, padx=5, sticky="ew")
remove_button = ttk.Button(button_frame, text="Remover Tarefa", command=self._remove_task)
remove_button.grid(row=0, column=1, padx=5, sticky="ew")
# Configurações de redimensionamento da grid
main_frame.grid_rowconfigure(2, weight=1) # Faz a listbox expandir verticalmente
main_frame.grid_columnconfigure(0, weight=1) # Faz o campo de entrada expandir horizontalmente
def _add_task(self):
"""Adiciona uma nova tarefa à lista."""
task_text = self.task_entry.get().strip()
if task_text:
self.tasks.append({"text": task_text, "completed": False})
self._update_task_listbox()
self.task_entry.delete(0, tk.END) # Limpa o campo de entrada
else:
messagebox.showwarning("Atenção", "O campo de tarefa não pode estar vazio!")
def _remove_task(self):
"""Remove a tarefa selecionada da lista."""
try:
selected_index = self.task_listbox.curselection()[0]
del self.tasks[selected_index]
self._update_task_listbox()
except IndexError:
messagebox.showwarning("Atenção", "Nenhuma tarefa selecionada para remover.")
def _mark_task_completed(self):
"""Marca a tarefa selecionada como concluída."""
try:
selected_index = self.task_listbox.curselection()[0]
self.tasks[selected_index]["completed"] = not self.tasks[selected_index]["completed"] # Alterna o status
self._update_task_listbox()
except IndexError:
messagebox.showwarning("Atenção", "Nenhuma tarefa selecionada para marcar.")
def _update_task_listbox(self):
"""Atualiza a Listbox com as tarefas da lista interna `self.tasks`."""
self.task_listbox.delete(0, tk.END) # Limpa a Listbox
for i, task in enumerate(self.tasks):
display_text = f"[CONCLUÍDA] {task['text']}" if task['completed'] else task['text']
self.task_listbox.insert(tk.END, display_text)
if task['completed']:
# Aplica um estilo visual para tarefas concluídas
self.task_listbox.itemconfig(i, fg="gray", selectforeground="gray")
else:
self.task_listbox.itemconfig(i, fg="black", selectforeground="black")
def _load_tasks(self):
"""Carrega as tarefas de um arquivo JSON."""
if os.path.exists("tasks.json"):
with open("tasks.json", "r") as f:
self.tasks = json.load(f)
self._update_task_listbox()
def _save_tasks(self):
"""Salva as tarefas em um arquivo JSON."""
with open("tasks.json", "w") as f:
json.dump(self.tasks, f, indent=4)
def _on_closing(self):
"""Chamado quando a janela é fechada, para salvar as tarefas."""
self._save_tasks()
self.root.destroy() # Destrói a janela e encerra o aplicativo
if __name__ == "__main__":
root = tk.Tk()
app = TodoApp(root)
root.mainloop()
Explicação Detalhada do Código 🧑🏫
Vamos quebrar o código em partes para entender cada componente:
1. Imports Essenciais
import tkinter as tk
from tkinter import ttk, messagebox
import json
import ostkinter as tk: O módulo principal do Tkinter.tkinter.ttk: Oferece widgets "temáticos" que se parecem mais modernos e nativos do sistema operacional. É sempre recomendado usarttkquando possível.tkinter.messagebox: Usado para exibir caixas de diálogo de aviso ou informação.json: Módulo padrão do Python para trabalhar com dados JSON, que usaremos para salvar e carregar nossas tarefas.os: Módulo para interagir com o sistema operacional, útil para verificar a existência do arquivotasks.json.
2. Classe TodoApp
class TodoApp:
def __init__(self, root):
self.root = root
self.root.title("Lista de Tarefas 📝")
self.root.geometry("400x550")
self.root.resizable(False, False) # Impede o redimensionamento da janela
# ... (restante do __init__)- O construtor
__init__recebe a janela principal (root) como argumento. self.root.title(): Define o título da janela.self.root.geometry(): Define o tamanho inicial da janela (largura x altura).self.root.resizable(False, False): Desabilita o redimensionamento da janela pelo usuário, o que pode ser útil para manter o layout fixo em aplicativos mais simples.
3. Estilização com ttk.Style
self.style = ttk.Style()
self.style.theme_use("clam") # Ou "alt", "default", "vista", "xpnative"
self.style.configure("TFrame", background="#f0f0f0")
# ... (outras configurações de estilo)ttk.Style(): Permite personalizar a aparência dos widgetsttk.self.style.theme_use("clam"): Define o tema visual. Experimente outros como "alt", "default", "vista" (Windows) ou "xpnative" (Windows) para ver as diferenças.self.style.configure(): Permite configurar propriedades de estilo para tipos específicos de widgets (ex: "TFrame", "TButton"). Isso ajuda a dar uma aparência mais consistente e moderna ao aplicativo.
4. Lista Interna de Tarefas (self.tasks)
self.tasks = [] # Lista interna para armazenar as tarefas- Esta lista Python armazenará nossos objetos de tarefa. Cada tarefa será um dicionário com
{"text": "Minha tarefa", "completed": False}. Usar uma estrutura de dados Python para gerenciar o estado do aplicativo é uma boa prática, pois aListboxé apenas uma representação visual.
5. Criação dos Widgets (_create_widgets)
def _create_widgets(self):
main_frame = ttk.Frame(self.root, padding="10")
main_frame.pack(fill=tk.BOTH, expand=True)
# ... (criação dos widgets)
# Configurações de redimensionamento da grid
main_frame.grid_rowconfigure(2, weight=1)
main_frame.grid_columnconfigure(0, weight=1)ttk.Frame: Um contêiner para agrupar outros widgets. Usamos ummain_framepara centralizar o conteúdo e aplicar umpadding.main_frame.pack(fill=tk.BOTH, expand=True): Faz com que o frame principal ocupe todo o espaço disponível na janela.ttk.Label: Para o título do aplicativo.ttk.Entry: Onde o usuário digita a nova tarefa.self.task_entry.bind("<Return>", lambda event: self._add_task()): Associa a teclaEnterno campo de entrada à função_add_task, tornando a adição de tarefas mais rápida.
ttk.Button: Para "Adicionar Tarefa", "Marcar Concluída" e "Remover Tarefa". O parâmetrocommandé crucial, pois ele vincula o clique do botão a uma função Python.tk.Listbox: Onde as tarefas são exibidas.selectmode=tk.SINGLE: Permite selecionar apenas uma tarefa por vez.bg,fg,selectbackground,selectforeground: Personaliza as cores daListboxe da seleção.
ttk.Scrollbar: Adiciona uma barra de rolagem àListboxpara lidar com muitas tarefas. A barra é configurada para rolar aListbox(command=self.task_listbox.yview) e aListboxé configurada para usar a barra (yscrollcommand=scrollbar.set).- Gerenciador de Layout
grid:widget.grid(row=X, column=Y, ...): Posiciona os widgets em uma grade (linhas e colunas).columnspan: Faz um widget ocupar múltiplas colunas.padx,pady: Adiciona espaçamento horizontal e vertical.sticky="nsew": Faz o widget "grudar" nas bordas da célula da grid, preenchendo o espaço disponível.main_frame.grid_rowconfigure(2, weight=1)emain_frame.grid_columnconfigure(0, weight=1): Estas linhas são importantes para o redimensionamento. El dizem que a linha 2 (onde está aListbox) e a coluna 0 (onde está oEntry) devem expandir-se quando a janela é redimensionada, garantindo que aListboxcresça verticalmente e oEntryhorizontalmente.
6. Funcionalidades do Aplicativo
-
_add_task():- Obtém o texto do
self.task_entry. - Verifica se o texto não está vazio (
strip()remove espaços em branco). - Adiciona um dicionário
{"text": task_text, "completed": False}à listaself.tasks. - Chama
_update_task_listbox()para refletir a mudança na UI. - Limpa o
task_entry. - Usa
messagebox.showwarningpara avisar se o campo estiver vazio.
- Obtém o texto do
-
_remove_task():- Usa
self.task_listbox.curselection()para obter o índice da tarefa selecionada. Retorna uma tupla de índices.[0]pega o primeiro (e único, poisselectmode=tk.SINGLE). del self.tasks[selected_index]: Remove a tarefa da lista interna.- Chama
_update_task_listbox(). - Usa um bloco
try-except IndexErrorpara lidar com o caso de nenhuma tarefa estar selecionada.
- Usa
-
_mark_task_completed():- Similar a
_remove_taskpara obter o índice. self.tasks[selected_index]["completed"] = not self.tasks[selected_index]["completed"]: Alterna o statuscompletedda tarefa (deTrueparaFalseou vice-versa).- Chama
_update_task_listbox().
- Similar a
-
_update_task_listbox(): Crucial para a atualização da UI.self.task_listbox.delete(0, tk.END): Limpa completamente aListbox.- Itera sobre
self.taskse insere cada tarefa novamente. - Formata o texto para exibir
[CONCLUÍDA]se a tarefa estiver concluída. self.task_listbox.itemconfig(i, fg="gray", selectforeground="gray"): Altera a cor do texto para cinza para tarefas concluídas, melhorando a visualização.
7. Persistência de Dados (JSON) 💾
-
_load_tasks():- Verifica se o arquivo
tasks.jsonexiste usandoos.path.exists(). - Se existir, abre o arquivo em modo de leitura (
"r"). json.load(f): Carrega o conteúdo JSON do arquivo paraself.tasks.- Chama
_update_task_listbox()para exibir as tarefas carregadas.
- Verifica se o arquivo
-
_save_tasks():- Abre o arquivo
tasks.jsonem modo de escrita ("w"). json.dump(self.tasks, f, indent=4): Salva a listaself.tasksno arquivotasks.jsonem formato JSON.indent=4formata o JSON para ser legível.
- Abre o arquivo
-
_on_closing():- Esta função é vinculada ao evento de fechamento da janela (
self.root.protocol("WM_DELETE_WINDOW", self._on_closing)). - Quando o usuário clica no "X" para fechar a janela,
_on_closingé chamado. - Ele primeiro chama
self._save_tasks()para garantir que todas as tarefas atuais sejam salvas. - Em seguida,
self.root.destroy()fecha a janela e encerra o aplicativo.
- Esta função é vinculada ao evento de fechamento da janela (
8. Execução do Aplicativo (if __name__ == "__main__":)
if __name__ == "__main__":
root = tk.Tk()
app = TodoApp(root)
root.mainloop()- Este bloco garante que o código dentro dele só será executado quando o script for rodado diretamente (não quando importado como módulo).
root = tk.Tk(): Cria a janela principal do Tkinter.app = TodoApp(root): Cria uma instância da nossa classeTodoApp, passando a janela principal.root.mainloop(): Inicia o loop de eventos do Tkinter. Este é o coração de qualquer aplicação Tkinter; ele escuta eventos (cliques, digitação, etc.) e os despacha para as funções apropriadas. O aplicativo permanece aberto até que a janela seja fechada.
3. Código de Exemplo Oficial (Melhores Práticas) 🧑💻
O código acima segue as melhores práticas para desenvolvimento de aplicações Tkinter, incluindo:
- Orientação a Objetos: Encapsulando a lógica da aplicação em uma classe.
- Uso de
ttk: Para uma interface mais moderna e nativa. - Gerenciamento de Layout: Uso eficiente do
gridpara posicionamento flexível e responsivo. - Separação de Preocupações: Embora seja um aplicativo simples, a lógica de UI e a lógica de dados (lista
self.tasks) são gerenciadas separadamente, com a_update_task_listboxagindo como um "renderizador". - Persistência de Dados: Integração com o módulo
jsonpara salvar e carregar o estado do aplicativo. - Tratamento de Eventos: Vinculação de comandos a botões e eventos de teclado (
<Return>). - Tratamento de Fechamento da Janela: Garantindo que os dados sejam salvos antes de o aplicativo ser encerrado.
4. Exercícios e Desafios 🚀
Agora que você tem um aplicativo funcional, é hora de expandir suas habilidades e adicionar novos recursos!
Aqui estão alguns desafios para você:
Tarefas Essenciais (para fixar o aprendizado):
- Validação de Entrada Aprimorada: Adicione uma verificação para garantir que o usuário não adicione tarefas duplicadas.
- Botão "Limpar Concluídas": Crie um novo botão que remova todas as tarefas marcadas como concluídas da lista.
- Confirmação de Exclusão: Antes de remover uma tarefa, use
messagebox.askyesno()para perguntar ao usuário se ele tem certeza. - Contador de Tarefas: Adicione um
ttk.Labelna parte inferior da janela que exiba o número total de tarefas e o número de tarefas concluídas. Atualize este contador sempre que a lista for modificada.
Desafios Extras (para ir além):
- Prioridade de Tarefas:
- Adicione um
ttk.Comboboxoutk.OptionMenupara permitir que o usuário selecione uma prioridade (Ex: "Alta", "Média", "Baixa") ao adicionar uma tarefa. - Modifique a exibição na
Listboxpara incluir a prioridade (Ex:[ALTA] Minha Tarefa). - Implemente a funcionalidade de ordenar as tarefas por prioridade.
- Adicione um
- Edição de Tarefas:
- Adicione um botão "Editar Tarefa". Quando clicado, a tarefa selecionada deve ser carregada de volta para o
task_entry, permitindo que o usuário a modifique e salve as alterações (talvez com um botão "Salvar Edição" que substitua o "Adicionar Tarefa" temporariamente).
- Adicione um botão "Editar Tarefa". Quando clicado, a tarefa selecionada deve ser carregada de volta para o
- Filtro de Tarefas:
- Adicione botões ou um
ttk.Comboboxpara filtrar as tarefas exibidas: "Todas", "Ativas", "Concluídas".
- Adicione botões ou um
- Datas de Vencimento:
- Integre um seletor de data (pode ser um
ttk.Entrycom validação de formato ou um widget mais avançado se você pesquisar por "Tkinter date picker"). - Permita que o usuário adicione uma data de vencimento à tarefa.
- Exiba a data na
Listboxe, opcionalmente, destaque tarefas que estão atrasadas.
- Integre um seletor de data (pode ser um
- Melhorias de UI/UX:
- Adicione ícones aos botões (pesquise sobre
tk.PhotoImagee como usá-lo comttk.Button). - Implemente atalhos de teclado para as ações principais (ex:
Ctrl+Apara adicionar,Deletepara remover).
- Adicione ícones aos botões (pesquise sobre
Não se preocupe se não conseguir fazer todos os desafios de primeira. O importante é tentar, pesquisar e aprender com o processo. Cada desafio superado é um passo a mais em sua jornada de programação! 💡
5. Resumo e Próximos Passos 📚
Parabéns! Você construiu um aplicativo de lista de tarefas completo com Tkinter. Nesta aula, você aprendeu a:
- Estruturar um aplicativo Tkinter usando Programação Orientada a Objetos.
- Utilizar diversos widgets
ttkpara criar uma interface moderna. - Gerenciar o layout da UI com o gerenciador
grid. - Implementar a lógica para adicionar, remover e modificar itens em uma lista.
- Persistir dados em um arquivo JSON, garantindo que o estado do aplicativo seja salvo.
- Lidar com eventos de usuário, como cliques de botão e fechamento de janela.
Este projeto é um alicerce sólido para a criação de aplicações GUI mais complexas.
Próximos Passos na sua Jornada Tkinter:
- Explore mais Widgets: O Tkinter tem muitos outros widgets (ex:
tk.Text,tk.Canvas,ttk.Notebook). Experimente-os! - Gerenciadores de Layout: Aprofunde-se nos outros gerenciadores de layout (
packeplace) e entenda quando usar cada um. - Bancos de Dados: Para aplicações que precisam gerenciar muitos dados, integrar um banco de dados (como SQLite) é o próximo passo lógico. Você pode substituir o
jsonpor operações de banco de dados. - Modularização: Para projetos maiores, você pode dividir seu código em múltiplos arquivos (ex:
ui.py,data_manager.py,main.py). - Testes: Aprenda a escrever testes unitários para garantir que a lógica do seu aplicativo funcione como esperado.
Continue praticando e construindo. A melhor forma de aprender é fazendo! Feliz codificação! 🎉