La odisea de trabajar con combobox en Tkinter

Posted by in Python

Ha llegado el primer gran reto de trabajar con Tkinter, y es que …. el control combobox (también le sucede a Listbox), y es que … debo confesar que estoy profundamente decepcionado. porque no puede asignar un identificador a devolver como objeto seleccionado para cada opción disponible. Sólo admite listas con las distintas opciones disponibles (es una lista interna la que se encarga de todo!).

Este comportamiento tan rudientario es poco práctico, a mi modo de ver. ¿por qué no admitir diccionarios? (es lo primero que me pregunto, donde las claves sí son únicas). Yo esperaba trabajar con combos de forma similar a VBA o VB6, pero sin duda, aunque me quedé muy sorprendido al trabajar con combos en C#.

Combobox en Tkinter

Combobox en Tkinter

Comboboxes en VBA, VB6 y C#

Hace mucho tiempo, os conté algunas diferencias al trabajar con combos en VB6 y VBA, no es que sean perfectos ni muy bonitos, pero sí son funcionales. Para cargar las opciones, hay que hacerlo manualmente mediante un bucle for, y puedes asignar un identificador de cada opción, que no coincide con lo que muestra.

En C# he trabajado con XAML como alternativa gráfica, y … realmente trabajar con comboboxes o con Listboxes es muy sencillo, pues es posible asociar al control una colección de objetos, y le indicas la propiedad con la que quieres que se represente dicho objeto (en formato string). El evento SelectItem, además, devuelve el propio objeto seleccionado (no sólo el identificador), para que sigas trabajando con dicho objeto. Todo lo que ocurre con la colección en poder del control, ocurre internamente, de forma ágil y muy rápida. Trabajar de esta manera facilita mucho la tarea cuando trabajamos contra bases de datos

Los comboboxes de Tkinter

Cómo sabeis, llevo poco tiempo trabajando con Tkinter como librería gráfica para trabajar con Python, donde mi referencia es el libro Tkinter GUI Application Development, del que ya se habló en manejandodatos.es. Pero a veces, no todo es cómo nos gustaría, y eso le pasa a los controles combobox (también en Listboxes). Me hubiera sorprendido muy gratamente que los widgets Tkinter trabajaran bien con colecciones de objetos, cómo lo hace C# y XAML, pero en el peor de los casos, tendría la misma funcionalidad que con los controles combo en VBA o VB6, pero …  no es el caso.

De hecho, muy conscientes de esto, detallan el problema aquí (http://www.tkdocs.com/tutorial/morewidgets.html#listbox), en el apartado «Keep Extra Item Data«, confirmando que NO es posible mantener información adicional sobre cada opción disponible, y por tanto, hay que hacerlo con ua lista adicional. En mi caso, he optado por crear un diccionario mediante

diccionario = dict(zip(descriptor, identificador))

donde descriptor e identificador son dos listas, de igual longitud, donde el elemento n de ambas están relacionados.

Pero para trabajar con el diccionario, es necesario incluir la siguiente función:


def find_key(dic, val):
"""return the key of dictionary dic given the value"""
return [k for k, v in dic.iteritems() if v == val][0]

Cuyo cometido es devolver las claves cuyos valores coinciden con «val».

Resumiento, un lío que vamos a ver con un ejemplo.


#-------------------------------------------------------------------------------
# Name:        Ejemplos Tkinter - Combos
# Copyright:   (c) David Trillo Montero 2014 - Manejando datos
#-------------------------------------------------------------------------------

miVersion = "Ejemplo Combos - version 0.1.0"
from Tkinter import *
import ttk

class colores():
def __init__(self, id, color):
self.id = id
self.color = color
def __str__(self):
return self.color

class Aplicacion(Frame):
def __init__(self, master):
Frame.__init__(self, master=None)
self.master.title(miVersion)
self.idcolor = StringVar(value=1)
# Cemporadas
Label(self.master, text="Colores").grid(row=1, sticky=W)
self.cbotemporadas = ttk.Combobox(self.master, textvariable=self.idcolor, state="readonly" )
self.cbotemporadas.grid(row=1, sticky=E, column=1, columnspan=3)
self.cbotemporadas.bind("<<ComboboxSelected>>", self.selecciona)
self.valores = self._cargaFromObject(o_colores, self.cbotemporadas, "color", "id", seleccionado, self.idcolor)

def selecciona(self, event):
val = self.idcolor.get()
print self.valores[val]

def _cargaFromObject(self, coleccion, objeto, campodesc, campoid, val2select, variable):
misc , misv = [] , []
for vv in coleccion:
misv.append(getattr(vv,campodesc))
misc.append(getattr(vv,campoid))
if getattr(vv,campoid) == val2select: variable.set(getattr(vv,campodesc))  # Selección de Quiniela
objeto["values"] = misv
return dict(zip(misv, misc))    # Crea diccionario

def creavalores():
c = colores(1,"rojo")
o_colores.append(c)
c = colores(2,"verde")
o_colores.append(c)
c = colores(3,"amarillo")
o_colores.append(c)

o_colores = []
seleccionado = 2
if __name__ == '__main__':
creavalores()
root = Tk()
a = Aplicacion(root)

root.mainloop()

Y aquí en funcionamiento:

Tkinter combo

Tkinter combo

Al cambiar de selección, en la consola podeis ver el ID del elemento seleccionado. Cómo siempre, el código lo teneis en GitHub.

En fin, espero que os resulte de interés, y espero que futuras versiones de Tkinter rompan con esta rudimentaria forma de trabajar con combos.