Search code examples
pythonpython-2.7notificationspywin32system-tray

Multiple WindowsBaloonTip / TrayTip Notifications?


If you were to use this code below to create TrayTips (BaloonTips) in the Notification Area you will notice that it only allows one message and then gets stuck and errors out.

The code was taken from here:

# -- coding: utf-8 --

from win32api import *
from win32gui import *
import win32con
import sys, os
import struct
import time

class WindowsBalloonTip:
    def __init__(self, title, msg):
        message_map = {
                win32con.WM_DESTROY: self.OnDestroy,
        }
        # Register the Window class.
        wc = WNDCLASS()
        hinst = wc.hInstance = GetModuleHandle(None)
        wc.lpszClassName = "PythonTaskbar"
        wc.lpfnWndProc = message_map # could also specify a wndproc.
        classAtom = RegisterClass(wc)
        # Create the Window.
        style = win32con.WS_OVERLAPPED | win32con.WS_SYSMENU
        self.hwnd = CreateWindow( classAtom, "Taskbar", style, \
                0, 0, win32con.CW_USEDEFAULT, win32con.CW_USEDEFAULT, \
                0, 0, hinst, None)
        UpdateWindow(self.hwnd)
        iconPathName = os.path.abspath(os.path.join( sys.path[0], "balloontip.ico" ))
        icon_flags = win32con.LR_LOADFROMFILE | win32con.LR_DEFAULTSIZE
        try:
           hicon = LoadImage(hinst, iconPathName, \
                    win32con.IMAGE_ICON, 0, 0, icon_flags)
        except:
          hicon = LoadIcon(0, win32con.IDI_APPLICATION)
        flags = NIF_ICON | NIF_MESSAGE | NIF_TIP
        nid = (self.hwnd, 0, flags, win32con.WM_USER+20, hicon, "tooltip")
        Shell_NotifyIcon(NIM_ADD, nid)
        Shell_NotifyIcon(NIM_MODIFY, \
                         (self.hwnd, 0, NIF_INFO, win32con.WM_USER+20,\
                          hicon, "Balloon  tooltip",msg,200,title))
        # self.show_balloon(title, msg)
        time.sleep(10)
        DestroyWindow(self.hwnd)
    def OnDestroy(self, hwnd, msg, wparam, lparam):
        nid = (self.hwnd, 0)
        Shell_NotifyIcon(NIM_DELETE, nid)
        PostQuitMessage(0) # Terminate the app.

def balloon_tip(title, msg):
    w=WindowsBalloonTip(title, msg)

Solution

  • As you can see if you were to try and send multiple balloon_tip's to the WindowsBalloonTip class you will get the following error:

    File "C:/WindowsBalloonTip.py", line 20, in __init__
        classAtom = RegisterClass(wc)
    error: (1410, 'RegisterClass', 'Class already exists.')
    

    Looking at Microsoft's documentation of the RegisterClass you need to Unregister the class as well by using UnregisterClass(lpClassName, hInstance)

    http://msdn.microsoft.com/en-us/library/ms644899

    We can do this by adding: classAtom = UnregisterClass(classAtom, hinst) but it must be added AFTER the window has been destroyed for example here is a fixed full version

    # -- coding: utf-8 --
    
    from win32api import *
    from win32gui import *
    import win32con
    import sys, os
    import struct
    import time
    
    # Class
    class WindowsBalloonTip:
        def __init__(self, title, msg):
            message_map = { win32con.WM_DESTROY: self.OnDestroy,}
    
            # Register the window class.
            wc = WNDCLASS()
            hinst = wc.hInstance = GetModuleHandle(None)
            wc.lpszClassName = 'PythonTaskbar'
            wc.lpfnWndProc = message_map # could also specify a wndproc.
            classAtom = RegisterClass(wc)
    
            # Create the window.
            style = win32con.WS_OVERLAPPED | win32con.WS_SYSMENU
            self.hwnd = CreateWindow(classAtom, "Taskbar", style, 0, 0, win32con.CW_USEDEFAULT, win32con.CW_USEDEFAULT, 0, 0, hinst, None)
            UpdateWindow(self.hwnd)
    
            # Icons managment
            iconPathName = os.path.abspath(os.path.join( sys.path[0], 'balloontip.ico' ))
            icon_flags = win32con.LR_LOADFROMFILE | win32con.LR_DEFAULTSIZE
            try:
                hicon = LoadImage(hinst, iconPathName, win32con.IMAGE_ICON, 0, 0, icon_flags)
            except:
                hicon = LoadIcon(0, win32con.IDI_APPLICATION)
            flags = NIF_ICON | NIF_MESSAGE | NIF_TIP
            nid = (self.hwnd, 0, flags, win32con.WM_USER+20, hicon, 'Tooltip')
    
            # Notify
            Shell_NotifyIcon(NIM_ADD, nid)
            Shell_NotifyIcon(NIM_MODIFY, (self.hwnd, 0, NIF_INFO, win32con.WM_USER+20, hicon, 'Balloon Tooltip', msg, 200, title))
            # self.show_balloon(title, msg)
            time.sleep(5)
    
            # Destroy
            DestroyWindow(self.hwnd)
            classAtom = UnregisterClass(classAtom, hinst)
        def OnDestroy(self, hwnd, msg, wparam, lparam):
            nid = (self.hwnd, 0)
            Shell_NotifyIcon(NIM_DELETE, nid)
            PostQuitMessage(0) # Terminate the app.
    
    # Function
    def balloon_tip(title, msg):
        w=WindowsBalloonTip(title, msg)
    
    # Main
    if __name__ == '__main__':
        # Example
        balloon_tip('Lorem Ipsum', 'Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit...')
        balloon_tip('Example two', 'There is no one who loves pain itself, who seeks after it and wants to have it, simply because it is pain...')
    

    There are issues on certain operating systems with the icon loading so if you want your own custom icon then change this line:

    iconPathName = os.path.abspath(os.path.join( sys.path[0], 'balloontip.ico' ))
    

    To something like:

    iconPathName = "C:\myfolder\myicon.ico"