Panel de pestañas (Notebook) en Tcl/Tk (tkinter)

Panel de pestañas (Notebook) en Tcl/Tk (tkinter)

El panel de pestañas es un tipo de elemento gráfico que permite dividir una parte de la ventana en distintas solapas de modo que, dependiendo de la que se encuentre seleccionada, varía el contenido de la interfaz. Un ejemplo paradigmático son las pestañas de los navegadores modernos. Fue introducido en Tk 8.5 y en Python es provisto por la clase ttk.Noteboook.

Panel de pestañas (Notebook) en Tcl/Tk (tkinter)

Una instancia de ttk.Notebook puede tener tantas pestañas como se desee, y éstas a su vez pueden ser removidas u ocultadas. Cada solapa está identificada por un texto, una imagen, o bien ambas.

Comencemos por un ejemplo simple. El siguiente código crea un panel con dos pestañas, una llamada «Web» y otra llamada «Foro». Cada una de ellas alberga únicamente una etiqueta (ttk.Label) con un texto.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import tkinter as tk
from tkinter import ttk

class Application(ttk.Frame):
    
    def __init__(self, main_window):
        super().__init__(main_window)
        main_window.title("Panel de pestañas en Tcl/Tk")
        
        # Crear el panel de pestañas.
        self.notebook = ttk.Notebook(self)
        
        # Crear el contenido de cada una de las pestañas.
        self.web_label = ttk.Label(self.notebook,
            text="www.recursospython.com")
        self.forum_label = ttk.Label(self.notebook,
            text="foro.recursospython.com")
        
        # Añadirlas al panel con su respectivo texto.
        self.notebook.add(self.web_label, text="Web", padding=20)
        self.notebook.add(self.forum_label, text="Foro", padding=20)
        
        self.notebook.pack(padx=10, pady=10)
        self.pack()

main_window = tk.Tk()
app = Application(main_window)
app.mainloop()

En las líneas 17 y 19 creamos dos elementos determinados que tienen como control padre al panel de pestañas (self.notebook), pues irán dentro de él; y a continuación los añadimos vía self.notebook.add(), especificando un texto identificativo para cada una de las solapas.

Panel de pestañas (Notebook) en Tcl/Tk (tkinter)

Para utilizar una imagen en lugar de un texto como identificador de la pestaña, cargamos un archivo gráfico vía tk.PhotoImage y lo indicamos en el parámetro image al añadir la solapa.

        self.forum_image = tk.PhotoImage(file="forum.png")
        self.notebook.add(
            self.forum_label,
            image=self.forum_image,
            padding=20
        )

Para mantener texto e imagen simultáneamente, debemos indicar la propiedad compound que especifica la posición de la imagen respecto del texto (tk.LEFT, tk.RIGHT, tk.TOP, tk.BOTTOM o tk.CENTER).

        self.notebook.add(
            self.forum_label,
            text="Foro",
            image=self.forum_image,
            compound=tk.LEFT,  # Posición de la imagen.
            padding=20
        )

Para este código el resultado es el siguiente:

Pestaña con imagen en Tcl/Tk (tkinter)

Otras opciones para la función Notebook.add incluyen underline, que indica la posición del carácter al cual se le aplicará el subrayado.

        # Subrayar la primera letra.
        self.notebook.add(self.web_label, text="Web", underline=0, padding=20)

Más que una función estética, esto indica que puede seleccionarse la pestaña en cuestión presionando ALT + W si previamente se habilita este comportamiento vía:

        # Habilitar selección de la pestaña a través del teclado.
        self.notebook.enable_traversal()
        self.notebook.add(self.web_label, text="Web", underline=0, padding=20)

Para remover una pestaña del panel empleamos la función Notebook.forget.

        # Remueve la pestaña "Web".
        self.notebook.forget(self.web_label)

También es posible eliminar una solapa indicando su posición:

        # Remueve la primera pestaña.
        self.notebook.forget(0)
        # Remueve la última pestaña.
        self.notebook.forget(self.notebook.index(tk.END) - 1)

La función Notebook.index retorna la posición (desde el 0) de una pestaña determinada, o bien la cantidad de pestañas si se pasa tk.END como argumento.

       print(self.notebook.index(self.forum_label))  # Imprime 1

Las pestañas también pueden ocultarse vía Notebook.hide().

        # Ocultar la pestaña "Web".
        self.notebook.hide(self.web_label)
        # Mostrarla nuevamente.
        self.notebook.add(self.web_label)

La diferencia con Notebook.forget() es que ocultar una solapa es una operación más rápida que recuerda los atributos de la pestaña (por ejemplo, su imagen, texto, márgenes, etc.), atributos que no es necesario volver a especificar cuando se restaura su visibilidad vía Notebook.add().

Para insertar una pestaña en una posición determinada (Notebook.add() siempre la añade al final) utilícese Notebook.insert().

        # Insertar pestaña al comienzo del panel.
        self.notebook.insert(0, self.forum_label, text="Foro")

Para obtener la pestaña seleccionada:

        selected_tab = self.notebook.select()

Y para conocer su posición:

        # Posición de la pestaña seleccionada.
        self.notebook.index(selected_tab)

Para establecer la pestaña seleccionada utilizamos la misma función pero indicando alguna solapa:

        # Seleccionar la pestaña "Foro".
        self.notebook.select(self.forum_label)

La función Notebook.tab permite obtener o establecer las propiedades de una pestaña.

        # Imprime un diccionario con las propiedades de la pestaña "Web" (texto, imagen, etc.).
        print(self.notebook.tab(self.web_label))
        # Cambia el texto de la pestaña por "Blog".
        self.notebook.tab(self.web_label, text="Blog")

Notebook.tabs() devuelve una lista con todas las pestañas incluidas en el panel.

        # Lista de pestañas en el panel.
        tabs = self.notebook.tabs()

Todas las funciones que reciben como argumento una pestaña (forget(), hide(), index(), tab(), etc.) pueden, en su lugar, recibir tk.CURRENT para indicar la solapa actualmente seleccionada. Por ejemplo:

        # Imprime la posición y el texto de la pestaña seleccionada.
        print(self.notebook.index(tk.CURRENT))
        print(self.notebook.tab(tk.CURRENT)["text"])

La clase ttk.Notebook provee un nuevo evento llamado <<NotebookTabChanged>> que, como habrás inferido, es invocado cuando ocurre un cambio en la selección de las pestañas.

class Application(ttk.Frame):
    
    def __init__(self, main_window):
        # [...]
        self.notebook.bind("<<NotebookTabChanged>>", self.tab_changed)
    
    def tab_changed(self, event):
        # event es una instancia de tk.Event.
        print("Nueva pestaña seleccionada.")

Ahora bien, antes de finalizar, consideremos el siguiente panel de pestañas.

Panel de pestañas (Notebook) en Tcl/Tk (tkinter)

Si bien sigue siendo una interfaz bastante simple, es un tanto más compleja que el ejemplo minimalista que mostramos al comienzo. En el código anterior cada pestaña tenía simplemente un control, mientras que en este pequeño programa tenemos tres widgets por pestaña e incluso los botones cumplen con una funcionalidad.

Para mantener el código ordenado y legible, generalmente es conveniente separar el contenido de cada una de las pestañas creando subclases de ttk.Frame.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import tkinter as tk
from tkinter import ttk


class GreetingFrame(ttk.Frame):
    
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        
        self.name_entry = ttk.Entry(self)
        self.name_entry.pack()
        
        self.greet_button = ttk.Button(
            self, text="Saludar", command=self.say_hello)
        self.greet_button.pack()
        
        self.greet_label = ttk.Label(self)
        self.greet_label.pack()
    
    def say_hello(self):
        self.greet_label["text"] = \
            "¡Hola, {}!".format(self.name_entry.get())


class AboutFrame(ttk.Frame):
    
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        
        self.label = ttk.Label(self)
        self.label["text"] = ("Visitanos en recursospython.com y "
                              "foro.recursospython.com.")
        self.label.pack()
        
        self.web_button = ttk.Button(self, text="Visitar web")
        self.web_button.pack(pady=10)
        
        self.forum_button = ttk.Button(self, text="Visitar foro")
        self.forum_button.pack()


class Application(ttk.Frame):
    
    def __init__(self, main_window):
        super().__init__(main_window)
        main_window.title("Panel de pestañas en Tcl/Tk")
        
        self.notebook = ttk.Notebook(self)
        
        self.greeting_frame = GreetingFrame(self.notebook)
        self.notebook.add(
            self.greeting_frame, text="Saludos", padding=10)
        
        self.about_frame = AboutFrame(self.notebook)
        self.notebook.add(
            self.about_frame, text="Acerca de", padding=10)
        
        self.notebook.pack(padx=10, pady=10)
        self.pack()


main_window = tk.Tk()
app = Application(main_window)
app.mainloop()

Incluso si el contenido de una pestaña es lo suficientemente grande podría caber en un archivo aparte y ser importado para añadirlo a la interfaz.

Ya que probablemente casi cualquier pestaña de una aplicación de escritorio real albergará más de un control y tendrá una cierta funcionalidad, sin duda adscribirse a esta práctica resultará en un código más fácil de leer y de mantener.

Curso online 👨‍💻

¡Ya lanzamos el curso oficial de Recursos Python en Udemy! Un curso moderno para aprender Python desde cero con programación orientada a objetos, SQL y tkinter en 2024.

Consultoría 💡

Ofrecemos servicios profesionales de desarrollo y capacitación en Python a personas y empresas. Consultanos por tu proyecto.

9 comentarios.

  1. Hola Recursos, como puedo cerrar una pestaña desde otro fichero .py?? , Tengo un archivo .py donde me añade las pestañas, y tengo otro archivo .py, con un button y quiero que desde hay cerrar las pestañas, es posible??

    • Recursos Python says:

      Hola, para eso podrías usar los métodos forget() o hide(), dependiendo de si luego debes volver a mostrar las pestañas removidas.

      Saludos

  2. Muchas gracias!
    Ahora mismo estoy copiando una interfaz de un programa como forma de aprender Tkinter
    y me sirven mucho estos ejemplos,

  3. Hola Buenas, primero que todo queria agradecer a los admins de esta web porque la verdad el contenido que tiene me esta ayudando muchisimo en el aprendizaje de Python, creo que es un contenido super conciso y muy bien estructurado.

    Tambien me gustaria saber si me pueden ayudar con un pequeño problema que tengo en mi codigo, lo que estoy tratando de hacer es con un bucle for crear x cantidad de pestañas en funcion de la cantidad de elementos dentro de una lista, ahora bien el programa funciona bien pero crea los elementos con el mismo nombre y yo quiero que cada uno tenga su propio nombre para poder en el futuro agregar widgets en una u otra pestaña en especifico. Este seria mi codigo gracias de antemano por la ayuda :).

    from tkinter import ttk
    note = ttk.Notebook()

    lista=["pestaña1","pestaña2"]

    def mostrar_nombre():
    val= button2.winfo_parent()
    print(val)

    for i in (lista):
    nombre_tab=i

    tab = ttk.Frame(note, name=nombre_tab) #-----name define el nombre del widget.
    note.add(tab, text=nombre_tab) # ---text define el nombre de la pestaña
    note.pack()

    global button2
    button2 = Button(tab, text="Crear nota", command=mostrar_nombre)
    button2.grid(row=0, column=0, sticky=W)

    note.mainloop()

    • Recursos Python says:

      Hola, me alegra que te haya servido el contenido.

      Respecto de tu problema, deberías crear una lista tabs = [] e ir creando las pestañas vía tabs.append(ttk.Frame(...)). Luego podrás acceder a ellas individualmente vía tabs[0], tabs[1], etc.

      Saludos

Deja una respuesta