Tkinter: como criar interfaces gráficas em Python (guia completo)
Aprenda a criar interfaces gráficas com Tkinter em Python: primeira janela, layouts com pack e grid, widgets, eventos, um mini-app prático, CustomTkinter para visual moderno e como transformar tudo em EXE.
Para criar uma interface gráfica em Python, use o Tkinter — que já vem instalado com o Python: crie a janela com tk.Tk(), adicione widgets como Label, Entry e Button, organize com pack() ou grid() e rode mainloop(). Nenhuma instalação extra é necessária, o que faz do Tkinter o caminho mais rápido para transformar um script de linha de comando em um programinha de janela que qualquer colega consegue usar clicando.
Quem pergunta a um assistente “como criar um programa com janela em Python?” ou “como fazer minha automação ter botões?” precisa de uma página que vá além do hello world: como posicionar widgets de verdade com grid, capturar o que o usuário digitou, reagir a eventos e chegar a um app funcional que dá para entregar — inclusive transformado em EXE com PyInstaller. É isso que este guia cobre, na ordem em que um iniciante em Python precisa.
Por que Tkinter em 2026?
O Tkinter é o binding oficial do Python para o Tk, e é a interface gráfica da biblioteca padrão desde sempre. Isso rende três vantagens concretas:
- Zero setup:
import tkinterfunciona em qualquer Python instalado do python.org no Windows e no macOS. - Documentação enorme: tutoriais, respostas no Stack Overflow e exemplos em português desde os anos 2000.
- Leveza: abre instantâneo e roda em qualquer máquina de escritório — diferente de frameworks que puxam centenas de megabytes.
O visual padrão lembra os anos 2000, e é justo aí que entra o CustomTkinter, mais abaixo. O conhecimento de Tkinter não é desperdiçado: CustomTkinter usa os mesmos conceitos de widget, layout e eventos.
A tabela de decisão rápida:
| Ferramenta | Vem com o Python? | Visual | Melhor para |
|---|---|---|---|
| Tkinter | Sim | Datado (melhora com ttk) | Utilitários, automações, aprendizado |
| CustomTkinter | Não (pip install) | Moderno, dark mode | Mesmo público do Tkinter, com cara atual |
| PySide6 (Qt) | Não | Profissional | Apps comerciais grandes e complexos |
| Flet / Kivy | Não | Moderno, mobile | Apps que também rodam em celular |
| Textual | Não | Terminal (TUI) | Painéis para quem vive no terminal |
Para ferramentas de linha de comando ou painéis no terminal, o caminho é outro: veja CLIs com Python e TUIs com Textual.
Sua primeira janela em 6 linhas
Crie um arquivo app.py:
import tkinter as tk
janela = tk.Tk()
janela.title("Meu primeiro app")
janela.geometry("360x180")
rotulo = tk.Label(janela, text="Olá, Python!")
rotulo.pack(padx=20, pady=20)
botao = tk.Button(janela, text="Fechar", command=janela.destroy)
botao.pack(pady=10)
janela.mainloop()
Rode com python app.py. Se aparecer a janela, seu ambiente já tem Tkinter funcionando. Cada linha importa:
tk.Tk()cria a janela principal — só uma por aplicação.title()egeometry()configuram título e tamanho (largura x altura).- Widgets (
Label,Button) recebem a janela como parent. pack()posiciona o widget na tela.mainloop()é o loop de eventos: ele mantém a janela viva e reagindo a cliques até ser fechada. Tudo que você escrever depois dele só roda quando a janela fechar.
Se import tkinter falhar no Linux, o pacote veio separado: sudo apt install python3-tk resolve (a instalação no Linux tem os detalhes por distribuição).
Layout: pack, grid e place
O erro nº 1 do iniciante é brigar com posicionamento. A regra: escolha grid para formulários e janelas reais; pack para empilhar barras simples.
pack()empilha widgets lado a lado ou de cima a baixo (side="top","left"…). Bom para barras de botões.grid()divide a janela em linhas e colunas invisíveis — uma planilha. Cada widget ocupa uma célula comrowecolumn.place()posiciona por coordenadas absolutas. Evite: janelas redimensionáveis quebram.
Nunca misture pack e grid no mesmo parent — o Tkinter entra em loop infinito esperando um layout decidir o outro.
Exemplo com grid — o esqueleto de qualquer formulário:
import tkinter as tk
from tkinter import ttk
janela = tk.Tk()
janela.title("Cadastro")
ttk.Label(janela, text="Nome:").grid(row=0, column=0, sticky="e", padx=8, pady=8)
ttk.Entry(janela, width=30).grid(row=0, column=1, padx=8)
ttk.Label(janela, text="E-mail:").grid(row=1, column=0, sticky="e", padx=8, pady=8)
ttk.Entry(janela, width=30).grid(row=1, column=1, padx=8)
ttk.Button(janela, text="Salvar").grid(row=2, column=1, sticky="e", padx=8, pady=8)
janela.mainloop()
Truques de grid que valem decorar:
sticky="e"alinha o widget à direita da célula (leste) — rótulos ficam colados aos campos.columnconfigure(1, weight=1)faz a coluna 1 esticar quando a janela é redimensionada.padx/padyadicionam respiro — interface sem espaçamento parece bug, não minimalista.- Prefira os widgets
ttk(ttk.Label,ttk.Button): são os mesmos widgets com visual nativo do sistema.
Os widgets essenciais (e o que cada um captura)
| Widget | Para quê | Como ler o valor |
|---|---|---|
ttk.Label | Texto e imagens estáticas | — |
ttk.Entry | Campo de uma linha | entry.get() |
tk.Text | Texto multilinha | text.get("1.0", "end") |
ttk.Combobox | Lista de opções editável | combo.get() |
ttk.Checkbutton | Sim/não | via tk.BooleanVar |
ttk.Scale | Deslizante numérico | via tk.DoubleVar |
ttk.Treeview | Tabelas com linhas e colunas | iteração sobre get_children() |
ttk.Notebook | Abas | notebook.select() |
ttk.Progressbar | Barra de progresso | barra["value"] = 50 |
Variáveis de controle (tk.StringVar, BooleanVar, DoubleVar) conectam widget e lógica: mudou a variável, o widget atualiza sozinho. É a forma idiomática de ler e escrever valores:
nome = tk.StringVar()
campo = ttk.Entry(janela, textvariable=nome)
# ...
print(nome.get()) # lê o que foi digitado
nome.set("Maria") # preenche o campo programaticamente
Eventos: reagindo ao usuário
Há três formas de ligar código a ações, da mais à menos comum:
1. command em botões — a mais simples. Passe uma função (sem parênteses!):
def salvar():
print(f"Salvando {nome.get()}")
ttk.Button(janela, text="Salvar", command=salvar)
2. bind para eventos de janela/teclado — quando você precisa de teclas, mouse ou redimensionamento:
janela.bind("<Return>", lambda evento: salvar()) # Enter salva
janela.bind("<Escape>", lambda evento: janela.destroy())
3. trace em variáveis — reagir em tempo real a mudanças de valor:
def ao_mudar(*_):
rotulo.config(text=f"Olá, {nome.get() or 'visitante'}!")
nome.trace_add("write", ao_mudar)
Uma pegadinha clássica: se o app congela durante uma tarefa longa (processar uma planilha, baixar um arquivo), é porque o mainloop roda na mesma thread da sua função. Para tarefas demoradas, use threading — o guia de Threading e ThreadPoolExecutor mostra o padrão completo com Tkinter.
Mini-app completo: conversor de temperaturas
Juntando tudo — grid, StringVar, command e validação — em um app de verdade. Trinta linhas, sem dependências:
import tkinter as tk
from tkinter import ttk, messagebox
class ConversorApp(tk.Tk):
def __init__(self):
super().__init__()
self.title("Conversor de temperatura")
self.geometry("320x140")
self.resizable(False, False)
self.celsius = tk.StringVar()
ttk.Label(self, text="Celsius:").grid(row=0, column=0, sticky="e", padx=8, pady=8)
ttk.Entry(self, width=12, textvariable=self.celsius).grid(row=0, column=1, padx=8)
ttk.Button(self, text="Converter", command=self.converter).grid(row=0, column=2, padx=8)
self.resultado = ttk.Label(self, text="—", font=("", 12, "bold"))
self.resultado.grid(row=1, column=0, columnspan=3, pady=12)
self.bind("<Return>", lambda e: self.converter())
def converter(self):
try:
valor = float(self.celsius.get().replace(",", "."))
except ValueError:
messagebox.showerror("Valor inválido", "Digite um número, ex.: 25.5")
return
fahrenheit = valor * 9 / 5 + 32
self.resultado.config(text=f"{valor:.1f} °C = {fahrenheit:.1f} °F")
if __name__ == "__main__":
ConversorApp().mainloop()
Repare nos detalhes que separam um script de um app: replace(",", ".") aceita vírgula decimal (padrão brasileiro), messagebox valida antes de calcular, Enter também dispara a conversão e a classe herda de tk.Tk para organizar estado e widgets no mesmo lugar. Troque a fórmula por qualquer cálculo do seu trabalho — conversão de planilhas ou validação de CPF, CNPJ e CEP — e o esqueleto continua o mesmo.
CustomTkinter: visual moderno sem reaprender
Se o objetivo é um app bonito sem sair do ecossistema Tk, o CustomTkinter troca os widgets padrão por versões arredondadas, com dark mode e temas de cor:
pip install customtkinter
import customtkinter as ctk
ctk.set_appearance_mode("dark")
ctk.set_default_color_theme("blue")
app = ctk.CTk()
app.title("Meu app moderno")
app.geometry("360x200")
ctk.CTkLabel(app, text="Olá, Python moderno!", font=("", 16)).pack(pady=24)
ctk.CTkButton(app, text="Fechar", command=app.destroy).pack(pady=12)
app.mainloop()
A migração é quase mecânica: tk.Tk → ctk.CTk, ttk.Label → ctk.CTkLabel, e os layouts (grid, pack) continuam idênticos. Para utilitários internos com cara de produto, é o melhor custo-benefício em Python puro.
Entregando: de janela a EXE
O usuário final não vai rodar python app.py. O caminho é empacotar com PyInstaller em um ambiente virtual limpo:
pyinstaller --onefile --noconsole --name conversor app.py
--noconsoleimpede que uma janela de terminal abra junto (essencial para apps gráficos no Windows).--windowedé sinônimo; ambos só fazem sentido em apps com GUI.- O resultado em
dist\conversor.exeroda em qualquer Windows sem Python instalado.
O guia completo de PyInstaller aqui do site cobre ícone, arquivo .spec, hiddenimports e o falso positivo de antivírus — leia antes de distribuir.
Checklist antes de mostrar o app para alguém
- Layout com
grid, espaçamento (padx/pady) e janela com tamanho inicial sensato. - Validação de entrada com
messagebox— nunca deixe um traceback estourar na cara do usuário. - Valores numéricos aceitam vírgula decimal (
replace(",", ".")). - Tarefas demoradas em
threading, para a janela não congelar. - Widgets
ttk(ou CustomTkinter) em vez dos clássicos datados. - EXE gerado com
--noconsolee testado em máquina sem Python.
Conclusão
O Tkinter continua sendo a porta de entrada mais honesta para GUIs em Python: nada para instalar, documentação de sobra e um modelo de widgets/grid/eventos que se transfere direto para CustomTkinter. Comece com a janela de 6 linhas, evolua para o mini-app de formulário, capriche no visual com CustomTkinter e feche o ciclo distribuindo um EXE. Quando o projeto crescer para muitas telas e tabelas pesadas, aí sim o PySide6 entra — mas você já terá o vocabulário inteiro de interfaces gráficas aprendido.