projeto

Projeto Guiado: Editor de Texto Básico com Funcionalidades de Arquivo

Aprenda sobre projeto guiado: editor de texto básico com funcionalidades de arquivo

90 min
Aula 4 de 5

Projeto Guiado: Editor de Texto Básico com Funcionalidades de Arquivo

Bem-vindo(a) ao seu primeiro projeto guiado com Tkinter! 🎉 Nesta aula, vamos construir um editor de texto simples, mas funcional, que permitirá criar, abrir e salvar arquivos de texto. Este projeto é fundamental para solidificar seu conhecimento em Tkinter, combinando diversos widgets e conceitos essenciais.

🎯 Objetivos de Aprendizagem

Ao final desta aula, você será capaz de:

  • Criar e configurar uma janela principal com Tkinter.
  • Utilizar o widget Text para edição de texto.
  • Implementar uma barra de menu (Menu) com submenus.
  • Manipular arquivos (abrir, salvar, salvar como) usando o módulo filedialog.
  • Gerenciar eventos e associar funções a ações do usuário.
  • Aplicar boas práticas de organização de código em um projeto Tkinter.

1. Introdução: O Que Vamos Construir?

Imagine um aplicativo como o Bloco de Notas do Windows ou o TextEdit do macOS, mas em uma versão minimalista. Nosso editor de texto terá as seguintes funcionalidades principais:

  • Novo Arquivo: Limpa o conteúdo atual para começar um novo documento.
  • Abrir Arquivo: Carrega o conteúdo de um arquivo de texto existente.
  • Salvar Arquivo: Salva o conteúdo atual em um arquivo.
  • Salvar Como: Permite escolher um novo nome e local para salvar o arquivo.
  • Sair: Fecha o aplicativo.

Este projeto nos dará uma base sólida para entender como Tkinter interage com o sistema de arquivos do seu computador e como construir interfaces de usuário mais complexas.


2. Explicação Detalhada com Exemplos

Vamos construir nosso editor passo a passo. Siga as instruções e os blocos de código para montar seu projeto.

2.1. Configuração Inicial da Janela e Importações Essenciais

Primeiro, precisamos importar os módulos necessários e configurar a janela principal do nosso aplicativo.

import tkinter as tk
from tkinter import filedialog, messagebox
 
# Variável global para armazenar o caminho do arquivo atual
# Isso nos ajuda a saber se estamos editando um arquivo existente ou um novo.
current_file_path = None
  • tkinter as tk: Importa a biblioteca Tkinter e a renomeia para tk por conveniência.
  • filedialog: Módulo essencial para abrir e salvar caixas de diálogo de arquivo.
  • messagebox: Módulo para exibir mensagens de aviso ou erro (útil para feedback ao usuário).
  • current_file_path: Uma variável que manterá o caminho do arquivo que está sendo editado no momento. É importante para a lógica de "Salvar" versus "Salvar Como".

2.2. Criando o Widget Text

O coração do nosso editor é o widget Text, que permite a entrada e exibição de texto multilinha.

def create_text_editor(root):
    """Cria e configura o widget Text para o editor."""
    text_widget = tk.Text(root, wrap='word', undo=True)
    text_widget.pack(expand=True, fill='both') # Faz o widget Text preencher a janela
 
    # Opcional: Adicionar uma barra de rolagem
    scrollbar = tk.Scrollbar(root, command=text_widget.yview)
    scrollbar.pack(side='right', fill='y')
    text_widget['yscrollcommand'] = scrollbar.set
 
    return text_widget
  • tk.Text(root, ...): Cria o widget Text dentro da janela root.
  • wrap='word': Garante que as linhas de texto que excedem a largura do widget serão quebradas em palavras, não no meio de uma palavra.
  • undo=True: Habilita a funcionalidade de desfazer/refazer nativa do widget Text.
  • pack(expand=True, fill='both'): Este é crucial! Faz com que o widget Text ocupe todo o espaço disponível na janela e se redimensione junto com ela.
  • Barra de Rolagem: Embora opcional para um editor básico, é uma boa prática incluir uma barra de rolagem. Conectamos a barra de rolagem (scrollbar.pack) ao Text widget usando command=text_widget.yview e text_widget['yscrollcommand'] = scrollbar.set.

2.3. Implementando a Barra de Menu

A barra de menu é onde o usuário encontrará as opções de arquivo.

def create_menu_bar(root, text_widget):
    """Cria a barra de menu com as opções de arquivo."""
    menu_bar = tk.Menu(root)
    root.config(menu=menu_bar) # Anexa a barra de menu à janela principal
 
    file_menu = tk.Menu(menu_bar, tearoff=0) # tearoff=0 remove a linha tracejada
    menu_bar.add_cascade(label="Arquivo", menu=file_menu)
 
    # Adiciona os comandos ao menu 'Arquivo'
    file_menu.add_command(label="Novo", command=lambda: new_file(root, text_widget))
    file_menu.add_command(label="Abrir...", command=lambda: open_file(root, text_widget))
    file_menu.add_command(label="Salvar", command=lambda: save_file(root, text_widget))
    file_menu.add_command(label="Salvar Como...", command=lambda: save_file_as(root, text_widget))
    file_menu.add_separator() # Adiciona uma linha separadora
    file_menu.add_command(label="Sair", command=root.quit) # root.quit() fecha a janela
  • tk.Menu(root): Cria a barra de menu principal.
  • root.config(menu=menu_bar): Anexa a barra de menu criada à janela principal.
  • tk.Menu(menu_bar, tearoff=0): Cria um submenu para "Arquivo". tearoff=0 impede que o menu seja "destacado" da barra.
  • menu_bar.add_cascade(label="Arquivo", menu=file_menu): Adiciona o submenu "Arquivo" à barra de menu principal.
  • file_menu.add_command(...): Adiciona itens ao submenu "Arquivo", cada um com um label e uma command (função a ser executada). Usamos lambda para passar argumentos para as funções.

2.4. Funções de Manipulação de Arquivos

Agora, vamos escrever as funções que serão chamadas pelos itens do menu.

def update_window_title(root, file_path):
    """Atualiza o título da janela com o nome do arquivo atual."""
    global current_file_path
    current_file_path = file_path
    if file_path:
        root.title(f"Editor de Texto - {file_path.split('/')[-1]}")
    else:
        root.title("Editor de Texto - Sem título")
 
def new_file(root, text_widget):
    """Cria um novo arquivo (limpa o editor)."""
    text_widget.delete(1.0, tk.END) # Limpa todo o conteúdo do widget Text
    update_window_title(root, None) # Reseta o título para "Sem título"
 
def open_file(root, text_widget):
    """Abre um arquivo de texto e carrega seu conteúdo no editor."""
    file_path = filedialog.askopenfilename(
        defaultextension=".txt",
        filetypes=[("Arquivos de Texto", "*.txt"), ("Todos os Arquivos", "*.*")]
    )
    if not file_path: # Se o usuário cancelar a caixa de diálogo
        return
 
    try:
        with open(file_path, "r", encoding="utf-8") as file:
            content = file.read()
            text_widget.delete(1.0, tk.END) # Limpa o conteúdo atual
            text_widget.insert(1.0, content) # Insere o novo conteúdo
        update_window_title(root, file_path)
    except Exception as e:
        messagebox.showerror("Erro ao Abrir Arquivo", f"Não foi possível abrir o arquivo: {e}")
 
def save_file(root, text_widget):
    """Salva o conteúdo atual no arquivo, ou chama 'Salvar Como' se for um novo arquivo."""
    if current_file_path:
        _save_content_to_file(root, text_widget, current_file_path)
    else:
        save_file_as(root, text_widget)
 
def save_file_as(root, text_widget):
    """Permite ao usuário escolher um nome e local para salvar o arquivo."""
    file_path = filedialog.asksaveasfilename(
        defaultextension=".txt",
        filetypes=[("Arquivos de Texto", "*.txt"), ("Todos os Arquivos", "*.*")]
    )
    if not file_path: # Se o usuário cancelar a caixa de diálogo
        return
 
    _save_content_to_file(root, text_widget, file_path)
 
def _save_content_to_file(root, text_widget, file_path):
    """Função auxiliar para salvar o conteúdo do widget Text em um arquivo."""
    try:
        content = text_widget.get(1.0, tk.END) # Obtém todo o conteúdo do widget Text
        with open(file_path, "w", encoding="utf-8") as file:
            file.write(content)
        update_window_title(root, file_path)
        messagebox.showinfo("Sucesso", "Arquivo salvo com sucesso!")
    except Exception as e:
        messagebox.showerror("Erro ao Salvar Arquivo", f"Não foi possível salvar o arquivo: {e}")
  • update_window_title(root, file_path): Uma função auxiliar para manter o título da janela atualizado com o nome do arquivo. Usa a variável global current_file_path.
  • new_file(): Simplesmente limpa o Text widget usando delete(1.0, tk.END) (do primeiro caractere à última posição) e redefine o título.
  • open_file():
    • filedialog.askopenfilename(...): Abre a caixa de diálogo "Abrir Arquivo".
    • defaultextension e filetypes: Filtram os tipos de arquivo que podem ser selecionados.
    • Usa with open(...) para garantir que o arquivo seja fechado corretamente.
    • text_widget.delete(...) e text_widget.insert(...): Limpa o editor e insere o conteúdo lido.
    • Bloco try-except: Essencial para lidar com erros de leitura de arquivo (permissões, arquivo corrompido, etc.).
  • save_file(): Verifica se current_file_path já existe. Se sim, chama a função auxiliar de salvamento; caso contrário, age como save_file_as().
  • save_file_as():
    • filedialog.asksaveasfilename(...): Abre a caixa de diálogo "Salvar Como".
    • Similar ao open_file, mas para escrita.
  • _save_content_to_file(): Uma função interna (o _ indica que é para uso interno) que realiza a operação de escrita no arquivo. Obtém o texto com text_widget.get(1.0, tk.END).

2.5. Integração e Execução Principal

Finalmente, juntamos todas as peças na função principal que inicializa o aplicativo.

def main():
    """Função principal para iniciar o aplicativo Tkinter."""
    root = tk.Tk()
    update_window_title(root, None) # Título inicial "Sem título"
    root.geometry("800x600") # Define um tamanho inicial para a janela
 
    text_widget = create_text_editor(root)
    create_menu_bar(root, text_widget)
 
    # Inicia o loop principal de eventos do Tkinter
    root.mainloop()
 
if __name__ == "__main__":
    main()
  • root = tk.Tk(): Cria a janela principal.
  • root.geometry("800x600"): Define as dimensões iniciais da janela.
  • main(): Chama todas as funções que criam os componentes da UI.
  • root.mainloop(): Inicia o loop de eventos do Tkinter. Sem isso, a janela não aparecerá e não responderá a interações.
  • if __name__ == "__main__":: Garante que main() seja chamada apenas quando o script for executado diretamente.

3. Código Completo do Editor de Texto

Aqui está o código completo, combinando todas as partes que exploramos. Este código segue as melhores práticas para organização e clareza, inspirando-se na forma como a documentação oficial do Tkinter apresenta exemplos.

import tkinter as tk
from tkinter import filedialog, messagebox
 
# Variável global para armazenar o caminho do arquivo atual
# Isso nos ajuda a saber se estamos editando um arquivo existente ou um novo.
current_file_path = None
 
def update_window_title(root, file_path):
    """
    Atualiza o título da janela com o nome do arquivo atual.
    Se nenhum caminho de arquivo for fornecido, o título indica "Sem título".
    """
    global current_file_path
    current_file_path = file_path
    if file_path:
        # Pega apenas o nome do arquivo da string do caminho
        root.title(f"Editor de Texto - {file_path.split('/')[-1]}")
    else:
        root.title("Editor de Texto - Sem título")
 
def new_file(root, text_widget):
    """
    Cria um novo arquivo, limpando o conteúdo do editor e resetando o título.
    """
    text_widget.delete(1.0, tk.END)  # Limpa todo o conteúdo do widget Text
    update_window_title(root, None)  # Reseta o título para "Sem título"
 
def open_file(root, text_widget):
    """
    Abre uma caixa de diálogo para selecionar um arquivo de texto,
    lê seu conteúdo e o carrega no editor.
    """
    file_path = filedialog.askopenfilename(
        defaultextension=".txt",
        filetypes=[("Arquivos de Texto", "*.txt"), ("Todos os Arquivos", "*.*")]
    )
    if not file_path:  # Se o usuário cancelar a caixa de diálogo
        return
 
    try:
        # Abre o arquivo em modo de leitura com codificação UTF-8
        with open(file_path, "r", encoding="utf-8") as file:
            content = file.read()
            text_widget.delete(1.0, tk.END)  # Limpa o conteúdo atual
            text_widget.insert(1.0, content)  # Insere o novo conteúdo
        update_window_title(root, file_path)
    except Exception as e:
        messagebox.showerror("Erro ao Abrir Arquivo", f"Não foi possível abrir o arquivo: {e}")
 
def save_file(root, text_widget):
    """
    Salva o conteúdo atual no arquivo. Se o arquivo já tiver sido aberto/salvo antes,
    ele o salva no mesmo local. Caso contrário, chama 'save_file_as'.
    """
    if current_file_path:
        _save_content_to_file(root, text_widget, current_file_path)
    else:
        save_file_as(root, text_widget)
 
def save_file_as(root, text_widget):
    """
    Abre uma caixa de diálogo para o usuário escolher um novo nome e local
    para salvar o conteúdo atual do editor.
    """
    file_path = filedialog.asksaveasfilename(
        defaultextension=".txt",
        filetypes=[("Arquivos de Texto", "*.txt"), ("Todos os Arquivos", "*.*")]
    )
    if not file_path:  # Se o usuário cancelar a caixa de diálogo
        return
 
    _save_content_to_file(root, text_widget, file_path)
 
def _save_content_to_file(root, text_widget, file_path):
    """
    Função auxiliar para salvar o conteúdo do widget Text em um arquivo.
    Atualiza o título da janela e exibe uma mensagem de sucesso ou erro.
    """
    try:
        content = text_widget.get(1.0, tk.END)  # Obtém todo o conteúdo do widget Text
        # Abre o arquivo em modo de escrita com codificação UTF-8
        with open(file_path, "w", encoding="utf-8") as file:
            file.write(content)
        update_window_title(root, file_path)
        messagebox.showinfo("Sucesso", "Arquivo salvo com sucesso!")
    except Exception as e:
        messagebox.showerror("Erro ao Salvar Arquivo", f"Não foi possível salvar o arquivo: {e}")
 
def create_text_editor(root):
    """
    Cria e configura o widget Text, incluindo uma barra de rolagem.
    """
    text_widget = tk.Text(root, wrap='word', undo=True)
    text_widget.pack(expand=True, fill='both')  # Faz o widget Text preencher a janela
 
    # Adiciona uma barra de rolagem vertical para o widget Text
    scrollbar = tk.Scrollbar(root, command=text_widget.yview)
    scrollbar.pack(side='right', fill='y')
    text_widget['yscrollcommand'] = scrollbar.set
 
    return text_widget
 
def create_menu_bar(root, text_widget):
    """
    Cria a barra de menu principal e adiciona o menu 'Arquivo' com suas opções.
    """
    menu_bar = tk.Menu(root)
    root.config(menu=menu_bar)  # Anexa a barra de menu à janela principal
 
    # Cria o menu 'Arquivo'
    file_menu = tk.Menu(menu_bar, tearoff=0)  # tearoff=0 remove a linha tracejada destacável
    menu_bar.add_cascade(label="Arquivo", menu=file_menu)
 
    # Adiciona os comandos ao menu 'Arquivo'
    file_menu.add_command(label="Novo", command=lambda: new_file(root, text_widget))
    file_menu.add_command(label="Abrir...", command=lambda: open_file(root, text_widget))
    file_menu.add_command(label="Salvar", command=lambda: save_file(root, text_widget))
    file_menu.add_command(label="Salvar Como...", command=lambda: save_file_as(root, text_widget))
    file_menu.add_separator()  # Adiciona uma linha separadora
    file_menu.add_command(label="Sair", command=root.quit)  # root.quit() fecha a janela
 
def main():
    """
    Função principal para inicializar e executar o aplicativo Tkinter.
    """
    root = tk.Tk()
    update_window_title(root, None)  # Define o título inicial da janela
    root.geometry("800x600")  # Define um tamanho inicial para a janela
 
    text_widget = create_text_editor(root)
    create_menu_bar(root, text_widget)
 
    # Inicia o loop principal de eventos do Tkinter
    root.mainloop()
 
if __name__ == "__main__":
    main()

4. Exercícios e Desafios 🚀

Agora que você tem um editor de texto funcional, que tal expandir suas capacidades? Tente implementar as seguintes funcionalidades:

Task List:

  • Contador de Linhas: Adicione um widget Canvas ou outro Text widget à esquerda do editor principal para exibir números de linha. Isso exigirá sincronizar a rolagem entre os dois widgets e atualizar os números quando o texto mudar.
  • Barra de Status: Inclua uma barra de status na parte inferior da janela que exiba informações como:
    • Número da linha e coluna atual do cursor.
    • Número total de caracteres ou palavras no documento.
  • Funcionalidade de Desfazer/Refazer (Menu): O widget Text já tem undo=True. Adicione itens "Desfazer" e "Refazer" ao menu "Editar" (você precisará criar este novo menu!) e associe-os aos métodos text_widget.edit_undo() e text_widget.edit_redo().
  • Pesquisar e Substituir: Crie uma nova janela (Toplevel) ou adicione widgets à interface principal para permitir que o usuário pesquise por uma palavra e, opcionalmente, a substitua.
  • Personalização de Fonte: Adicione opções no menu para mudar a fonte e o tamanho do texto no editor. Pesquise sobre o módulo tkinter.font.
  • Atalhos de Teclado: Implemente atalhos de teclado para as operações de arquivo (ex: Ctrl+N para Novo, Ctrl+O para Abrir, Ctrl+S para Salvar). Pesquise sobre root.bind().

5. Resumo e Próximos Passos

Parabéns! Você construiu um editor de texto básico e funcional com Tkinter. 🎉

Nesta aula, você aprendeu a:

  • Estruturar uma aplicação Tkinter com funções separadas para diferentes componentes.
  • Utilizar os widgets Text, Menu e Scrollbar.
  • Interagir com o sistema de arquivos para abrir e salvar documentos.
  • Lidar com erros usando blocos try-except.
  • Manter a interface do usuário responsiva e informativa.

Este projeto é um excelente ponto de partida. A capacidade de manipular arquivos e criar interfaces dinâmicas abre portas para muitos outros tipos de aplicações.

Próximos Passos:

  • Tente completar os desafios propostos para aprimorar seu editor.
  • Explore outros widgets do Tkinter, como Entry, Button, LabelFrame, Canvas, etc.
  • Pesquise sobre layouts mais avançados com grid() e place() para um controle mais preciso sobre o posicionamento dos widgets.
  • Comece a pensar em como você pode usar esses conhecimentos para construir suas próprias ferramentas e utilitários!

Continue praticando, e logo você estará criando interfaces gráficas complexas e poderosas com Python e Tkinter!

© 2025 Escola All Dev. Todos os direitos reservados.