Separar el código de la interfaz gráfica – (Inicia tu proyecto) PyQt5

Hey lectores hoy como prometí vamos a ver como separar el código python de la interfaz gráfica de usuario!

Como había dicho antes cuando hablamos de los módulos y librerías en python es muy importante tener en cuenta que nuestro programa no «debería» estar compuesto por miles de funciones, gráfica, complementos todo en el mismo archivo. No solo porque no es una buena practica de programacion; sino porque se nos van a complicar las cosas a la hora de querer modificarlo u actualizarlo.

Podemos separar el código perfectamente de nuestras aplicaciones o programas no solo de la forma que se recomienda, sino también de la que nos quede más cómoda a nosotros y a nuestro usuario. De esta manera ademas de estar mejor organizado optimizamos mucho la ejecución, lectura e interpretación del código, entre otras ventajas que seguro iras viendo por ti mismo.

Bien ahora si, a lo que íbamos. Que me voy por las ramas!

¿Por qué separar el código de la interfaz gráfica?

El problema de no separar el código:

En la lección anterior vimos como crear una ventana con QtDesigner, la exportamos en un archivo .ui el cual finalmente convertimos a un archivo de Python. Hasta ahí todo bien!

El verdadero problema existe cuando tu comienzas a añadir código funcional a ese archivo (que fue convertido) llamemosle en el ejemplo: miventana_ui.py

Si tu añadiste código funcional a ese archivo py y luego quieres editar el diseño usando Qtdesigner. Estarás en un problema ya que deberás generar un nuevo archivo .ui. Y habrás perdido los cambios que hallas realizado en cuanto a código funcional. Porque tendrás un nuevo archivo. Me explico?

Al crear un nuevo archivo .ui, debemos volver a convertirlo a (.py) y allí no se encontrara nuestro código funcional (el que brinda las acciones del programa). Deberemos volver a copiarlo, modificarlo y así sucesivamente perdiéndonos en un laberinto de archivos de residuo y modificaciones interminables. Porque ambos sabemos que no vas a diseñar la interfaz una sola vez y así va a quedar.

 

La solución; dividir el código:

 

Separar el código

Así deberíamos organizar nuestros archivos; de esta manera tendremos nuestro código funcional en Mi programa. Y la interfaz gráfica en otro archivo (_ui.py) que podremos reemplazar tranquilamente luego de modificarlo con QtDesigner!

Cuando comiences un nuevo proyecto de una aplicación entonces puedes definirte por una de estas dos maneras de organizar tu código. Yo te recomiendo la manera 2, por ser mas practica.

 


≥ Manera 1 de dividir tu proyecto

Así que vamos a ver como haríamos esto basándonos en el código de la ventana que creamos en Pyqt5.

 

Carpeta miprograma

Vamos a crear un nuevo archivo de Python usando nuestro IDE, en mi caso geany y vamos a guardarlo junto a nuestro archivo «miventana_ui.py». Llamare a este archivo Miprograma.py y colocare todo dentro de una carpeta incluido el archivo «miventana.ui»

 

Lo que vamos a hacer es utilizar miventana_ui.py desde Miprograma.py

Por empezar en Miprograma.py debemos importar todo lo que esta declarado en miventana_ui.py así:

Importar archivo ui en python Estamos importando el código del archivo «.py», no el «.ui» que quede claro. Fíjate bien en los nombres de archivos.

Utilizamos from «modulo» import * (todo explícito)

 

 

 Creamos la clase VentanaPrincipal heredando de Qtwidgets.QMainWindow

 

Qwidgets y las librerías de Pyqt ya están importadas dentro de «miventana_ui.py» por lo que podremos utilizarlas sin necesidad de volver a importar en Miprograma.py. Así que vamos a crear la clase VentanaPrincipal que nos permitirá cargar la ventana principal de nuestro programa:

 

Hasta aquí hemos importado la ventana principal mediante Qtwidgets.QMainWindow utilizando una clase llamada VentanaPrincipal. Colocaremos dentro de ella un simple «pass». Para que pase de ella, esa clase aun no contiene nada.

Prueba a ejecutar el archivo desde la consola para corroborar que no tienes errores al importar, si obtienes un error NameError es probable que te hallas equivocado importando el archivo «.py» o en la herencia de Qtwidgets.QMainWindow. De lo contrario no sucedera nada.. Porque..

Creando el bucle de ejecución de nuestro programa

Recuerdas que explicamos un condicional dentro de miventana_ui.py?. Pues ese condicional esta evitando ahora mismo que cuando tu ejecutes Miprograma.py no se abra miventana_ui.py. Y tampoco queremos que sea así!, por lo que este condicional nos permite que miventana_ui.py funcione como un módulo de Miprograma.py. 

En este condicional (que ya no se cumple porque ahora esta como módulo) se encontraba un bucle de ejecución recuerdas?. Pues eso es lo que necesitamos para Miprogramaa.py!

Este es el condicional del que te hablaba:

if __name__ == "__main__": #Condicional que comprueba si ha sido ejecutado
    import sys #Importa el módulo Sys
    app = QtWidgets.QApplication(sys.argv) #crea un objeto de aplicacion (Argumentos de sys)
    MainWindow = QtWidgets.QMainWindow() #Instancia
    ui = Ui_MainWindow() #instancia
    ui.setupUi(MainWindow) #Llama al Metodo setupUI con MainWindow como argumento
    MainWindow.show() #Método que muestra la ventana
    sys.exit(app.exec_()) #Inicia el ciclo de eventos y bloquea hasta que se cierre la aplicación

Excepto que vamos a copiarlo y pegarlo en Miprograma.py, pero vamos a modificarlo instanciando nuestra clase VentanaPrincipal a nuestro objeto «ventana» que sera el que determine el comportamiento de la ventana principal.

Así que este condicional queda así:

if __name__ == "__main__": #Condicional que comprueba si ha sido ejecutado o importado
			#NO importamos el módulo Sys
    app = QtWidgets.QApplication([]) #Creamos app y le pasamos una lista de argumentos vacíos
	#Borramos todo el resto del código y ahora vamos a instanciar nuestra clase MainWindow:
    ventana = VentanaPrincipal()
	#Ahora es hora de mostrar esta ventana:
    ventana.show() 
	#Aquí creamos el bucle de ejecución desde app
    app.exec_() #Usamos app.exec_()

 

Código de Miprograma.py:

 

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#https://pythones.net
from miventana_ui import *
class VentanaPrincipal (QtWidgets.QMainWindow):
	pass
if __name__ == "__main__": #Condicional que comprueba si ha sido ejecutado o importado
			#NO importamos el módulo Sys
    app = QtWidgets.QApplication([]) #Creamos app y le pasamos una lista de argumentos vacíos
	#Borramos todo el resto del código y ahora vamos a instanciar nuestra clase MainWindow:
    ventana = VentanaPrincipal()
	#Ahora es hora de mostrar esta ventana:
    ventana.show() 
	#Aquí creamos el bucle de ejecución desde app
    app.exec_() #Usamos app.exec_() en vez de sys.exit()

Como resultado de ejecución obtenemos lo siguiente:

Separar el código de pyqt5

 

 

-Pero eso es una ventana cutre, vacía, sin sentido en la vida!

-Si porque solo importamos solo la ventana principal, ahora vamos a traer nuestro diseño a ella!!

Para eso debemos heredar la clase UI_MainWindow que es la que contiene las propiedades de nuestro diseño (el que realizamos en QtDesigner). Dicha clase se modifica cada vez que cambiamos nuestro diseño. Haaaa.. Ahora entiendes!. Cuando cambiemos el diseño se importara la clase con el nuevo código y obtendremos el nuevo diseño!!

A ello! que no tengo todo el día!

 

Importando nuestro diseño, clase UI_MainWindow

Esta clase se encuentra en miventana_ui.py y vamos a colocarla como herencia dentro de nuestra clase VentanaPrincipal que hemos creado al principio! Quedando así:

class VentanaPrincipal (QtWidgets.QMainWindow, Ui_MainWindow):
	pass

Ahora debemos llamar al método constructor de la ventana dentro de esta clase MainWindow, recuerdas cual era?

SetupUI!

Pero como sabrás primero debemos crear el constructor de la clase. Usando __init__

Así que en nuestra clase VentanaPrincipal debemos crear un constructor y llamar el método setupUI indicando como parámetro self (a si mismo (objeto)).

class VentanaPrincipal (QtWidgets.QMainWindow, Ui_MainWindow):
	def __init__(self):
		self.setupUi(self)

Aquí estamos creando el constructor de la clase VentanaPrincipal y llamando al método setupUi con el parámetro self. Pero obtendremos un error grande como una casa:

Traceback (most recent call last):
File «Miprograma.py», line 13, in <module>
ventana = VentanaPrincipal()
File «Miprograma.py», line 8, in __init__
self.setupUi(self)
File «/home/pyro/Escritorio/Miprograma/miventana_ui.py», line 15, in setupUi
MainWindow.setObjectName(«MainWindow») #Define el nombre de la ventana (objeto) mediante método (H)
RuntimeError: super-class __init__() of type VentanaPrincipal was never called

¿Por qué?dividir el codigo

Traducción del error usando el traductor de google:

RuntimeError: la superclase __init __ () del tipo VentanaPrincipal nunca fue llamada

Porque al crear la clase VentanaPrincipal estamos usando herencia  y no llamamos al constructor de las clases padres entonces el interprete no encuentra el método setupUI. Sabe que hereda porque lo hemos especificado, pero no encuentra el método!!

Venga te lo esta diciendo, el interprete nos pide Super()

 

Aplicando super() en pyqt

Así que aplicando un simple super accedemos al bendito método; quedando nuestro código así:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#https://pythones.net
from miventana_ui import *
class VentanaPrincipal (QtWidgets.QMainWindow, Ui_MainWindow):
	def __init__(self):
		super().__init__()
		self.setupUi(self)
if __name__ == "__main__": #Condicional que comprueba si ha sido ejecutado o importado
			#NO importamos el módulo Sys
    app = QtWidgets.QApplication([]) #Creamos app y le pasamos una lista de argumentos vacíos
	#Borramos todo el resto del código y ahora vamos a instanciar nuestra clase MainWindow:
    ventana = VentanaPrincipal()
	#Ahora es hora de mostrar esta ventana:
    ventana.show() 
	#Aqui creamos el bucle de ejecucion desde app
    app.exec_() #Usamos app.exec_() para crear el bucle de ejecución
Atencion!

Pero eso si, si respetas el orden de la herencia múltiple de las clases: (QtWidgets.QMainWindow, Ui_MainWindow). Pues el método setupUI esta en la clase Ui_MainWindow y  alli es donde buscara python siguiendo el MRO. Que explicamos en super(). Caso contrario, deberíamos especificar el nombre de la clase llamando su constructor «Ui_MainWindow.__init__». Si todo esto no te resulta familiar lee aquí sobre la función super() y el MRO.

 

Resultado de la ejecución de Miprograma.py:

Ventana principal + diseño propio Designer Pyqt5

 

Añadido!

Guaala!! Logramos separar el código del diseño!. Así como así!

Haber.. mmm.. si te quedan dudas vamos a agregar otro botón y cambiar esos textos feos en QtDesigner!

 

El resultado esperado! Pyqt5 separado el código del diseño

 

Vamos nuevamente a QtDesigner! y abrimos nuestro archivo «.ui» nuevamente!

Carpeta con archivos de python Recuerda siempre tener todos los archivos en una carpeta de tu programa para evitar el desorden de archivos.

En QtDesigner vas a abrir..>y alli buscas tu archivo.ui

Editas lo que sea necesario, en mi caso solo hago algunas modificaciones para que sea notable que se encuentra separada la lógica del diseño de nuestro programa como ves mas abajo.

 

QtDesigner cambiar texto label

 

Aquí como estas viendo estoy jugando con los tamaños de letra de la fuente del label. Quedando mi ventanita así:

Mi primera ventana en pyqt5 con el código separado
Ahora vamos otra vez a repetir el proceso que hicimos antes de exportarlo como «.ui» y luego pasarlo a «.py». Pero en mi caso solo voy a reemplazar los archivos, porque me tengo fe!. Porque quiero y porque puedo!

Vamos a guardar como…> La carpeta de nuestro programa ..> reemplazamos el archivo miventana.ui

Ahora vamos a borrar miventana_ui.py y lo volveremos a crear con el mismo nombre, para no tener que modificar el código de mi programa e importar nuevamente miventana_ui.py como modulo. Vamos a la consola y procedemos a convertir el ui nuevamente a py.

«Venga que python es fácil, cualquier programador de html puede aprenderlo :OOOOOO» <- Es broma,  no vengamos aquí a montar un debate.

Bien, ahora una vez convertida nuestra ventana_ui.py ha cambiado, se ha reemplazado. Y como tenemos las cosas separadas al ejecutar Miprograma.py debería verse la modificación!

  • Ya ejecuta esa mierda de una vez!!

Pythones es python perras

 

 


≥ Manera 2 de organizar tu proyecto

Esta segunda manera seguro te resultara mas fácil y mas organizada, si solo vas a trabajar con Designer en este caso voy a aplicarla a Miprograma.py nuevamente. De esta forma no necesitas convertir el archivo .ui a .py sino que lo llamas directamente haciendo uso del módulo uic.

Fijare en el siguiente código (recuerda que en este caso utilizo la misma clase que en la manera 1 «VentanaPrincipal» )

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

import sys #Importamos módulo sys
from PyQt5 import uic, QtWidgets #Importamos módulo uic y Qtwidgets

qtCreatorFile = "miventana.ui" # Nombre del archivo UI aquí.

Ui_MainWindow, QtBaseClass = uic.loadUiType(qtCreatorFile) #El modulo ui carga el archivo

class VentanaPrincipal(QtWidgets.QMainWindow, Ui_MainWindow): #Y venga esa ventana
    def __init__(self): #Constructor de la clase
        QtWidgets.QMainWindow.__init__(self) #Constructor
        Ui_MainWindow.__init__(self) #Constructor
        self.setupUi(self) # Método Constructor de la ventana
        
        #Aquí ira nuestro código funcional

if __name__ == "__main__":
    app =  QtWidgets.QApplication(sys.argv)
    window = VentanaPrincipal()
    window.show()
    sys.exit(app.exec_())

Y el resultado:

 

Bueno se vera un poco diferente en estética pero es porque yo he cambiado de SO y PC. Pero en fin, funciona bien!
Y en este caso nuestra carpeta de proyecto solo necesitara los dos archivos, el Py con nuestro código funcional y el archivo .ui con la interfaz gráfica.

 

 


 

Y así me despido, cambiaría un poco el código de Miprograma.py para que al presionar «OK» se cerrase la ventana, pero se me hace muy larga la entrada y mi pc con 1gb de ram esta que pela fuego, así que en la siguiente nos vemos!. Y también quería contarles que mañana llega mi hermano con una super PC gamer para mi y prometo partir el blog a posteos! Así que si no lo han hecho, es momento de suscribirse perras!

 

 

Compartir es agradecer! :)

4 comentarios en “Separar el código de la interfaz gráfica – (Inicia tu proyecto) PyQt5”

    1. Hola, lo que hacemos aquí es cuestión de organización ¿vale?. Y evita el siguiente problema:

      1 – Tu creas tu interfaz gráfica con el programa Designer.
      2 – Exportas esta interfaz en un archivo «.ui»
      3 – Conviertes este archivo de «ui» a «.py»
      4 – Escribes el código funcional sobre este .py.
      5 – Deseas editar la interfaz gráfica porque olvidaste un botón.
      6 – Modificar nuevamente el archivo «.ui» y debes de generar otro «.py»
      7 – Te encuentras que es diferente al anterior y que debes pasar tu código de uno a otro!!
      8 – Ya se te hizo un lío de miles de líneas de código!!!!
      Aquí a estas alturas te habrás metido en un problemón..

      Para tener el código funcional de nuestro programa separado de el código que genera la interfaz gráfica Pyqt. Ya que haciéndolo del modo tradicional si tu programaste el código funcional junto con el que te genera la aplicación Designer de Pyqt y luego se te diera por volver a editar la interfaz gráfica, perderías todos los avances porque te vuelve a generar otro archivo «.ui».

      Para ello hay dos maneras, las cual explico en esta entrada y de las que recomiendo la segunda.

      Así por ejemplo usando la segunda manera, tu guardas el código que te doy como .py y en la línea 7 escribes el nombre de tu archivo .ui que puedes modificar miles de veces sin problema y sin necesidad de exportarlo a «.py».
      En ese archivo con el código que te dí escribes la parte «funcional» del programa, y te queda aparte el archivo de la interfaz gráfica (.ui)

      Eso es todo, si puede sonar raro el código pero si has leído oop en el blog te tendría que ser un poco más claro. Saludos

  1. Hola Mariano. Me gustó mucho el cómo abordaste el tema de inicio en GUI con Python usando este framework PyQt5. Yo antes estaba intentando con Kivy pero me fue imposible que funcionara el Kivy Designer en mis distro linux las cuales son Manjaro, Fedora, y Kubuntu. Tengo otras 5 distro más instaladas en mi laptop hp de 8GB de RAM pero solo con esas tres intenté por ser de las más populares. Bueno, Manjaro no es tan popular de hecho pero igual le intenté. Manjaro es mi distro base sobre la que hago casi todo mi trabajo y es de donde estoy siguiendo tus excelentes tutoriales.
    Me voy a concentrar por un mes en PyQt5 ya que mi objetivo es hacer apps de matemáticas de diversas índoles.

    Te felicito por tus posts tutoriales, son excelentes y hasta ahorita todo va de maravilla en mi sistema. Saludos desde Guadalajara, México.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.