Vista de árbol (Treeview) en Tcl/Tk (tkinter)

Vista de árbol (Treeview) en Tcl/Tk (tkinter)

Actualizado el 23/03/2022.

La vista de árbol (o lista jerárquica) es un elemento gráfico que presenta información de modo jerárquico y, opcionalmente, en forma de tabla o grilla (filas y columnas). Fue introducido en Tk 8.5 y en Python es provisto por el módulo ttk.

Explorador de archivos y carpetas en Tcl/Tk (tkinter)

Para crear una vista de árbol, creamos una ventana básica de tkinter y una instancia de la clase ttk.Treeview.

import tkinter as tk
from tkinter import ttk

# Creación de la ventana principal.
main_window = tk.Tk()
# Establecer un título.
main_window.title("Vista de árbol en Tkinter")
# Creación de la vista de árbol.
treeview = ttk.Treeview()
treeview.pack()
main_window.mainloop()

import tkinter as tk
from tkinter import ttk

class Application(ttk.Frame):
    
    def __init__(self, main_window):
        super().__init__(main_window)
        # Establecer un título.
        main_window.title("Vista de árbol en Tkinter")
        # Creación de la vista de árbol.
        self.treeview = ttk.Treeview(self)
        self.treeview.pack()
        self.pack()

# Creación de la ventana principal.
main_window = tk.Tk()
app = Application(main_window)
app.mainloop()

Añadir y obtener elementos

Una vez creado el objeto treeview (o self.treeview en la versión orientada a objetos), añadimos información en forma de ítems o filas vía el método Treeview.insert().

import tkinter as tk
from tkinter import ttk

main_window = tk.Tk()
main_window.title("Vista de árbol en Tkinter")
treeview = ttk.Treeview()
treeview.insert("", tk.END, text="Elemento 1")
treeview.pack()
main_window.mainloop()

import tkinter as tk
from tkinter import ttk

class Application(ttk.Frame):
    
    def __init__(self, main_window):
        super().__init__(main_window)
        main_window.title("Vista de árbol en Tkinter")
        self.treeview = ttk.Treeview(self)
        self.treeview.insert("", tk.END, text="Elemento 1")
        self.treeview.pack()
        self.pack()

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

Vista de árbol (Treeview) simple en Tcl/Tk (tkinter)

Ya que la vista en árbol presenta información de forma jerárquica, el primer argumento que le pasamos a insert() es un ítem o una cadena vacía ("") para indicar que el nuevo ítem no tiene predecesor.

El segundo argumento indica la posición en la que queremos insertar el ítem o tk.END para ubciarlo al final del árbol. Así, insert("", 0, text="Elemento 1") lo ubicaría al comienzo.

La función retorna el ID del ítem creado, generalmente abreviado como iid. De esta forma, podemos agregar un nuevo elemento dentro del Elemento 1:

import tkinter as tk
from tkinter import ttk

main_window = tk.Tk()
main_window.title("Vista de árbol en Tkinter")
treeview = ttk.Treeview()
item = treeview.insert("", tk.END, text="Elemento 1")
treeview.insert(item, tk.END, text="Subelemento 1")
treeview.pack()
main_window.mainloop()

import tkinter as tk
from tkinter import ttk

class Application(ttk.Frame):
    
    def __init__(self, main_window):
        super().__init__(main_window)
        main_window.title("Vista de árbol en Tkinter")
        self.treeview = ttk.Treeview(self)
        item = self.treeview.insert("", tk.END, text="Elemento 1")
        self.treeview.insert(item, tk.END, text="Subelemento 1")
        self.treeview.pack()
        self.pack()

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

Vista de árbol (Treeview) con dos elementos en Tcl/Tk (tkinter)

Incluso nuestro Subelemento 1 puede tener otros ítems dentro de él; y así sucesivamente.

import tkinter as tk
from tkinter import ttk

main_window = tk.Tk()
main_window.title("Vista de árbol en Tkinter")
treeview = ttk.Treeview()
item = treeview.insert("", tk.END, text="Elemento 1")
subitem = treeview.insert(item, tk.END, text="Subelemento 1")
treeview.insert(subitem, tk.END, text="Otro elemento")
treeview.pack()
main_window.mainloop()

import tkinter as tk
from tkinter import ttk

class Application(ttk.Frame):
    
    def __init__(self, main_window):
        super().__init__(main_window)
        main_window.title("Vista de árbol en Tkinter")
        self.treeview = ttk.Treeview(self)
        item = self.treeview.insert("", tk.END, text="Elemento 1")
        subitem = self.treeview.insert(item, tk.END, text="Subelemento 1")
        self.treeview.insert(subitem, tk.END, text="Otro elemento")
        self.treeview.pack()
        self.pack()

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

Tk crea automáticamente un ID para cada ítem, en forma de cadena, con el formato I001, I002, etc. Rara vez querrás crear por tu cuenta un ID para un nuevo ítem; pero de ser necesario, puedes hacerlo indicando el parámetro iid.

import tkinter as tk
from tkinter import ttk

main_window = tk.Tk()
main_window.title("Vista de árbol en Tkinter")
treeview = ttk.Treeview()
my_iid = "id_unico"  # Cualquier cadena es aceptable.
treeview.insert("", tk.END, text="Elemento 1", iid=my_iid)
treeview.pack()
main_window.mainloop()

import tkinter as tk
from tkinter import ttk

class Application(ttk.Frame):
    
    def __init__(self, main_window):
        super().__init__(main_window)
        main_window.title("Vista de árbol en Tkinter")
        self.treeview = ttk.Treeview(self)
        self.my_iid = "id_unico"  # Cualquier cadena es aceptable.
        self.treeview.insert("", tk.END, text="Elemento 1", iid=self.my_iid)
        self.treeview.pack()
        self.pack()

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

Esto puede ser en ocasiones útil para obtener o modificar información más adelante sobre dicho ítem. Por ejemplo, el siguiente código muestra el texto del ítem en un cuadro de diálogo (messagebox) al ser presionado un botón:

import tkinter as tk
from tkinter import messagebox, ttk

def show_info():
    # Obtener el texto del Elemento 1.
    text = treeview.item(my_iid, option="text")
    # Mostrarlo en un cuadro de diálogo.
    messagebox.showinfo(title="Información", message=text)

main_window = tk.Tk()
main_window.title("Vista de árbol en Tkinter")
treeview = ttk.Treeview()
my_iid = "id_unico"
treeview.insert("", tk.END, text="Elemento 1", iid=my_iid)
treeview.pack()
button = ttk.Button(text="Mostrar información", command=show_info)
button.pack()
main_window.mainloop()

import tkinter as tk
from tkinter import messagebox, ttk

class Application(ttk.Frame):
    
    def __init__(self, main_window):
        super().__init__(main_window)
        main_window.title("Vista de árbol en Tkinter")
        self.treeview = ttk.Treeview(self)
        self.my_iid = "id_unico"
        self.treeview.insert("", tk.END, text="Elemento 1", iid=self.my_iid)
        self.treeview.pack()
        self.button = ttk.Button(text="Mostrar información",
                                 command=self.show_info)
        self.button.pack(after=self.treeview)
        self.pack()
    
    def show_info(self):
        # Obtener el texto del Elemento 1.
        text = self.treeview.item(self.my_iid, option="text")
        # Mostrarlo en un cuadro de diálogo.
        messagebox.showinfo(title="Información", message=text)

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

La función Treeview.item() permite acceder a los datos de un ítem y eventualmente modificarlos. El código anterior muestra en el cuadro de diálogo la propiedad text del ítem con ID my_iid. Si no se especifica ninguna opción, la función retorna un diccionario con toda la información disponible.

def show_info():
    # Obtener toda la información del Elemento 1.
    info = treeview.item(my_iid)
    # Mostrarla en un cuadro de diálogo.
    messagebox.showinfo(title="Información", message=info)

    def show_info(self):
        # Obtener toda la información del Elemento 1.
        info = self.treeview.item(self.my_iid)
        # Mostrarla en un cuadro de diálogo.
        messagebox.showinfo(title="Información", message=info)

Aquí la variable info contiene un diccionario como el siguiente:

{'image': '', 'open': 0, 'tags': '', 'text': 'Elemento 1', 'values': ''}

Para alterar alguno de estos valores, simplemente se lo pasamos como argumento:

# Modificar el texto del ítem con ID my_iid.
treeview.item(my_iid, text="Nuevo elemento")

# Modificar el texto del ítem con ID my_iid.
self.treeview.item(self.my_iid, text="Nuevo elemento")

Pueden obtenerse todos los elementos dentro del árbol o contenidos en otro elemento con la función get_children(), que retorna una tupla de IDs.

import tkinter as tk
from tkinter import ttk

def show_info():
    # Imprime los elementos del árbol.
    treeview_children = treeview.get_children()
    print(treeview_children)
    # Imprime los elementos dentro del Elemento 1.
    item_children = treeview.get_children(item)
    print(item_children)

main_window = tk.Tk()
main_window.title("Vista de árbol en Tkinter")
treeview = ttk.Treeview()
item = treeview.insert("", tk.END, text="Elemento 1")
treeview.insert(item, tk.END, text="Subelemento 1")
treeview.pack()
button = ttk.Button(text="Mostrar información", command=show_info)
button.pack()
main_window.mainloop()

import tkinter as tk
from tkinter import ttk

class Application(ttk.Frame):
    
    def __init__(self, main_window):
        super().__init__(main_window)
        main_window.title("Vista de árbol en Tkinter")
        self.treeview = ttk.Treeview(self)
        self.item = self.treeview.insert("", tk.END, text="Elemento 1")
        self.treeview.insert(self.item, tk.END, text="Subelemento 1")
        self.treeview.pack()
        self.button = ttk.Button(text="Mostrar información",
                                 command=self.show_info)
        self.button.pack(after=self.treeview)
        self.pack()
    
    def show_info(self):
        # Imprime los elementos del árbol.
        treeview_children = self.treeview.get_children()
        print(treeview_children)
        # Imprime los elementos dentro del Elemento 1.
        item_children = self.treeview.get_children(self.item)
        print(item_children)

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

Aquí treeview_children es una tupla que contiene los ID (cadenas) de todos los elementos principales (es decir, no incluye subelementos) dentro del árbol. La tupla item_children contiene todos los subelementos dentro del Elemento 1 (cuyo ID se encuentra en item).

Mover y eliminar elementos

Es posible mover un elemento de un lugar a otro utilizando move(). Por ejemplo, considerando los siguientes dos ítems que se encuentran en la raíz del árbol:

item1 = treeview.insert("", tk.END, text="Elemento 1")
item2 = treeview.insert("", tk.END, text="Elemento 2")

self.item1 = self.treeview.insert("", tk.END, text="Elemento 1")
self.item2 = self.treeview.insert("", tk.END, text="Elemento 2")

Podemos mover el Elemento 1 dentro del Elemento 2 vía:

treeview.move(item1, item2, tk.END)

self.treeview.move(self.item1, self.item2, tk.END)

La función obtiene el ítem que queremos mover como primer argumento. En segundo lugar, o bien otro elemento dentro del cual queremos colocar el ítem original o bien "" para ubicarlo en la raíz de la lista. El tercer argumento indica la posición.

Para remover uno o varios elementos, Tk provee dos funciones: delete() y detach(). La diferencia es que la primera elimina completamente los ítems que se le pasan como argumento. La segunda, en cambio, únicamente los desvincula del árbol, pudiendo ser re-vinculados utilizando move().

# Elimina el elemento 2.
treeview.delete(item2)
# Desvincula el elemento 1.
treeview.detach(item1)

# Elimina el elemento 2.
self.treeview.delete(self.item2)
# Desvincula el elemento 1.
self.treeview.detach(self.item1)

Luego del código anterior, la función Treeview.exists, que posibilita saber si un elemento existe dentro del árbol, retorna False para exists(item2) pero True para exists(item1).

print(treeview.exists(item2))  # False.
print(treeview.exists(item1))  # True.

print(self.treeview.exists(self.item2))  # False.
print(self.treeview.exists(self.item1))  # True.

Para eliminar todos los elementos de la vista de árbol utilícese:

treeview.delete(*treeview.get_children())

self.treeview.delete(*self.treeview.get_children())

Otras funciones

Treeview.focus() retorna el ID del ítem que tiene el foco (que puede diferir del elemento seleccionado) o una cadena vacía en caso de no haber ninguno. Para poner el foco sobre un elemento determinado, se pasa su ID como argumento.

treeview.focus(item)  # Pone el foco en item.
print(treeview.focus())  # Retorna el ID de item.

self.treeview.focus(self.item)  # Pone el foco en item.
print(self.treeview.focus())  # Retorna el ID de item.

El método Treeview.index() devuelve la posición del elemento especificado. Nótese que dicha posición es relativa al elemento padre. Por esta razón, dos ítems pueden tener la misma posición si están ubicados en distintas ramas del árbol.

item1 = treeview.insert("", tk.END, text="Elemento 1")
item2 = treeview.insert("", tk.END, text="Elemento 2")
print(treeview.index(item1))  # 0
print(treeview.index(item2))  # 1

self.item1 = self.treeview.insert("", tk.END, text="Elemento 1")
self.item2 = self.treeview.insert("", tk.END, text="Elemento 2")
print(self.treeview.index(self.item1))  # 0
print(self.treeview.index(self.item2))  # 1

Creando columnas

Un treeview puede tener múltiples columnas, razón por la cual es habitualmente empleado para mostrar información en formato de tabla. Por ejemplo, supongamos que nuestra vista de árbol muestra una lista jerárquica de archivos y carpetas. El texto de cada ítem correspondería al nombre de un fichero, y podríamos añadir una columna que indique el tamaño y otra, la última modificación.

Primero es necesario indicarle a la clase Treeview las columnas que requerimos.

import tkinter as tk
from tkinter import ttk

main_window = tk.Tk()
main_window.title("Vista de árbol en Tkinter")
treeview = ttk.Treeview(columns=("size", "lastmod"))
treeview.pack()
main_window.mainloop()

import tkinter as tk
from tkinter import ttk

class Application(ttk.Frame):
    
    def __init__(self, main_window):
        super().__init__(main_window)
        main_window.title("Vista de árbol en Tkinter")
        self.treeview = ttk.Treeview(self, columns=("size", "lastmod"))
        self.treeview.pack()
        self.pack()

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

Luego añadimos un fichero a la lista con sus respectivos nombre e información.

import tkinter as tk
from tkinter import ttk

main_window = tk.Tk()
main_window.title("Vista de árbol en Tkinter")
treeview = ttk.Treeview(columns=("size", "lastmod"))
treeview.insert(
    "",
    tk.END,
    text="README.txt",
    values=("850 bytes", "18:30")
)
treeview.pack()
main_window.mainloop()

import tkinter as tk
from tkinter import ttk

class Application(ttk.Frame):
    
    def __init__(self, main_window):
        super().__init__(main_window)
        main_window.title("Vista de árbol en Tkinter")
        self.treeview = ttk.Treeview(self, columns=("size", "lastmod"))
        self.treeview.insert(
            "",
            tk.END,
            text="README.txt",
            values=("850 bytes", "18:30")
        )
        self.treeview.pack()
        self.pack()

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

Así, indicamos que el texto del ítem es «README.txt», y los valores para las columnas size y lastmod son «850 bytes» y «18:30» vía el parámetro values.

También es posible configurar el título de cada columna.

import tkinter as tk
from tkinter import ttk

main_window = tk.Tk()
main_window.title("Vista de árbol en Tkinter")
treeview = ttk.Treeview(columns=("size", "lastmod"))
treeview.heading("#0", text="Archivo")
treeview.heading("size", text="Tamaño")
treeview.heading("lastmod", text="Última modificación")
treeview.insert(
    "",
    tk.END,
    text="README.txt",
    values=("850 bytes", "18:30")
)
treeview.pack()
main_window.mainloop()

import tkinter as tk
from tkinter import ttk

class Application(ttk.Frame):
    
    def __init__(self, main_window):
        super().__init__(main_window)
        main_window.title("Vista de árbol en Tkinter")
        self.treeview = ttk.Treeview(self, columns=("size", "lastmod"))
        self.treeview.heading("#0", text="Archivo")
        self.treeview.heading("size", text="Tamaño")
        self.treeview.heading("lastmod", text="Última modificación")
        self.treeview.insert(
            "",
            tk.END,
            text="README.txt",
            values=("850 bytes", "18:30")
        )
        self.treeview.pack()
        self.pack()

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

Vista de árbol (Treeview) con columnas en Tcl/Tk (tkinter)

Tk utiliza internamente el nombre #0 para referirse a la primera columna que se crea por defecto y en donde se muestra el texto del ítem, mientras que size y lastmod son nombres arbitrarios que escogimos durante la creación de treeview para hacer referencia a las dos nuevas columnas.

Para obtener el valor de las columnas de un ítem determinado, invocamos la función set (aunque el nombre indique lo contrario).

item = treeview.insert("", tk.END, text="README.txt",
                       values=("850 bytes", "18:30"))
# Imprime {'lastmod': '18:30', 'size': '850 bytes'}.
print(treeview.set(item))

self.item = self.treeview.insert("", tk.END, text="README.txt",
                                 values=("850 bytes", "18:30"))
# Imprime {'lastmod': '18:30', 'size': '850 bytes'}.
print(self.treeview.set(self.item))

Para conocer el valor de una columna específica, añadimos un segundo argumento con el identificador.

# Imprime 18:30.
print(treeview.set(item, "lastmod"))

# Imprime 18:30.
print(self.treeview.set(self.item, "lastmod"))

Por último, para establecer un nuevo valor añadimos un tercer argumento.

# Cambia 18:30 por 19:30.
treeview.set(item, "lastmod", "19:30")

# Cambia 18:30 por 19:30.
self.treeview.set(self.item, "lastmod", "19:30")

Incluyendo íconos

Tk permite incluir una imagen como ícono para un ítem vía el parámetro image.

import tkinter as tk
from tkinter import ttk

main_window = tk.Tk()
main_window.title("Vista de árbol en Tkinter")
treeview = ttk.Treeview(columns=("size", "lastmod"))
treeview.heading("#0", text="Archivo")
treeview.heading("size", text="Tamaño")
treeview.heading("lastmod", text="Última modificación")
icon = tk.PhotoImage(file="file.png")
treeview.insert(
    "",
    tk.END,
    text="README.txt",
    values=("850 bytes", "18:30"),
    image=icon
)
treeview.pack()
main_window.mainloop()

import tkinter as tk
from tkinter import ttk

class Application(ttk.Frame):
    
    def __init__(self, main_window):
        super().__init__(main_window)
        main_window.title("Vista de árbol en Tkinter")
        self.treeview = ttk.Treeview(self, columns=("size", "lastmod"))
        self.treeview.heading("#0", text="Archivo")
        self.treeview.heading("size", text="Tamaño")
        self.treeview.heading("lastmod", text="Última modificación")
        self.icon = tk.PhotoImage(file="file.png")
        self.treeview.insert(
            "",
            tk.END,
            text="README.txt",
            values=("850 bytes", "18:30"),
            image=self.icon
        )
        self.treeview.pack()
        self.pack()

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

El archivo file.png debe encontrarse en la misma carpeta que nuestro código (puedes descargarlo desde el archivo ZIP al final del artículo). En caso de residir en otro lugar del sistema de archivos, indíquese la ruta completa.

Nótese que por una limitación de Tcl/Tk, debe mantenerse una referencia a la imagen (icon o self.icon). El siguiente código se ejecuta correctamente, pero la imagen no se muestra:

treeview.insert(
    "",
    tk.END,
    text="README.txt",
    values=("850 bytes", "18:30"),
    # Error: no se mantiene una referencia a la imagen.
    image=tk.PhotoImage(file="file.png")
)

self.treeview.insert(
    "",
    tk.END,
    text="README.txt",
    values=("850 bytes", "18:30"),
    # Error: no se mantiene una referencia a la imagen.
    image=tk.PhotoImage(file="file.png")
)

Además, tk.PhotoImage puede recibir directamente el contenido de una imagen en lugar del nombre de un archivo. Esto permite cargar una imagen desde la memoria que no se encuentre en un archivo.

icon = tk.PhotoImage(data=image_content)

self.icon = tk.PhotoImage(data=image_content)

En este caso, image_content debe ser una cadena o una instancia de bytes.

Obtener y establecer elementos seleccionados

La función Treeview.selection() retorna una tupla con los ID de los elementos seleccionados o una tupla vacía en caso de no haber ninguno. Por ejemplo, el siguiente programa contiene un botón que al ser presionado muestra un cuadro de diálogo con el elemento seleccionado:

import tkinter as tk
from tkinter import messagebox, ttk

def show_selection():
    try:
        # Obtener el ID del primer elemento seleccionado.
        item = treeview.selection()[0]
    except IndexError:
        # Si la tupla está vacía, entonces no hay ningún
        # elemento seleccionado.
        messagebox.showwarning(
            message="Debe seleccionar un elemento.",
            title="No hay selección"
        )
    else:
        # A partir de este ID retornar el texto del elemento.
        text = treeview.item(item, option="text")
        # Mostrarlo en un cuadro de diálogo.
        messagebox.showinfo(message=text, title="Selección")

main_window = tk.Tk()
main_window.title("Vista de árbol en Tkinter")
treeview = ttk.Treeview()
treeview.insert("", tk.END, text="Elemento 1")
treeview.insert("", tk.END, text="Elemento 2")
treeview.pack()
button = ttk.Button(text="Mostrar selección",
                    command=show_selection)
button.pack()
main_window.mainloop()

import tkinter as tk
from tkinter import messagebox, ttk

class Application(ttk.Frame):
    
    def __init__(self, main_window):
        super().__init__(main_window)
        main_window.title("Vista de árbol en Tkinter")
        self.treeview = ttk.Treeview(self)
        self.treeview.insert("", tk.END, text="Elemento 1")
        self.treeview.insert("", tk.END, text="Elemento 2")
        self.treeview.pack()
        self.button = ttk.Button(text="Mostrar selección",
                                 command=self.show_selection)
        self.button.pack(after=self.treeview)
        self.pack()
    
    def show_selection(self):
        try:
            # Obtener el ID del primer elemento seleccionado.
            item = self.treeview.selection()[0]
        except IndexError:
            # Si la tupla está vacía, entonces no hay ningún
            # elemento seleccionado.
            messagebox.showwarning(
                message="Debe seleccionar un elemento.",
                title="No hay selección"
            )
        else:
            # A partir de este ID retornar el texto del elemento.
            text = self.treeview.item(item, option="text")
            # Mostrarlo en un cuadro de diálogo.
            messagebox.showinfo(message=text, title="Selección")

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

Por defecto una vista de árbol permite seleccionar múltiples elementos. Para permitir únicamente una selección debe pasarse el valor tk.BROWSE al parámetro selectmode.

treeview = ttk.Treeview(selectmode=tk.BROWSE)

self.treeview = ttk.Treeview(self, selectmode=tk.BROWSE)

Otras funciones para manejar los elementos seleccionados incluyen:

  • selection_add(): añade elementos a la selección.
  • selection_remove(): remueve elementos de la selección.
  • selection_set(): similar a selection_add(), pero remueve los elementos previamente seleccionados.
  • selection_toggle(): cambia la selección de un elemento.

Por ejemplo, el siguiente código selecciona los Elementos 1 y 2 luego de añadirlos a la tabla:

treeview = ttk.Treeview()
item1 = treeview.insert("", tk.END, text="Elemento 1")
item2 = treeview.insert("", tk.END, text="Elemento 2")
# O simplemente:
# treeview.selection_add((item1, item2))
treeview.selection_add(item1)
treeview.selection_add(item2)

self.treeview = ttk.Treeview(self)
self.item1 = self.treeview.insert("", tk.END, text="Elemento 1")
self.item2 = self.treeview.insert("", tk.END, text="Elemento 2")
# O simplemente:
# self.treeview.selection_add((self.item1, self.item2))
self.treeview.selection_add(self.item1)
self.treeview.selection_add(self.item2)

Eventos

Es posible conocer cuándo un elemento es seleccionado y, si contiene otros elementos dentro de él, también conocer si se abre o se cierra.

La vista de árbol provee tres eventos virtuales para logarlo: <<TreeviewSelect>>, <<TreeviewOpen>> y <<TreeviewClose>>.

import tkinter as tk
from tkinter import ttk

def item_selected(event):
    """Item seleccionado."""
    print("Seleccionado.")

def item_opened(event):
    """Item abierto."""
    print("Abierto.")

def item_closed(event):
    """Item cerrado."""
    print("Cerrado.")

main_window = tk.Tk()
main_window.title("Vista de árbol en Tkinter")
treeview = ttk.Treeview()
# Crear una nueva etiqueta.
treeview.tag_bind("mytag", "<<TreeviewSelect>>", item_selected)
treeview.tag_bind("mytag", "<<TreeviewOpen>>", item_opened)
treeview.tag_bind("mytag", "<<TreeviewClose>>", item_closed)
# Añadir dos elementos indicando la etiqueta anterior para
# que respondan a los eventos.
item = treeview.insert("", tk.END, text="Elemento 1", tags=("mytag",))
treeview.insert(item, tk.END, text="Subelemento 1", tags=("mytag",))
treeview.pack()
main_window.mainloop()

import tkinter as tk
from tkinter import ttk

class Application(ttk.Frame):
    
    def __init__(self, main_window):
        super().__init__(main_window)
        main_window.title("Vista de árbol en Tkinter")
        self.treeview = ttk.Treeview(self)
        # Crear una nueva etiqueta.
        self.treeview.tag_bind("mytag", "<<TreeviewSelect>>",
                               self.item_selected)
        self.treeview.tag_bind("mytag", "<<TreeviewOpen>>",
                               self.item_opened)
        self.treeview.tag_bind("mytag", "<<TreeviewClose>>",
                               self.item_closed)
        # Añadir dos elementos indicando la etiqueta anterior para
        # que respondan a los eventos.
        item = self.treeview.insert("", tk.END, text="Elemento 1",
                                    tags=("mytag",))
        self.treeview.insert(item, tk.END, text="Subelemento 1",
                             tags=("mytag",))
        self.treeview.pack()
        self.pack()
    
    def item_selected(self, event):
        """Item seleccionado."""
        print("Seleccionado.")
    
    def item_opened(self, event):
        """Item abierto."""
        print("Abierto.")
    
    def item_closed(self, event):
        """Item cerrado."""
        print("Cerrado.")

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

En Tk los eventos se asocian a una etiqueta determinada (mytag en el código anterior), y luego cada ítem puede asociarse a una o más etiquetas para responder a aquellos eventos.

Ejemplos

Simple explorador de archivos y carpetas utilizando ttk.Treeview.

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

import tkinter as tk
from tkinter import ttk
from os import listdir, sep
from os.path import isdir, join, abspath


class Application(ttk.Frame):
    
    def __init__(self, main_window):
        super().__init__(main_window)
        main_window.title("Explorador de archivos y carpetas")
        
        self.treeview = ttk.Treeview(self)
        self.treeview.grid(row=0, column=0, sticky="nsew")
        
        # Asociar el evento de expansión de un elemento con la
        # función correspondiente.
        self.treeview.tag_bind(
            "fstag", "<<TreeviewOpen>>", self.item_opened)
        
        # Expandir automáticamente.
        for w in (self, main_window):
            w.rowconfigure(0, weight=1)
            w.columnconfigure(0, weight=1)
        
        self.grid(row=0, column=0, sticky="nsew")
        
        # Este diccionario conecta los IDs de los ítems de Tk con
        # su correspondiente archivo o carpeta.
        self.fsobjects = {}
        
        self.file_image = tk.PhotoImage(file="file.png")
        self.folder_image = tk.PhotoImage(file="folder.png")
        
        # Cargar el directorio raíz.
        self.load_tree(abspath(sep))
    
    def listdir(self, path):
        try:
            return listdir(path)
        except PermissionError:
            print("No tienes suficientes permisos para acceder a",
                  path)
            return []
    
    def get_icon(self, path):
        """
        Retorna la imagen correspondiente según se especifique
        un archivo o un directorio.
        """
        return self.folder_image if isdir(path) else self.file_image
    
    def insert_item(self, name, path, parent=""):
        """
        Añade un archivo o carpeta a la lista y retorna el identificador
        del ítem.
        """
        iid = self.treeview.insert(
            parent, tk.END, text=name, tags=("fstag",),
            image=self.get_icon(path))
        self.fsobjects[iid] = path
        return iid
    
    def load_tree(self, path, parent=""):
        """
        Carga el contenido del directorio especificado y lo añade
        a la lista como ítemes hijos del ítem "parent".
        """
        for fsobj in self.listdir(path):
            fullpath = join(path, fsobj)
            child = self.insert_item(fsobj, fullpath, parent)
            if isdir(fullpath):
                for sub_fsobj in self.listdir(fullpath):
                    self.insert_item(sub_fsobj, join(fullpath, sub_fsobj),
                                     child)
        
    def load_subitems(self, iid):
        """
        Cargar el contenido de todas las carpetas hijas del directorio
        que se corresponde con el ítem especificado.
        """
        for child_iid in self.treeview.get_children(iid):
            if isdir(self.fsobjects[child_iid]):
                self.load_tree(self.fsobjects[child_iid],
                               parent=child_iid)
    
    def item_opened(self, event):
        """
        Evento invocado cuando el contenido de una carpeta es abierto.
        """
        iid = self.treeview.selection()[0]
        self.load_subitems(iid)


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

Descarga: explorer.zip.

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.

29 comentarios.

  1. Hola Recursos Python, ¿ es posible definir el ancho del widget Treeview ?. Defino el alto con «height» pero veo que la opcion «width» No existe.

    Gracias.

  2. Excelente trabajo al explicar como funciona el Widget treeview. Gracias.
    Tengo una consulta, ¿ Como puedo crear una tabla donde el usuario digite datos ?. Es decir que cada casilla sea un objeto que funcione como un Entry(). ¿ es esto posible ?.
    De lo contario tendria que realizar mediante ciclos for muchos entry en la ventana para que den comportamiento de que hay una tabla ?. Si esto es asi cuando la tabla tiene muchas casilla, ¿ es posible asignarle un scrollBar al Frame que agrupa los widget tipo Entry ?

    Gracias.

  3. Gracias por el contenido , cuando ejecuto el ejemplo del explorador de archivos se ejecuto pero ala vez me manda este mensaje:

    No tienes suficientes permisos para acceder a c:\Archivos de programa
    No tienes suficientes permisos para acceder a c:\Documents and Settings
    No tienes suficientes permisos para acceder a c:\PerfLogs

    ¿hay alguna forma de poder preguntar al usuario que le seda estos permisos?

    • Recursos Python says:

      Hola. Al convertir el archivo a un ejecutable, habría que indicar que requiere permisos de administrador. La forma de hacerlo depende de la herramienta que uses (PyInstaller, py2exe, etc.).

      Saludos!

  4. José María says:

    Aunque el programa explore.pyr funciona correctamente,

    la sentencia de la linea 16 :

    self.treeview = ttk.Treeview(self)

    en vez de self, no debería ser

    self.treeview = ttk.Treeview(main_window)

    Si estoy equivocado, ¿Me puede explicar en que?.

    Una duda más, también en el programa explorer, si se codifica un evento para la contracción de una rama,
    ¿Se debería de programa dentro de el el borrado de los items que se dejan de mostrar?

    Por último, comparto el punto de viata de flipo man, con las adiciones que indica, quedaría casi perfecto.

    Muchas gracias

    • Recursos Python says:

      Hola José María. El control treeview está dentro de Application (que es un ttk.Frame), y este está dentro de la ventana. Por eso indicamos entre paréntesis self (una referencia a Application) y no main_window.

      En cuanto a la otra pregunta, si entiendo bien, no es necesario programar nada para que se contraigan los elementos.

      Saludos

      • José María says:

        Disculpe, no acabo de entender su explicación,

        Estoy empezado en esto, y observo que siempre que se crea un widget, se hace referencia, en primer lugar, a widget padre que lo va a contener, y asi lo vengo observando en casi todos los ejemplos que he visto.

        Con su razonamiento, podría entender, que siempre que se instancia un widget, dentro de una clase, situación muy frecuente, no hace salta referirse al widget padre, basta con self ya que esta contenido en esa clase. Seguro que este razonamiento es erroneo, pero es lo que podría entenderse.

        Respecto a la segunda parte, me refería a que al contraer una rama, los recursos que se dedicaron a los items que residian en esa rama, y que ya no hacen falta, se deberían liberar. Seguramente no hace falta porque desde luego observo que en ningún ejemplo se hace esto.

        • Recursos Python says:

          Tu razonamiento es correcto. Se comprende la confusión porque Application (al que nos referimos como self) está heredando la funcionalidad de ttk.Frame, que es un widget. Esto tiene por objetivo un código más organizado, pero es similar a lo siguiente:


          main_window = tk.Tk()
          app = ttk.Frame(main_window)
          treeview = ttk.Treeview(app)

          ttk.Frame no tiene ningún aspecto, simplemente se usa para agrupar conjuntos de widgets relacionados. Es similar a la etiqueta div de HTML.

          Y decís bien, por otro lado, que no es necesario liberar recurso alguno.

          Saludos

      • Hola recursos Python, gracias por el contenido, una pregunta, si yo quiero que los items de la carpetas, me salgan con un background diferente, donde tendría que configurarlo ?? , He cambiado color a todo el treeview, pero me gustaría que los folders tengan un tamaño de letra y color diferente

        • Recursos Python says:

          Hola, José.

          Podés cambiarle el fondo a las carpetas definiendo un tag tras crear el treeview:

          self.treeview.tag_configure("folder", background="#ff0000")

          Y luego dentro de insert_item():

          iid = self.treeview.insert(
              parent,
              tk.END,
              text=name,
              tags=("fstag",) + (("folder",) if isdir(path) else ()),
              image=self.get_icon(path)
          )

          Saludos

    • Prueba con este codigo:

      style = ttk.Style()
      style.configure("mystyle.Treeview", highlightthickness=0, bd=0, font=('Calibri', 12), rowheight=30)
      style.configure("mystyle.Treeview.Heading", font=('Calibri', 13))

      treeTable = ttk.Treeview(root, style="mystyle.Treeview", yscrollcommand=treeScroll.set, selectmode='extended', height=22)
      treeTable.grid(row=0, column=0, sticky=E + W + N + S)

  5. Muy bueno el tuto. Me hubiera gustado un poco más de detalle de los tag_bind y sus self.item_selected , self.item_opened , self.item_closed. pero solucionó mis dudas al respecto de nombrar la primer columna, y de cómo añadir elementos a la lista. el tuto oficial de tkinter se guarda muchos secretos en cuanto a treeview. gracias!

  6. antser rodriguez says:

    cuando uso treeviw y le inserto elemento se encoje y no me deja ver todas columnas, intente ponerle un width pero al parecer solo se puede con las columna y no con el widget en si, podrías decirme como establecerle in tamaño fijo para que no pase esto

    • Hola recursos Python , he visto un pequeño problema y es que cuando expande una pestaña , agrega los ficheros de la carpeta hasta hay correcto, pero si vuelves a expandir, se duplica los ficheros es como si no se borra lo anterior, se podría hacer que no se dupliquen los ficheros, no sé dónde enviártelo una imagen

Deja una respuesta