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.



Deja un comentario