19

I am writing simple tray for windows using python.

I succeeded in creating a tray icon, menu, sub menu. I stucked at adding image for particular tray item.

here is code I used. (Link) Even this code did not work. Windows documentation is not clear.

def addMenuItem(self, wID, title, menu):
        path = os.path.dirname(os.path.abspath(__file__))
        path += "\print_pref.ico"
        option_icon = self.prep_menu_icon(path)
        item, extras = win32gui_struct.PackMENUITEMINFO(text=title,
                                                                hbmpItem=option_icon,
                                                                wID=wID)

        win32gui.InsertMenuItem(menu, 0, 1, item)


def prep_menu_icon(self, icon):
        # First load the icon.
        ico_x = win32api.GetSystemMetrics(win32con.SM_CXSMICON)
        ico_y = win32api.GetSystemMetrics(win32con.SM_CYSMICON)
        hicon = win32gui.LoadImage(0, icon, win32con.IMAGE_ICON, ico_x, ico_y, win32con.LR_LOADFROMFILE)

        hdcBitmap = win32gui.CreateCompatibleDC(0)
        hdcScreen = win32gui.GetDC(0)
        hbm = win32gui.CreateCompatibleBitmap(hdcScreen, ico_x, ico_y)
        hbmOld = win32gui.SelectObject(hdcBitmap, hbm)
        # Fill the background.
        brush = win32gui.GetSysColorBrush(win32con.COLOR_MENU)
        win32gui.FillRect(hdcBitmap, (0, 0, 16, 16), brush)
        # unclear if brush needs to be feed.  Best clue I can find is:
        # "GetSysColorBrush returns a cached brush instead of allocating a new
        # one." - implies no DeleteObject
        # draw the icon
        win32gui.DrawIconEx(hdcBitmap, 0, 0, hicon, ico_x, ico_y, 0, 0, win32con.DI_NORMAL)
        win32gui.SelectObject(hdcBitmap, hbmOld)
        win32gui.DeleteDC(hdcBitmap)
        return hbm

Can someone help me.

Edit

self.tray = win32gui.CreatePopupMenu()
self.addMenuItem(1, "Open", self.tray)

Attaching image. In small box beside "Open" I want image to come. enter image description here

Durgaprasad
  • 1,910
  • 2
  • 25
  • 44
  • 1
    any chance you can post a complete example, i tried to help with this, but i can't even get as far as making the menu appear, i have used this in the past so have some experience with it, just a bit rusty... – James Kent Aug 23 '17 at 14:46
  • The link I gave has complete code for example. – Durgaprasad Aug 28 '17 at 09:16

3 Answers3

9

There are issues with the handles against types that may not result in errors.

I got this working by using the win32ui classes like PyCDC and PyCBitMap instead of handles.

Try to change prep_menu_icon to this:

def prep_menu_icon(self, icon):
    # First load the icon.
    ico_x = win32api.GetSystemMetrics(win32con.SM_CXSMICON)
    ico_y = win32api.GetSystemMetrics(win32con.SM_CYSMICON)
    hIcon = win32gui.LoadImage(0, icon, win32con.IMAGE_ICON, ico_x, ico_y, win32con.LR_LOADFROMFILE)

    hwndDC = win32gui.GetWindowDC(self.hwnd)
    dc = win32ui.CreateDCFromHandle(hwndDC)
    memDC = dc.CreateCompatibleDC()
    iconBitmap = win32ui.CreateBitmap()
    iconBitmap.CreateCompatibleBitmap(dc, ico_x, ico_y)
    oldBmp = memDC.SelectObject(iconBitmap)
    brush = win32gui.GetSysColorBrush(win32con.COLOR_MENU)

    win32gui.FillRect(memDC.GetSafeHdc(), (0, 0, ico_x, ico_y), brush)
    win32gui.DrawIconEx(memDC.GetSafeHdc(), 0, 0, hIcon, ico_x, ico_y, 0, 0, win32con.DI_NORMAL)

    memDC.SelectObject(oldBmp)
    memDC.DeleteDC()
    win32gui.ReleaseDC(self.hwnd, hwndDC)

    return iconBitmap.GetHandle()

And I get the menu item icons:

Popup menu with icons

Christos Lytras
  • 36,310
  • 4
  • 80
  • 113
  • @obgnaw I know it because I am writing [*MFC*](https://en.wikipedia.org/wiki/Microsoft_Foundation_Class_Library) since 2000. *MFC* class `CBitmap` has the [`HBITMAP`](https://msdn.microsoft.com/en-us/library/6t1yfd35.aspx#cbitmap__operator_hbitmap) operator to get the handle and since there can't be such operator in python, I looked in [`PyCBitmap`](http://docs.activestate.com/activepython/3.4/pywin32/PyCBitmap.html) for something similar to get the handle and there it was the [`PyCBitmap.GetHandle`](http://docs.activestate.com/activepython/3.4/pywin32/PyCBitmap__GetHandle_meth.html). – Christos Lytras Aug 26 '17 at 07:39
3

I can't seem to get the package set up on my computer, so can't really test this, but this line

option_icon = self.prep_menu_icon("\print_pref.ico")

gives me some concern. I'm not sure if you are reading the file that you think you are.

That \ is going to indicate an escape sequence. On Windows, you need to double those backslashes to prevent them from being escaped like "\\print_pref.ico". If you are trying to load a file in the current directory, you may not need that at all and can just give the file name - "print_pref.ico". If you are trying to locate a file in the drive's root directory, you need to give the drive letter "C:\\print_pref.ico".

Matthew
  • 7,440
  • 1
  • 24
  • 49
  • I have edited question. tried using os.path.dirname . It goes to correct .ico file. May be .ico file has any problem? where can I get sample icon file. – Durgaprasad Aug 21 '17 at 10:57
  • 1
    @Durgaprasad I think that you misunderstood my point (and this may or may not be the problem). You can't do `\print_pref.ico`. Python will interpret `\p` as some escape sequence. You need to do `\\print_pref.ico`. Windows directories don't play nice because they use the backslash as a path separator, but that means an escape sequence in code, so they must always be doubled (alternatively, you can do `r"\print_pref.ico"` - the `r` means to interpret that literally without escape sequences). – Matthew Aug 21 '17 at 23:16
  • 2
    or just use `os.path.join()` or `os.path.normpath()`which also seems more readable than `dir += filename` – C8H10N4O2 Aug 23 '17 at 13:53
  • 1
    @Matthew part of the problem may be the path and filenames and how he tries to escape the backslash character, but before that, the win gui function to create the menu item images is not working. The sample code he provides [here](http://www.brunningonline.net/simon/blog/archives/SysTrayIcon.py.html) isn't working because there is some issues mixing win32gui classes and handles. – Christos Lytras Aug 26 '17 at 07:45
2

Change the code line 167 to item, extras = win32gui_struct.PackMENUITEMINFO(text=title,hbmpItem=5,wID=wID), then you will find a close icon.

But there is no different between the MENUITEMINFOs constructed by 5 and option_icon.

Type mismatch is the only reason I can imagine. The type of option_icon is hgdiObjdect, and the MENUITEMINFO.hbmpItem require a HBITMAP. There should be a cast.

It's weird, I don't think hbmpitem is a handle, it can be assign to 5, so it's more like an index of some table in the kernel. If that so the type shouldn't matter.

Discuss about the handle problem:

You can try all the number predefined in the MENUITEMINFO, then print the item, you will find the number just pass into the struct. And a handle is some kind of pointer, this number is not a memory address, so it's some kind of index.

The prep_menu_icon is a python version of a normal C++ function which translate the hcion to hbitmap.

The python version lack some type cast, and it doesn't work. But then GetHandle does some magic.

Christos Lytras
  • 36,310
  • 4
  • 80
  • 113
obgnaw
  • 3,007
  • 11
  • 25
  • `hbm` is a handle to a bitmap created with `CreateCompatibleBitmap` and `MENUITEMINFO.hbmpItem` can take bitmap handles and some special values; check [MENUITEMINFO structure](https://msdn.microsoft.com/en-us/library/windows/desktop/ms647578(v=vs.85).aspx) to see these values. `hbmpItem` will be converted to `int()` thus I don't think there is any type mismatch issue, see [`PackMENUITEMINFO`](https://github.com/SublimeText/Pywin32/blob/master/lib/x32/win32/lib/win32gui_struct.py#L184) `hbmpItem` struct pack. – Christos Lytras Aug 25 '17 at 19:05
  • It's not only some kind of an index, it's either a *HANDLE* or *Predefined Constant*. All handle types end up to be 4 byte pointers to some memory location. What I am telling is that the `hbmpItem` accepts both bitmap handles and some predefined constants; you can see that if you go at the documentation *`hbmpItem` Type: HBITMAP; A handle to the bitmap to be displayed*, so it's not *weird* because `hbmpItem` accepts handles even. `prep_menu_iconis` does not translate anything; it creates a new bitmap and draws a grey rectangle and the icon image on that bitmap; then it returns the handle. – Christos Lytras Aug 26 '17 at 09:02
  • Thank you for your patient explanation.@ChristosLytras – obgnaw Aug 28 '17 at 00:15