Tkinter no Python: Guia Completo – Crie Interfaces do Zero

Aprenda a usar o Tkinter no Python do zero ao avançado: caixas de diálogo, widgets, grid system e sistema completo com CustomTkinter.

O Tkinter é a biblioteca de interface gráfica que já vem instalada com o Python: basta import tkinter, sem instalar nada. Com ela você cria a janela, define título e tamanho, posiciona os elementos com pack() ou grid() e mantém tudo em execução com mainloop(). Para um visual moderno, o CustomTkinter aproveita o mesmo código.

O que você vai ver aqui?

Introdução

Se você já criou alguma automação ou script em Python e pensou “seria incrível ter uma tela para isso”, saiba que a solução já está dentro do próprio Python. Usar o Tkinter no Python é a forma mais direta de transformar um código que roda no terminal em um sistema visual com janelas, botões, campos de texto e barras de progresso, sem precisar instalar nada extra.

Neste tutorial, você vai aprender tudo sobre o Tkinter do absoluto zero: como criar sua primeira janela, quais são os widgets disponíveis, como organizar layouts complexos usando grid system e, no final, como dar um salto visual usando o CustomTkinter, a extensão moderna que traz dark mode e aparência profissional para as suas telas. Cada conceito vem acompanhado de código real e comentado para você copiar, adaptar e aplicar nos seus próprios projetos.

Ao final do artigo, você vai ter construído um sistema de cadastro completo com barra lateral, abas, campos de formulário, slider de volume e barra de progresso, tudo funcionando. Então bora começar!

Para fazer o download do(s) arquivo(s) utilizados na aula, preencha com o seu e-mail:

Não vamos te encaminhar nenhum tipo de SPAM! A Hashtag Treinamentos é uma empresa preocupada com a proteção de seus dados e realiza o tratamento de acordo com a Lei Geral de Proteção de Dados (Lei n. 13.709/18). Qualquer dúvida, nos contate.

O que é o Tkinter no Python e para que serve

O Tkinter é a biblioteca padrão do Python para criação de interfaces gráficas. O nome vem de Tk Interface, onde Tk é o Tool Kit da linguagem TCL (Tool Command Language), sobre o qual o Tkinter funciona como uma camada de abstração orientada a objetos. Na prática, você não precisa entender nada de TCL para usá-lo: basta saber Python.

Como descrito na documentação oficial do Python, o Tkinter não é um invólucro fino, mas adiciona uma boa quantidade de lógica própria para tornar a experiência mais pythônica. Isso significa que criar janelas, botões, campos e menus segue os mesmos padrões que você já conhece do Python, como classes, funções e listas.

Na prática, o Tkinter serve para dois cenários principais. O primeiro é quando você tem uma automação ou script e quer dar uma interface mínima para o usuário: uma janela para selecionar um arquivo, um botão para confirmar uma ação ou uma caixa pedindo um dado. O segundo é quando você quer construir um sistema desktop completo, com múltiplas abas, campos de formulário, menus laterais e muito mais.

Por que o Tkinter já vem instalado no Python

Uma das grandes vantagens do Tkinter é que ele é uma biblioteca padrão do Python, ou seja, já vem instalado junto com a linguagem. Você não precisa rodar nenhum pip install para começar a usá-lo. Basta importar e colocar para funcionar.

Para confirmar que o Tkinter está disponível no seu ambiente, execute o seguinte comando no terminal:

Codigo
python -m tkinter

Se uma janelinha de demonstração abrir, está tudo certo. Esse comando também informa qual versão do Tcl/Tk está instalada, o que pode ser útil na hora de consultar a documentação específica daquela versão.

Por já vir embutido no Python, o Tkinter é muito usado aqui na Hashtag para automações internas, como o processo de seleção de arquivos da base de alunos antes de rodar uma rotina de dados. Não é necessário instalar nada no computador de quem vai usar, o que facilita muito a distribuição dos scripts.

Como criar sua primeira janela com Tkinter

A lógica do Tkinter é simples e intuitiva: você sempre cria uma janela e depois a coloca para rodar. Todo o código de interface fica entre esses dois momentos. Vamos ver como isso funciona na prática.

Defina título, geometria e mantenha a janela aberta com mainloop()

Para criar uma janela básica, importe o Tkinter com o apelido tk (esse é o padrão recomendado pela própria documentação do Python), instancie a janela principal e chame o mainloop():

Python
import tkinter as tk

janela = tk.Tk()

janela.title("Sistema de Cadastro de Usuários")
janela.geometry("900x600")

janela.mainloop()

Cada linha tem um papel bem definido aqui. O tk.Tk() cria a janela principal, uma janelinha cinza vazia que serve como base para tudo que você vai adicionar. O title() define o texto que aparece na barra de título do sistema operacional. O geometry() define o tamanho inicial da janela em pixels no formato largura x altura, neste caso 900 de largura por 600 de altura.

Já o mainloop() é o mais importante de todos. Ele é um loop infinito que mantém a janela aberta e responsiva enquanto o usuário está interagindo com ela. No momento em que o usuário clica para fechar a janela, o loop para e o código encerra. Sem esse comando, a janela abre e fecha em frações de segundo antes de você conseguir ver qualquer coisa.

Adicione elementos à tela com Label e pack()

Com a janela criada, você pode começar a adicionar elementos. O processo segue sempre dois passos: primeiro você cria o elemento, depois você posiciona ele na tela. O elemento de texto mais básico do Tkinter é o Label:

Python
import tkinter as tk

janela = tk.Tk()
janela.title("Meu App")
janela.geometry("900x600")

# Passo 1: criar o elemento
titulo = tk.Label(text="Meu App")

# Passo 2: posicionar na janela
titulo.pack()

subtitulo = tk.Label(text="Bem-vindo!")
subtitulo.pack()

janela.mainloop()

O método .pack() é o gerenciador de layout mais simples do Tkinter. Ele posiciona os elementos um abaixo do outro, de cima para baixo, na ordem em que são chamados. Para telas simples e diretas, ele é suficiente. Para telas com colunas lado a lado ou posicionamento mais preciso, você vai usar o .grid(), que veremos mais adiante.

Além do Label, o Tkinter padrão oferece outros widgets essenciais que seguem o mesmo padrão de criação e posicionamento. Os principais são o tk.Button para botões clicáveis, o tk.Entry para campos de texto onde o usuário pode digitar, o tk.Checkbutton para caixas de marcação e o tk.Radiobutton para seleção de uma opção entre várias.

Controle o espaçamento com padx e pady

Por padrão, os elementos ficam colados uns nos outros. Para adicionar espaçamento, você usa os parâmetros padx e pady dentro do .pack(). Esses parâmetros funcionam como margens: o padx controla a distância nas laterais (eixo X) e o pady controla a distância acima e abaixo do elemento (eixo Y).

Você pode passar um valor único, que se aplica igualmente nos dois lados, ou uma tupla com dois valores separando o espaçamento de cima e de baixo:

Codigo
# pady com valor único: 20px acima e 20px abaixo
titulo.pack(pady=20)

# pady com tupla: 30px acima e 10px abaixo
titulo.pack(pady=(30, 10))

# padx + pady juntos
subtitulo.pack(padx=20, pady=(5, 20))

A diferença entre padx/pady e ipadx/ipady é importante: o pad define a margem externa, ou seja, a distância entre o elemento e os outros elementos ao redor. Já o ipad define o preenchimento interno, a distância entre o conteúdo e a borda do próprio elemento, como no caso de um botão onde você quer aumentar a área clicável.

Personalize fontes com tkinter.font

Você tem duas formas de personalizar a fonte de um Label ou qualquer outro widget de texto no Tkinter. A primeira é passando uma tupla diretamente no parâmetro font, onde a ordem importa: primeiro o nome da família, depois o tamanho e por fim os modificadores como bold ou italic:

Codigo
# Fonte simples via tupla (ordem é obrigatória)
titulo = tk.Label(text="Meu App", font=("Arial", 22, "bold"))

A segunda forma, mais recomendada, é importar o objeto Font do módulo tkinter.font. Com ele, você passa cada parâmetro pelo nome, tornando o código muito mais legível e fácil de manter:

Python
import tkinter as tk
from tkinter.font import Font

janela = tk.Tk()
janela.geometry("900x600")

# Fonte via objeto Font: parâmetros nomeados, mais legível
minha_fonte = Font(
    family="Arial",
    size=22,
    weight="bold",     # "normal" ou "bold"
    slant="roman",     # "roman" ou "italic"
    underline=False,
    overstrike=False
)

titulo = tk.Label(text="Meu App", font=minha_fonte)
titulo.pack(pady=(20, 20))

janela.mainloop()

A vantagem do objeto Font vai além da legibilidade: você pode reutilizá-lo em vários widgets, garantindo consistência visual, e também pode modificar a fonte em tempo de execução com o método configure(), o que é muito útil para criar temas dinâmicos.

Caixas de diálogo prontas do Tkinter

Uma das partes mais práticas do Tkinter são as caixas de diálogo pré-prontas. Elas ficam em submódulos que precisam ser importados separadamente, porque o Tkinter foi construído assim para não carregar tudo na memória de uma vez. Para caixas de mensagem você usa o messagebox, para pedir dados ao usuário o simpledialog e para navegação de arquivos o filedialog.

Exiba mensagens com messagebox

O messagebox é perfeito para notificar o usuário quando uma tarefa termina, exibir um alerta ou pedir uma confirmação. Ele usa as caixas de diálogo nativas do seu sistema operacional, então o visual vai ser o padrão do Windows, macOS ou Linux onde o código estiver rodando.

Python
from tkinter import messagebox

# Exibir mensagem de sucesso (informação)
messagebox.showinfo("Sucesso", "Sua automação rodou corretamente!")

# Exibir aviso
messagebox.showwarning("Atenção", "O arquivo está quase atingindo o limite de tamanho.")

# Exibir erro
messagebox.showerror("Erro", "Não foi possível abrir o arquivo selecionado.")

# Caixa de confirmação Sim/Não
resposta = messagebox.askyesno("Confirmação", "Deseja iniciar a automação?")
# resposta = True (Sim) ou False (Não)

# Caixa com três opções: Sim / Não / Cancelar
resposta = messagebox.askyesnocancel("Confirmação", "Salvar antes de sair?")
# resposta = True (Sim), False (Não) ou None (Cancelar)

Repare que as funções askyesno e askyesnocancel retornam valores que você pode armazenar em variáveis e usar na lógica do seu código. O retorno de askyesnocancel segue a convenção: True para Sim, False para Não e None para Cancelar, o que permite tratar as três situações com um simples if/elif/else.

Colete dados do usuário com simpledialog

Quando você precisa pedir uma informação ao usuário, como o nome dele ou um número de referência, o simpledialog é a solução mais rápida. Ele exibe uma caixa com um campo de texto e retorna o que foi digitado, ou None se o usuário cancelou:

Python
from tkinter import simpledialog

# Pedir uma string (texto)
nome = simpledialog.askstring("Identificação", "Escreva o seu nome:")

if nome:
    print(f"Bem-vindo, {nome}!")
else:
    print("Nenhum nome informado.")

# Pedir um número inteiro
quantidade = simpledialog.askinteger("Configuração", "Quantas linhas processar?")

# Pedir um número decimal
taxa = simpledialog.askfloat("Configuração", "Informe a taxa de conversão:")

Note que é sempre recomendado verificar se o retorno é diferente de None antes de usar o valor, pois o usuário pode cancelar a caixa a qualquer momento. Esse padrão de validação evita erros de atributo no restante da automação.

Selecione arquivos e pastas com filedialog e filetypes

O filedialog é um dos recursos mais usados do Tkinter em automações práticas. Ele abre o explorador de arquivos nativo do sistema e retorna o caminho completo do arquivo ou pasta selecionado. Na Hashtag, por exemplo, ele é usado em automações de dados para que o operador selecione a base de alunos atualizada antes de rodar um processamento, sem precisar hardcodar o caminho no código.

Veja as principais funções disponíveis:

Python
from tkinter import filedialog

# Selecionar um único arquivo
arquivo = filedialog.askopenfilename(
    title="Selecione a base de alunos atualizada",
    filetypes=[
        ("Arquivos de texto", "*.txt"),
        ("Planilhas Excel", "*.xlsx"),
        ("Todos os arquivos", "*.*")
    ]
)
print(arquivo)  # Retorna o caminho completo: "C:/Users/.../base.xlsx"

# Selecionar múltiplos arquivos (retorna tupla)
arquivos = filedialog.askopenfilenames(
    title="Selecione os arquivos para processar"
)
for arq in arquivos:
    print(arq)

# Selecionar uma pasta
pasta = filedialog.askdirectory(
    title="Selecione a pasta de destino"
)
print(pasta)  # Retorna o caminho da pasta selecionada

# Definir local para salvar um arquivo
destino = filedialog.asksaveasfilename(
    defaultextension=".xlsx",
    filetypes=[("Planilhas Excel", "*.xlsx")]
)
print(destino)

O parâmetro filetypes recebe uma lista de tuplas. Cada tupla tem dois elementos: a descrição legível para o usuário e a extensão com asterisco (*.xlsx). O asterisco antes do ponto é uma expressão regular que significa "qualquer nome de arquivo", ou seja, *.xlsx aceita qualquer arquivo com extensão .xlsx.

Você pode combinar quantos filtros quiser na lista. Para projetos de automação em Python, confira também o artigo da Hashtag sobre projetos de automação para iniciantes, que mostra o filedialog aplicado em contextos reais.

Ícone PythonPython Impressionador

Você vai aprender a linguagem de Programação que mais cresce no Mundo para fazer automações incríveis, desenvolver sites, fazer análises de dados, trabalhar com Ciência de Dados, Inteligência Artificial, mesmo que você nunca tenha tido nenhum contato com Programação na vida.

Começar agoraSeta para a direita
Fundo PythonTelas Python
Luz Python

Construa sistemas completos com orientação a objetos

Para janelas simples com poucos elementos, criar tudo de forma procedural (uma linha após a outra) funciona bem. Mas quando você começa a ter barra lateral, múltiplas abas, dezenas de widgets e funções que precisam compartilhar variáveis entre si, o código vira um caos rapidamente. É aí que a orientação a objetos entra como a abordagem certa para organizar tudo.

Por que criar uma classe para sua janela no Tkinter

Ao usar uma classe para representar sua janela, você ganha três vantagens concretas. A primeira é o compartilhamento de variáveis: qualquer widget criado como self.campo_nome fica acessível em qualquer método da classe, sem precisar passar variáveis como parâmetros. A segunda é a organização: você separa a construção de cada parte da tela em métodos diferentes (construir_barra_lateral(), construir_aba_perfil()), o que torna o código muito mais fácil de navegar e manter. A terceira é a modularidade: você pode minimizar e expandir cada método no seu editor, o que é essencial quando a tela tem muitos elementos.

A estrutura básica de uma janela orientada a objetos com Tkinter ficaria assim:

Python
import tkinter as tk

class MeuApp(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("Meu Sistema")
        self.geometry("900x600")
        self.construir_tela()

    def construir_tela(self):
        self.label_titulo = tk.Label(self, text="Meu App")
        self.label_titulo.pack(pady=20)

        self.botao = tk.Button(self, text="Clique aqui", command=self.acao_botao)
        self.botao.pack()

    def acao_botao(self):
        self.label_titulo.configure(text="Botão clicado!")

app = MeuApp()
app.mainloop()

Como usar super().__init__() e o self para organizar o código

Dois conceitos são fundamentais nessa estrutura: o super().__init__() e o self. O super().__init__() garante que o código de inicialização da classe pai (no caso, tk.Tk) seja executado antes das suas personalizações. Sem ele, a janela simplesmente não seria criada corretamente, porque o Python não rodaria a lógica interna do Tkinter que instancia a janela de verdade.

Já o self é a referência ao próprio objeto da classe, ou seja, à sua janela. Quando você escreve self.label_titulo, está criando um atributo da janela que pode ser acessado de qualquer método da mesma classe. Isso é o que permite, por exemplo, que o método acao_botao() acesse e modifique o label_titulo que foi criado em construir_tela().

Para quem quer aprofundar o entendimento sobre classes, herança e orientação a objetos no Python, o conceito de super() e subclasse está explicado em detalhe na documentação oficial. Também confira o artigo da Hashtag sobre múltiplas janelas no Tkinter, que usa essa mesma estrutura de classe para gerenciar diferentes janelas em um projeto.

Divida a tela com Grid System no Tkinter

O .pack() é ótimo para layouts simples, mas quando você quer criar uma barra lateral fixa ao lado de uma área de conteúdo principal, você precisa de um sistema de linhas e colunas. É exatamente isso que o Grid System do Tkinter oferece.

Crie linhas e colunas com grid_rowconfigure e grid_columnconfigure

Para criar um layout com uma coluna lateral (fixa) e uma coluna de conteúdo (expansível), você configura o peso de cada coluna e linha. A propriedade weight define se aquela coluna ou linha cresce junto com a janela quando o usuário redimensiona: weight=1 expande, weight=0 (padrão) fica fixo.

Python
import tkinter as tk

class MeuApp(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("Meu Sistema")
        self.geometry("900x600")

        # Coluna 0 = barra lateral (fixa, weight=0 por padrão)
        # Coluna 1 = conteúdo principal (expande com a janela)
        self.grid_columnconfigure(1, weight=1)

        # Linha 0 = a única linha, expande verticalmente
        self.grid_rowconfigure(0, weight=1)

        self.construir_layout()

    def construir_layout(self):
        # Frame da barra lateral na coluna 0
        self.barra_lateral = tk.Frame(self, width=200, bg="#2b2b2b")
        self.barra_lateral.grid(row=0, column=0, sticky="nsew")

        # Frame do conteúdo na coluna 1
        self.area_conteudo = tk.Frame(self, bg="#3c3f41")
        self.area_conteudo.grid(row=0, column=1, sticky="nsew", padx=10)

app = MeuApp()
app.mainloop()

Com isso, ao redimensionar a janela, a barra lateral mantém sua largura de 200 pixels enquanto a área de conteúdo se expande para ocupar o espaço restante. Isso é exatamente o comportamento que você vê em sistemas profissionais com menu lateral.

Frames e TabView: estruture partes independentes da tela

Ao construir um sistema com o Tkinter ou o CustomTkinter, você vai trabalhar com dois tipos de contêiner principais: o Frame e o TabView. O Frame é um retângulo estático onde você agrupa e posiciona outros widgets. Ele é perfeito para a barra lateral, painéis fixos e seções delimitadas da tela.

Já o TabView (disponível no CustomTkinter como CTkTabview) é um contêiner com abas, como as abas de um navegador. Cada aba é uma área independente onde você coloca seus próprios widgets. O usuário clica na aba para alternar entre os painéis, sem que a janela recarregue. Essa estrutura é ideal para sistemas de cadastro com seções distintas, como Perfil, Preferências e Dashboard.

A hierarquia é sempre: janela principal > frame ou tabview > widgets. Ao criar qualquer widget, o primeiro argumento é sempre o contêiner pai onde ele deve aparecer. Esse conceito de "mestre e filho" é o que estrutura toda a tela.

sticky: faça os elementos preencherem o espaço disponível

Quando você usa o .grid() para posicionar um frame ou widget, por padrão ele ocupa apenas o espaço mínimo que precisa para existir. Para fazê-lo preencher toda a célula da grade disponível, você usa o parâmetro sticky.

O sticky recebe uma string com as direções para as quais o elemento deve se "colar" e expandir: "n" (norte/cima), "s" (sul/baixo), "e" (leste/direita) e "w" (oeste/esquerda). Para preencher todo o espaço disponível em todas as direções, use "nsew":

Codigo
# Preenche todo o espaço disponível da célula (cima, baixo, esquerda, direita)
self.barra_lateral.grid(row=0, column=0, sticky="nsew")

# Preenche apenas verticalmente (sobe e desce com a janela)
self.barra_lateral.grid(row=0, column=0, sticky="ns")

# Com padx para dar margem entre os dois frames
self.area_conteudo.grid(row=0, column=1, sticky="nsew", padx=10)

Sem o sticky="nsew", os frames criam a divisão correta de colunas no grid, mas ficam encolhidos no centro da célula, dando a impressão de que a janela tem espaços em branco. Com ele, cada frame ocupa exatamente a área destinada a ele, criando o layout profissional que você espera.

Eleve o visual com CustomTkinter

O Tkinter padrão funciona muito bem, mas tem uma limitação: a aparência é aquela interface cinza dos anos 90, sem bordas arredondadas, sem modo escuro nativo, sem temas modernos. Quando você quer entregar algo visualmente profissional, a solução mais popular e robusta é o CustomTkinter.

O CustomTkinter, criado por Tom Schimansky e disponível no GitHub, é uma biblioteca Python baseada no Tkinter que oferece widgets modernos e totalmente customizáveis. Como destacado no repositório oficial, os widgets e as cores da janela se adaptam automaticamente ao modo de aparência do sistema operacional ou ao modo definido manualmente pelo desenvolvedor, com suporte completo a HiDPI no Windows e macOS.

A grande vantagem é que todos os comandos são os mesmos do Tkinter. A diferença é que onde você escrevia tk., você passa a escrever ctk.. A curva de aprendizado é praticamente zero para quem já sabe Tkinter.

Como instalar e importar o CustomTkinter

Ao contrário do Tkinter, o CustomTkinter precisa ser instalado via pip. Abra o terminal do seu editor e execute:

Codigo
pip install customtkinter

Após a instalação, importe e use assim:

Python
import customtkinter as ctk

app = ctk.CTk()
app.geometry("900x600")
app.title("Meu Sistema Moderno")
app.mainloop()

Já dá para perceber a diferença visual: a janela tem cantos levemente arredondados, fundo mais escuro e uma aparência muito mais próxima dos aplicativos modernos do Windows 11 ou macOS.

Configure dark mode, light mode e temas de cores

O CustomTkinter tem dois tipos de configuração global: o modo de aparência e o tema de cores. O modo de aparência define se a interface vai ser escura, clara ou seguir o sistema operacional. O tema de cores define as cores dos botões, sliders e outros elementos interativos.

Python
import customtkinter as ctk

# Modo de aparência: "Dark", "Light" ou "System" (segue o SO)
ctk.set_appearance_mode("Dark")

# Tema de cores dos widgets: "blue" (padrão), "green" ou "dark-blue"
ctk.set_default_color_theme("blue")

app = ctk.CTk()
app.geometry("900x600")
app.mainloop()

Como detalhado na documentação oficial do CustomTkinter, as cores dos widgets podem ser definidas de três formas: como nome de cor ("red"), como string hexadecimal ("#FF0000") ou como uma tupla com cor para modo claro e modo escuro (("#DB3E39", "#821D1A")). Quando você usa a tupla, o widget escolhe automaticamente a cor certa de acordo com o modo de aparência ativo. Isso permite que você crie temas que funcionam tanto no light mode quanto no dark mode sem precisar reescrever nada.

Para quem já criou projetos com o Tkinter padrão e quer ver a diferença visual na prática, o artigo da Hashtag sobre o sistema de login com CustomTkinter mostra uma comparação lado a lado entre os dois.

Widgets exclusivos: CTkEntry, CTkRadioButton, CTkCheckBox, CTkOptionMenu, CTkSlider e CTkProgressBar

O CustomTkinter traz versões modernas de todos os widgets do Tkinter. A tabela abaixo resume os principais e suas características:

WidgetPara que serveDiferencial no CustomTkinter
CTkEntryCampo de texto para o usuário digitarPlaceholder, bordas arredondadas, integração com dark mode
CTkButtonBotão clicávelfg_color, hover_color, cantos arredondados, ícones com PhotoImage
CTkRadioButtonSeleção de uma opção entre váriasVisual moderno, integrado ao IntVar para capturar o valor selecionado
CTkCheckBoxMarcação on/offVisual limpo, retorna 1 (marcado) ou 0 (desmarcado) via .get()
CTkOptionMenuMenu dropdown de opçõesValores como lista, armazena automaticamente a seleção atual
CTkSliderControle deslizante de valor numéricoParâmetros from_, to e command que passa o valor atual para a função
CTkProgressBarBarra de progresso visual (0 a 1)Atualização via .set(valor), requer self.update() para exibir em tempo real
CTkSwitchToggle on/off estilo mobile.select() e .deselect() para definir estado inicial, .get() para ler o valor
CTkTabviewContêiner com abas navegáveis.add("nome") para criar aba, .tab("nome") para acessar e preencher

O CTkRadioButton tem uma lógica específica que vale destacar. Para vincular vários botões de rádio entre si (garantindo que só um seja selecionado por vez), você precisa criar uma variável compartilhada do tipo ctk.IntVar() e passá-la no parâmetro variable de cada botão. Cada botão recebe um value diferente, e ao clicar nele, esse valor é atribuído à variável compartilhada:

Python
import customtkinter as ctk

app = ctk.CTk()
ctk.set_appearance_mode("Dark")

# Variável compartilhada entre os radio buttons
nivel_usuario = ctk.IntVar(value=0)

radio_basico = ctk.CTkRadioButton(app,
    text="Básico",
    variable=nivel_usuario,
    value=1)
radio_basico.pack(pady=10)

radio_admin = ctk.CTkRadioButton(app,
    text="Admin",
    variable=nivel_usuario,
    value=2)
radio_admin.pack(pady=10)

app.mainloop()

O CTkProgressBar também tem uma particularidade importante: ele vai de 0 a 1, não de 0 a 100. Isso significa que para representar 50%, você chama .set(0.5), e para representar 100%, chama .set(1.0). Lembre-se disso na hora de calcular o progresso proporcional ao tamanho da tarefa que você está monitorando.

Implemente funcionalidades reais nos seus widgets

Criar a interface é metade do trabalho. A outra metade é fazer os widgets fazerem algo quando o usuário interage com eles. Para isso, você conecta funções aos widgets e aprende a ler e atualizar os valores da tela em tempo real.

O parâmetro command: conecte funções a botões e switches

Todo widget clicável ou interativo no Tkinter e no CustomTkinter tem um parâmetro command. Você passa para ele a função que deve ser executada quando o usuário interagir com aquele elemento. O ponto de atenção é que você passa a referência da função, sem os parênteses, não a execução dela:

Python
import customtkinter as ctk

class MeuApp(ctk.CTk):
    def __init__(self):
        super().__init__()
        ctk.set_appearance_mode("Dark")
        self.geometry("400x300")

        self.botao = ctk.CTkButton(self,
            text="Mudar título",
            command=self.mudar_titulo)   # Sem parênteses!
        self.botao.pack(pady=20)

        self.switch = ctk.CTkSwitch(self,
            text="Modo Escuro",
            command=self.alternar_modo)
        self.switch.pack(pady=10)
        self.switch.select()  # Liga o switch por padrão

    def mudar_titulo(self):
        self.title("Título Alterado!")

    def alternar_modo(self):
        if self.switch.get() == 1:
            ctk.set_appearance_mode("Dark")
        else:
            ctk.set_appearance_mode("System")

app = MeuApp()
app.mainloop()

O slider tem um comportamento especial: a função passada no command dele recebe automaticamente o valor atual do slider como parâmetro toda vez que ele é movido. Por isso, a função precisa ter um parâmetro extra além do self:

Python
# O parâmetro "novo_valor" recebe automaticamente a posição atual do slider
def atualizar_volume(self, novo_valor):
    self.label_volume.configure(text=f"{int(novo_valor)}%")

Recupere valores dos widgets com .get()

Para ler o valor atual de qualquer widget que armazena informação, você usa o método .get(). Cada tipo de widget retorna um tipo de dado diferente:

  • CTkEntry: retorna a string digitada pelo usuário.
  • CTkCheckBox: retorna 1 se marcado, 0 se desmarcado.
  • CTkSwitch: retorna 1 se ligado, 0 se desligado.
  • CTkSlider: retorna um float com o valor atual na escala configurada.
  • CTkOptionMenu: retorna a string da opção atualmente selecionada.
  • ctk.IntVar() (RadioButton): chame .get() na variável compartilhada, não no botão.
Python
def salvar_perfil(self):
    nome = self.campo_nome.get()

    # Radio button: lê a variável, não o widget diretamente
    nivel_valor = self.nivel_usuario.get()  # Retorna 0, 1 ou 2
    nivel = "Admin" if nivel_valor == 2 else "Básico"

    recebe_notificacoes = self.checkbox_notificacoes.get()  # 1 ou 0

    print(f"Nome: {nome}")
    print(f"Nível: {nivel}")
    print(f"Notificações: {recebe_notificacoes}")

Atualize elementos dinamicamente com .configure()

Um padrão muito poderoso no Tkinter e no CustomTkinter é criar elementos com conteúdo vazio ou padrão na construção da tela e depois atualizá-los em resposta a ações do usuário usando o .configure(). Isso é muito mais eficiente do que destruir e recriar widgets.

O .configure() aceita os mesmos parâmetros que você usa na criação do widget. Você pode mudar o texto, a cor, o estado (habilitado/desabilitado) e a fonte sem precisar recriar nada:

Python
def salvar_perfil(self):
    nome = self.campo_nome.get()
    nivel_valor = self.nivel_usuario.get()
    nivel = "Admin" if nivel_valor == 2 else "Básico"

    # Atualiza o título da barra lateral com o nome do usuário
    self.label_titulo.configure(text=f"{nome} App")

    # Atualiza o subtítulo com o nível de acesso
    self.label_subtitulo.configure(text=nivel)

    # Desabilita o botão após salvar para evitar duplo clique
    self.botao_salvar.configure(state="disabled", text="Salvo!")

Esse é o macete principal para criar interfaces dinâmicas no Tkinter: você cria todos os elementos na construção da tela, alguns com texto vazio ou estado padrão, e depois usa o configure() nas funções para atualizá-los conforme o usuário interage. Nunca crie e destrua widgets dentro de funções de evento, pois isso causa vazamentos de memória e comportamento imprevisível.

Projeto completo: sistema de cadastro com CustomTkinter

Agora vamos juntar tudo que vimos em um sistema completo. O código abaixo cria um sistema de cadastro com barra lateral, três abas (Perfil, Preferências e Dashboard), modo escuro, slider de volume com atualização em tempo real e barra de progresso animada. Esse é o tipo de sistema que você pode adaptar para qualquer projeto real, desde uma automação de dados até uma ferramenta interna da empresa.

Python
import customtkinter as ctk
import time

ctk.set_appearance_mode("Dark")
ctk.set_default_color_theme("blue")


class Aplicativo(ctk.CTk):
    def __init__(self):
        super().__init__()
        self.title("Sistema de Cadastro de Clientes")
        self.geometry("900x600")

        # Grid: coluna 1 expande com a janela
        self.grid_columnconfigure(1, weight=1)
        self.grid_rowconfigure(0, weight=1)

        # Barra lateral (coluna 0 - fixa)
        self.barra_lateral = ctk.CTkFrame(self, width=200)
        self.barra_lateral.grid(row=0, column=0, sticky="nsew")

        # Área principal com abas (coluna 1 - expansível)
        self.janela_abas = ctk.CTkTabview(self, width=400)
        self.janela_abas.grid(row=0, column=1, sticky="nsew", padx=10)

        # Cria as três abas
        self.janela_abas.add("Perfil")
        self.janela_abas.add("Preferências")
        self.janela_abas.add("Dashboard")

        # Preenche cada parte
        self.construir_abalateral()
        self.construir_abaperfil()
        self.construir_abapreferencias()
        self.construir_abasistema()

    # ─── BARRA LATERAL ────────────────────────────────────────────────────
    def construir_abalateral(self):
        self.label_titulo = ctk.CTkLabel(
            self.barra_lateral,
            text="Meu App",
            font=ctk.CTkFont(size=24, weight="bold"))
        self.label_titulo.pack(pady=(30, 5), padx=20)

        # Subtítulo vazio: será preenchido ao salvar o perfil
        self.label_subtitulo = ctk.CTkLabel(self.barra_lateral, text="")
        self.label_subtitulo.pack(pady=(0, 5))

        self.botao_dashboard = ctk.CTkButton(
            self.barra_lateral,
            text="Dashboard Principal",
            command=self.ir_para_dashboard)
        self.botao_dashboard.pack(pady=(30, 30), padx=10)

        self.switch_dark = ctk.CTkSwitch(
            self.barra_lateral,
            text="Modo Escuro",
            command=self.mudar_modo_dark)
        self.switch_dark.pack(pady=10, side="bottom")
        self.switch_dark.select()  # Liga por padrão

    # ─── ABA PERFIL ───────────────────────────────────────────────────────
    def construir_abaperfil(self):
        self.aba_perfil = self.janela_abas.tab("Perfil")

        self.campo_nome = ctk.CTkEntry(
            self.aba_perfil,
            placeholder_text="Digite o seu nome",
            width=300)
        self.campo_nome.pack(pady=(20, 20))

        # Radio buttons de nível de usuário
        self.nivel_usuario = ctk.IntVar(value=0)
        self.radio_label = ctk.CTkLabel(self.aba_perfil, text="Nível de Usuário")
        self.radio_basico = ctk.CTkRadioButton(
            self.aba_perfil, text="Básico",
            variable=self.nivel_usuario, value=1)
        self.radio_admin = ctk.CTkRadioButton(
            self.aba_perfil, text="Admin",
            variable=self.nivel_usuario, value=2)

        self.radio_label.pack()
        self.radio_basico.pack()
        self.radio_admin.pack()

        # Checkbox de notificações
        self.checkbox_notificacoes = ctk.CTkCheckBox(
            self.aba_perfil, text="Receber notificações por e-mail")
        self.checkbox_notificacoes.pack(pady=(20, 20))

        # Botão de salvar
        self.botao_salvar = ctk.CTkButton(
            self.aba_perfil,
            text="Salvar Perfil",
            fg_color="green",
            hover_color="darkgreen",
            command=self.salvar_perfil)
        self.botao_salvar.pack(pady=(20, 20))

    # ─── ABA PREFERÊNCIAS ─────────────────────────────────────────────────
    def construir_abapreferencias(self):
        self.aba_pref = self.janela_abas.tab("Preferências")

        self.label_idioma = ctk.CTkLabel(self.aba_pref, text="Selecione o idioma")
        self.label_idioma.pack(pady=(20, 5))

        self.menu_idiomas = ctk.CTkOptionMenu(
            self.aba_pref, values=["Português", "Inglês", "Espanhol"])
        self.menu_idiomas.pack()

        self.label_volume = ctk.CTkLabel(self.aba_pref, text="Volume do sistema")
        self.label_volume.pack(pady=(30, 5))

        self.slider_volume = ctk.CTkSlider(
            self.aba_pref, from_=0, to=100,
            command=self.atualizar_volume)
        self.slider_volume.pack()
        self.slider_volume.set(50)

        self.label_valor_volume = ctk.CTkLabel(self.aba_pref, text="50%")
        self.label_valor_volume.pack()

    # ─── ABA DASHBOARD ────────────────────────────────────────────────────
    def construir_abasistema(self):
        self.aba_dashboard = self.janela_abas.tab("Dashboard")

        self.label_carga = ctk.CTkLabel(
            self.aba_dashboard,
            text="Testar Carregamento do Sistema",
            font=ctk.CTkFont(size=16))
        self.label_carga.pack(pady=(30, 30))

        self.barra_progresso = ctk.CTkProgressBar(self.aba_dashboard, width=400)
        self.barra_progresso.pack(pady=(10, 10))
        self.barra_progresso.set(0)

        self.botao_carregar = ctk.CTkButton(
            self.aba_dashboard,
            text="Iniciar Carregamento",
            command=self.carregar)
        self.botao_carregar.pack(pady=(20, 20))

    # ─── FUNCIONALIDADES ─────────────────────────────────────────────────
    def ir_para_dashboard(self):
        self.janela_abas.set("Dashboard")

    def mudar_modo_dark(self):
        modo = "Dark" if self.switch_dark.get() == 1 else "System"
        ctk.set_appearance_mode(modo)

    def salvar_perfil(self):
        nome = self.campo_nome.get()
        nivel = "Admin" if self.nivel_usuario.get() == 2 else "Básico"
        notif = self.checkbox_notificacoes.get()

        # Atualiza a barra lateral dinamicamente
        self.label_titulo.configure(text=f"{nome} App")
        self.label_subtitulo.configure(text=nivel)

        print(f"Nome: {nome} | Nível: {nivel} | Notificações: {notif}")

    def atualizar_volume(self, novo_valor):
        self.label_valor_volume.configure(text=f"{int(novo_valor)}%")

    def carregar(self):
        # Simula uma tarefa que demora: atualiza a barra de progresso a cada iteração
        for i in range(100):
            time.sleep(0.05)  # Substitua aqui pela tarefa real
            self.barra_progresso.set((i + 1) / 100)
            self.update()  # Força o redesenho da interface


janela = Aplicativo()
janela.mainloop()

Para projetos que combinam esse tipo de interface com manipulação de dados, confira o artigo da Hashtag sobre ferramenta de cadastro com Tkinter e Pandas, que mostra como conectar uma tela como essa a uma planilha Excel para leitura e gravação de dados.

Projeto completo de sistema de cadastro com CustomTkinter e Tkinter no Python

Tkinter, CustomTkinter ou outra biblioteca? Quando usar cada um

O Tkinter não é a única opção para criar interfaces gráficas em Python, mas cada ferramenta tem seu lugar. Entender as diferenças ajuda a escolher certo desde o início do projeto e evitar retrabalho. O artigo da Hashtag sobre as 6 ferramentas para criar telas e interfaces em Python abrange todas as opções em profundidade, mas aqui vai um resumo direto:

BibliotecaQuando usarPontos de atenção
TkinterAutomações simples, seleção de arquivo, scripts internos sem preocupação estéticaVisual desatualizado, mas já vem instalado com o Python
CustomTkinterSistemas desktop com boa aparência, dark mode, projetos que serão usados por outras pessoasRequer instalação via pip, mas os comandos são idênticos ao Tkinter
TTKBootstrapQuer temas prontos do Bootstrap no Tkinter com mínimo de esforçoSubstitui os widgets padrão pelo equivalente estilizado do TTKBootstrap
PyQt6Interface robusta com visual nativo do sistema operacional, projetos complexosMaior curva de aprendizado, licença comercial paga para projetos proprietários
StreamlitDashboards e apps de dados voltados para a web, compartilhamento via linkRoda no navegador, não é uma aplicação desktop nativa
Flask / DjangoSistemas web completos com banco de dados, autenticação e APIsExige conhecimento de HTML/CSS além do Python

A regra prática é simples: se o objetivo é criar uma tela para rodar no seu próprio computador ou na máquina de um colega, sem se preocupar com distribuição e estética avançada, o Tkinter resolve. Se você quer entregar algo com cara de produto profissional para o usuário final, o CustomTkinter ou o TTKBootstrap são a escolha certa. Para o TTKBootstrap especificamente, a Hashtag tem um artigo completo sobre como criar interfaces gráficas bonitas com TTKBootstrap no Python.

E se o sistema precisar ser acessado por outras pessoas via navegador, aí o Streamlit ou um framework web já faz mais sentido do que qualquer solução desktop. O Python tem o ecossistema para cada um desses casos.

Conclusão

Neste tutorial você viu o Tkinter no Python de ponta a ponta: desde a janela mais simples com um Label e um pack() até um sistema completo com barra lateral, abas, formulários, slider e barra de progresso animada usando o CustomTkinter.

Você aprendeu como as caixas de diálogo prontas do messagebox, simpledialog e filedialog resolvem os casos de uso mais comuns em automações, e como a orientação a objetos com classes torna o código de interfaces gráficas muito mais organizado e fácil de manter conforme o projeto cresce.

Se você quer continuar evoluindo e aprender a criar automações completas com interface visual no Python, aproveite para acessar o minicurso gratuito de automação em Python da Hashtag, onde você vai construir projetos práticos do zero com tudo que aprendeu aqui.

Caso prefira esse conteúdo no formato de vídeo-aula, assista ao vídeo abaixo ou acesse o nosso canal do YouTube!

O que você aprende neste vídeo

Resposta rápida: A aula ensina a criar interfaces desktop em Python com o Tkinter, desde janelas e diálogos prontos até um sistema completo. Você aprende a posicionar widgets com pack() e grid(), organizar a aplicação em classes, usar CustomTkinter, capturar valores, ligar eventos a funções e atualizar uma barra de progresso.

Neste vídeo (126 min):

  • 2:00 - Importação do Tkinter, criação da janela com tk.Tk() e estrutura básica de uma aplicação gráfica
  • 3:00 - Caixas prontas com messagebox para mostrar mensagens de informação, aviso e erro
  • 7:00 - Coleta de texto e números do usuário com simpledialog e tratamento da resposta cancelada
  • 11:00 - Seleção de arquivos, vários arquivos ou pastas com filedialog
  • 19:00 - Criação de uma janela própria, configuração de título e tamanho e uso de mainloop()
  • 31:00 - Transição para CustomTkinter e organização de interfaces maiores com classes
  • 47:00 - Divisão da tela em barra lateral e área principal com o sistema de linhas e colunas do grid()
  • 63:00 - Construção da barra lateral com título, botão de navegação e switch de modo escuro
  • 73:00 - Montagem da aba de perfil com campo de texto, radio buttons, checkbox e botão
  • 86:00 - Criação das abas de preferências e dashboard com menu, slider e barra de progresso
  • 101:00 - Ligação dos controles às funções por meio de command e leitura dos valores preenchidos
  • 119:00 - Atualização visual da barra de progresso durante uma tarefa e ajuste do valor percentual

Trechos do vídeo:

  • Com Tkinter, o fluxo básico é importar a biblioteca, criar a janela e mantê-la ativa com mainloop().
  • Cada widget precisa de um contêiner e de um gerenciador de layout: pack() empilha, enquanto grid() distribui em linhas e colunas.
  • Em controles interativos, command liga o evento a uma função e get(), set() ou configure() leem e alteram o estado.

FAQ - Perguntas Frequentes sobre Tkinter no Python

1. Preciso instalar o Tkinter para usar no Python?

Não. O Tkinter já vem instalado junto com o Python em todas as plataformas (Windows, macOS e Linux). Para confirmar que está disponível no seu ambiente, basta rodar o comando python -m tkinter no terminal. Se uma janela de demonstração abrir, está tudo certo e você pode importar e usar sem nenhuma instalação extra.

2. Qual a diferença entre Tkinter e CustomTkinter?

O Tkinter é a biblioteca padrão do Python para interfaces gráficas, já incluída na instalação. O CustomTkinter é uma extensão que moderniza o visual do Tkinter com dark mode, bordas arredondadas e temas de cores, sem mudar a lógica de programação. Os comandos são praticamente os mesmos: onde você usava tk., passa a usar ctk.. O CustomTkinter precisa ser instalado via pip install customtkinter.

3. O Tkinter funciona no Windows, macOS e Linux?

Sim. O Tkinter é multiplataforma e roda nos três sistemas operacionais. O visual das janelas segue o estilo nativo de cada sistema quando você usa o modo System, e pode ser padronizado com o CustomTkinter para ter a mesma aparência em todos. A única diferença prática entre plataformas é no caminhos de arquivo retornados pelo filedialog, que usam barras diferentes no Windows (\) e no macOS/Linux (/).

Hashtag Treinamentos

Para acessar outras publicações de Python, clique aqui!


Quer aprender mais sobre Python com um minicurso básico gratuito?

Posts mais recentes de Python

Posts mais recentes da Hashtag Treinamentos