Jump to content

Adding icon images to tray items


TimRude
 Share

Recommended Posts

Here's a code sample showing how I add icon images (in this simple case just colored blocks) to the tray items. It should be noted that I used solid blocks to simplify the example, but anything can be set as the bitmap (and in any size) using the appropriate code to generate a bitmap to feed into the _GUICtrlMenu_SetItemBmp function.

#NoTrayIcon

#include <TrayConstants.au3>
#include <ColorConstants.au3>
#include <GuiMenu.au3>
#include <WinAPIGdi.au3>

Opt("TrayMenuMode", 3) ; The default tray menu items will not be shown and items are not checked when selected. These are options 1 and 2 for TrayMenuMode.
Opt("TrayOnEventMode", 1) ; Enable TrayOnEventMode.

Global $idTrayText, $idUDFText

Example()

Func Example()
    Local $iBmpSize = 32

    TrayCreateItem("About")
    TrayItemSetOnEvent(-1, "About")
    _TrayItemSetBmp(-1, $iBmpSize, $COLOR_BLUE)

    TrayCreateItem("") ; Create a separator line.

    $idTrayText = TrayCreateItem("Read Text with TrayItemGetText")
    TrayItemSetOnEvent(-1, "TrayText")
    _TrayItemSetBmp(-1, $iBmpSize, $COLOR_GREEN)

    $idUDFText = TrayCreateItem("Read Text with _GUICtrlMenu_GetItemText")
    TrayItemSetOnEvent(-1, "UDFText")
    _TrayItemSetBmp(-1, $iBmpSize, $COLOR_ORANGE)

    TrayCreateItem("") ; Create a separator line.

    TrayCreateItem("Exit")
    TrayItemSetOnEvent(-1, "ExitScript")
    _TrayItemSetBmp(-1, $iBmpSize, $COLOR_RED)

    TraySetState($TRAY_ICONSTATE_SHOW) ; Show the tray menu.

    While 1
        Sleep(100) ; An idle loop.
    WEnd
EndFunc   ;==>Example

Func About()
    MsgBox(0, "Tray Menu Item Image Icons", "AutoIt tray menu item image icons example.")
EndFunc   ;==>About

Func TrayText()
    ConsoleWrite("TrayText: " & TrayItemGetText($idTrayText) & @CRLF)
EndFunc   ;==>TrayText

Func UDFText()
    Local $hMenu = TrayItemGetHandle(0)
    ConsoleWrite("UDFText: " & _GUICtrlMenu_GetItemText($hMenu, $idUDFText, False) & @CRLF)
EndFunc   ;==>UDFText

Func ExitScript()
    Exit
EndFunc   ;==>ExitScript

Func _TrayItemSetBmp($idControl, $iBmpSize, $iColor, $bRGB = 1)

    ; Adds an image (bitmap) to a tray menu item - solid block of color

    ; $idControl = the control ID of the menu item (or -1 for last created item)
    ; $iBmpSize = the size of the Bmp (height & width are the same)
    ; $iColor = the color of the bitmap, stated in RGB
    ; $bRGB [optional] = if True converts to COLOREF (0x00bbggrr)

    Local $hMenu = TrayItemGetHandle(0) ; handle for standard tray menu
    If $idControl = -1 Then $idControl = _GUICtrlMenu_GetItemID($hMenu, _GUICtrlMenu_GetItemCount($hMenu) - 1, True) ; last item in menu
    _GUICtrlMenu_SetItemBmp($hMenu, $idControl, _WinAPI_CreateSolidBitmap(WinGetHandle(AutoItWinGetTitle()), $iColor, $iBmpSize, $iBmpSize, $bRGB), False)

EndFunc   ;==>_TrayItemSetBmp

This example also illustrates an odd quirk. Once you add an image to a tray menu item using the _GUICtrlMenu_SetItemBmp function, you can no longer use TrayItemGetText or TrayItemSetText on that item. Sometimes TrayItemGetText returns gibberish (like it's reading the wrong portion of memory) and sometimes it returns nothing. And TrayItemSetText doesn't change the item text. However, the functions _GUICtrlMenu_GetItemText and _GUICtrlMenuSetItemText work just fine as replacements.

Any ideas from the developers (or anyone else) as to why adding an image messes up the TrayItemGetText and TrayItemSetText functions? Maybe something for the Bug Tracker?

Edited by TimRude
Fixed the variable name - Thanks mistersquirrle
Link to comment
Share on other sites

I tried just messing around with this some, see if it was the same on my computer. First of all, your code currently returns nothing when you click on 'Read Text with TrayItemGetText' because you're using a different variable than your global variable, so you're not referencing the correct menu (or any menu).

 

Though with that fixed, I do get the 'incorrect' text. Here's my modified script:

#NoTrayIcon

#include <WinAPI.au3>

#include <TrayConstants.au3>
#include <ColorConstants.au3>
#include <GuiMenu.au3>
#include <WinAPIGdi.au3>

Opt("TrayMenuMode", 3) ; The default tray menu items will not be shown and items are not checked when selected. These are options 1 and 2 for TrayMenuMode.
Opt("TrayOnEventMode", 1) ; Enable TrayOnEventMode.

;http://www.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/
Local $aCodePages[] = [0, 1, 2, 3, 42, 65000, 65001, 874, 932, 936, 949, 950, 1250, 1251, 1252, 1253, 1254, 1255, 1256, 1257, 1258]

Global $idTrayText, $idUDFText

Example()

Func Example()
    Local $iBmpSize = 32

    Local $idAbout = TrayCreateItem("About")
    TrayItemSetOnEvent($idAbout, "About")
    _TrayItemSetBmp($idAbout, $iBmpSize, $COLOR_BLUE)

    TrayCreateItem("") ; Create a separator line.

    $idTrayText = TrayCreateItem("Read Text with TrayItemGetText")
    TrayItemSetOnEvent($idTrayText, "TrayText")
    _TrayItemSetBmp($idTrayText, $iBmpSize, $COLOR_GREEN)

    $idUDFText = TrayCreateItem("Read Text with _GUICtrlMenu_GetItemText")
    TrayItemSetOnEvent($idUDFText, "UDFText")
    _TrayItemSetBmp($idUDFText, $iBmpSize, $COLOR_ORANGE)

    TrayCreateItem("") ; Create a separator line.

    Local $idExit = TrayCreateItem("Exit")
    TrayItemSetOnEvent($idExit, "ExitScript")
    _TrayItemSetBmp($idExit, $iBmpSize, $COLOR_RED)

    TraySetState($TRAY_ICONSTATE_SHOW) ; Show the tray menu.

    While 1
        Sleep(100) ; An idle loop.
    WEnd
EndFunc   ;==>Example

Func About()
    MsgBox(0, "Tray Menu Item Image Icons", "AutoIt tray menu item image icons example.")
EndFunc   ;==>About

Func TrayText()
    ConsoleWrite("TrayText: " & TrayItemGetText($idTrayText) & @CRLF)
    ConsoleWrite("TrayText VarGetType: " & VarGetType(TrayItemGetText($idTrayText)) & @CRLF)
    ConsoleWrite("TrayText StringLen: " & StringLen(TrayItemGetText($idTrayText)) & @CRLF)
    ConsoleWrite("TrayText StringToBinary: " & StringToBinary(TrayItemGetText($idTrayText)) & @CRLF)
    ConsoleWrite("TrayText _WinAPI_WideCharToMultiByte($string): " & _WinAPI_WideCharToMultiByte(TrayItemGetText($idTrayText)) & @CRLF)
    ConsoleWrite("TrayText _ANSIToUnicode($string): " & _ANSIToUnicode(TrayItemGetText($idTrayText)) & @CRLF)

    For $iCodePage = 0 To UBound($aCodePages) - 1
        ConsoleWrite("TrayText _WinAPI_WideCharToMultiByte($string, " & $aCodePages[$iCodePage] & "): " & _WinAPI_WideCharToMultiByte(TrayItemGetText($idTrayText), $aCodePages[$iCodePage]) & @CRLF)
    Next
EndFunc   ;==>TrayText

Func UDFText()
    Local $hMenu = TrayItemGetHandle(0)
    ConsoleWrite("UDFText: " & _GUICtrlMenu_GetItemText($hMenu, $idUDFText, False) & @CRLF)
EndFunc   ;==>UDFText

Func ExitScript()
    Exit
EndFunc   ;==>ExitScript

Func _TrayItemSetBmp($idControl, $iBmpSize, $iColor, $bRGB = 1)

    ; Adds an image (bitmap) to a tray menu item - solid block of color

    ; $idControl = the control ID of the menu item (or -1 for last created item)
    ; $iBmpSize = the size of the Bmp (height & width are the same)
    ; $iColor = the color of the bitmap, stated in RGB
    ; $bRGB [optional] = if True converts to COLOREF (0x00bbggrr)

    Local $hMenu = TrayItemGetHandle(0) ; handle for standard tray menu
    If $idControl = -1 Then
        $idControl = _GUICtrlMenu_GetItemID($hMenu, _GUICtrlMenu_GetItemCount($hMenu) - 1, True) ; last item in menu
        ConsoleWrite('Defaulting control id to latest item: ' & $idControl & @CRLF)
    EndIf


    ConsoleWrite("TrayText for " & $idControl & " before: " & TrayItemGetText($idControl) & @CRLF)

    Local $hWnd = WinGetHandle(AutoItWinGetTitle())
;~     ConsoleWrite("TrayText for "&$idControl&" after 1: " & TrayItemGetText($idControl) & @CRLF)
    Local $hBitmap = _WinAPI_CreateSolidBitmap($hWnd, $iColor, $iBmpSize, $iBmpSize, $bRGB)
;~     ConsoleWrite("TrayText for "&$idControl&" after 2: " & TrayItemGetText($idControl) & @CRLF)
    _GUICtrlMenu_SetItemBmp($hMenu, $idControl, $hBitmap, False)
    ConsoleWrite("TrayText for " & $idControl & " after 3: " & TrayItemGetText($idControl) & @CRLF)


EndFunc   ;==>_TrayItemSetBmp

; https://www.autoitscript.com/forum/topic/146184-solved-convert-string-into-ansi-from-utf-8unicode/
Func _ANSIToUnicode($sString)
    #cs
        Local Const $SF_ANSI = 1
        Local Const $SF_UTF16_LE = 2
        Local Const $SF_UTF16_BE = 3
        Local Const $SF_UTF8 = 4
    #ce
    Local Const $SF_ANSI = 1, $SF_UTF8 = 4
    Return BinaryToString(StringToBinary($sString, $SF_ANSI), $SF_UTF8)
EndFunc   ;==>_ANSIToUnicode

 

And here's the output that I got:

TrayText for 7 before: About
TrayText for 7 after 3: O??
TrayText for 9 before: Read Text with TrayItemGetText
TrayText for 9 after 3: O??
TrayText for 10 before: Read Text with _GUICtrlMenu_GetItemText
TrayText for 10 after 3: O??
TrayText for 12 before: Exit
TrayText for 12 after 3: O??
TrayText: ??????
TrayText VarGetType: String
TrayText StringLen: 24
TrayText StringToBinary: 0x2C4D5320476F74686963
TrayText _WinAPI_WideCharToMultiByte($string): ??????????????????????????????????????
TrayText _ANSIToUnicode($string): ?o?r?v?y?}????????????????
TrayText _WinAPI_WideCharToMultiByte($string, 874): 
TrayText _WinAPI_WideCharToMultiByte($string, 932): ?????Á
TrayText _WinAPI_WideCharToMultiByte($string, 936): ol
TrayText _WinAPI_WideCharToMultiByte($string, 949): 
TrayText _WinAPI_WideCharToMultiByte($string, 950): ,MS Gothic
TrayText _WinAPI_WideCharToMultiByte($string, 1250): 
TrayText _WinAPI_WideCharToMultiByte($string, 1251): 
TrayText _WinAPI_WideCharToMultiByte($string, 1252): 
TrayText _WinAPI_WideCharToMultiByte($string, 1253): thicMINGLIU.TTC,PMingLiU
TrayText _WinAPI_WideCharToMultiByte($string, 1254): thicMINGLIU.TTC,PMingLiU
TrayText _WinAPI_WideCharToMultiByte($string, 1255): 
TrayText _WinAPI_WideCharToMultiByte($string, 1256): ,MS Gothic
TrayText _WinAPI_WideCharToMultiByte($string, 1257): soft YaHei UI
TrayText _WinAPI_WideCharToMultiByte($string, 1258): ,MS Gothic

Mostly it looks like that I'm getting information about the font (MS Gothic), instead of the text. 

 

I think the main reason that _GUICtrlMenu_GetItemText() uses "GetMenuStringW", and when the image/bitmap is set: "SetMenuItemInfoW" is used. I tried a edited version of the file using GetMenuStringA, SetMenuItemInfoA and GetMenuString, SetMenuItemInfo, and they ended up giving different results. Without a suffix (A = ANSI and W = Wide/Unicode), I was getting mostly window handles it looked like. Seems like that one of these functions is messing something up. 

 

That's about all I have, helpful or not.

We ought not to misbehave, but we should look as though we could.

Link to comment
Share on other sites

6 hours ago, mistersquirrle said:

I tried just messing around with this some, see if it was the same on my computer. First of all, your code currently returns nothing when you click on 'Read Text with TrayItemGetText' because you're using a different variable than your global variable, so you're not referencing the correct menu (or any menu).

D'oh! That's what I get for trying to knock out some quick demo code and forgetting to add Opt("MustDeclareVars", 1). I was wondering why all of a sudden I wasn't getting corrupted text but it was late and I was ready to sleep.

Thanks for throwing some additional tests at it and confirming that the issue with TrayItemGetText it isn't just me.

Quote

Mostly it looks like that I'm getting information about the font (MS Gothic), instead of the text. 

What I get varies from run to run. Sometimes I get what looks like bits of code. It's starting to sound like something for the Bug Tracker.

Edited by TimRude
Link to comment
Share on other sites

@TimRude as you noticed, with your initial script, the Console shows :

TrayText: 
UDFText: Read Text with _GUICtrlMenu_GetItemText

Now if we modify the script like this...

;;; _GUICtrlMenu_SetItemBmp($hMenu, $idControl, _WinAPI_CreateSolidBitmap(WinGetHandle(AutoItWinGetTitle()), $iColor, $iBmpSize, $iBmpSize, $bRGB), False)

Local $hBmp = _WinAPI_CreateSolidBitmap(WinGetHandle(AutoItWinGetTitle()), $iColor, $iBmpSize, $iBmpSize, $bRGB)
_GUICtrlMenu_SetItemBitmaps($hMenu, $idControl, $hBmp, $hBmp, False)

...then the Console shows :

TrayText: Read Text with TrayItemGetText
UDFText: Read Text with _GUICtrlMenu_GetItemText

 

Link to comment
Share on other sites

@pixelsearch I think you just helped progress my theory, that function (_GUICtrlMenu_SetItemBitmaps) doesn't use a "W" dll call, it's using "SetMenuItemBitmaps". I think that calling something like "SetMenuItemInfoW" with the "W" (Wide/Unicode) encoding messes some things up (obviously). Seems to me like it's changing the encoding of *something somewhere* and messing up some pointers/addresses that TrayItemGetText must use. But that's all above me :)

 

Also, nice job finding an alternative way of doing it

Edit: Maybe it's the not the "W" part specifically, I tried editing the _GUICtrlMenu_SetItemBitmaps function to use SetMenuItemBitmapsW as the DLL call, and everything still works just fine.

Edited by mistersquirrle

We ought not to misbehave, but we should look as though we could.

Link to comment
Share on other sites

@pixelsearch I confirm that using _GUICtrlMenu_SetItemBitmaps to set both the checked and unchecked images works great. I have also tested TrayItemSetText after setting the images and it works now as well. Great catch!

@mistersquirrle I agree with your later conclusion that it doesn't have anything to do with the 'A' vs 'W' function calls.

I think it actually has to do with whether or not both the checked and unchecked images are set. If only one is set, it causes a problem.

To test this, I tried using _GUICtrlMenu_SetItemBmpChecked and _GUICtrlMenu_SetItemBmpUnchecked to set both of the images. The interesting thing about these two functions is that they use the exact same method of setting the image as the _GUICtrlMenu_SetItemBmp function, including the same 'W' flavor of SetMenuItemInfoW.

Setting both images this way works properly and doesn't mess up TrayItemGetText and TrayItemSetText.

; My original code that doesn't get along with TrayItemGetText or TrayItemSetText
    ; _GUICtrlMenu_SetItemBmp($hMenu, $idControl, _WinAPI_CreateSolidBitmap(WinGetHandle(AutoItWinGetTitle()), $iColor, $iBmpSize, $iBmpSize, $bRGB), False)

    ; Working code suggested by pixelsearch to set both checked and unchecked images at the same time
    ; Local $hBmp = _WinAPI_CreateSolidBitmap(WinGetHandle(AutoItWinGetTitle()), $iColor, $iBmpSize, $iBmpSize, $bRGB)
    ; _GUICtrlMenu_SetItemBitmaps($hMenu, $idControl, $hBmp, $hBmp, False)

    ; Working code for setting each image (checked and unchecked) individually
    _GUICtrlMenu_SetItemBmpChecked($hMenu, $idControl, _WinAPI_CreateSolidBitmap(WinGetHandle(AutoItWinGetTitle()), $iColor, $iBmpSize, $iBmpSize, $bRGB), False)
    _GUICtrlMenu_SetItemBmpUnchecked($hMenu, $idControl, _WinAPI_CreateSolidBitmap(WinGetHandle(AutoItWinGetTitle()), $iColor, $iBmpSize, $iBmpSize, $bRGB), False)

Of course, _GUICtrlMenu_SetItemBitmaps is the easier and neater way to set both images (and what I'll stick with), but I tried it this way to test the underlying function calls. And I deliberately didn't assign the bitmap to variable in this test because I wanted to exactly duplicate the problematic line with the two lines for controlled testing.

Link to comment
Share on other sites

Now that the kinks have been worked out, here's the more able function that I actually use to assign icon images to tray menu items.

Func _MenuItemSetIcon($hMenu, $idControl, $sIconFile, $iIconIndex, $iResourceNum, $iWidth, $iHeight)

    ; $hMenu is the handle of the menu (if 0, will assume handle for standard tray menu is to be used)
    ; $idControl is the control ID of the menu item (or -1 for last created item)
    ; $sIconFile and $iIconIndex will be used if running in script mode
    ; $iResourceNum will be used if running in compiled mode
    ; Supply both in the call so it works in both script and compiled mode

    If $hMenu = 0 Then $hMenu = TrayItemGetHandle(0) ; default to standard tray menu if handle not supplied
    If $idControl = -1 Then $idControl = _GUICtrlMenu_GetItemID($hMenu, _GUICtrlMenu_GetItemCount($hMenu) - 1, True) ; last item in menu
    If @Compiled Then
        Local $hBitmap = _CreateBitmapFromIconResource(_WinAPI_GetSysColor($COLOR_MENU), $iResourceNum, $iWidth, $iHeight)
        _GUICtrlMenu_SetItemBitmaps($hMenu, $idControl, $hBitmap, $hBitmap, False)
    Else
        Local $hBitmap = _CreateBitmapFromIcon(_WinAPI_GetSysColor($COLOR_MENU), $sIconFile, $iIconIndex, $iWidth, $iHeight)
        _GUICtrlMenu_SetItemBitmaps($hMenu, $idControl, $hBitmap, $hBitmap, False)
    EndIf

EndFunc   ;==>_MenuItemSetIcon

Func _CreateBitmapFromIconResource($iBackground, $iIconNum, $iWidth, $iHeight) ; Based on code by Yashied, modified by TimRude

    Local $hDC, $hBackDC, $hBackSv, $hIcon, $hBitmap, $hInstance

    $hDC = _WinAPI_GetDC(0)
    $hBackDC = _WinAPI_CreateCompatibleDC($hDC)
    $hBitmap = _WinAPI_CreateSolidBitmap(0, $iBackground, $iWidth, $iHeight)
    $hBackSv = _WinAPI_SelectObject($hBackDC, $hBitmap)
    $hInstance = _WinAPI_GetModuleHandle(Null)
    $hIcon = _WinAPI_LoadImage($hInstance, $iIconNum, $IMAGE_ICON, $iWidth, $iHeight, 0)
    If Not @error Then
        _WinAPI_DrawIconEx($hBackDC, 0, 0, $hIcon, 0, 0, 0, 0, $DI_NORMAL)
        _WinAPI_DestroyIcon($hIcon)
    EndIf
    _WinAPI_SelectObject($hBackDC, $hBackSv)
    _WinAPI_ReleaseDC(0, $hDC)
    _WinAPI_DeleteDC($hBackDC)
    Return $hBitmap
EndFunc   ;==>_CreateBitmapFromIconResource

Func _CreateBitmapFromIcon($iBackground, $sIconFile, $iIndex, $iWidth, $iHeight) ; By Yashied

    Local $hDC, $hBackDC, $hBackSv, $hIcon, $hBitmap

    $hDC = _WinAPI_GetDC(0)
    $hBackDC = _WinAPI_CreateCompatibleDC($hDC)
    $hBitmap = _WinAPI_CreateSolidBitmap(0, $iBackground, $iWidth, $iHeight)
    $hBackSv = _WinAPI_SelectObject($hBackDC, $hBitmap)
    $hIcon = _WinAPI_PrivateExtractIcon($sIconFile, $iIndex, $iWidth, $iHeight)
    If Not @error Then
        _WinAPI_DrawIconEx($hBackDC, 0, 0, $hIcon, 0, 0, 0, 0, $DI_NORMAL)
        _WinAPI_DestroyIcon($hIcon)
    EndIf
    _WinAPI_SelectObject($hBackDC, $hBackSv)
    _WinAPI_ReleaseDC(0, $hDC)
    _WinAPI_DeleteDC($hBackDC)
    Return $hBitmap

EndFunc   ;==>_CreateBitmapFromIcon

Func _WinAPI_PrivateExtractIcon($sIcon, $iIndex, $iWidth, $iHeight) ; By Yashied

    Local $hIcon, $tIcon = DllStructCreate('hwnd'), $tID = DllStructCreate('hwnd')
    Local $Ret = DllCall('user32.dll', 'int', 'PrivateExtractIcons', 'str', $sIcon, 'int', $iIndex, 'int', $iWidth, 'int', $iHeight, 'ptr', DllStructGetPtr($tIcon), 'ptr', DllStructGetPtr($tID), 'int', 1, 'int', 0)

    If (@error) Or ($Ret[0] = 0) Then
        Return SetError(1, 0, 0)
    EndIf
    $hIcon = DllStructGetData($tIcon, 1)
    If ($hIcon = Ptr(0)) Or (Not IsPtr($hIcon)) Then
        Return SetError(1, 0, 0)
    EndIf
    Return $hIcon

EndFunc   ;==>_WinAPI_PrivateExtractIcon

The _MenuItemSetIcon function is what is called to assign the images. It works under the assumption that the icons will be compiled as resources into the executable, but that you'll want to see them also while you're working on and debugging the script. So in the call you specify both the icon filename and index number (i.e. which icon in the file if the file contains more than one) for use when scripting, as well as the resource ID number that the icon will be addressed by in the compiled version. Then it uses the appropriate method as needed to load the image.

The code for actually turning the icons into bitmaps for the tray menu comes from this post by Yashied.

Edited by TimRude
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...