Mailing List Archive

[pythonwin] popup menu problems (was: [Pythonwin] Help needed creating a Window)
Thanks to Mark Hammond's help I now have an app that displays an icon
in the system tray which I can process messages from. However when I
try to create a popup menu menu for it nothing happens. The call to
PyCMenu.TrackPopupMenu doesnt display anything and never returns. I
have tried both using it to send a notify message to the window and
returning a command, but it didnt make any difference. I am unsure
whether it is a failure of my understanding of the way Windows does
these things, a bug in my code, or a problem with pythonwin.


I also have the minor problem that when I destroy the icon it doesnt
disappear until I move the mouse pointer over it. Does anyone know how
to force the system tray to redraw?

Any help would be greatly appreciated.

Dave K


Here is the relevant code:

#--------------- 8< --------------------------------------
class Icon:
#constructor
# param: dll - handle to the dll containing the icon resources
# param: startIcon - the initial icon resource ID to display
# param: tooltip = the string to display when the cursor is over
# the icon
def __init__( self, dll, startIcon, tooltip ):
self.iconMap = {}
self.msgID = win32con.WM_USER+20
launchID = win32con.WM_USER+21
quitID = win32con.WM_USER+22
message_map = {
win32con.WM_DESTROY: self.OnDestroy,
self.msgID : self.OnTaskbarNotify,
win32con.WM_CONTEXTMENU : self.OnContextMenu,
}
#the menu_map contains the information on the popup menu
#entries -
# message ID, callback function, text string
menu_map = (
( launchID, self.OnLaunch, "Launch Browser" ),
( quitID, self.OnExit, "Exit program" )
)

self.dll = dll

#add menu commands to the message_map dictionary
for entry in menu_map:
message_map[entry[0]] = entry[1]

#define a window class for the icon handler
wc = win32gui.WNDCLASS()
hinst = wc.hInstance = win32gui.GetModuleHandle(None)
wc.lpszClassName = "BaxterTaskbar"
wc.style = win32con.CS_VREDRAW | win32con.CS_HREDRAW
wc.hCursor = win32gui.LoadCursor( 0, win32con.IDC_ARROW )
wc.hbrBackground = win32con.COLOR_WINDOW
wc.lpfnWndProc = message_map
classAtom = win32gui.RegisterClass(wc)

#create the window
style = win32con.WS_OVERLAPPED | win32con.WS_SYSMENU
self.hwnd = win32gui.CreateWindow( classAtom, "title", style,
0,0, win32con.CW_USEDEFAULT, win32con.CW_USEDEFAULT, 0, 0, hinst,
None)
win32gui.UpdateWindow(self.hwnd)
self.cWnd = win32ui.CreateWindowFromHandle( self.hwnd )

#initialise the icon display
icon = win32gui.LoadIcon( 0, win32con.IDI_APPLICATION )
flags = win32gui.NIF_ICON \
| win32gui.NIF_MESSAGE \
| win32gui.NIF_TIP
nid = (self.hwnd, 0, flags, self.msgID, icon, tooltip)
win32gui.Shell_NotifyIcon(win32gui.NIM_ADD, nid)
self.changeIcon( startIcon )

#initialise the popup menu
self.cMenu = win32ui.CreateMenu()
for entry in menu_map:
self.cMenu.AppendMenu( win32con.MF_STRING, entry[0],
entry[2] )

####################################
# this is the bit that actually calls the popup menu
####################################
def OnTaskbarNotify(self, hwnd, msg, wparam, lparam):
if lparam==win32con.WM_LBUTTONDBLCLK:
print "double clicked"
elif lparam==win32con.WM_RBUTTONUP:
pos = win32api.GetCursorPos()
print "opening menu at", pos
self.cMenu.TrackPopupMenu( pos, owner=self.cWnd )

#alternative attempt returning a command:
#cmd = self.cMenu.TrackPopupMenu( pos,
# win32con.TPM_LEFTALIGN
# |win32.TPM_LEFTBUTTON
# |win32con.TPM_NONOTIFY
# |win32con.TPM_RETURNCMD, None )

print "finished popup menu"
return 1

#the following methods are included for completeness, but are not
# part of the problem

#change to a new icon - if hasnt been loaded then do so, otherwise

#use cached icon
def changeIcon( self, iconResource ):
if self.iconMap.has_key( iconResource ):
icon = self.iconMap[iconResource]
else:
icon = win32gui.LoadIcon(self.dll, iconResource)
self.iconMap[iconResource] = icon

flags = win32gui.NIF_ICON
nid = (self.hwnd, 0, flags, 0, icon)
win32gui.Shell_NotifyIcon(win32gui.NIM_MODIFY, nid)

def changeTooltip( self, tip ):
flags = win32gui.NIF_TIP
nid = (self.hwnd, 0, flags, 0, 0, tip)
win32gui.Shell_NotifyIcon(win32gui.NIM_MODIFY, nid)


#destructor - remove icon
def __del__( self ):
nid = (self.hwnd, 0, win32gui.NIF_MESSAGE, self.msgID )
win32gui.Shell_NotifyIcon(NIM_DELETE, nid)
win32gui.UpdateWindow(self.hwnd)

def OnDestroy(self, hwnd, msg, wparam, lparam):
nid = (self.hwnd, 0, win32gui.NIF_MESSAGE, self.msgID )
win32gui.Shell_NotifyIcon(NIM_DELETE, nid)
win32gui.UpdateWindow(self.hwnd)

def OnContextMenu(self, *args):
print "Icon.OnContextMenu", args

def OnLaunch(self, *args ):
print "Icon.OnLaunch", args

def OnExit(self, *args ):
print "Icon.OnExit", args
[pythonwin] popup menu problems (was: [Pythonwin] Help needed creating a Window) [ In reply to ]
On Mon, 14 Jun 1999 15:53:03 GMT, I wrote:

>Thanks to Mark Hammond's help I now have an app that displays an icon
>in the system tray which I can process messages from. However when I
>try to create a popup menu menu for it nothing happens. The call to
>PyCMenu.TrackPopupMenu doesnt display anything and never returns. I
>have tried both using it to send a notify message to the window and
>returning a command, but it didnt make any difference. I am unsure
>whether it is a failure of my understanding of the way Windows does
>these things, a bug in my code, or a problem with pythonwin.
> [ example code snipped ]

I have discovered what the problem was - the line
self.cMenu.TrackPopupMenu( pos, owner=self.cWnd )
was failing with an exception since it didnt like the use of named
parameters, but the exception was geting discarded somewhere along the
way. I only found it when I enclosed the line in a try/except clause.
I *think* the exception is getting caught & thrown away somewhere in
the pythonwin code since I didnt have any try clauses in my code at
that point.

I now have the popup menu working, except for one small problem. When
I run it using python(w).exe the menu appears in the correct place and
the user can select a menu item ok, but clicking outside of the menu
doesn't dismiss it. There is no way to get rid of the menu without
selecting a menu item. When I run it from within Pythonwin.exe it
works fine[1], so I presume that the problem is that there is no top
level window to process the WM_CANCELMODE message. Is there a way to
register my icon window to handle this message, or do I need to go the
full PythonWin route? My app doesnt use the doc/view architecture so I
want to avoid unnecessary complication if possible. From looking at
the source for win32ui.dll the menu routines call the C API directly,
bypassing MFC, so I would have thought it was possible.

Dave K


[1] well, almost - further experimentation shows that it only closes
the menu if I click within the Pythonwin window, rather than anywhere
on the screen.
[pythonwin] popup menu problems (was: [Pythonwin] Help needed creating a Window) [ In reply to ]
Dave Kirby wrote in message <376f6b7a.760974@nnrp.uk.insnet.net>...

>way. I only found it when I enclosed the line in a try/except clause.
>I *think* the exception is getting caught & thrown away somewhere in
>the pythonwin code since I didnt have any try clauses in my code at
>that point.

Eeek. If you can determine this is true, I would like to know about it.

>level window to process the WM_CANCELMODE message. Is there a way to
>register my icon window to handle this message, or do I need to go the

win32ui.HookMessage() should do the job?

>[1] well, almost - further experimentation shows that it only closes
>the menu if I click within the Pythonwin window, rather than anywhere
>on the screen.

Well, maybe you should set the owner to the desktop?

Mark.