Proyecto: Calculadora GUI con Tkinter en Python

👤 Admin 📅 28 de octubre, 2025 ⏱ 17 min 🏷 Proyectos Python

¿Qué es Tkinter?

Tkinter es la biblioteca estándar de Python para crear interfaces gráficas (GUI). Viene incluida con Python, no requiere instalación adicional y es perfecta para aplicaciones de escritorio simples.

Configuración Inicial

import tkinter as tk
from tkinter import ttk

# Crear ventana principal
ventana = tk.Tk()
ventana.title("Mi Primera Ventana")
ventana.geometry("400x300")  # ancho x alto

# Iniciar loop de eventos
ventana.mainloop()

Calculadora Básica - Versión 1

import tkinter as tk

class Calculadora:
    def __init__(self, ventana):
        self.ventana = ventana
        self.ventana.title("Calculadora")
        self.ventana.geometry("300x400")
        self.ventana.resizable(False, False)
        
        # Variable para el display
        self.expresion = ""
        
        # Crear interfaz
        self.crear_display()
        self.crear_botones()
    
    def crear_display(self):
        # Frame para el display
        display_frame = tk.Frame(self.ventana, bg="black")
        display_frame.pack(expand=True, fill="both")
        
        # Label para mostrar números
        self.display = tk.Label(
            display_frame,
            text="0",
            font=("Arial", 24, "bold"),
            bg="black",
            fg="white",
            anchor="e",  # alinear a la derecha
            padx=10
        )
        self.display.pack(expand=True, fill="both")
    
    def crear_botones(self):
        # Frame para botones
        botones_frame = tk.Frame(self.ventana)
        botones_frame.pack(expand=True, fill="both")
        
        # Definir botones
        botones = [
            ['7', '8', '9', '/'],
            ['4', '5', '6', '*'],
            ['1', '2', '3', '-'],
            ['C', '0', '=', '+']
        ]
        
        # Crear botones
        for fila, botones_fila in enumerate(botones):
            for columna, boton in enumerate(botones_fila):
                self.crear_boton(
                    botones_frame,
                    boton,
                    fila,
                    columna
                )
    
    def crear_boton(self, frame, texto, fila, columna):
        boton = tk.Button(
            frame,
            text=texto,
            font=("Arial", 18),
            command=lambda: self.click_boton(texto)
        )
        boton.grid(row=fila, column=columna, sticky="nsew", padx=1, pady=1)
        
        # Hacer que los botones se expandan
        frame.grid_rowconfigure(fila, weight=1)
        frame.grid_columnconfigure(columna, weight=1)
    
    def click_boton(self, texto):
        if texto == 'C':
            self.expresion = ""
            self.display.config(text="0")
        
        elif texto == '=':
            try:
                resultado = eval(self.expresion)
                self.display.config(text=str(resultado))
                self.expresion = str(resultado)
            except:
                self.display.config(text="Error")
                self.expresion = ""
        
        else:
            self.expresion += texto
            self.display.config(text=self.expresion)

# Ejecutar
if __name__ == "__main__":
    ventana = tk.Tk()
    calculadora = Calculadora(ventana)
    ventana.mainloop()

Calculadora Avanzada - Versión 2

import tkinter as tk
from tkinter import ttk
import math

class CalculadoraAvanzada:
    def __init__(self, ventana):
        self.ventana = ventana
        self.ventana.title("Calculadora Científica")
        self.ventana.geometry("400x600")
        self.ventana.configure(bg="#2c3e50")
        
        self.expresion = ""
        self.resultado_anterior = ""
        
        self.crear_interfaz()
    
    def crear_interfaz(self):
        # Display superior (historial)
        self.historial = tk.Label(
            self.ventana,
            text="",
            font=("Arial", 12),
            bg="#34495e",
            fg="#95a5a6",
            anchor="e",
            height=2,
            padx=10
        )
        self.historial.pack(fill="x")
        
        # Display principal
        self.display = tk.Label(
            self.ventana,
            text="0",
            font=("Arial", 32, "bold"),
            bg="#2c3e50",
            fg="white",
            anchor="e",
            height=2,
            padx=10
        )
        self.display.pack(fill="x")
        
        # Frame de botones
        botones_frame = tk.Frame(self.ventana, bg="#2c3e50")
        botones_frame.pack(expand=True, fill="both", padx=5, pady=5)
        
        # Botones científicos
        botones_cientificos = [
            ['sin', 'cos', 'tan', 'π'],
            ['√', 'x²', 'xʸ', 'log'],
            ['(', ')', '%', 'C']
        ]
        
        for fila, botones_fila in enumerate(botones_cientificos):
            for columna, boton in enumerate(botones_fila):
                self.crear_boton_cientifico(
                    botones_frame,
                    boton,
                    fila,
                    columna
                )
        
        # Botones numéricos
        botones_numericos = [
            ['7', '8', '9', '/'],
            ['4', '5', '6', '*'],
            ['1', '2', '3', '-'],
            ['0', '.', '=', '+']
        ]
        
        fila_offset = len(botones_cientificos)
        for fila, botones_fila in enumerate(botones_numericos):
            for columna, boton in enumerate(botones_fila):
                self.crear_boton_numerico(
                    botones_frame,
                    boton,
                    fila + fila_offset,
                    columna
                )
        
        # Configurar grid
        for i in range(len(botones_cientificos) + len(botones_numericos)):
            botones_frame.grid_rowconfigure(i, weight=1)
        for j in range(4):
            botones_frame.grid_columnconfigure(j, weight=1)
    
    def crear_boton_cientifico(self, frame, texto, fila, columna):
        boton = tk.Button(
            frame,
            text=texto,
            font=("Arial", 14),
            bg="#34495e",
            fg="white",
            activebackground="#5d6d7e",
            command=lambda: self.click_boton(texto)
        )
        boton.grid(row=fila, column=columna, sticky="nsew", padx=2, pady=2)
    
    def crear_boton_numerico(self, frame, texto, fila, columna):
        color = "#e67e22" if texto in ['/', '*', '-', '+', '='] else "#3498db"
        
        boton = tk.Button(
            frame,
            text=texto,
            font=("Arial", 18, "bold"),
            bg=color,
            fg="white",
            activebackground="#5d6d7e",
            command=lambda: self.click_boton(texto)
        )
        boton.grid(row=fila, column=columna, sticky="nsew", padx=2, pady=2)
    
    def click_boton(self, texto):
        if texto == 'C':
            self.expresion = ""
            self.resultado_anterior = ""
            self.actualizar_display()
        
        elif texto == '=':
            self.calcular()
        
        elif texto == '√':
            self.expresion += 'math.sqrt('
            self.actualizar_display()
        
        elif texto == 'sin':
            self.expresion += 'math.sin(math.radians('
            self.actualizar_display()
        
        elif texto == 'cos':
            self.expresion += 'math.cos(math.radians('
            self.actualizar_display()
        
        elif texto == 'tan':
            self.expresion += 'math.tan(math.radians('
            self.actualizar_display()
        
        elif texto == 'log':
            self.expresion += 'math.log10('
            self.actualizar_display()
        
        elif texto == 'π':
            self.expresion += str(math.pi)
            self.actualizar_display()
        
        elif texto == 'x²':
            self.expresion += '**2'
            self.actualizar_display()
        
        elif texto == 'xʸ':
            self.expresion += '**'
            self.actualizar_display()
        
        else:
            self.expresion += texto
            self.actualizar_display()
    
    def calcular(self):
        try:
            resultado = eval(self.expresion)
            self.resultado_anterior = f"{self.expresion} ="
            self.expresion = str(resultado)
            self.actualizar_display()
        except Exception as e:
            self.display.config(text="Error")
            self.expresion = ""
    
    def actualizar_display(self):
        texto = self.expresion if self.expresion else "0"
        self.display.config(text=texto)
        self.historial.config(text=self.resultado_anterior)

# Ejecutar
if __name__ == "__main__":
    ventana = tk.Tk()
    calculadora = CalculadoraAvanzada(ventana)
    ventana.mainloop()

Funcionalidades Adicionales

Teclado

def __init__(self, ventana):
    # ... código anterior ...
    
    # Vincular teclas del teclado
    self.ventana.bind('<Key>', self.tecla_presionada)

def tecla_presionada(self, evento):
    tecla = evento.char
    
    if tecla.isdigit() or tecla in ['+', '-', '*', '/', '.', '(', ')']:
        self.expresion += tecla
        self.actualizar_display()
    
    elif tecla == '\r':  # Enter
        self.calcular()
    
    elif tecla == '\x08':  # Backspace
        self.expresion = self.expresion[:-1]
        self.actualizar_display()

Botón de Borrar (Backspace)

def crear_interfaz(self):
    # ... código anterior ...
    
    # Agregar botón backspace
    boton_back = tk.Button(
        botones_frame,
        text="⌫",
        font=("Arial", 18),
        bg="#c0392b",
        fg="white",
        command=self.borrar_ultimo
    )
    boton_back.grid(row=2, column=4, sticky="nsew", padx=2, pady=2)

def borrar_ultimo(self):
    self.expresion = self.expresion[:-1]
    self.actualizar_display()

Historial de Cálculos

class CalculadoraConHistorial:
    def __init__(self, ventana):
        # ... código anterior ...
        self.historial_calculos = []
        self.crear_menu_historial()
    
    def crear_menu_historial(self):
        # Crear menú
        menubar = tk.Menu(self.ventana)
        self.ventana.config(menu=menubar)
        
        # Menú Historial
        historial_menu = tk.Menu(menubar, tearoff=0)
        menubar.add_cascade(label="Historial", menu=historial_menu)
        historial_menu.add_command(label="Ver Historial", command=self.mostrar_historial)
        historial_menu.add_command(label="Limpiar Historial", command=self.limpiar_historial)
    
    def calcular(self):
        try:
            resultado = eval(self.expresion)
            calculo = f"{self.expresion} = {resultado}"
            self.historial_calculos.append(calculo)
            
            self.resultado_anterior = calculo
            self.expresion = str(resultado)
            self.actualizar_display()
        except:
            self.display.config(text="Error")
            self.expresion = ""
    
    def mostrar_historial(self):
        ventana_historial = tk.Toplevel(self.ventana)
        ventana_historial.title("Historial de Cálculos")
        ventana_historial.geometry("400x300")
        
        # Scrollbar
        scrollbar = tk.Scrollbar(ventana_historial)
        scrollbar.pack(side="right", fill="y")
        
        # Listbox
        listbox = tk.Listbox(
            ventana_historial,
            font=("Arial", 12),
            yscrollcommand=scrollbar.set
        )
        listbox.pack(expand=True, fill="both")
        scrollbar.config(command=listbox.yview)
        
        # Agregar cálculos
        for calculo in reversed(self.historial_calculos):
            listbox.insert(tk.END, calculo)
    
    def limpiar_historial(self):
        self.historial_calculos = []
        tk.messagebox.showinfo("Historial", "Historial limpiado")

Temas y Estilos

def aplicar_tema_oscuro(self):
    colores = {
        'bg': '#1e1e1e',
        'fg': '#ffffff',
        'display_bg': '#2d2d2d',
        'boton_bg': '#3c3c3c',
        'boton_operador': '#ff9500'
    }
    
    self.ventana.configure(bg=colores['bg'])
    self.display.configure(bg=colores['display_bg'], fg=colores['fg'])
    # ... aplicar a más componentes

def aplicar_tema_claro(self):
    colores = {
        'bg': '#f0f0f0',
        'fg': '#000000',
        'display_bg': '#ffffff',
        'boton_bg': '#e0e0e0',
        'boton_operador': '#ff9500'
    }
    
    self.ventana.configure(bg=colores['bg'])
    self.display.configure(bg=colores['display_bg'], fg=colores['fg'])
    # ... aplicar a más componentes

Guardar y Cargar Configuración

import json

def guardar_configuracion(self):
    config = {
        'historial': self.historial_calculos,
        'tema': self.tema_actual,
        'posicion': f"{self.ventana.winfo_x()}x{self.ventana.winfo_y()}"
    }
    
    with open('calculadora_config.json', 'w') as f:
        json.dump(config, f)

def cargar_configuracion(self):
    try:
        with open('calculadora_config.json', 'r') as f:
            config = json.load(f)
            self.historial_calculos = config.get('historial', [])
            # Aplicar más configuraciones...
    except FileNotFoundError:
        pass

Proyecto Completo Integrado

# calculadora_completa.py
import tkinter as tk
from tkinter import messagebox
import math
import json

class CalculadoraCompleta:
    def __init__(self, ventana):
        self.ventana = ventana
        self.ventana.title("Calculadora Profesional")
        self.ventana.geometry("400x650")
        self.ventana.configure(bg="#2c3e50")
        
        self.expresion = ""
        self.resultado_anterior = ""
        self.historial_calculos = []
        
        self.cargar_configuracion()
        self.crear_menu()
        self.crear_interfaz()
        self.vincular_teclas()
        
        # Guardar al cerrar
        self.ventana.protocol("WM_DELETE_WINDOW", self.al_cerrar)
    
    def crear_menu(self):
        menubar = tk.Menu(self.ventana)
        self.ventana.config(menu=menubar)
        
        # Menú Ver
        ver_menu = tk.Menu(menubar, tearoff=0)
        menubar.add_cascade(label="Ver", menu=ver_menu)
        ver_menu.add_command(label="Historial", command=self.mostrar_historial)
        ver_menu.add_separator()
        ver_menu.add_command(label="Tema Oscuro", command=self.aplicar_tema_oscuro)
        ver_menu.add_command(label="Tema Claro", command=self.aplicar_tema_claro)
        
        # Menú Ayuda
        ayuda_menu = tk.Menu(menubar, tearoff=0)
        menubar.add_cascade(label="Ayuda", menu=ayuda_menu)
        ayuda_menu.add_command(label="Atajos de Teclado", command=self.mostrar_atajos)
        ayuda_menu.add_command(label="Acerca de", command=self.mostrar_acerca_de)
    
    def crear_interfaz(self):
        # ... código de interfaz completo ...
        pass
    
    def vincular_teclas(self):
        self.ventana.bind('<Key>', self.tecla_presionada)
    
    def al_cerrar(self):
        self.guardar_configuracion()
        self.ventana.destroy()
    
    # ... resto de métodos ...

if __name__ == "__main__":
    ventana = tk.Tk()
    calculadora = CalculadoraCompleta(ventana)
    ventana.mainloop()

Buenas Prácticas

  • Organiza tu código: Usa clases y métodos bien definidos
  • Maneja excepciones: Prevé errores de cálculo
  • Interfaz intuitiva: Diseño claro y fácil de usar
  • Atajos de teclado: Mejora la experiencia del usuario
  • Guarda configuración: Persistencia de preferencias
  • Comenta tu código: Especialmente la lógica compleja

Conclusión

Crear una calculadora con Tkinter es un excelente proyecto para aprender desarrollo de GUI en Python. Este proyecto combina lógica de programación, diseño de interfaces y manejo de eventos. Puedes extenderlo agregando más funciones matemáticas, gráficos o conversiones de unidades.