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