IronPython / PythonNET - NotifyIcon
Introducción: ¿Qué es un NotifyIcon?
Un NotifyIcon
es un icono en el área de notificación (en la barra de tareas, al lado de la fecha y hora).
Un NotifyIcon
es un componente que muestra un icono. Puede, además, mostrar un mensaje o tener asociado un menú contextual que puede abrirse haciendo click derecho en el icono.
Normalmente se utiliza para mostrar notificaciones al usuario o para acceder a un menú adicional de la aplicación.
Crear un NotifyIcon básico
El siguiente código muestra un NotifyIcon
, pero no muestra ningún mensaje ni tiene asociado un menú contextual:
# -*- coding: utf-8 -*-
import clr
clr.AddReference('System.Windows.Forms')
clr.AddReference('System.Drawing')
from System.Windows.Forms import NotifyIcon, Application
from System.Drawing import Icon
notify_icon = NotifyIcon() # Instanciar NotifyIcon
notify_icon.Icon = Icon('icon.ico') # Asignar Icono
notify_icon.Visible = True # Hacerlo visible
Application.Run() # Iniciar bucle de eventos
Para este código, en el mismo directorio en el que se encuentra el script debe existir un archivo icon.ico.
Al ejecutar este script se muestra un NotifyIcon
que no hace absolutamente nada. Puede verse así:
O puede verse junto a otros NotifyIcons en el menú desplegable:
A continuación vamos a ver como mostrar un BalloonTip
, es decir, un mensaje asociado al NotifyIcon
.
NotifyIcon con BalloonTip
Vamos a ampliar el código anterior:
# -*- coding: utf-8 -*-
import clr
clr.AddReference('System.Windows.Forms')
clr.AddReference('System.Drawing')
from System.Windows.Forms import *
from System.Drawing import *
# Crear NotifyIcon visible
notify_icon = NotifyIcon()
notify_icon.Icon = Icon('icon.ico')
notify_icon.Visible = True
# Mostrar BallonTip
notify_icon.ShowBalloonTip(0, 'Hey!', 'Que tengas un buen día! :D', ToolTipIcon.None)
# En PythonNET no puede usarse ToolTipIcon.None, en su lugar: getattr(ToolTipIcon, 'None')
Application.Run()
Al ejecutar este código, además del NotifyIcon
se muestra un mensaje: notify_icon.ShowBalloonTip(0, 'Hey!', 'Que tengas un buen día! :D', ToolTipIcon.None)
El método ShowBalloonTip
recibe los siguientes parámetros:
Tiempo de duración - Es el tiempo que permanece visible el
BalloonTip
(el mensaje). Realmente, el máximo y mínimo de este valor depende del sistema operativo, y he asignado el valor0
porque en Windows 10 este parámetro parece ignorarse (elBalloonTip
permanece visible el mismo tiempo independientemente del valor pasado para el tiempo).Título del mensaje
Texto del mensaje
Icono - Es un logo que se muestra a la izquierda del texto del mensaje. Puede tomar los siguientes valores:
ToolTipIcon.Error
ToolTipIcon.Info
ToolTipIcon.None
ToolTipIcon.Warning
En pythonnet no puede utilizarse
ToolTipIcon.None
directamente. En su lugar utilizagetattr(ToolTipIcon, 'None')
. En ironpython puede utilizarseToolTipIcon.None
directamente.
Para cerrar el mensaje puedes hacer click en el botón X. Fíjate en que aunque cierres el BalloonTip
, la aplicación sigue abierta gracias a Application.Run()
.
Podemos dotar a nuestros NotifyIcon
y BalloonTip
de algo mas de funcionalidad. Continuemos añadiendo código a nuestro script:
# -*- coding: utf-8 -*-
import clr, sys
clr.AddReference('System.Windows.Forms')
clr.AddReference('System.Drawing')
from System.Windows.Forms import *
from System.Drawing import *
notify_icon = NotifyIcon()
notify_icon.Icon = Icon('icon.ico')
notify_icon.Visible = True
# Al hacer click en el Icono (si el BalloonTip está abierto y se hace click en el icono, este evento se ignora)
notify_icon.Click += lambda *args: sys.stdout.write('Has hecho Click en el NotifyIcon\n')
# Al hacer click en el BallonTip
notify_icon.BalloonTipClicked += lambda *args: sys.stdout.write('Has hecho Click en el BalloonTip\n')
# Al cerrar el BallonTip (pulsando X o al cumplirse el tiempo indicado en ShowBalloonTip)
notify_icon.BalloonTipClosed += lambda *args: sys.stdout.write('El BalloonTip se ha cerrado\n')
ToolTipIcon.NONE = getattr(ToolTipIcon, 'None')
notify_icon.ShowBalloonTip(0, 'Hey!', 'Que tengas un buen día! :D', ToolTipIcon.NONE)
Application.Run()
En este código estamos manejando 3 eventos posibles:
NotifyIcon.Click
- Tiene lugar cuando se hace click en el icono (click izquierdo o derecho). Solamente se dispara si elBalloonTip
ya ha sido cerrado. Si elBalloonTip
está abierto y se hace click en elNotifyIcon
cuenta como si se hubiese hecho click en este último.NotifyIcon.BalloonTipClicked
- Tiene lugar cuando se hace click en elBalloonTip
o, si este aun permanece abierto, en elNotifyIcon
.NotifyIcon.BalloonTipClosed
- Tiene lugar cuando se cierra elBalloonTip
, bien haciendo click en el botón X (si está presente, depende del sistema operativo) o bien cuando se cierra automáticamente tras pasar un tiempo.
Hagamos lo que hagamos, la aplicación permanece abierta. Vamos a modificar este código y ver un ejemplo de uso un poco mas realista:
# -*- coding: utf-8 -*-
import clr
clr.AddReference('System.Windows.Forms')
clr.AddReference('System.Drawing')
from System.Windows.Forms import *
from System.Drawing import *
def close(*args):
notify_icon.Dispose()
Application.Exit()
notify_icon = NotifyIcon()
notify_icon.Icon = Icon('icon.ico')
notify_icon.Visible = True
notify_icon.BalloonTipClicked += close
notify_icon.BalloonTipClosed += close
ToolTipIcon.NONE = getattr(ToolTipIcon, 'None')
notify_icon.ShowBalloonTip(0, 'Hey!', 'Que tengas un buen día! :D', ToolTipIcon.NONE)
Application.Run()
En este ejemplo hemos definido una función close
que cierra el NotifyIcon
y finalmente la Application
. Esta función va a ser el manejador de los eventos notify_icon.BalloonTipClicked
y notify_icon.BalloonTipClosed
.
No he utilizado el evento notify_icon.Click
porque solamente se dispara cuando el BalloonTip
se ha cerrado, momento en el cual ya hemos definido que tiene que cerrarse la aplicación: notify_icon.BalloonTipClosed += close
.
NotifyIcon con ContextMenu
Un NotifyIcon
no tiene por qué llevar asociado un BalloonTip
. Otra opción es añadirle un ContextMenu
. Seguramente has utilizado aplicaciones con un NotifyIcon
en el que si haces click derecho aparece un menú con opciones. Vamos a implementarlo a continuación:
# -*- coding: utf-8 -*-
import clr
clr.AddReference('System.Windows.Forms')
clr.AddReference('System.Drawing')
from System.Windows.Forms import *
from System.Drawing import Icon
notify_icon = NotifyIcon()
notify_icon.Icon = Icon('icon.ico')
notify_icon.ContextMenu = ContextMenu()
notify_icon.Visible = True
Application.Run()
No ejecutes este código, ya que solamente mostrará el icono, y aunque hemos añadido un ContextMenu
, como este todavía no contiene nada, si haces click derecho en el icono no sucede nada.
Vamos a añadir dos MenuItem
al ContextMenu
. Uno de ellos será un botón con el texto "Open" y otro con el texto "Close":
open_option = MenuItem()
open_option.Text = 'Open'
close_option = MenuItem()
close_option.Text = 'Close'
Pero todavía tienes que añadir estas opciones al ContextMenu
:
notify_icon.ContextMenu.MenuItems.Add(open_option)
notify_icon.ContextMenu.MenuItems.Add(close_option)
Si ahora ejecutas el script y haces click derecho en el NotifyIcon
verás el siguiente ContextMenu
:
Sin embargo, al hacer click en cualquiera de los dos botones todavía no sucede nada. Para que suceda algo al hacer click simplemente tienes que manejar el evento Click
de cada una de las opciones:
# Manejamos el evento Click de open_option
open_option.Click += lambda *args: notify_icon.ShowBalloonTip(0,
'Mensaje',
'Has hecho click en [ Open ]',
ToolTipIcon.Info
)
# Manejamos el evento Click de close_option
close_option.Click += lambda *args: (
notify_icon.Dispose(),
Application.Exit()
)
En este caso, si haces click en [Open] se muestra un BalloonTip
, y si haces click en [Close] se cierra primero el NotifyIcon
y a continuación la aplicación.
El código completo de este ejemplo es el siguiente:
# -*- coding: utf-8 -*-
import clr
clr.AddReference('System.Windows.Forms')
clr.AddReference('System.Drawing')
from System.Windows.Forms import *
from System.Drawing import Icon
notify_icon = NotifyIcon()
notify_icon.Icon = Icon('icon.ico')
notify_icon.ContextMenu = ContextMenu()
notify_icon.Visible = True
open_option = MenuItem()
open_option.Text = 'Open'
close_option = MenuItem()
close_option.Text = 'Close'
# Manejamos el evento Click de open_option
open_option.Click += lambda *args: notify_icon.ShowBalloonTip(0,
'Mensaje',
'Has hecho click en [ Open ]',
ToolTipIcon.Info
)
# Manejamos el evento Click de close_option
close_option.Click += lambda *args: (
notify_icon.Dispose(),
Application.Exit()
)
notify_icon.ContextMenu.MenuItems.Add(open_option)
notify_icon.ContextMenu.MenuItems.Add(close_option)
Application.Run()
NotifyIcon con ContextMenu al hacer click en X
Por defecto, cuando se hace click en el botón X de una aplicación, esta se cierra. Pero el programador de la aplicación puede cambiar este comportamiento. Algunas aplicaciones en vez de cerrarse, se ocultan al usuario cuando se hace click en el botón X, pero permanecen abiertas en segundo plano.
Para ver como implementar este comportamiento crearemos una sencilla aplicación WinForms. Empecemos:
import clr
clr.AddReference('System.Windows.Forms')
clr.AddReference('System.Drawing')
from System.Windows.Forms import *
from System.Drawing import *
class MainForm(Form):
def __init__(self):
self.Text = 'App'
self.Icon = Icon('icon.ico')
form = MainForm()
Application.EnableVisualStyles()
Application.Run(form)
Si ejecutas este código, se muetra un formulario vacío con el título "App", y el icono icon.ico. Si haces click en el botón X la aplicación se cierra.
Vamos a definir un método en el que crearemos un NotifyIcon
con un ContextMenu
. Será el método MainForm.make_notify_icon
:
def make_notify_icon(self):
# Crear un NotifyIcon con un Menú contextual
self.notify_icon = NotifyIcon()
self.notify_icon.Icon = self.Icon
self.notify_icon.ContextMenu = ContextMenu()
self.notify_icon.Visible = False
# Crear las opciones del Menú contextual
open_option = MenuItem()
open_option.Text = 'Open'
open_option.Click += self.show_me
close_option = MenuItem()
close_option.Text = 'Close'
close_option.Click += self.on_close
# Añadir las opciones al Menú contextual
self.notify_icon.ContextMenu.MenuItems.Add(open_option)
self.notify_icon.ContextMenu.MenuItems.Add(close_option)
Fíjate en la línea self.notify_icon.Visible = False
: Al crear el NotifyIcon
este será invisible. Tenemos que crear dos métodos que serán los manejadores del evento Click
para las opciones del ContextMenu
: Para open_option
crearemos MainForm.show_me
y para close_option
crearemos MainForm.on_close
.
Por otra parte, como ya hemos utilizado el archivo icon.ico para que sea el icono de MainForm
, en lugar de crear un nuevo System.Drawing.Icon
, lo reutilizaremos: self.notify_icon.Icon = self.Icon
En estos momentos, el código de la clase MainForm
se ve así:
class MainForm(Form):
def __init__(self):
self.Text = 'App'
self.Icon = Icon('icon.ico')
self.make_notify_icon()
def make_notify_icon(self):
# Crear un NotifyIcon con un Menú contextual
self.notify_icon = NotifyIcon()
self.notify_icon.Icon = self.Icon
self.notify_icon.ContextMenu = ContextMenu()
self.notify_icon.Visible = False
# Crear las opciones del Menú contextual
open_option = MenuItem()
open_option.Text = 'Open'
open_option.Click += self.show_me
close_option = MenuItem()
close_option.Text = 'Close'
close_option.Click += self.on_close
# Añadir las opciones al Menú contextual
self.notify_icon.ContextMenu.MenuItems.Add(open_option)
self.notify_icon.ContextMenu.MenuItems.Add(close_option)
def show_me(self, *args):
pass
def on_close(self, *args):
pass
De momento los métodos show_me
y on_close
no hacen nada. En el método __init__
hemos añadido la sentencia self.make_notify_icon()
para crear el NotifyIcon
de la aplicación en el momento de instanciar a MainForm
. Recuerda que, en principio, el notify-icon es invisible: self.notify_icon.Visible = False
.
Definamos pues los métodos show_me
y on_close
:
Método show_me
# Se ejecuta cuando se hace click en el botón [Open] del ContextMenu
# Hace visible a MainForm y oculta al NotifyIcon
def show_me(self, *args):
self.Visible = True
self.notify_icon.Visible = False
Método on_close
# Se ejecuta cuando se hace click en el botón [Close] del ContextMenu
# Cierra el NotifyIcon, el MainForm y finalmente la Application
def on_close(self, *args):
self.notify_icon.Dispose()
self.Dispose()
Application.Exit()
Ya sólo nos falta modificar lo que sucede al hacer click en el botón X, que por defecto cierra la aplicación. Podemos modificar este comportamiento manejando el evento Closing
del formulario. Añade lo siguiente al método __init__
:
self.Closing += self.cancel_close_and_notify_icon
Ahora tenemos que definir el método MainForm.cancel_close_and_notify_icon
, que contendrá el código que debe ejecutarse cuando se haga click en el botón X:
def cancel_close_and_notify_icon(self, sender, e):
e.Cancel = True
self.Visible = False
self.notify_icon.Visible = True
Para cancelar lo acción por defecto, es decir, para cancelar el cierre de la aplicación, utilizamos e.Cancel = True
. A continuación hacemos invisible a MainForm
y visible al NotifyIcon
. Este método hace lo contrario que el método show_me
.
El código de esta aplicación que no se cierra al hacer click en el botón X queda finalmente así:
# -*- coding: utf-8 -*-
# Al hacer click en el botón X de la caja de control de MainForm, este se hace invisible
# y un NotifyIcon que tiene un ContextMenu se hace visible. El ContextMenu permite
# volver al estado anterior o cerrar la aplicación completamente.
import clr
clr.AddReference('System.Windows.Forms')
clr.AddReference('System.Drawing')
from System.Windows.Forms import *
from System.Drawing import *
class MainForm(Form):
def __init__(self):
self.Text = 'App'
self.Icon = Icon('icon.ico')
self.make_notify_icon()
self.Closing += self.cancel_close_and_notify_icon
def make_notify_icon(self):
# Crear un NotifyIcon con un Menú contextual
self.notify_icon = NotifyIcon()
self.notify_icon.Icon = self.Icon
self.notify_icon.ContextMenu = ContextMenu()
self.notify_icon.Visible = False
# Crear las opciones del Menú contextual
open_option = MenuItem()
open_option.Text = 'Open'
open_option.Click += self.show_me
close_option = MenuItem()
close_option.Text = 'Close'
close_option.Click += self.on_close
# Añadir las opciones al Menú contextual
self.notify_icon.ContextMenu.MenuItems.Add(open_option)
self.notify_icon.ContextMenu.MenuItems.Add(close_option)
def cancel_close_and_notify_icon(self, sender, e):
e.Cancel = True
self.Visible = False
self.notify_icon.Visible = True
def show_me(self, *args):
self.Visible = True
self.notify_icon.Visible = False
def on_close(self, *args):
self.notify_icon.Dispose()
self.Dispose()
Application.Exit()
form = MainForm()
Application.EnableVisualStyles()
Application.Run(form)
NotifyIcon con ContextMenu al minimizar
Puedes hacer que el formulario de la aplicación "desaparezca" al hacer click en el botón de minimizar de la caja de control, y que entonces se muestre el NotifyIcon
con opciones en su ContextMenu
.
Para hacerlo, toma el código que hemos creado anteriormente (el de mostrar el NotifyIcon
y ocultar el formulario al presionar el botón X) y aplica las siguientes modificaciones:
Elimina el método
MainForm.cancel_close_and_notify_icon
y en el método__init__
sustituyeself.Closing += self.cancel_close_and_notify_icon
porself.Closing += self.on_close
para que al hacer click en el botón X se cierre correctamente la aplicación.Crea un método llamado
on_resize
y haz que sea el manejador del eventoResize
del formulario:def __init__(self): self.Text = 'App' self.Icon = Icon('icon.ico') self.make_notify_icon() self.Resize += self.on_resize # <-- Atención self.Closing += self.on_close def on_resize(self, *args): pass
Modifica el método
show_me
así:def show_me(self, *args): self.Visible = True self.WindowState = FormWindowState.Normal # <-- Atención self.notify_icon.Visible = False
De esta manera, cuando hagamos visible al formulario, este recuperará su estado normal. Si no asignamos
self.WindowState = FormWindowState.Normal
después de hacer visible al formulario, este aparecerá lo mas pequeño posible.
Una vez aplicadas estas modificaciones, vamos a definir el método on_resize
. El evento Resize
tiene lugar siempre que se redimensiona el formulario, y cuando se hace click en el botón de minimizar de la caja de control lo que sucede es una redimensión del formulario.
Lo que queremos conseguir es cuando se minimice, el formulario se haga invisible y aparezca el NotifyIcon
:
def on_resize(self, *args):
if self.WindowState == FormWindowState.Minimized:
self.Visible = False
self.notify_icon.Visible = True
Con este código el formulario se hace invisible (tampoco aparece en la barra de tareas), y el NotifyIcon
se hace visible cuando se dispara el evento Resize
, que invoca a on_resize
y verifica que el estado de la ventana es minimizado.
El código completo de este ejemplo de aplicación es el siguiente:
# -*- coding: utf-8 -*-
# Al minimizar el MainForm, este se hace invisible y un NotifyIcon
# que tiene un ContextMenu se hace visible. El ContextMenu permite
# volver al estado anterior o cerrar la aplicación completamente.
import clr
clr.AddReference('System.Windows.Forms')
clr.AddReference('System.Drawing')
from System.Windows.Forms import *
from System.Drawing import *
class MainForm(Form):
def __init__(self):
self.Text = 'App'
self.Icon = Icon('icon.ico')
self.make_notify_icon()
self.Resize += self.on_resize
self.Closing += self.on_close
def make_notify_icon(self):
# Crear un NotifyIcon con un Menú contextual
self.notify_icon = NotifyIcon()
self.notify_icon.Icon = self.Icon
self.notify_icon.ContextMenu = ContextMenu()
self.notify_icon.Visible = False
# Crear las opciones del Menú contextual
open_option = MenuItem()
open_option.Text = 'Open'
open_option.Click += self.show_me
close_option = MenuItem()
close_option.Text = 'Close'
close_option.Click += self.on_close
# Añadir las opciones al Menú contextual
self.notify_icon.ContextMenu.MenuItems.Add(open_option)
self.notify_icon.ContextMenu.MenuItems.Add(close_option)
def on_resize(self, *args):
if self.WindowState == FormWindowState.Minimized:
self.Visible = False
self.notify_icon.Visible = True
def show_me(self, *args):
self.Visible = True
self.WindowState = FormWindowState.Normal
self.notify_icon.Visible = False
def on_close(self, *args):
self.notify_icon.Dispose()
self.Dispose()
Application.Exit()
form = MainForm()
Application.EnableVisualStyles()
Application.Run(form)