Personal Website

My Web: MindEchoes.com

Sunday, March 7, 2010

PyQT: Aplicacion en SystemTray

La idea de este Post es explicar de forma simple, como realizar una aplicación en Python, utilizando PyQt, donde esta aplicación corra en el SystemTray (o pueda minimizarse al SystemTray) y tengamos un menu desplegable al presionar el icono de la aplicación en el SystemTray.

Primero que nada necesitamos importar algunos modulos que seran usados por nuestra aplicación:
import sys
from PyQt4 import QtGui, QtCore
  • El modulo "sys", nos permite poner a correr la aplicación usando utilidades del sistema.
  • El modulo PyQt4, es el binding de Qt para Python y contiene todos los componentes graficos que podamos precisar, manejo de eventos de los componentes, etc. Principalmente se usara "QtGui" para crear instancias de los componentes gráficos, y "QtCore" para conectar señales de estos componentes con alguna operación.
Una vez importados los modulos necesarios, procedemos a crear una clase que extienda de QWidget para construir la aplicación, y le daremos un icono y nombre a la ventana cuando se muestre:
import sys
from PyQt4 import QtGui, QtCore

class PyTest(QtGui.QWidget):

    def __init__(self):
        QtGui.QWidget.__init__(self)
        #cargar imagen para icono
        pixmap = QtGui.QPixmap('pytv.png')
        #setear el nombre de la ventana
        self.setWindowTitle('PyTest!')
        #colocar el icono cargado a la ventana
        self.setWindowIcon(QtGui.QIcon(pixmap))
        #creamos objeto Style para hacer uso de los iconos de Qt
        self.style = self.style()

En la primer linea del método lo que debemos hacer es invocar el constructor de la clase Padre, luego creamos un objeto QPixmap pasandole la ruta del archivo imagen (en este caso es una ruta relativa en el mismo directorio de ejecución), y luego le damos el nombre e icono a la ventana.

Ahora para crear un Menu y agregarle Acciones (cada elemento del menu) hay diversas formas, podemos crear un objeto accion y luego agregarlo al menu pasandole ese objeto, tambien podemos llamar al método "addAction" de menu pero esta vez pasarle un String y nos devolvera una instancia de un objeto QAction con ese nombre ya inicializado, y diversas formas mas.
En este caso vamos a usar la opción del String que es más fácil y directa:
#Menu
self.menu = QtGui.QMenu('PyTv')
#accion mostrar
show = self.menu.addAction(self.style.standardIcon
  (QtGui.QStyle.SP_ArrowRight), 'Show Window')
#accion salir
exit = self.menu.addAction(self.style.standardIcon
       (QtGui.QStyle.SP_TitleBarCloseButton), 'exit')
Ahora que contamos con el Menu creado, vamos a conectar las distintas señales del Menu con operaciones que queremos que se realicen para responder a los eventos del usuario.
#SIGNAL->SLOT
QtCore.QObject.connect(exit, QtCore.SIGNAL("triggered()"),
   sys.exit)
QtCore.QObject.connect(self.menu, QtCore.SIGNAL("clicked()"),
   lambda: self.menu.popup(QtGui.QCursor.pos()))
QtCore.QObject.connect(show, QtCore.SIGNAL("triggered()"),
   self.showWindow)
"QtCore.QObject.connect" nos permite conectar una señal de alguno de nuestros objetos de QtGui con una acción que queremos que se realice.
La conexión SIGNAL->SLOT es una forma de decir que con determinada señal se va a ejecutar determinado fragmento de código. En Python a diferencia de C++ no hace falta que implementemos un método SLOT, simplemente podemos pasarle el nombre del método que deseamos que se ejecute como se ve en el tercer caso donde se conecta la señal "triggered()" del objeto QAction "show" con el método "showWindow(self)" de la actual Clase.
En cambio, la segunda señal se prentende conectar a un método que debe recibir algún parametro al ser invocado, para estos casos podemos hacer uso de "lambda" en Python y llamar a cualquier función que deseemos.
En el caso del menu, es necesario que sea definido como una variable de la Clase, porque sino es posible que la señal asociada al menu deje de responder en algún momento.

Ahora vamos a crear el icono de SystemTray, hacerlo visible en el SystemTray y agregarle el Menu que ya creamos para que este se despliegue al presionar el boton derecho del mouse sobre el icono:
#SystemTray
self.tray = QtGui.QSystemTrayIcon(QtGui.QIcon(pixmap), self)
self.tray.setToolTip('PyTest')
self.tray.setVisible(True)
self.tray.setContextMenu(self.menu)
Ahora procedemos a crear la función que se ejecuta al presionar la acción del Menu: "Show Window" (este método lo que hara es mostrar la ventana del programa cuando este evento es disparado)
    def showWindow(self):
        self.setVisible(True)
Ahora si queremos que al cerrar la ventana no se cierre la aplicación sino que se minimice al SystemTray, lo que tenemos que hacer es sobreescribir el método "closeEvent" de la clase QWidget de la que heredamos de la siguiente forma:
    def closeEvent(self, event):
        event.ignore()
        self.hide()
  • "event.ignore()" lo que hace es ignorar la señal de cierre de la aplicación que esta por ejecutarse.
  • "self.hide()" oculta la ventana pero mantiene corriendo la aplicación
Ahora supongamos que queremos que al abrirse la aplicación no se muestre ninguna ventana, sino que se inicie directamente solo con el icono en el SystemTray, para ello debemos sobreescribir el método "show" de la Clase padre QWidget:
    def show(self):
        pass
Debido a esto, cuando necesitemos mostrar la ventana, como en el caso de la acción "Show Window", deberemos invocar el metodo "setVisible(bool)" como se puede ver en el método mas arriba, ya que si llamaramos a "self.show()" no sucederia nada.

Y por último, hacer que nuestra aplicación pueda ser ejecutable:
app = QtGui.QApplication(sys.argv)
pytest = PyTest()
pytest.show()

sys.exit(app.exec_())
Para descargar el ejemplo completo: pytest.zip

No comments: