SetMenuItemImage and GetMenuItemImage [Windows only]
Posted: Mon Jan 13, 2025 8:33 pm
[Edit 02 feb. 2025]This code has been completely rewritten to be more powerful. Some bugs have been fixed. To get a better version, please go to https://www.purebasic.fr/english/viewtopic.php?t=86194
Have you dreamed of a SetMenuItemImage function?
Have you dreamed of displaying icons for the main menu bar titles?
Have you dreamed to change the icon of a menu item without having to distroy and rebuild the menu?
Have you dreamed to retreive the ImageID of a menu item without having to store it by yourself?
Have you dreamed of doing all that without having to go through an 'OwnerDrawn' menu?
This code is for you!
Have you dreamed of a SetMenuItemImage function?
Have you dreamed of displaying icons for the main menu bar titles?
Have you dreamed to change the icon of a menu item without having to distroy and rebuild the menu?
Have you dreamed to retreive the ImageID of a menu item without having to store it by yourself?
Have you dreamed of doing all that without having to go through an 'OwnerDrawn' menu?
This code is for you!
Code: Select all
;
; *****************************************************************************
;
; Set-GetImageTo-FromMenuItem.pbi
; Release 1.1 - Zapman - Jan 2025 - Windows only
;
; Thanks to the forum members
; for testing and debugging.
; (idle is a real boss)
;
; This file should be saved under the name "Set-GetImageTo-FromMenuItem.pbi".
;
; This library offers functions to set of get images to/from menu items.
; Its goal is to overcome the limitations of the current PureBasic (6.12) functions:
; • It is not possible to change the icon of a menu item after assigning it.
; You have to destroy the menu and rebuild it entirely to be able to change one
; of its icons.
; • It is not possible to retrieve the ImageID of a menu icon. You have to memorize
; it in a variable at the time you assign it to be able to retrieve it later.
; • It is not possible to assign an icon to an item of the main menu of a window,
; if this item has subitems.
;
; Now, you can do all of that!
;
;
; The last part of this file includes a demonstration code.
;
; The functions of this library are duplicated
; in the SetGadgetItemEx.pbi Zapman library.
; So, if you include SetGadgetItemEx.pbi in your project, you don't need to
; (and should not) include also this one.
;
;
; *****************************************************************************
;- 1. FIRST PART: SOME EXPLANATIONS
; _____________________________________________________________________________
;
; A brief description of how PureBasic handles menus
; and the hassle of trying to manipulate them:
; _____________________________________________________________________________
; • Windows has a native way to handle menu icons: To assign an icon, you have
; to enter its ImageID in the 'hbmpItem' field of the 'MenuItemInfo' Structure,
; then call the 'SetMenuItemInfo()' function.
; • But, for some reason, the PureBasic Team chose not to use this option.
; When you look at the MenuItemInfo Structure in the PureBasic 6.12 Structure
; viewer, you can see that the 'hbmpItem' field is not even there.
; There may be a historical reason for this: this field may not have existed
; when the team registered this structure... and at the time when it was decided
; to add icons to the menus.
; • The PureBasic way to do this is to store the ImageID of the menu icon in
; a proprietary Structure, then store the address of that Structure in the
; 'dwItemData' field of 'MenuItemInfo'.
; • This creates a first difficulty: when we want to find or modify the ImageID,
; we must manage a proprietary structure on which we have no information.
; • There is a second difficulty: since PureBasic has created this proprietary
; structure, it also stores the text of the menu item in it.
; • But this structure is only created and used when a menu has icons.
; Otherwise, the classic Windows method is used to store the menu text.
; • Therefore, the way to find the text of an item is not the same depending on
; whether the menu was created with 'CreateMenu()' or 'CreateImageMenu()'.
; • It's exactly the same for 'CreatePopupMenu()' and CreatePopupImageMenu()'.
; • It seems that the PureBasic team's choice had other consequences since it is
; impossible to assign an icon to a menu item when this item is an entry for a
; submenu. For the main menu of a window, it is therefore impossible to assign
; an icon to its titles, from the moment these titles are provided with subitems.
; • I also noticed a very strange (and very, very peculiar) bug: when you assign
; the 'OwnerDrawn' style to a menu created with CreateMenu() and try to access
; the text of one of its items using the GetGadgetItemText() function,
; it doesn't always work. It doesn't work,in particular, when you are handling
; the #WM_INITMENU And #WM_INITMENUPOPUP messages of the main window and call
; GetGadgetItemText() after having set the style to OwnerDrawn.
; _____________________________________________________________________________
;
; The path chosen to overcome the difficulties encountered
; _____________________________________________________________________________
;
; The approach taken by the functions in this library is to transparently deal with
; both methods of saving information for menu items (Windows and PureBasic)
; depending on the needs and how the menu was created (with CreateMenu/CreatePopupMenu,
; or CreateImageMenu/CreatePopupMenu).
; • When the menu was created with CreateMenu() or CreatePopupMenu(), the icons
; of the menu items are saved in the Windows way. Because, yes!, you can add icons
; to a menu that was NOT created with CreateImageMenu().
; • For the main menu of a window (which never has icons when created in PureBasic
; and has subitems), the Windows data saving method is also used.
; • In all other cases, the PureBasic method is retained. In order to modify the data
; of the proprietary structure used, I had to observe what it contained in various
; cases, in order to be able to recreate it and to manipulate it.
; Fortunately, this structure is very simple.
;
; _____________________________________________________________________________
;
; The limitations of this library
; _____________________________________________________________________________
;
; So far, I have identified two small limitations:
; 1- If you create your menu with CreateMenu() or CreatePopupMenu() and assign images
; to items with the MenuItem(x, "ItemText", ImageID(x)) function, it still won't do
; anything. It seems that the image ID passed in this case is lost for good.
; To assign an image with MenuItem(), the menu must have been created with
; CreateImageMenu(). However, you can still assign ImageIDs with SetMenuItemImageEx(),
; regardless of how the menu was created.
; 2- If you assign images to items in a menu that was created with CreateMenu()
; or CreatePopupMenu() using the SetMenuItemMenuEx() function, everything will be fine
; until you use the SetMenuItemText() function. If you use it, the image previously
; assigned to the item will be deleted. Therefore, if you want to change the text of
; an item to which you have already assigned an image, you will have to use
; SetMenuItemTextEx() instead.
;
; *****************************************************************************
;
;- 2. SECOND PART: STRUCTURES AND CONSTANTES
;
CompilerIf #PB_Compiler_IsMainFile
EnableExplicit
CompilerEndIf
;
Structure PBMenuItemData
; This structure is intended to correspond to how PureBasic
; stores the data of menu items when the menu was created
; with the CreateImageMenu() or CreatePopupImageMenu() commands.
;
; The data in this structure can be accessed through the field
; *drawItem\itemData when the #WM_DRAWITEM message is sent
; to the main window of the program.
;
; It can also be accessed with the GetMenuItemInfo_() Windows
; API function. See GetMenuItemInfos() as example.
;
; Due to a lack of documentation on this internal structure
; of PureBasic's functionality, the fields have been filled
; based on assumptions and observations.
;
*MenuItemTextPntr ; This field contains a pointer
; to the text of the menu item.
*MenuItemImage ; This field contains a pointer to the image
; illustrating the menu item.
Reserved1.i ; The two following fields
Reserved2.i ; always contain zero.
Unknown.i ; The rest is unknown, even the exact number
; of fields of the structure.
; But we get enough here for our needs
EndStructure
;
Structure MENUITEMINFO_Fixed Align #PB_Structure_AlignC
; The MENUITEMINFO structure described in PureBasic 6.12
; is uncomplete.
; Great thanks to idle from english PureBasic forum,
; for the right form of this structure.
cbSize.l
fMask.l
fType.l
fState.l
wID.l
hSubMenu.i
hbmpChecked.i
hbmpUnchecked.i
dwItemData.i
*dwTypeData
cch.l
hbmpItem.i ; This field is missing in the PureBasic 6.20 description of MENUITEMINFO.
EndStructure
;
; *****************************************************************************
;
;- 3. THIRD PART: THE LIBRARY ITSELF
;
CompilerIf Not (Defined(GetAnImageForFree, #PB_Procedure))
Procedure GetAnImageForFree(Start = 1, NbAttempt = 1000, MinSize = 16, PBImage = -1)
; By Zapman - Jan 2025.
;
; Explore the memory an return the first found image from 'Start' address.
;
; 'NbAttempt' is the limit number of attempts before stopping the exploration.
;
; 'MinSize' contains the minimum size for the searched image.
;
; If PBImage contains a valid image created with 'CreateImage()',
; the found image will be copied to PBImage.
; If PBImage = -1, the memory will only be checked for an existing image
; and nothing will be copied nowhere.
;
; If existing, PBImage can be 24 bits or 32 bits.
;
Protected Result = 0, bitmap.BITMAP, hDestBitmap
Protected ct, w, h, ImgWidth, ImgHeight, IsAlpha
Protected x, y, PixelColor, NotEmpty
Protected EndOfExploration = Start + NbAttempt
Protected oldDestBitmap, oldSrcBitmap
Protected blend, *blend.BLENDFUNCTION = @blend
Protected hdcDest, hdcSrc = CreateCompatibleDC_(#Null) ; Create a memory hDC.
;
If OpenLibrary(0, "Msimg32.dll")
If hdcSrc
If IsImage(PBImage) ; Check if the destination given image is valid.
hDestBitmap = ImageID(PBImage) ; Get the destination image handle.
If GetObject_(hDestBitmap, SizeOf(BITMAP), @bitmap) ; get the destination image size.
ImgWidth = bitmap\bmWidth
ImgHeight = bitmap\bmHeight
IsAlpha = bitmap\bmBitsPixel
If ImgWidth * ImgHeight ; Check if the destination image has not a null size.
If StartDrawing(ImageOutput(PBImage))
; Erase the given image background:
If isAlpha = 32
; Transparent background
DrawingMode(#PB_2DDrawing_AllChannels)
Box(0, 0, ImgWidth, ImgHeight, 0)
Else
; White background
Box(0, 0, ImgWidth, ImgHeight, $FFFFFF)
EndIf
StopDrawing()
Else
Goto GAIFF_CleanAndReturn
EndIf
;
hdcDest = CreateCompatibleDC_(#Null) ; Create a memory hDC for the destination image
oldDestBitmap = SelectObject_(hdcDest, hDestBitmap) ; Associate the destination image with hdcDest.
Else
Goto GAIFF_CleanAndReturn
EndIf
If oldDestBitmap = 0
Goto GAIFF_CleanAndReturn
EndIf
Else
Goto GAIFF_CleanAndReturn
EndIf
ElseIf PBImage <> -1
Goto GAIFF_CleanAndReturn
EndIf
;
For ct = Start To EndOfExploration
If GetObject_(ct, SizeOf(BITMAP), @bitmap) ; Check is there is an image at this address.
w = bitmap\bmWidth ; If any, get the found image size.
h = bitmap\bmHeight
If w >= MinSize And h >= MinSize ; Ensure that the found image has good dimensions.
oldSrcBitmap = SelectObject_(hdcSrc, ct) ; Associate the found image with hdcSrc
If oldSrcBitmap
Result = ct
If hDestBitmap
; Copy the found image into PBImage:
*blend\BlendOp = #AC_SRC_OVER
*blend\BlendFlags = 0
*blend\AlphaFormat = #AC_SRC_ALPHA
*blend\SourceConstantAlpha = 255
CallFunction(0, "AlphaBlend", hdcDest, 0, 0, ImgWidth, ImgHeight, hdcSrc, 0, 0, w, h, blend)
;
; Now, check if the resulting image is empty:
;
NotEmpty = #False
If StartDrawing(ImageOutput(PBImage))
If IsAlpha = 32
DrawingMode(#PB_2DDrawing_AllChannels)
EndIf
; Check each pixel to see if all are equal to a solid color (e.g., white).
For y = 0 To ImgHeight - 1
For x = 0 To ImgWidth - 1
PixelColor = Point(x, y)
If IsAlpha = 24
; Check if the pixel is white:
If PixelColor <> RGB(255, 255, 255) ; If a different pixel is found...
NotEmpty = #True
Break
EndIf
ElseIf IsAlpha = 32 And Alpha(PixelColor); Check if the pixel is transparent.
NotEmpty = #True
Break
EndIf
Next x
Next y
StopDrawing()
Else
Result = 0
EndIf
;
If NotEmpty = #False
Result = 0
EndIf
EndIf
If Result : Break : EndIf
EndIf
EndIf
EndIf
Next
;
GAIFF_CleanAndReturn:
If oldDestBitmap
SelectObject_(hdcDest, oldDestBitmap)
EndIf
If hdcDest : DeleteDC_(hdcDest) : EndIf
If oldSrcBitmap
SelectObject_(hdcSrc, oldSrcBitmap)
EndIf
If hdcSrc : DeleteDC_(hdcSrc) : EndIf
EndIf
CloseLibrary(0)
EndIf
ProcedureReturn Result
EndProcedure
CompilerEndIf
;
#GMI_StringSearch$ = "GetMenuItemTextExString"
#GMI_SearchFirstImage$ = "GetFirstImageFromMenuItemString"
#GMI_Ignore = -1
;
Procedure.s GetClassicMenuStringFromPosition(hMenu, Position)
;
Protected MenuItemInfo.MENUITEMINFO_Fixed, ItemString$
;
; Get the size of the string (the number of chars):
MenuItemInfo\cbSize = SizeOf(MENUITEMINFO_Fixed)
MenuItemInfo\fMask = #MIIM_STRING
MenuItemInfo\dwTypeData = 0
GetMenuItemInfo_(hMenu, Position, #MF_BYPOSITION, @MenuItemInfo)
;
If MenuItemInfo\cch
; Allocate memory for a unicode string:
ItemString$ = Space(MenuItemInfo\cch)
MenuItemInfo\cch + 1 ; Add room for the ending character.
; Put the item string buffer address in MenuItemInfo\dwTypeData:
MenuItemInfo\dwTypeData = @ItemString$
; Retreive the string:
GetMenuItemInfo_(hMenu, Position, #MF_BYPOSITION, @MenuItemInfo)
EndIf
ProcedureReturn ItemString$
EndProcedure
;
Procedure GetMenuItemInfos(hMenu, ItemNum = #GMI_Ignore, ByPositionOrCommand = #MF_BYCOMMAND, *StringPointer = #GMI_Ignore)
;
; Explore hMenu items to find an image and/or a text.
; If an image is found, the ImageID (handle) of the image
; is returned.
; If *StringPointer is a valid pointer to a string, the corresponding
; item text is copied into *StringPointer.
;
; If ItemNum = #GMI_Ignore, the ImageID (handle) of the first found image
; is returned (if there is at least one image in the menu items).
;
; If ItemNum <> #GMI_Ignore, the ImageID of the image of the item designed
; by ItemNum is returned.
;
; If ByPositionOrCommand = #MF_BYPOSITION, ItemNum must contain the position
; of the item in the menu. Else, it must contain the item ID.
;
; If ByPositionOrCommand = #MF_BYCOMMAND, all the submenus of hMenu
; will be explored to find 'ItemNum'. Else, only the items of hMenu
; will be examined to find the required position.
;
; If *StringPointer is a valid pointer to a string, the text
; of the found item is returned into this string, whatever the
; way the menu was created (CreateImageMenu() or CreateMenu()
; or CreatePopupMenu() or CreatePopupImageMenu()).
;
; If ItemNum = #GMI_Ignore and *StringPointer points to a string
; equal to #GMI_StringSearch$, the first found item text
; is returned in *StringPointer, even if this item has no image.
; If ItemNum = #GMI_Ignore and *StringPointer is a valid pointer
; but does not point to a string equal to #GMI_StringSearch$,
; the item text of the first item with an image is returned
; in *StringPointer. If not any item has an image, the first
; found item text is returned in *StringPointer.
; If ItemNum <> #GMI_Ignore and *StringPointer is a valid string
; pointer, the item text corresponding to ItemNum and
; ByPositionOrCommand will be copied in *StringPointer even if
; the item has no image. If it has an image, the imageID will
; be returned. Else, zero will be returned.
;
Protected Counter, MenuItemInfo.MENUITEMINFO_Fixed, Result = #False
Protected *PBMenuItemData.PBMenuItemData, bitmap.bitmap
Protected Cont, SearchFirstImage = 0
If *StringPointer And *StringPointer <> #GMI_Ignore And PeekS(*StringPointer) = #GMI_SearchFirstImage$
SearchFirstImage = 1
EndIf
;
If IsMenu(hMenu)
hMenu = MenuID(hMenu)
EndIf
;
For Counter = 0 To GetMenuItemCount_(hMenu) - 1
Cont = 0
If ItemNum = #GMI_Ignore
Cont = 1
Else
If ByPositionOrCommand = #MF_BYPOSITION And Counter = ItemNum
Cont = 1
ElseIf ByPositionOrCommand = #MF_BYCOMMAND And GetMenuItemID_(hMenu, Counter) = ItemNum
Cont = 1
EndIf
EndIf
If Cont
MenuItemInfo\cbSize = SizeOf(MENUITEMINFO_Fixed)
MenuItemInfo\fMask = #MIIM_DATA
GetMenuItemInfo_(hMenu, Counter, #MF_BYPOSITION, @MenuItemInfo)
If MenuItemInfo\dwItemData
; PureBasic as registered data in MenuItemInfo\dwItemData.
; The precise structure of this data is private to PureBasic
; and is not documented. But observation permitted to know
; that the first field of this structure contains a pointer
; to the item string and the second one contains a pointer
; to the item image.
*PBMenuItemData = MenuItemInfo\dwItemData
Result = *PBMenuItemData\MenuItemImage
;
If *PBMenuItemData\MenuItemTextPntr And *StringPointer And *StringPointer <> #GMI_Ignore
If PeekS(*StringPointer) = #GMI_StringSearch$
; A string is requested and we get one.
Result = 1
EndIf
If SearchFirstImage = 0
PokeS(*StringPointer, PeekS(*PBMenuItemData\MenuItemTextPntr))
EndIf
EndIf
;
Else
; PureBasic did not register data in MenuItemInfo\dwItemData.
;
; Retreive the item image using the classical Windows way:
MenuItemInfo\fMask = #MIIM_BITMAP
GetMenuItemInfo_(hMenu, Counter, #MF_BYPOSITION, @MenuItemInfo)
Result = MenuItemInfo\hbmpItem
;
; Retreive the item text using the classical Windows way:
Protected ItemString$ = GetClassicMenuStringFromPosition(hMenu, Counter)
If ItemString$ And *StringPointer And *StringPointer <> #GMI_Ignore
If PeekS(*StringPointer) = #GMI_StringSearch$ And Result = 0
; A string is requested and we get one.
Result = 1
EndIf
If SearchFirstImage = 0
PokeS(*StringPointer, PeekS(@ItemString$))
EndIf
EndIf
;
EndIf
If Result <> 1 And Result <> 0
; It seems that we get an image. Check if it is valid:
If GetObject_(Result, SizeOf(BITMAP), @bitmap.bitmap)
If SearchFirstImage
If ByPositionOrCommand = #MF_BYCOMMAND
PokeS(*StringPointer, Str(GetMenuItemID_(hMenu, Counter)))
Else
PokeS(*StringPointer, Str(Counter))
EndIf
EndIf
; We get an image. Stop searching.
Break
Else
Result = 0
EndIf
ElseIf Result = 1
; We get a text. Stop searching.
Break
EndIf
EndIf
If ByPositionOrCommand = #MF_BYCOMMAND
; When ItemNum is an ID, look for
; a submenu having an item with this ID.
Protected SubMenu = GetSubMenu_(hMenu, Counter)
; Another way of doing the same thing is the following:
; MenuItemInfo\cbSize = SizeOf(MENUITEMINFO_Fixed)
; MenuItemInfo\fMask = #MIIM_SUBMENU
; MenuItemInfo\fType = 0
; GetMenuItemInfo_(hMenu, Counter, #MF_BYPOSITION, @MenuItemInfo)
; SubMenu = MenuItemInfo\hSubMenu
;
If SubMenu
; The item is an entry of a submenu.
; Explore the submenu:
Result = GetMenuItemInfos(SubMenu, ItemNum, ByPositionOrCommand, *StringPointer)
If Result
Break
EndIf
EndIf
EndIf
Next
;
ProcedureReturn Result
;
EndProcedure
;
Procedure GetMenuItemImageEx(hMenu, ItemNum)
;
; This function could be called 'GetMenuItemImage()' but it is possible that
; the PureBasic team decides to create seach a function with that name.
; So, the name 'GetMenuItemImageEx()' has been choosen.
;
; It returns the ImageID of the item designed by ItemNum.
; ItemNum must contain the item ID.
;
; All the submenus of hMenu will be explored to find 'ItemNum'.
;
; When omitting the fourth parameter of GetMenuItemInfos(), image search is preferred.
ProcedureReturn GetMenuItemInfos(hMenu, ItemNum, #MF_BYCOMMAND)
EndProcedure
;
Procedure GetMenuTitleImageEx(hMenu, ItemNum)
;
; This function could be called 'GetMenuTitleImage()' but it is possible that
; the PureBasic team decides to create seach a function with that name.
; So, the name 'GetMenuTitleImageEx()' has been choosen.
;
; It returns the ImageID of the item designed by ItemNum.
; ItemNum must contain the position of the item in the menu.
;
; When omitting the fourth parameter of GetMenuItemInfos(), image search is preferred.
ProcedureReturn GetMenuItemInfos(hMenu, ItemNum, #MF_BYPOSITION)
EndProcedure
;
Procedure.s GetMenuItemTextEx(hMenu, ItemNum, ByPositionOrCommand = #MF_BYCOMMAND)
; This functions does exactly the same thing As GetMenuItemText()
;
; BUT : When the menu has been created using CreateMenu() or CreatePopupMenu()
; and then set to 'OwnerDrawn' by the program, the PureBasic GetMenuItemText()
; doesn't allways work (for an unknown reason).
; This function will work in any case.
Protected *String = AllocateMemory(1000) ; Reserve space for menu item text of any length.
PokeS(*String, #GMI_StringSearch$) ; Signal a preferred string search.
GetMenuItemInfos(hMenu, ItemNum, ByPositionOrCommand, *String)
Protected String$ = PeekS(*String)
FreeMemory(*String)
If String$ <> #GMI_StringSearch$
ProcedureReturn String$
EndIf
EndProcedure
;
Procedure.s GetMenuTitleTextEx(hMenu, ItemNum)
;
; This functions does exactly the same thing As GetMenuTitleText()
;
ProcedureReturn GetMenuItemTextEx(hMenu, ItemNum, #MF_BYPOSITION)
EndProcedure
;
Procedure GetFirstMenuItemImage(hMenu, ByPositionOrCommand = #MF_BYCOMMAND)
;
; Returns the first menu item number that has an image.
; If ByPositionOrCommand = #MF_BYCOMMAND, the value returned
; is the ID of the menu item, and submenus will eventually
; be explored to find the image.
; Else, the item position will be returned and submenus
; won't be explored.
Protected String$ = #GMI_SearchFirstImage$
GetMenuItemInfos(hMenu, #GMI_Ignore, ByPositionOrCommand, @String$)
If String$ = #GMI_SearchFirstImage$
ProcedureReturn -1
Else
ProcedureReturn Val(String$)
EndIf
EndProcedure
;
Procedure SetMenuItemInfos(hMenu, ItemNum, *ImageID = #GMI_Ignore, ItemString$ = "", ByPositionOrCommand = #MF_BYCOMMAND)
;
; If ByPositionOrCommand = #MF_BYPOSITION, ItemNum must contain the position
; of the item in the menu. Else, it must contain the item ID.
;
; If ByPositionOrCommand = #MF_BYCOMMAND, all the submenus of hMenu
; will be explored to find 'ItemNum'. Else, only the items of hMenu
; will be examined to find the required position of the item in the menu.
;
; *ImageID can contain the ImageID you want to attribute to the menu item.
; ItemString$ can contain the string you want to attribute to the menu item.
;
Protected Counter, MenuItemInfo.MENUITEMINFO_Fixed, Result = #False
Protected *PBMenuItemData.PBMenuItemData, Cont, hWindow, itemID
;
; Store ItemString$ in a static list to ensure that the strings
; pointers used by the menu will allways point to a valid memory
; address.
Static NewList ListOfString.String()
Protected Found = 0
If ItemString$
ForEach ListOfString()
If ListOfString()\s = ItemString$
Found = 1
Break
EndIf
Next
If Found = 0
AddElement(ListOfString())
ListOfString()\s = ItemString$
EndIf
EndIf
;
If IsMenu(hMenu)
hMenu = MenuID(hMenu)
EndIf
;
If IsImage(*ImageID)
*ImageID = ImageID(*ImageID)
EndIf
;
For Counter = 0 To GetMenuItemCount_(hMenu) - 1
Cont = 0
If ByPositionOrCommand = #MF_BYPOSITION And Counter = ItemNum
Cont = 1
ElseIf ByPositionOrCommand = #MF_BYCOMMAND And GetMenuItemID_(hMenu, Counter) = ItemNum
Cont = 1
EndIf
If Cont
MenuItemInfo\cbSize = SizeOf(MENUITEMINFO_Fixed)
MenuItemInfo\fMask = #MIIM_DATA
GetMenuItemInfo_(hMenu, Counter, #MF_BYPOSITION, @MenuItemInfo)
;
If MenuItemInfo\dwItemData = 0
; PureBasic has not created a *PBMenuItemData for this menu,
; probably because this menu has been created using CreateMenu()
; or CreatePopupMenu(), OR because this is the main window menu
; (PureBasic does'nt allow to add an image to the items of the
; main menu when they have subitems).
;
If ItemString$
; To set the text, use the classical way of doing the thing
; using Windows API.
; The content of ItemString$ has been saved in a static list
; to ensure that the given address always points to a valid
; string, whatever the user does whith the original string.
MenuItemInfo\cbSize = SizeOf(MENUITEMINFO_Fixed)
MenuItemInfo\fMask = #MIIM_STRING
MenuItemInfo\dwTypeData = @ListOfString()\s
SetMenuItemInfo_(hMenu, Counter, #MF_BYPOSITION, @MenuItemInfo)
;
; Now, redraw the menu to update it, if it is the window main menu:
If IsWindow(EventWindow())
hWindow = WindowID(EventWindow())
If hMenu = GetMenu_(hWindow)
DrawMenuBar_(hWindow)
EndIf
EndIf
Result = 1
EndIf
;
If *ImageID <> #GMI_Ignore
; To set the image, use the classical way of doing the thing
; using Windows API.
; The ImageID is copied into the hbmpItem field of MenuItemInfo.
; Doing that, we can print a menu image EVEN IF the menu was created
; using CreateMenu() or CreatePopupMenu(). This also allows to print
; an image in a main menu title (even if it has subitems),
; while there is no way to do that using native PureBasic functions.
;
MenuItemInfo\fMask = #MIIM_BITMAP
MenuItemInfo\hbmpItem = *ImageID
SetMenuItemInfo_(hMenu, Counter, #MF_BYPOSITION, @MenuItemInfo)
;
; Now, redraw the menu to update it, if it is the window main menu:
If IsWindow(EventWindow())
hWindow = WindowID(EventWindow())
If hMenu = GetMenu_(hWindow)
DrawMenuBar_(hWindow)
EndIf
EndIf
Result = 1
EndIf
Else
; PureBasic has created a *PBMenuItemData for this menu.
; This means that this menu was created using CreateImageMenu()
; or CreatePopupImageMenu().
*PBMenuItemData = MenuItemInfo\dwItemData
If *ImageID <> #GMI_Ignore
*PBMenuItemData\MenuItemImage = *ImageID
Result = 1
EndIf
If ItemString$
; The content of ItemString$ has been saved in a static list
; to ensure that the given address always points to a valid
; string, whatever the user does whith the original string.
*PBMenuItemData\MenuItemTextPntr = @ListOfString()\s
Result = 1
EndIf
EndIf
Break
EndIf
If ByPositionOrCommand = #MF_BYCOMMAND
; When ItemNum is an ID, look for
; a submenu having an item with this ID.
Protected SubMenu = GetSubMenu_(hMenu, Counter)
; Another way of doing the same thing is the following:
; MenuItemInfo\cbSize = SizeOf(MENUITEMINFO_Fixed)
; MenuItemInfo\fMask = #MIIM_SUBMENU
; MenuItemInfo\fType = 0
; GetMenuItemInfo_(hMenu, Counter, #MF_BYPOSITION, @MenuItemInfo)
; SubMenu = MenuItemInfo\hSubMenu
If SubMenu
; The item is an entry of a submenu.
; Explore the submenu:
Result = SetMenuItemInfos(SubMenu, ItemNum, *ImageID, ItemString$, ByPositionOrCommand)
If Result
Break
EndIf
EndIf
EndIf
Next
ProcedureReturn Result
;
EndProcedure
;
Procedure SetMenuItemImageEx(hMenu, ItemNum, *ImageID, ByPositionOrCommand = #MF_BYCOMMAND)
;
; This function could be called 'SetMenuItemImage()' but it is possible that
; the PureBasic team decides to create seach a function with that name.
; So, the name 'SetMenuItemImageEx()' has been choosen.
ProcedureReturn SetMenuItemInfos(hMenu, ItemNum, *ImageID, "", ByPositionOrCommand)
EndProcedure
;
Procedure SetMenuTitleImageEx(hMenu, ItemNum, *ImageID)
;
; This function could be called 'SetMenuTitleImage()' but it is possible that
; the PureBasic team decides to create seach a function with that name.
; So, the name 'SetMenuTitleImageEx()' has been choosen.
ProcedureReturn SetMenuItemInfos(hMenu, ItemNum, *ImageID, "", #MF_BYPOSITION)
EndProcedure
;
Procedure SetMenuItemTextEx(hMenu, ItemNum, Text$, ByPositionOrCommand = #MF_BYCOMMAND)
;
; This function does the same as SetMenuItemText()
;
; The difference is that when a menu has not been created using CreateImageMenu()
; or CreatePopupImageMenu(), and you call the standard PureBasic functions
; SetMenuItemText(), PureBasic will erase the image eventually associated With
; the corresponding item.
;
; So, by using SetMenuItemTextEx() instead of using SetMenuItemText()
; you preserve the images you've attributed to the items using SetMenuItemImageEx(),
; even if the menu has been created by using CreateMenu() or CreatePopupMenu().
;
ProcedureReturn SetMenuItemInfos(hMenu, ItemNum, #GMI_Ignore, Text$, ByPositionOrCommand)
EndProcedure
;
Procedure SetMenuTitleTextEx(hMenu, ItemNum, Text$)
;
; This function does the same as SetMenuTitleText()
;
; The difference is that when a menu has not been created using CreateImageMenu()
; or CreatePopupImageMenu(), and you call the standard PureBasic functions
; SetMenuTitleText(), PureBasic will erase the image eventually associated With
; the corresponding item.
;
; So, by using SetMenuTitleTextEx() instead of using SetMenuTitleText(),
; you preserve the images you've attributed to the items using SetMenuItemImageEx(),
; specially if you attributed an image to the main window menu main items.
;
ProcedureReturn SetMenuItemInfos(hMenu, ItemNum, #GMI_Ignore, Text$, #MF_BYPOSITION)
EndProcedure
;
;
; *****************************************************************************
;
;- 4. FORTH PART: A DEMONSTRATION
;
;
CompilerIf #PB_Compiler_IsMainFile
;
; The following won't run when this file is used as 'Included'.
;
Procedure.s RefreshMsg(MainMenu)
;
Protected ItemPosition, ItemID, ImageID
Protected Msg$ = ""
;
ItemPosition = GetFirstMenuItemImage(MainMenu, #MF_BYPOSITION)
If ItemPosition <> -1
Msg$ + "First found image for menu:" + #CR$
Msg$ + " Position = " + Str(ItemPosition) + " - ImageID = " + GetMenuTitleImageEx(MainMenu, ItemPosition) + #CR$
Msg$ + " Corresponding text is: " + GetMenuTitleTextEx(MainMenu, ItemPosition) + #CR$
Msg$ + #CR$
Else
ItemID = GetFirstMenuItemImage(MainMenu, #MF_BYCOMMAND)
If ItemID <> -1
Msg$ + "First found image for menu:" + #CR$
Msg$ + " ItemID = " + Str(ItemID) + " - ImageID = " + GetMenuItemImageEx(MainMenu, ItemID) + #CR$
Msg$ + " Corresponding text is: " + GetMenuItemTextEx(MainMenu, ItemID) + #CR$
Msg$ + #CR$
Else
Msg$ + "There is no image in the menu." + #CR$
Msg$ + #CR$
EndIf
EndIf
ItemID = 3
ImageID = GetMenuItemImageEx(MainMenu, ItemID)
If ImageID
Msg$ + "There is an image for ItemID " + Str(ItemID) + ": " + Str(ImageID) + #CR$
Msg$ + " Corresponding text is: " + GetMenuItemTextEx(MainMenu, ItemID) + #CR$
Else
Msg$ + "There is no image for ItemID " + Str(ItemID) + "." + #CR$
Msg$ + " Corresponding text is: " + GetMenuItemTextEx(MainMenu, ItemID) + #CR$
EndIf
Msg$ + #CR$
Msg$ + "Looking by ID, the text having the ID 1 is: " + GetMenuItemTextEx(MainMenu, 1, #MF_BYCOMMAND) + #CR$
Msg$ + "Looking by position, the text at position 1 is: " + GetMenuItemTextEx(MainMenu, 1, #MF_BYPOSITION) + #CR$
Msg$ + " because each time you call 'MenuTitle()', PureBasic open a submenu" + #CR$
Msg$ + " to register the items of the main menu title. By the way, 'MenuTitle()'" + #CR$
Msg$ + " does exactly the same thing as 'OpenSubmenu()', except that it first." + #CR$
Msg$ + " call CloseSubMenu() if a precedent submenu (or MenuTitle) has been" + #CR$
Msg$ + " opened." + #CR$
Msg$ + " So, the main menu has only two positions: 'File' at 0, and 'Edit' at 1." + #CR$
;
Msg$ + #CR$
Msg$ + #CR$
Msg$ + "Open the 'File' menu and look at its content." + #CR$
Msg$ + "Then click on the button below and look again" + #CR$
Msg$ + "at the 'File' menu content." + #CR$
;
ProcedureReturn Msg$
EndProcedure
;
; Create a menu for démonstration
If OpenWindow(0, 0, 0, 400, 420, "GetMenuInfo Example", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
CreateImage(1, 24, 24, 32)
CreateImage(2, 24, 24, 32)
CreateImage(3, 24, 24, 32)
Define LastImage = GetAnImageForFree(171, 100, 24, 1)
LastImage = GetAnImageForFree(LastImage + 4, 100, 24, 2)
LastImage = GetAnImageForFree(LastImage + 4, 100, 24, 3)
;
Define MainMenu = CreateImageMenu(#PB_Any, WindowID(0))
MenuTitle("File")
MenuItem(1, "Open")
MenuItem(2, "Save", ImageID(1))
MenuBar()
MenuItem(3, "Exit")
;
MenuTitle("Edit")
MenuItem(4, "Cut")
;
Define TextGadget = TextGadget(#PB_Any, 10, 10, WindowWidth(0) - 20, 330, RefreshMsg(MainMenu))
;
Define ButtonWidth = 290
Define ButtonHeight = 25
Define ButtonX = (WindowWidth(0) - ButtonWidth) / 2
Define ButtonY = WindowHeight(0) - ButtonHeight - 30
Define ChangeMenuButton = ButtonGadget(#PB_Any, ButtonX, ButtonY, ButtonWidth, ButtonHeight, "Add an image to Open and change 'Open' to 'Test'")
;
;
Define Event
Repeat
Event = WaitWindowEvent()
If Event And EventGadget() = ChangeMenuButton
SetMenuItemImageEx(MainMenu, 1, ImageID(2))
;
; Add an image to a main menu title:
SetMenuItemImageEx(MainMenu, 0, ImageID(3), #MF_BYPOSITION)
SetMenuTitleTextEx(MainMenu, 0, "File2")
;
;
SetMenuItemTextEx(MainMenu, 1, "Test")
SetGadgetText(TextGadget, RefreshMsg(MainMenu))
EndIf
Until Event = #PB_Event_CloseWindow
CloseWindow(0)
EndIf
;
CompilerEndIf