viernes, 6 de noviembre de 2020

Hola Mundo modelo MVC PyQt COn Interface VISTA y Lógica Control

 

Hola Mundo modelo MVC PyQt COn Interface VISTA y Lógica Control

https://medium.com/@hektorprofe/primeros-pasos-en-pyqt-5-y-qt-designer-programas-gr%C3%A1ficos-con-python-6161fba46060


Primeros pasos en PyQt 5 y Qt Designer: Programas gráficos con Python

Image for postImage for post

Hola a todos, me estreno en esta plataforma con el objetivo de compartir algunos apuntes y tutoriales sobre Python.

Y es que no cabe duda de que hablamos del lenguaje de moda: analistas, matemáticos, físicos, estadistas… una oleada de nuevos usuarios ha llegado, espero, para quedarse. Pero Python es mucho más que eso, es un lenguaje para uso general que puede dar respuesta a cualquier necesidad, como la de implementar interfaces gráficas. ¿Y a quién no le gustaría ejecutar sus códigos presionando un botón?

Quiero mantener este tutorial bastante práctico, así que me saltaré la explicación de qué es PyQt, pero os dejo el enlace a la wikipedia por si queréis investigar por vuestra cuenta.

Instalar Miniconda

Lo peor de Python es toda la parafarnalia que hay que hacer para ponerse a trabajar. Yo recomiendo simplemente instalar Miniconda con Python 3.6, la suite de Anaconda minificada que hará tu vida más sencilla.

Una vez tengáis Miniconda instalado en vuestro sistema vamos a actualizar los paquetes y a instalar PyQt 5. Sólo tenéis que abrir la terminal Anaconda Prompt que encontraréis en Inicio (una terminal normal si estáis en Mac OS o GNU/Linux):

Image for postImage for post

Instalar PyQt 5 y Qt Designer

Allí escribiremos los siguientes comandos, confirmando con una ‘y’ cuando sea necesario:

conda update --all
conda install qt
conda install pyqt

Con esto tendremos instalada la versión más reciente de PyQt 5, así como el maravilloso editor gráfico Qt Designer. Para abrirlo sólo tenemos que escribir en la terminal:

designer

Y así se nos abrirá el programa, normalmente en el mismo idioma que tenemos el sistema operativo, en mi caso en español:

Image for postImage for post

Nuestro primer proyecto

Cada vez que ponemos en marcha Qt Designer el programa nos pide qué queremos crear. Como sólo vamos a tantear el terreno vamos a indicarle crear una “Main Window” o ventana principal que normalmente representará la raíz de un programa gráfico:

Image for postImage for post

Qt Desiginer, o mejor dicho Qt, funciona con un sistema de Widgets. Podríamos considerar un Widget como un componente de la interfaz. Prácticamente todo son Widgets, desde la propia ventana, los botones, las etiquetas de texto… y lo que en realidad representan en código son clases y objetos, pero eso lo veremos luego.

Por ahora os recomiendo hacer pequeño experimento para aprender un poco de qué va el asunto.

Podríamos por ejemplo arrastrar un par de widgets de la Caja de widgets a la ventana que nos aparece en medio. Uno de tipo Push Button y una Label, podéis incluso redimensionar la ventana para hacerla más pequeña, que os quede algo así:

Image for postImage for post

Con esto tenemos listo el diseño de nuestro programa, podríamos ver como queda presionando la combinación Control+R:

Image for postImage for post

Ahora lo que tenemos que hacer es exportar esta UI (User Interface) y transformarla a un fichero *.py. Simplemente vamos a Archivo > Guardar y guardamos la plantilla *.ui (por ejemplo con el nombre ventana.ui) en algún lugar al que podamos acceder desde la terminal.

Transformando el diseño .ui a un script .py

Cuando lo tengamos volveremos a la terminal, nos situaremos en el directorio de la plantilla ventana.ui y ejecutaremos el siguiente comando para transformarla a un script python:

pyuic5 -x ventana.ui -o ventana_ui.py

Ahora podremos ejecutar desde la misma terminal este script y si todo va bien se abrirá nuestra interfaz:

python ventana_ui.py
Image for postImage for post

Muy bien, ahí tenemos nuestro programa, el problema es que esto es sólo una interfaz, nos falta añadir alguna funcionalidad, por ejemplo que al presionar el botón cambie el texto de la etiqueta (label).

Separando la lógica del diseño

En este punto es cuando empezaremos a mancharnos las manos… vamos a escribir código.

Estando en el mismo directorio que tenemos el script ventana_ui.py, vamos a abrir nuestro editor de código favorito y crearemos un nuevo script python llamado ventana.py.

La clave es hacer uso de los componentes de ventana_ui.py dentro de ventana.py sin modificar en ningún momento el diseño, ahora mismo lo veréis.

Dentro del script empezaremos importando todo lo que hay en el script de la interfaz:

from ventana_ui import *

A continuación vamos a crear una nueva clase que representará nuestra ventana principal (MainWindow), pero no la crearemos desde cero, vamos a heredar del Widget QMainWindow de PyQt que encontraremos en el módulo QtWidgets:

class MainWindow(QtWidgets.QMainWindow):
pass

Si os preguntáis de donde sale QtWidgets, veréis que se ha importado previamente en el script de la interfaz (ventana_ui.py), por lo que ya está cargado en la memoria.

Con esto tenemos la ventana, pero todavía nos falta lo más importante, iniciar el bucle de la aplicación de PyQt. Para hacerlo pondremos debajo lo siguiente:

if __name__ == "__main__":
app = QtWidgets.QApplication([])
window = MainWindow()
window.show()
app.exec_()

Con la comprobación de la primera línea estamos añadiendo una cláusula por la que el contenido del if sólo se ejecutará al llamar el propio script en la terminal. Sirve básicamente para evitar ejecuciones duplicadas en caso de que importemos el fichero ventana.py en otro script.

A continuación creamos una aplicación de PyQt con el widget QApplication, al que le pasaremos una lista vacía. Normalmente aquí se pasarían argumentos de la linea de comandos, pero vamos a evitar su uso por ahora. Sólo asignaremos su ejecución a una variable app que nos permitirá controlar el bucle del programa.

A continuación crearemos una instancia de nuestra MainWindow y la mostraremos con su método show().

Finalmente pondremos en marcha el bucle del programa con app.exec_(). Si no ponemos esta última línea el programa se iniciará, pero se cerrará inmediatamente.

En resumen el código del fichero ventana.py quedaría así:

from ventana_ui import *class MainWindow(QtWidgets.QMainWindow):
pass
if __name__ == "__main__":
app = QtWidgets.QApplication([])
window = MainWindow()
window.show()
app.exec_()

Si lo ejecutamos en nuestra terminal nos aparecerá una ventana vacía:

Image for postImage for post

¿Cuál es la gracia ahora? Pues añadir nuestro diseño sobre esta ventana vacía que hemos creado, y es super sencillo.

Primero tenemos que heredar de la clase Ui_MainWindow además del widget QMainWindow, es decir, herencia múltiple, pero con prioridad del widget original a la izquierda:

class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow)):
pass

Sin embargo esto no es suficiente, también tenemos que renderizar la estructura y para ello sobreescribiremos el constructor y llamaremos al método setupUi encargado de generar la intefaz:

class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self, *args, **kwargs):
QtWidgets.QMainWindow.__init__(self, *args, **kwargs)
self.setupUi()

Como veis se utiliza mucho la herencia, la sobreescritura de métodos, los argumentos indeterminados, etc. No voy a entrar en detalle, pues considero que son conceptos básicos de Programación Orientada a Objetos (si os interesa aprender os recomiendo inscribiros a mi curso de Python en Udemy).

En resumen estamos sobreescribiendo el constructor, pero llamándolo también para no perder la funcionalidad de crear la ventana. Luego simplemente llamamos al método interno setupUi que generará la interfaz, pero pasándolo self que representa el propio objeto de la ventana (si os fijáis en el código de la interfaz veréis que requiere un objeto MainWindow).

Con esto si ejecutamos el programa ya nos aparecerá el diseño original maquetado en Qt Designer:

from ventana_ui import *class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self, *args, **kwargs):
QtWidgets.QMainWindow.__init__(self, *args, **kwargs)
self.setupUi(self)
if __name__ == "__main__":
app = QtWidgets.QApplication([])
window = MainWindow()
window.show()
app.exec_()
Image for postImage for post

¿Todo esto para qué? Pues para tener la lógica separada del diseño que no es poco.

Añadiendo funcionalidades (1)

Ahora lo que vamos a hacer es interactuar un poco con los componentes (o widgets) de la etiqueta (Label) y el botón (PushButton).

Por ejemplo, si queremos recuperar esos componentes y cambiar su contenido podemos hacerlo justo después de setupUi() en el constructor de la clase.

El nombre de esos componentes los encontraremos en Qt Designer, en el menú derecho:

Image for postImage for post

Como veis son “label” y “pushButton”, así que vamos a hacer referencia a ellos y a modificar sus valores iniciales haciendo uso de su método setText().

class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self, *args, **kwargs):
QtWidgets.QMainWindow.__init__(self, *args, **kwargs)
self.setupUi(self)
self.label.setText("Haz clic en el botón")
self.pushButton.setText("Presióname")

Si ejecutamos de nuevo el programa veremos como cambia su texto:

El problema como veis es que nuestra etiqueta tiene un tamaño demasiado pequeño y no cabe todo el texto.

Este es un problema que me viene como anillo al dedo para enseñaros lo útil que es tener separada la lógica del diseño.

Vamos de nuevo a Qt Designer para hacer la etiqueta más grande:

Image for postImage for post

También podríamos cambiar la alineación del texto al centro desde el Editor de propiedades, buscando alignment y cambiando la propiedad Horizontal al valor “AlinearCentroH”:

Image for postImage for post

Volvemos a guardar el diseño ventana.ui y lo transformamos otra vez a ventana_ui.py sobreescribiendo el que teníamos antes:

pyuic5 -x ventana.ui -o ventana_ui.py

Si lo hemos hecho bien, podemos volver a ejecutar nuestro programa y ahora debería verse todo el texto:

Image for postImage for post

Esta es la razón principal de separar lógica y diseño, poder rehacer el diseño en cualquier momento y no perder ninguna funcionalidad.

Añadiendo funcionalidades (2)

Lo último que vamos a hacer es añadir una acción al evento de presionado del botón, de manera que al hacer clic cambie el texto de la etiqueta.

Para hacerlo vamos a empezar creando un método en nuestra clase, podemos llamarlo como queramos por ejemplo actualizar(). Dentro simplemente accederemos al componente de la etiqueta y cambiaremos el texto:

class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self, *args, **kwargs):
QtWidgets.QMainWindow.__init__(self, *args, **kwargs)
self.setupUi(self)
self.label.setText("Haz clic en el botón")
self.pushButton.setText("Presióname")
def actualizar(self):
self.label.setText("¡Acabas de hacer clic en el botón!")

Sólo nos falta conectar el evento de clic con la llamada de nuestro método, algo que haremos mediante el atributo clicked del widget pushButton, que a su vez contiene un método connect para pasarle la función o método a ejecutar:

class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self, *args, **kwargs):
QtWidgets.QMainWindow.__init__(self, *args, **kwargs)
self.setupUi(self)
self.label.setText("Haz clic en el botón")
self.pushButton.setText("Presióname")
# Conectamos los eventos con sus acciones
self.pushButton.clicked.connect(self.actualizar)

¡Y con esto ya lo tendríamos! Si presionamos el botón debería cambiar el contenido de la etiqueta:

Image for postImage for post

El código final del programa quedaría así:

from ventana_ui import *class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self, *args, **kwargs):
QtWidgets.QMainWindow.__init__(self, *args, **kwargs)
self.setupUi(self)
self.label.setText("Haz clic en el botón")
self.pushButton.setText("Presióname")
self.pushButton.clicked.connect(self.actualizar)
def actualizar(self):
self.label.setText("¡Acabas de hacer clic en el botón!")
if __name__ == "__main__":
app = QtWidgets.QApplication([])
window = MainWindow()
window.show()
app.exec_()

Espero que os haya servido esta pequeña introducción donde os he mostrado como crear un pequeño diseño y añadirle funcionalidad de una forma práctica. Ahora es vuestro turno de investigar y aprender más por vuestra cuenta:

No hay comentarios:

Publicar un comentario