En este tutorial aprenderás cómo crear una interfaz gráfica en Python para visualizar bases de datos alojadas en un servidor MySQL. Con ejemplos prácticos y explicaciones detalladas, te guiaré paso a paso en el proceso de creación. Ten en cuenta, que este capítulo es solo una parte de un gran proyecto para trabajar con MySQL desde Python, de modo, que, quizás, necesites ver los capítulos anteriores del curso Máster en Python para poder entenderlo mejor.
Widgets necesarios para la ventana
Para crear esta ventana, necesitaremos los siguientes componentes:
- Una ventana secundaria (TopLevel o CTkTopLevel).
- Un marco para agrupar todo el contenido de la ventana.
- Una etiqueta para mostrar el título de la ventana.
- Una entrada de texto para poder buscar bases de datos.
- Una caja de texto para mostrar los resultados.
- Una etiqueta para contar los resultados obtenidos en cada momento.
- Un botón de búsqueda.
- Un botón para actualizar los resultados.
Como puedes ver, con un método tan simple como el de la llamada a «SHOW DATABASES», he creado algo mucho más complejo e interesante. Nos podemos limitar a mostrar un listado de bases de datos, o podemos hacerlo a lo grande, como verás en este capítulo.
Creación de la ventana secundaria (TopLevel o CTkTopLevel).
Nos vamos a ir al método ventana_mostrar_bases_datos() del archivo de interfaz gráfica. Es aquí, donde haremos casi todo lo que tengamos que hacer.
Lo primero es crear la ventana secundaria. Yo haré todo el proceso con CustomTkinter. Si tienes dudas para hacerlo con Tkinter, déjame un comentario.
# Se crea la ventana ventana = ctk.CTkToplevel() # Se le da un título ventana.title("Ventana para mostrar las bases de datos del servidor.") # Se le da un tamaño ventana.geometry("400x565") # Se evita su redimensión ventana.resizable(0,0)
Con eso, la instanciamos, le damos un título y un tamaño inicial.
Con resizable(0,0) indicamos que no se pueda redimensionar la ventana ni en vertical ni en horizontal. Anteriormente, expliqué estos valores con False, que es lo mismo que 0.
Creación del marco de la ventana
El siguiente paso es crear el marco de la ventana, el cual, contendrá todos los widgets.
#Se crea un marco marco = ctk.CTkFrame(ventana) marco.pack(padx=10, pady=10)
Creación de una etiqueta de título
Lo siguiente será crear una etiqueta de título y la posicionamos dentro del marco. Esta va a servir para indicar al usuario, de forma clara, el propósito de la ventana.
# Se crea una etiqueta informativa para la ventana ctk.CTkLabel(marco, text="Listado de las bases de datos en el servidor", font=fuente_widgets).pack(padx=10, pady=10)
La fuente utilizada es «fuente_widgets», por si no lo recuerdas, es esta:
# Fuente para algunos widgets fuente_widgets = ('Raleway', 16, BOLD)
Creación de la caja de resultados
La caja de resultados, va a ser un widget «Text» si estás usando Tkinter y Textbox si estás usando CustomTkinter.
self.texto = ctk.CTkTextbox(marco, font=fuente_widgets, width=300, height=300) self.texto.pack(padx=10, pady=10)
Creación de una etiqueta para mostrar resultados
Ahora, creamos la etiqueta que va a mostrar el número de resultados de la consulta «SHOW DATABASES». Si el servidor tiene 7 bases de datos, sacará una frase como «Se encontraron 7 resultados.».
Inicialmente, tiene un valor en «text» de string vacío. De esta forma, la posicionamos, pero no sacamos nada hasta que no haya resultados.
# Se crea una etiqueta para mostrar el número de resultados self.resultados_label = ctk.CTkLabel(marco, text="", font=fuente_widgets) self.resultados_label.pack(pady=10)
Creación de los botones «Buscar» y «Actualizar».
Solo nos falta añadir un par de botones con los que buscar ciertos resultados y otro para actualizar el contenido de la caja de texto.
# Se crea un botón para buscar bases de datos boton_buscar = ctk.CTkButton(marco, text="Buscar") boton_buscar.pack(pady=10) # Se crea un botón para actualizar los resultados de la caja boton_actualizar = ctk.CTkButton(marco, text="Actualizar") boton_actualizar.pack(pady=10)
El resultado es el siguiente:
Variable de control StringVar()
Para poder buscar con el Entry() (que haremos a continuación), necesitamos una variable de control de tipo StringVar().
# Agregar un campo de entrada para la búsqueda self.busqueda_control = tk.StringVar()
Recuerda importar Tkinter si solo usas CustomTkinter:
import tkinter as tk
Puesto que hace mucho que expliqué el tema de las variables de control de Tkinter (capítulo 16), te lo explico aquí.
Con una variable de control de tipo StringVar, puedes guardar un string y utilizarlo de manera fácil en widgets como Label, Entry, etc.
La clase StringVar de Tkinter
En cualquier parte del documento .py escribe lo siguiente:
tk.StringVar.
Se te desplegará la lista de alcance de la clase StringVar(). Nos fijaremos en los métodos «set», «get». Con «set» podemos establecer un valor string en el widget asociado y con «get» obtenerlo. No hay porque complicar más la explicación.
Ya puedes borrar esa línea, era solo para que vieras la lista de métodos.
Diferencia entre StringVar y un string normal de Python
Un StringVar es un tipo especial de variable en Tkinter (una biblioteca para crear interfaces gráficas de usuario en Python) que se utiliza para almacenar cadenas y vincularlas a widgets como entradas o etiquetas. A diferencia de una cadena normal en Python, un StringVar puede ser asociado con uno o más widgets en una interfaz gráfica de usuario y su valor puede ser actualizado automáticamente cuando el usuario interactúa con esos widgets.
Funciones internas de actualización y búsqueda
Ahora que ya he explicado lo de la variable de control StringVar(), lo siguiente, es crear una serie de funcionalidades para poder actualizar la consulta y para buscar.
Creación de la entrada de texto para buscar
Ahora, vamos a crear el Entry() que nos servirá para buscar bases de datos entre los resultados obtenidos, buscará directamente en el servidor con una llamada al método mostrar_bd() y luego los filtrará.
Esta entrada de texto usa el atributo «textvariable» para utilizar la variable de control. Así de fácil la podemos asociar al widget.
# Se crear la entrada de texto para búsquedas ctk.CTkEntry(marco, font=fuente_widgets, textvariable=self.busqueda_control, width=300).pack(padx=10)
Función de actualización
Primero, empezaremos por la función de actualización de la ventana. La podemos colocar antes de los botones.
# Función interna de actualización SHOW DATABASES def actualizar(): # Se establece el valor de la variable de control a string vacío (reset) self.busqueda_control.set('') # Se elimina el contenido de la caja de resultados self.texto.delete('1.0', 'end') # Se realiza la llamada al método mostrar_bd (SHOW DATABASES) y se guarda en resultado resultado = base_datos.mostrar_bd() # Se itera el resultado y se presenta línea a línea en la caja de texto. for bd in resultado: self.texto.insert('end', f"-{bd[0]}\n") # Actualizar la etiqueta con el número de resultados numero_resultados = len(resultado) self.resultados_label.configure(text=f"Se encontraron {numero_resultados} resultado/s.")
En la primera línea, se resetea el valor de la variable de control, se deja con un string vacío.
Con el delete() borra todo el contenido de la caja de texto.
En la variable resultado, se llama al método mostrar_bd() (SHOW DATABASES) que guarda una tupla con listas (fetchall()). Cada lista viene con un solo valor (índice 0), el del nombre de una de las bases de datos.
Por lo tanto, con el bucle, formateamos con ese insert() el nombre de una base de datos por fila.
Después, con numero_resultados, se cuenta la longitud de listas que tiene la tupla fetchall() y se actualiza el valor de la etiqueta que inicialmente estaba vacía. Ahora, sacará una frase como «Se encontraron x resultado/s».
Función de búsqueda
Pasemos a lo más difícil del capítulo. Hacer un método que busque entre los resultados obtenidos en la consulta de mostrar_bd().
# Función interna de búsqueda def buscar(): # Se elimina el contenido de la caja de resultados self.texto.delete('1.0', 'end') # Se realiza la llamada al método mostrar_bd (SHOW DATABASES) y se guarda en resultado resultado = base_datos.mostrar_bd() # Se obtiene el valor string de la variable de control (lo que se busca en el Entry()) busqueda = self.busqueda_control.get().lower() # Se crea una lista vacía donde almacenar los resultados filtrados resultado_filtrado = [] # Se itera la tupla fetchall. for bd in resultado: #Si lo que tiene la StringVar está en cada lista de la tupla, se añade a la lista if busqueda in bd[0]: resultado_filtrado.append(bd) # Se itera la lista ya filtrada, con lo que se insertan los resultados en la caja de resultados for bd in resultado_filtrado: self.texto.insert('end', f"-{bd[0]}\n") # Se actualiza la etiqueta con el número de resultados numero_resultados = len(resultado_filtrado) self.resultados_label.configure(text=f"Se encontraron {numero_resultados} resultado/s.")
- Línea 4: se borra el contenido de la caja de resultados, para luego, posicionar los posibles resultados.
- Línea 6: se guarda el resultado de la llamada al método mostrar_bd().
- Línea 8: se guarda, gracias a la variable de control, el valor escrito por el usuario en el Entry(). Puesto que los nombres de bases de datos siempre están en minúsculas, es importante evitar el posible fallo de buscar algo en mayúsculas y que no salgan resultados. Esto se consigue transformando el texto a minúsculas.
- Línea 11: se crea una lista vacía donde ir añadiendo las coincidencias de la búsqueda.
- Línea 13: se itera la tupla fetchall() que tiene todos los nombres de las bases de datos en el servidor. Cada vuelta comprueba si en cada lista, índice 0 (cada nombre de base de datos) coinciden con los valores de la lista «resultado_filtrado». Lee una a una sus posiciones.
Ejemplo, tienes estas bases de datos:
- information_schema
- mysql
- performance_schema
- sakila
- sys
- world
Buscas la palabra «schema».
En la variable «busqueda» tienes guardada el valor «schema».
Con el bucle for, se itera la tupla con listas que lleva todas las bases de datos.
Con el if, se comprueba con cada una de ellas, si el valor de «busqueda» (schema), está en el iterador «bd», el cual, lleva cada vuelta una de las bases de datos.
Si coinciden, se van añadiendo con append a la lista «resultado_filtrado». En este caso, nos quedan dos coincidencias, «information_schema» y «performace_schema».
- Línea 19: se itera la lista ya filtrada y se va colocando en la caja de resultados.
- Línea 23 y 24: se actualiza el número de resultados mostrados en la etiqueta.
Finalmente, debajo de los botones, colocaremos una llamada a la propia función de actualización, de forma, que cuando abras la ventana de mostrar bases de datos, se haga la consulta automáticamente, sin necesidad de pulsar nada.
Lo de este for, yo lo pondría usando la comprensión de listas de Python. No lo voy a explicar en este capítulo, ya que aún no hemos entrado en ese tema en el curso, pero te dejo como se haría:
resultado_filtrado = [bd for bd in resultado if busqueda in bd[0].lower()]
Esto es lo mismo, a efectos prácticos que el código que tienes a continuación:
# Se crea una lista vacía donde almacenar los resultados filtrados resultado_filtrado = [] # Se itera la tupla fetchall. for bd in resultado: #Si lo que tiene la StringVar está en cada lista de la tupla, se añade a la lista if busqueda in bd[0]: resultado_filtrado.append(bd)
Adaptación del método de consola mostrar_bd()
El método mostrar_bd(), está preparado para trabajar en la consola, tenemos que adaptarlo ligeramente para que funcione en la interfaz gráfica.
Así es como lo tenemos ahora mismo:
@conexion def mostrar_bd(self): try: # Se informa de que se están obteniendo las bases de datos print("Aquí tienes el listado de las bases de datos del servidor:") # Realiza la consulta para mostrar las bases de datos self.cursor.execute("SHOW DATABASES") resultado = self.cursor.fetchall() # Recorre los resultados y los muestra por pantalla for bd in resultado: print(f"-{bd[0]}.") except: # Si ocurre una excepción, se avisa en la consola print("No se pudieron obtener las bases de datos. Comprueba la conexión con el servidor.")
Sencillamente, hay que dejarlo así:
@conexion def mostrar_bd(self): # Realiza la consulta para mostrar las bases de datos self.cursor.execute("SHOW DATABASES") self.resultado = self.cursor.fetchall()
¿Recuerdas que en el capítulo anterior modificamos el decorador conexión par devolver el valor de «resultado»?
Pues bien, este decorador, seguirá funcionando mientras el nombre de la variable de los métodos que tienen que devolver cosas se llamen «resultado».
El resultado final es este:
Cambiar la frase que muestra el número de resultados
Puede ser que la frase «Se encontraron ‘x’ resultado/s» no te guste. Quizás, quieras establecer dos posibles etiquetas, una para el singular «Se encontró 1 resultado» y otra para el plural, «Se encontraron 10 resultados».
Esto lo puedes hacer con un sencillo condicional if else:
En el método de actualización, coloca este código:
# Se evalúa el resultado para deteminar la frase singular o plural numero_resultados = len(resultado) if numero_resultados == 1: self.resultados_label.configure(text=f"Se encontró {numero_resultados} resultado.") else: self.resultados_label.configure(text=f"Se encontraron {numero_resultados} resultados.")
En el de búsqueda este otro:
# Se evalúa el resultado para deteminar la frase singular o plural numero_resultados = len(resultado_filtrado) if numero_resultados == 1: self.resultados_label.configure(text=f"Se encontró {numero_resultados} resultado.") else: self.resultados_label.configure(text=f"Se encontraron {numero_resultados} resultados.")
No te pierdas nada del curso Máster en Python.