== Introduction == Sometimes you may need to catch a Windows message that is not already handled by wxWidgets, so there is no wxEvent for it. With a bit of help from the `PyWin32` modules it is possible to hook into the `WndProc` chain for a wxWindow and watch for the message you are interested in. The magic is in the `win32gui.SetWindowLong` function. When used with the `win32con.GWL_WNDPROC` flag it causes a new `WndProc` to be set for the window, and returns the old one. This lets you write a function in Python that can get first crack at all the Windows messages being sent to the window, and if you are not interested in them then pass them on to the original wxWidgets `WndProc`. == Code Sample == {{{ #!python import wx import win32api import win32gui import win32con class TestFrame(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, title="WndProc Test", size=(200,150)) p = wx.Panel(self) # Set the WndProc to our function self.oldWndProc = win32gui.SetWindowLong(self.GetHandle(), win32con.GWL_WNDPROC, self.MyWndProc) # Make a dictionary of message names to be used for printing below self.msgdict = {} for name in dir(win32con): if name.startswith("WM_"): value = getattr(win32con, name) self.msgdict[value] = name def MyWndProc(self, hWnd, msg, wParam, lParam): # Display what we've got. print (self.msgdict.get(msg), msg, wParam, lParam) # Restore the old WndProc. Notice the use of wxin32api # instead of win32gui here. This is to avoid an error due to # not passing a callable object. if msg == win32con.WM_DESTROY: win32api.SetWindowLong(self.GetHandle(), win32con.GWL_WNDPROC, self.oldWndProc) # Pass all messages (in this case, yours may be different) on # to the original WndProc return win32gui.CallWindowProc(self.oldWndProc, hWnd, msg, wParam, lParam) app = wx.PySimpleApp() f = TestFrame() f.Show() app.MainLoop() }}} Here is a similar example using the ctypes module. {{{#!python import ctypes import wx from ctypes import c_long, c_int # grab the functions we need - unicode/not doesn't really matter # for this demo SetWindowLong = ctypes.windll.user32.SetWindowLongW CallWindowProc = ctypes.windll.user32.CallWindowProcW # a function type for the wndprc so ctypes can wrap it WndProcType = ctypes.WINFUNCTYPE(c_int, c_long, c_int, c_int, c_int) # constants GWL_WNDPROC = -4 class TestFrame(wx.Frame): def __init__(self, parent): wx.Frame.__init__(self, parent) # need to hold a reference to WINFUNCTYPE wrappers, # so they don't get GCed self.newWndProc = WndProcType(self.MyWndProc) self.oldWndProc = SetWindowLong( self.GetHandle(), GWL_WNDPROC, self.newWndProc ) def MyWndProc(self, hWnd, msg, wParam, lParam): print msg,wParam,lParam return CallWindowProc( self.oldWndProc, hWnd, msg, wParam, lParam ) app =wx.App(False) f = TestFrame(None) f.Show() app.MainLoop() }}} === Comments === -- M. Vernier -- Michael Krause Thanks for the fixes and suggestions guys. I've incorporated your code above. --RobinDunn You must do this to capture any Windows messages that are outside the domain of the wxPython (and wxWidgets) system. This includes you own custom messages and more obscure things like WM_DEVICECHANGE. If wx doesn't catch it at the low level, then your event registration is not going to help. I originally was trying solutions based on PythonEvents.py in the PythonDemo app, but Robin pointed me here. Thanks Robin! I wrote a little module that provides a class that you can mixin with any wxWindows window to get this functionality. I've tested it with WxFrame and wxPanel, but I didn't go any lower that that. Comments are welcome. It is attached as [[attachment:WndProcHookMixin.py]] --KevinGMoore I've modified Kevin's mixin class to be pure ctypes, so you can get this functionality with no additional dependencies under python 2.5. Attached: [[attachment:WndProcHookMixinCtypes.py]] --ChrisMellon