Refresh Windows Balloon Tip from Python thread

2019-02-28 09:32发布

问题:

I am using a module for balloon tip notifications in the system tray, whenever the user receives a new message. (using Python 3.3 and Kivy 1.8. I am deploying to windows 7.)

I believe the original code comes from this github.

This is module as I have altered it::

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

print ("Importing pypops")

class WindowsBalloonTip:
    def __init__(self, title, msg):

        message_map = {
                win32con.WM_DESTROY: self.OnDestroy,
                # win32con.WM_CLOSE: self.onClose
        }
        # 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")

        #Notify
        Shell_NotifyIcon(NIM_ADD, nid)
        Shell_NotifyIcon(NIM_MODIFY, \
                         (self.hwnd, 0, NIF_INFO, win32con.WM_USER+20,\
                          hicon, "Balloon  tooltip",msg,400,title))

        ### from original code
        # time.sleep(10) #cannot use this. it kills the whole app
        # DestroyWindow(self.hwnd) 
        # classAtom = UnregisterClass(classAtom, hinst) 

    def onClose(self, hwnd): #to be called from thread as a method of the class instance
        DestroyWindow(hwnd)
        classAtom = UnregisterClass(classAtom, hinst)
        return True

    def OnDestroy(self, hwnd, msg, wparam, lparam):
        nid = (self.hwnd, 0)
        Shell_NotifyIcon(NIM_DELETE, nid)
        PostQuitMessage(0) # Terminate the app.

I am instantiating the WindowsBalloonTip class in a thread, if and only if a new message comes in. After the class is instantiated, a if a subsequent thread detects another new message, I was trying to call onClose as a method of the pypops class,and getting

pywintypes.error: (1400, 'DestroyWindow', 'Invalid window handle.').

I know from other sources that this is because the thread that creates the window must be used to destroy it. What is the way around this?

The time.sleep solution, commented out in my code, is unfeasible for at least these reasons:

  1. I need the window to stay alive until a) user clicks or b) a new message comes in
  2. time.sleep() kills my whole app, prevents user input, redraws, etc

I have researched: Handling invalid window handle and many others.

How can I destroy the window and unregister the class dynamically from a thread or update the window Text?

When I try to DestroyWindow as a sequential --init-- step, it works fine. Logging 'hwnd' yields an integer. However, when I try to call onClose, I am getting the 1400 error, even though logging 'hwnd' in this case yields the same exact value as the other 'hwnd': in other words, the parameter I am passing to DestroyWindow is the same value whether DestroyWindow happens in the --init-- stage, or as part of a method I call later. What is tipping off the error in the latter case?

I am very familiar with Python, and getting better with Kivy. However, this is my first foray into any windows gui/api intercompatability.

Thanks for any answers, suggestions, criticisms, leads!

"Leads? Leads, yeah sure. I'll uh, just check with the boys down at the Crime Lab. They uh, got uh, four more detectives working on the case. They've got us working in shifts."