Jump to content
Tuts 4 You

How to set diffrent colors in a single menu string?


LCF-AT

Recommended Posts

Posted

Hi guys,

I have a new question.I wanna ask whether its possible to use diffrent colors for a single menu item string entry?

Example: ContextMenu

Menu
----------------------------
ABC 123   <-- blue & red
111 222   <-- only red
AAB SAB   <-- only blue
1A2 B4C   <-- blue & red
----------------------------

Above you see 4 menu item entry / string.Just some letters and numbers.Now I would like to show the letters in color A (Blue) and the numbers in color B (Red).So is that possible to do that anyhow in a same single menu item?I am  also not sure whether its possible with a Ownerdraw to seperate the text color.Does anyone know that or had already done this?

greetz

Posted

Probably have to create your own control with a WS_POPUP window and use DrawText for the individual parts in the different colors. And have to calc the 'menu item' positions, and store the 'menu text' strings in an array or structures etc. Also calc position of the control relative to where mouse/cursor position was, for the placement to show it at.

  • Like 1
Posted

Hi fearless,

ok thanks.Sounds not so easy again.Have you seen any example for this already somewhere?

What about this.....lets say I wanna just show a menu string entry with a BOLD parts between like this....

Here is any String to see and THIS I wanna set in text bold only

Is that doable with any easier method maybe?

greetz

Posted

Hi LCF-AT, usually you have to use owner-drawn menus: you just tell windows you would take the burden to measure and draw the content by yourself.

A very very quick Google search takes you to

http://winapi.freetechsecrets.com/win32/WIN32Example_of_OwnerDrawn_Menu_Items.htm
https://www.codeproject.com/Articles/8715/Owner-drawn-menus-in-two-lines-of-code
https://www.codeguru.com/cpp/controls/menu/article.php/c3719/The-Easiest-Way-to-Code-the-Owner-Drawn-Menu.htm

Don't know if there's available an example in pure ASM, I'm afraid.

Regards,
Tony

 

  • Like 1
Teddy Rogers
Posted

I use something like this if I want to make a menu item bold...

bold.MENUITEMINFO
bold\cbSize = SizeOf(bold)
bold\fMask = #MIIM_STATE
bold\fState = #MFS_DEFAULT

SetMenuItemInfo_(MenuID(0), 2, #True, bold) ;"2" is the MenuItem to be made bold

https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-setmenuiteminfow
https://docs.microsoft.com/en-au/windows/desktop/api/winuser/ns-winuser-tagmenuiteminfoa

Ted.

  • Like 2
Posted

Hi guys,

thanks so far for the infos and the example tonyweb.

@Teddy Rogers

I am not sure whether this function SetMenuItemInfo using  MENUITEMINFO struct can do this (I think not).So my case is a little diffrent so I dont wanna make the whole single menu item entry in BOLD.Only just a part of this string like I did post above.

Context Menu
--------------------------
1 | Here is any String to see and >>THIS<< I wanna set in text bold only
2 | normaltext >>here in Bold<<
3 | >>this in bold<< and this not
--------------------------

I need some mixed strings as menu text items.A part is normal and a other part should be in bold etc.Just need to show this in context menu to focus the user eyes on it without to search long for this entry part.I wanna make a context menu what does show long http link and on a specific place in the links I wanna just mark / highlight so that the user can see it quickly you know.Thats what I wanna do.Best would be to set this part in a other color like this...below the whole single menu item text enty.

Here some String and >> this << should be in focus only in bold or in any else color like this or maybe both......you know

In ownerdraw I only can set the whole menu item entry to bold or xy color but not seperated so thats the tricky thing I dont know how to do that yet.

greetz

Teddy Rogers
Posted

Here is a working sample (in PureBasic) for you. This sets one of the menu items to be bold...

If OpenWindow(0,0,0,250,100,"Right click in the window...", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  If CreatePopupMenu(0)
    MenuItem(0,"MenuItem 0")
    MenuItem(1,"MenuItem 1")
    MenuItem(2,"MenuItem 2")
    MenuItem(3,"MenuItem 3")
    
    bold.MENUITEMINFO
    bold\cbSize = SizeOf(bold)
    bold\fMask = #MIIM_STATE
    bold\fState = #MFS_DEFAULT
    
    SetMenuItemInfo_(MenuID(0), 2, #True, bold) ;"2" is the MenuItem to be made bold
  EndIf
  
  Repeat
    Event = WaitWindowEvent()
    If Event = #WM_RBUTTONUP
      DisplayPopupMenu(0, WindowID(0))
    EndIf
  Until Event = #PB_Event_CloseWindow
  
EndIf

If you want to add colours and the like you will have to consider using #MFT_OWNERDRAW and manually draw the menu items on #WM_DRAWITEM event...

Ted.

Bold Menu Item.exe

  • Like 2
Posted

Hi Ted,

thanks but your file dosent run on x86 I use.

By the way,if I read your code example then you just set one menu item 2 into bold = whole item right?So I want this item into normal & bold style mixed.See my picture below how I mean it.Anyway whether in color or just bold if possible but it has to be in the same item / text string = mixed.

Colortextpart.png.b21ff7e7d5a364c14126f515deff9273.png

The 4you in this example above.I just used a paint app to make this quickly to show it and how I mean it.

greetz

Posted

Check Ted's answer again:

10 hours ago, Teddy Rogers said:

If you want to add colours and the like you will have to consider using #MFT_OWNERDRAW and manually draw the menu items on #WM_DRAWITEM event...

So if you want colors (any at all) or mix normal/bold then you will need to draw the items yourself using the GDI api SetTextColor and TextOut and those functions after responding to the draw item event by setting the owner draw flag.

  • Like 2
Posted

Hi again,

ok thanks again for the info.I have test it now with TextOut and ExtTextOut + new color function and it seems to work. :) Now I got a small another problem to receive WM_DRAWITEM and WM_MEASUREITEM messages.So in my case I did create a contextmenu by button press and not via right mouse.How to handle this problem now to get triggered at the 2 messages?

greetz

  • Like 1
Teddy Rogers
Posted

I knocked up a quick example, you could do something similar to this...

Declare.i WinProc(hWnd, Msg, wParam, lParam)
Declare.i SetMenuItemBold(MenuNum)

Global hMenu

If OpenWindow(0, 0, 0, 250, 100,"Right click in the window...", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  If SetWindowCallback(@WinProc())
    
    hMenu = CreatePopupMenu(0)
    
    If hMenu
      
      ; Create a text array for the menu item text.
      
      Global Dim menutext.s(4)
      menutext(0) = " MenuItem 0"   
      menutext(1) = " MenuItem 1"   
      menutext(2) = " MenuItem 2"
      menutext(3) = " End"
      
      ; Create the menu items and point to the array containing the text.
      
      MenuItem(0, menutext(0))
      MenuItem(1, menutext(1))
      MenuItem(2, menutext(2))
      MenuItem(3, menutext(3))
      
      ; Set menu items to #MFT_OWNERDRAW
      
      For a = 0 To 3
        With tag.MENUITEMINFO
          \cbSize = SizeOf(MENUITEMINFO)
          \fMask = #MIIM_TYPE
          \fType = #MFT_OWNERDRAW
          \dwTypeData = @menutext(a)
          SetMenuItemInfo_(hMenu, a, #True, @tag)
        EndWith
      Next
      
    EndIf
    
    ; PureBasic window event loop.
    
    Repeat
      Event = WaitWindowEvent()
      
      Select Event
        Case #PB_Event_RightClick
          DisplayPopupMenu(0, WindowID(0))
          
          ; When a menu item is clicked on set it to bold.
          
        Case #PB_Event_Menu
          Select EventMenu()
            Case 0 : SetMenuItemBold(EventMenu())
            Case 1 : SetMenuItemBold(EventMenu())
            Case 2 : SetMenuItemBold(EventMenu())
            Case 3 : End
          EndSelect
          
      EndSelect
      
    Until Event = #PB_Event_CloseWindow
    
  EndIf
EndIf

Procedure.i WinProc(hWnd, Msg, wParam, lParam)
  Static hbrush
  
  Select Msg
    Case #WM_DESTROY
      
      ; Delete created objects once the window is destroyed.
      
      DeleteObject_(hbrush)
      
    Case #WM_MEASUREITEM
      
      ; lParam - Pointer to a MEASUREITEMSTRUCT structure that contains the dimensions of the owner-drawn control or menu item.
      
      *lpm.MEASUREITEMSTRUCT = lParam
      
      ; Define the width and height for the menu item to be created.
      
      *lpm\itemWidth = 200
      *lpm\itemHeight = 30
      
    Case #WM_DRAWITEM:
      
      ; lParam - Pointer to a DRAWITEMSTRUCT structure containing information about the item to be drawn and the type of drawing required.
      
      *lpd.DRAWITEMSTRUCT = lParam
      
      ; If a menu item is selected, use #COLOR_MENUHILIGHT.
      
      If *lpd\itemState & #ODS_SELECTED
        hbrush = CreateSolidBrush_(GetSysColor_(#COLOR_MENUHILIGHT))
        SelectObject_(*lpd\hDC, hbrush)
      EndIf
      
      ; Set the background mix mode of the specified device context to #TRANSPARENT.
      ; This sets the text background to #TRANSPARENT (otherwise its background will be filled a different colour from that of the menu).
      
      SetBkMode_(*lpd\hDC, #TRANSPARENT)

      ; Set the device context boundary pen colour, the null pen draws nothing.
      
      SelectObject_(*lpd\hDC, GetStockObject_(#NULL_PEN))
      
      ; A rectangle that defines the boundaries of the control to be drawn.
      ; When drawing menu items, the owner window must not draw outside the boundaries of the rectangle defined by the rcItem member.
      
      Rectangle_(*lpd\hDC, *lpd\rcItem\left, *lpd\rcItem\top, *lpd\rcItem\right, *lpd\rcItem\bottom)
      
      If menutext(*lpd\itemID) = menutext(1)
        
        SetTextColor_(*lpd\hDC, #Green)
        DrawText_(*lpd\hDC, menutext(*lpd\itemID), -1, @*lpd\rcItem, #Null)
        
      ElseIf menutext(*lpd\itemID) = menutext(2)
        
        ; Calculate the length of the menu item text.
        
        DrawText_(*lpd\hDC, menutext(*lpd\itemID), -1, @*lpd\rcItem, #DT_CALCRECT)
        
        ; Set the menu item text colour and then draw it.
        
        SetTextColor_(*lpd\hDC, #Blue)
        DrawText_(*lpd\hDC, menutext(*lpd\itemID), -1, @*lpd\rcItem, #Null)
        
        ; Save the old right co-ordinate so we can offset the additional menu item text.
        
        oldRight = *lpd\rcItem\right
        
        ; Calculate the length of the additional menu item text.
        
        DrawText_(*lpd\hDC, " Tuts4You", -1, @*lpd\rcItem, #DT_CALCRECT)
        
        ; Calculate the offset to add the new text in the menu.
        
        *lpd\rcItem\left = oldRight
        *lpd\rcItem\right + oldRight
        
        ; Set the menu item text colour and then draw it.
        
        SetTextColor_(*lpd\hDC, #Red)
        DrawText_(*lpd\hDC, " Tuts4You", -1, @*lpd\rcItem, #Null)

      Else
        DrawText_(*lpd\hDC, menutext(*lpd\itemID), -1, @*lpd\rcItem, #Null)
      EndIf
      
  EndSelect
  
  ProcedureReturn #PB_ProcessPureBasicEvents
EndProcedure

Procedure SetMenuItemBold(hMenuNumSel)

    bold.MENUITEMINFO
    bold\cbSize = SizeOf(bold)
    bold\fMask = #MIIM_STATE
    bold\fState = #MFS_DEFAULT
    
    SetMenuItemInfo_(hMenu, hMenuNumSel, #True, bold)
  
EndProcedure

Ted.

Coloured Menu Item.exe

  • Like 2
Posted

MFT_OWNERDRAW flag should get the messages sent.  It should be set on creation as in Ted's example above or possibly other ways like SetStyle API.

  • Like 1
Posted (edited)

Hi again,

I have another question.I have created a menu again with dynamic IDs and Strings using AppendMenu & ModifyMenu (MF_OWNERDRAW) & SetMenuItemBitmaps.If I dont use MF_OWNERDRAW flag = not calling WM_DRAWITEM then the whole menu gets shown right.If I use MF_OWNERDRAW flag then the icons will not shown.

So my question in this case is how to show that icon?Is it possible to use SetMenuItemBitmaps function with AppendMenu & ModifyMenu (MF_OWNERDRAW)?How to receive then the bitmap handle of a menu entry in WM_DRAWITEM?

My goal is it to set icons X (are dynamic) before I enter WM_DRAWITEM.On the same place where I call AppendMenu / ModifyMenu if possible.In this case I wouldnt need to create a extra array to store ID & icon which belongs to ID into etc.

greetz

EDIT: I see I also have a problem to use GetMenuString function after I did choose a menu entry.The functions gives a error called Menu_item_not_found in Ownerdraw mode!?In not Ownerdraw mode it works.Strange.The ID & menu handle are correct.

Edited by LCF-AT
Teddy Rogers
Posted

If you want to display an icon in the menu you can use something like DrawIconEx. If it is a bitmap you can BitBlt or similar. The icon needs to be placed at the beginning of the menu, you then offset the placement of any subsequent text in the menu after the icon.

I am not entirely sure what you mean by dynamic icons or what you are trying to achieve - I'll have a guess... The menu will be drawn each time it is requested to be shown, any icons can be reloaded and used in any preferred order. You will need to keep a track of your images and icons as you will need to free up these resources at some time otherwise you will risk GDI leaks. If I am guessing at what you are trying to do with dynamic icons (and if I guessed correctly) there is no way around it, you will have to track your icons handles. I have had to do something similar in the past and used structured arrays with defined types. A dynamic example would be tracking windows; titles, position, order, icon, window handle, etc. This information is captured and stored in a structured array and then the necessary information is displayed in the menu.

In the below example I have expanded on the previous code I posted and added icons in to the menu. Code is a bit crude though it gives you the idea...

Spoiler

Declare.i WinProc(hWnd, Msg, wParam, lParam)
Declare.i SetMenuItemBold(MenuNum)
Declare.i DrawTextColoured(hColour, *lpd.DRAWITEMSTRUCT, oldright, hText.s)

Global hMenu

If OpenWindow(0, 0, 0, 250, 100,"Right click in the window...", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  If SetWindowCallback(@WinProc())
    
    hMenu = CreatePopupMenu(0)
    
    If hMenu
      
      ; Create a text array for the menu item text.
      
      Global Dim menutext.s(4)
      menutext(0) = " MenuItem 0"   
      menutext(1) = " MenuItem 1"   
      menutext(2) = " "
      menutext(3) = " End"
      
      ; Create the menu items and point to the array containing the text.
      
      MenuItem(0, menutext(0))
      MenuItem(1, menutext(1))
      MenuItem(2, menutext(2))
      MenuItem(3, menutext(3))

      ; Set menu items to #MFT_OWNERDRAW
      
      For a = 0 To 3
        With tag.MENUITEMINFO
          \cbSize = SizeOf(MENUITEMINFO)
          \fMask = #MIIM_TYPE
          \fType = #MFT_OWNERDRAW
          \dwTypeData = @menutext(a)
          SetMenuItemInfo_(hMenu, a, #True, @tag)
        EndWith
      Next
      
    EndIf
    
    ; PureBasic window event loop.
    
    Repeat
      Event = WaitWindowEvent()
      
      Select Event
        Case #PB_Event_RightClick
          DisplayPopupMenu(0, WindowID(0))
          
          ; When a menu item is clicked on set it to bold.
          
        Case #PB_Event_Menu
          Select EventMenu()
            Case 0 : SetMenuItemBold(EventMenu())
            Case 1 : SetMenuItemBold(EventMenu())
            Case 2 : SetMenuItemBold(EventMenu())
            Case 3 : End
          EndSelect
          
      EndSelect
      
    Until Event = #PB_Event_CloseWindow
    
  EndIf
EndIf

Procedure.i WinProc(hWnd, uMsg, wParam, lParam)
  Static hbrush
  
  Select uMsg
    Case #WM_DESTROY
      
      ; Delete created objects once the window is destroyed.
      
      DeleteObject_(hbrush)
      
    Case #WM_MEASUREITEM
      
      ; lParam - Pointer to a MEASUREITEMSTRUCT structure that contains the dimensions of the owner-drawn control or menu item.
      
      *lpm.MEASUREITEMSTRUCT = lParam
      
      ; Define the width and height for the menu item to be created.
      
      *lpm\itemWidth = 200
      *lpm\itemHeight = 30
      
    Case #WM_DRAWITEM:
      
      ; lParam - Pointer to a DRAWITEMSTRUCT structure containing information about the item to be drawn and the type of drawing required.
      
      *lpd.DRAWITEMSTRUCT = lParam
      
      ; If a menu item is selected, use #COLOR_MENUHILIGHT.
      
      If *lpd\itemState & #ODS_SELECTED
        hbrush = CreateSolidBrush_(GetSysColor_(#COLOR_MENUHILIGHT))
        SelectObject_(*lpd\hDC, hbrush)
      EndIf
      
      ; Set the background mix mode of the specified device context to #TRANSPARENT.
      ; This sets the text background to #TRANSPARENT (otherwise its background will be filled a different colour from that of the menu).
      
      SetBkMode_(*lpd\hDC, #TRANSPARENT)
      
      ; Set the device context boundary pen colour, the null pen draws nothing.
      
      SelectObject_(*lpd\hDC, GetStockObject_(#NULL_PEN))
      
      ; A rectangle that defines the boundaries of the control to be drawn.
      ; When drawing menu items, the owner window must not draw outside the boundaries of the rectangle defined by the rcItem member.
      
      Rectangle_(*lpd\hDC, *lpd\rcItem\left, *lpd\rcItem\top, *lpd\rcItem\right, *lpd\rcItem\bottom)
      
      ; Load an icon and draw it at the beginning of each menu.
      
      #OIC_ERROR = 32513

      hImage = LoadImage_(0, #OIC_ERROR, #IMAGE_ICON, 0, 0, #LR_SHARED)
      DrawIconEx_(*lpd\hDC, 2, *lpd\rcItem\top+2, hImage, 26, 26, 0, #Null, #DI_NORMAL)
      
      ; Set the offset any menu text is to start - after the placement of the icon.
      
      *lpd\rcItem\left = 30
      
      ; Look for specific menu items to customise the menu text colour.
      
      If menutext(*lpd\itemID) = menutext(1)
        
        SetTextColor_(*lpd\hDC, #Red)
        DrawText_(*lpd\hDC, menutext(*lpd\itemID), -1, @*lpd\rcItem, #Null)
        
      ElseIf menutext(*lpd\itemID) = menutext(2)

        oldright = DrawTextColoured(#Black, *lpd.DRAWITEMSTRUCT, *lpd\rcItem\left, "")
        oldright = DrawTextColoured(#Red, *lpd.DRAWITEMSTRUCT, oldright, "T")
        oldright = DrawTextColoured(#Green, *lpd.DRAWITEMSTRUCT, oldright, "u")
        oldright = DrawTextColoured(#Blue, *lpd.DRAWITEMSTRUCT, oldright, "t")
        oldright = DrawTextColoured(#Red, *lpd.DRAWITEMSTRUCT, oldright, "s")
        oldright = DrawTextColoured(#Green, *lpd.DRAWITEMSTRUCT, oldright, " ")
        oldright = DrawTextColoured(#Blue, *lpd.DRAWITEMSTRUCT, oldright, "4")
        oldright = DrawTextColoured(#Red, *lpd.DRAWITEMSTRUCT, oldright, " ")
        oldright = DrawTextColoured(#Green, *lpd.DRAWITEMSTRUCT, oldright, "Y")
        oldright = DrawTextColoured(#Blue, *lpd.DRAWITEMSTRUCT, oldright, "o")
        oldright = DrawTextColoured(#Red, *lpd.DRAWITEMSTRUCT, oldright, "u")
       
      Else
        DrawText_(*lpd\hDC, menutext(*lpd\itemID), -1, @*lpd\rcItem, #Null)
      EndIf

  EndSelect
  
  ProcedureReturn #PB_ProcessPureBasicEvents
EndProcedure

Procedure.i SetMenuItemBold(hMenuNumSel)
  
  bold.MENUITEMINFO
  bold\cbSize = SizeOf(bold)
  bold\fMask = #MIIM_STATE
  bold\fState = #MFS_DEFAULT
  
  SetMenuItemInfo_(hMenu, hMenuNumSel, #True, bold)
  
EndProcedure

Procedure.i DrawTextColoured(hColour, *lpd.DRAWITEMSTRUCT, hOldright, hText.s)
  Protected hLength
  
  ; Will we be drawing text from menutext or adding some custom characters?
  
  If hText.s = ""
    hText.s = menutext(*lpd\itemID)
  EndIf
  
  ; Calculate the length of the required text rectangle.
  
  DrawText_(*lpd\hDC, hText.s, -1, @*lpd\rcItem, #DT_CALCRECT)
  
  ; Save the length of the text rectangle, we will need to add this to correct the new right position.
  
  hLength = (*lpd\rcItem\right - *lpd\rcItem\left)
  
  ; Set the new left position, offset this from the old saved right position (if there was one).

  *lpd\rcItem\left = hOldRight
  
  ; Set the new right position, this is offset from the new left position.
  ; Add two for padding between characters.
  
  *lpd\rcItem\right = (*lpd\rcItem\left + hLength + 2)
  
  ; Set the menu item text colour and then draw it.
  
  SetTextColor_(*lpd\hDC, hColour)
  DrawText_(*lpd\hDC, hText.s, -1, @*lpd\rcItem, #Null)
  
  ; Return with the last right position in the menu.
  
  ProcedureReturn *lpd\rcItem\right
EndProcedure

 

Ted.

Coloured Menu Item + Icon.exe

  • Like 1
Posted

Hi Ted,

thanks again for trying to help me.Your executables do not run on my system x86.

Dynamic icons I do create by myself in a new device (hdc) with color XY and a value counter starting with 1 till X.Depends on the menu entrys.Similar like you have seen in my posted pics before already.I am also using dynamic ID values for each menu entry which I do catch at WM_COMMAND if I choose any menu entry.All working normaly as it should if I dont use & handle MF_OWNERDRAW flag.Now in my new case I would like to color some menu entry parts and need to use & handle the OWNERDRAW stuff in here I got some issues now.First issue is that I cant set the icon/s directly into menu where I am calling AppendMenu function and can just do it at WM_DRAWITEM what means I need to store all actually icons handles / IDs & menu text content (alloc space) extra somewhere in a array to read and use them at WM_DRAWITEM = extra work I dont need to do if I am using not OWNERDRAW.Now in the case I do handle it via array so far I get the new problem that the function GetMenuString does fail if I choose any menu entry and catch the menu ID which belongs to at WM_COMMAND / wparam.The function does just fail and tells me menu_item_not_found (error) what I do not understand yet.Menu handle & IDs are correct so it also works on same way if I dont use OWNERDRAW but now it dosent work anymore.Now I tried to use a alternativ function GetMenuItemInfo and this does also fail to get just the string using MIIM_STRING / MFT_STRING etc and tells me the same error (menu_item_not_found).Now if I use this function with MIIM_DATA mask then I get a pointer back into dwItemData where a part of the menu string is stored but not the whole string.My menu strings can have many byte lenghts and should be not limited.At the moment I am reading my ini file to get the whole menu string I did choose at this place.Pretty much of a de-tour I have to do just to set a menu string in diffrent color parts but it seems there is no better way to do this easier anyhow.

greetz

Posted

Hi again,

ok I have a new  small question about DrawText function and using diffrent colors at a specific position.How to get the right rect / left to color a specific text part right over the present text?So I tried this now but somehow the new text part & color dosent match with the real position.

Example: This is my menu text entry I did draw into menu entry.

111111111111dadjkhakjhdkjahsjdkjs2222222222222222adsVBA------------adasdas121212

Now after this I do check for this -------- and found it at position 55 byte.The string I found has a lenght of 12 Byte.Now I know I need to color the ------ from position 55 byte with a lenght of 12 byte.If I now add the lenght of 55 byte to DRAWITEMSTRUCT.rcItem.left and calling the again DrawText then the new string (I did copy that part before) will created but not at same position as in menu.My question now is how to find out the right rect.left of the string part ------- and using the right rect.left for another DrawText function call?You know what I mean right.Is there any rect X function for that?Not sure how to handle this problem now.

greetz

Teddy Rogers
Posted

If you have a look at my DrawTextColoured procedure in my previous example and the one below you can see how I change the text colour and calculate the required positioning in the menu.

The example (below) uses AppendMenu function to add items in to the owner drawn menu. It then adds the new item in to the menu array. You can also see how to add an icon/image in to these menu items. I included an example on how you can process menu events from WM_COMMAND.

I suggest having a read through the Windows developers documents regarding menus, particularly the About section.

https://docs.microsoft.com/en-au/windows/desktop/menurc/menus

I have compiled the example for you this time in x32...

Spoiler

Declare.i WinProc(hWnd, Msg, wParam, lParam)
Declare.i SetMenuItemBold(MenuNum)
Declare.i DrawTextColoured(hColour, *lpd.DRAWITEMSTRUCT, oldright, hText.s)
Declare.i AddMenuItem(hMenu, uIDNewItem, MenuText.s)
Declare.i AddMenuIcon(*lpd.DRAWITEMSTRUCT, OICicon)

; Create a text array for the menu item text and hMenu.

Global hMenu
Global Dim menutext.s(0)

#OIC_SAMPLE   = 32512
#OIC_HAND     = 32513
#OIC_QUES     = 32514
#OIC_BANG     = 32515
#OIC_NOTE     = 32516

If OpenWindow(0, 0, 0, 250, 100,"Right click in the window...", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  If SetWindowCallback(@WinProc())
    
    hMenu = CreatePopupMenu(0)
    
    If hMenu
      AddMenuItem(hMenu, GetMenuItemCount_(hMenu), " 0 AddMenuItem")
      AddMenuItem(hMenu, GetMenuItemCount_(hMenu), " 1 Change to Bold")
      AddMenuItem(hMenu, GetMenuItemCount_(hMenu), " 2 ")
      AddMenuItem(hMenu, GetMenuItemCount_(hMenu), " 3 End")
    EndIf
    
    ; PureBasic window event loop.
    
    Repeat
      Event = WaitWindowEvent()
      
      Select Event
        Case #PB_Event_RightClick
          DisplayPopupMenu(0, WindowID(0))
          
      EndSelect
      
    Until Event = #PB_Event_CloseWindow
    
  EndIf
EndIf

Procedure.i WinProc(hWnd, uMsg, wParam, lParam)
  Static hbrush
  
  Select uMsg
    Case #WM_COMMAND
      Select wParam >> 16 & $ffff
        Case #BN_CLICKED
          Select (wParam & $ffff)
            Case 0
              uIDNewItem = GetMenuItemCount_(hMenu)
              AddMenuItem(hMenu, uIDNewItem, " " + Str(uIDNewItem) + " MenuItem")
            Case 1
              SetMenuItemBold((wParam & $ffff))
            Case 2
              SetMenuItemBold((wParam & $ffff))
            Case 3
              End
            Default
              MessageRequester("A Random Title...", "You selected MenuItem " + Str(wParam & $ffff), #PB_MessageRequester_Ok)
          EndSelect
      EndSelect  
      
    Case #WM_DESTROY
      
      ; Delete created objects once the window is destroyed.
      
      DeleteObject_(hbrush)
      
    Case #WM_MEASUREITEM
      
      ; lParam - Pointer to a MEASUREITEMSTRUCT structure that contains the dimensions of the owner-drawn control or menu item.
      
      *lpm.MEASUREITEMSTRUCT = lParam
      
      ; Define the width and height for the menu item to be created.
      
      *lpm\itemWidth = 200
      *lpm\itemHeight = 30
      
    Case #WM_DRAWITEM:
      
      ; lParam - Pointer to a DRAWITEMSTRUCT structure containing information about the item to be drawn and the type of drawing required.
      
      *lpd.DRAWITEMSTRUCT = lParam
      
      ; If a menu item is selected, use #COLOR_MENUHILIGHT.
      
      If *lpd\itemState & #ODS_SELECTED
        hbrush = CreateSolidBrush_(GetSysColor_(#COLOR_MENUHILIGHT))
        SelectObject_(*lpd\hDC, hbrush)
      EndIf
      
      ; Set the background mix mode of the specified device context to #TRANSPARENT.
      ; This sets the text background to #TRANSPARENT (otherwise its background will be filled a different colour from that of the menu).
      
      SetBkMode_(*lpd\hDC, #TRANSPARENT)
      
      ; Set the device context boundary pen colour, the null pen draws nothing.
      
      SelectObject_(*lpd\hDC, GetStockObject_(#NULL_PEN))
      
      ; A rectangle that defines the boundaries of the control to be drawn.
      ; When drawing menu items, the owner window must not draw outside the boundaries of the rectangle defined by the rcItem member.
      
      Rectangle_(*lpd\hDC, *lpd\rcItem\left, *lpd\rcItem\top, *lpd\rcItem\right, *lpd\rcItem\bottom)

      ; Set the offset any menu text is to start - after the placement of the icon.
      
      *lpd\rcItem\left = 30
      
      ; Look for specific menu items to customise the menu text colour and icon placement.
      
      If menutext(*lpd\itemID) = menutext(1)
        
        AddMenuIcon(lParam, #OIC_NOTE)
        
        SetTextColor_(*lpd\hDC, #Red)
        DrawText_(*lpd\hDC, menutext(*lpd\itemID), -1, @*lpd\rcItem, #Null)
        
      ElseIf menutext(*lpd\itemID) = menutext(2)
        
        AddMenuIcon(lParam, #OIC_SAMPLE)
        
        oldright = DrawTextColoured(#Black, *lpd.DRAWITEMSTRUCT, *lpd\rcItem\left, "")
        oldright = DrawTextColoured(#Red, *lpd.DRAWITEMSTRUCT, oldright, "T")
        oldright = DrawTextColoured(#Green, *lpd.DRAWITEMSTRUCT, oldright, "u")
        oldright = DrawTextColoured(#Blue, *lpd.DRAWITEMSTRUCT, oldright, "t")
        oldright = DrawTextColoured(#Red, *lpd.DRAWITEMSTRUCT, oldright, "s")
        oldright = DrawTextColoured(#Green, *lpd.DRAWITEMSTRUCT, oldright, " ")
        oldright = DrawTextColoured(#Blue, *lpd.DRAWITEMSTRUCT, oldright, "4")
        oldright = DrawTextColoured(#Red, *lpd.DRAWITEMSTRUCT, oldright, " ")
        oldright = DrawTextColoured(#Green, *lpd.DRAWITEMSTRUCT, oldright, "Y")
        oldright = DrawTextColoured(#Blue, *lpd.DRAWITEMSTRUCT, oldright, "o")
        oldright = DrawTextColoured(#Red, *lpd.DRAWITEMSTRUCT, oldright, "u")
        
      ElseIf menutext(*lpd\itemID) = menutext(3)
        AddMenuIcon(lParam, #OIC_HAND)
        DrawText_(*lpd\hDC, menutext(*lpd\itemID), -1, @*lpd\rcItem, #Null)
      Else
        AddMenuIcon(lParam, #OIC_QUES)
        DrawText_(*lpd\hDC, menutext(*lpd\itemID), -1, @*lpd\rcItem, #Null)
      EndIf

  EndSelect
  
  ProcedureReturn #PB_ProcessPureBasicEvents
EndProcedure

Procedure.i SetMenuItemBold(hMenuNumSel)
  
  bold.MENUITEMINFO
  bold\cbSize = SizeOf(bold)
  bold\fMask = #MIIM_STATE
  bold\fState = #MFS_DEFAULT
  
  SetMenuItemInfo_(hMenu, hMenuNumSel, #True, bold)
  
EndProcedure

Procedure.i DrawTextColoured(hColour, *lpd.DRAWITEMSTRUCT, hOldright, hText.s)
  Protected hLength
  
  ; Will we be drawing text from menutext or adding some custom characters?
  
  If hText.s = ""
    hText.s = menutext(*lpd\itemID)
  EndIf
  
  ; Calculate the length of the required text rectangle.
  
  DrawText_(*lpd\hDC, hText.s, -1, @*lpd\rcItem, #DT_CALCRECT)
  
  ; Save the length of the text rectangle, we will need to add this to correct the new right position.
  
  hLength = (*lpd\rcItem\right - *lpd\rcItem\left)
  
  ; Set the new left position, offset this from the old saved right position (if there was one).

  *lpd\rcItem\left = hOldRight
  
  ; Set the new right position, this is offset from the new left position.
  ; Add two for padding between characters.
  
  *lpd\rcItem\right = (*lpd\rcItem\left + hLength + 2)
  
  ; Set the menu item text colour and then draw it.
  
  SetTextColor_(*lpd\hDC, hColour)
  DrawText_(*lpd\hDC, hText.s, -1, @*lpd\rcItem, #Null)
  
  ; Return with the last right position in the menu.
  
  ProcedureReturn *lpd\rcItem\right
EndProcedure

Procedure.i AddMenuItem(hMenu, uIDNewItem, lpNewItem.s)
  
  ; Append a new menu item at the end of hMenu
  
  If AppendMenu_(hMenu, #MF_STRING, uIDNewItem, lpNewItem.s)
    
    ; If we successfully appended the new menu item add the entry to our menu text array.
    
    ReDim menutext.s(uIDNewItem)
    
    menutext(uIDNewItem) = lpNewItem.s
    
    ; Set the new menu item to be owner drawn.
    
    With tag.MENUITEMINFO
      \cbSize = SizeOf(MENUITEMINFO)
      \fMask = #MIIM_TYPE
      \fType = #MFT_OWNERDRAW
      \dwTypeData = @menutext(uIDNewItem)
      SetMenuItemInfo_(hMenu, uIDNewItem, #True, @tag)
    EndWith
  EndIf
  
EndProcedure

Procedure AddMenuIcon(*lpd.DRAWITEMSTRUCT, OICicon)
  
  ; Load a shared system icon and draw it at the beginning of each menu.
  
  hImage = LoadImage_(0, OICicon, #IMAGE_ICON, 0, 0, #LR_SHARED)
  DrawIconEx_(*lpd\hDC, 2, *lpd\rcItem\top+2, hImage, 26, 26, 0, #Null, #DI_NORMAL)
  
EndProcedure

 

Ted.

Appended Menu Items.exe

  • Like 1
Posted

Hi again,

so I tried it again to use DrawText function with DT_CALCRECT and got it working now.Its little tricky so I have to draw & calc 3 times but it seems to work now right. :) The next problem I still have is that menu thing.GetMenuString function dosent work in this case (no clue why) and using AppendMenu at WM_COMMAND (?!) dosent work.Anyway,I stored the menu strings into array and pick the right one at WM_COMMAND which belongs to ID.Its again a little laborious (similar I had before with ODraw) but anyway now.

Thanks again so far.

greetz

Teddy Rogers
Posted

GetMenuString function works fine, refer to the code snippet below...

cchMax = GetMenuString_(hMenu, (wParam & $ffff), #Null, #Null, #MF_BYCOMMAND)
cchMax = cchMax + 1
lpString.s = Space(cchMax)
GetMenuString_(hMenu, (wParam & $ffff), @lpString.s, cchMax, #MF_BYCOMMAND)
Debug lpString.s

I suspect what you are trying to do is read text in the menu that has previously been drawn using the DrawText function. GetMenuString will not find these strings, you will have to work something else out.

If the intention of using GetMenuString is a way of identifying a menu item that has been clicked on I think this is not the best way to go about it. When you add a menu item you should already know its position ID and the text that has been added. You can add and use this information in your array. When the menu item is clicked on you can find the position ID in wParam. You should then be able to use this as the reference point in your array...

Ted.

  • Like 1
Posted

Hi Ted,

thanks for that info about that function so I did wonder already why its not working in OD mode.Thats the bad thing if I use ODraw = need to store menu string locations & IDs into a array.As I said,a little too much effort just to get some menu strings colored as I wanted.In my case the normal mode (not using ODraw) would be enough but now I need to make this extra de-tour etc.

PS: Your exe file is still x64.Dosent run for me. :) In CFF I get also PE64 to see.Just as info.

greetz

Teddy Rogers
Posted

You can still read menu item strings using the GetMenuString function. What I meant previously was that you have to change my example code to work differently with the DrawText function as the string of characters for, "Tuts 4 You", was not saved in the menu structure.

The next example has, "Tuts 4 You", saved in the menu structure so you will be able to find this using GetMenuString. Each character is individually retrieved from the menu string and DrawText is used to display it. It also cycles through each RGB colour as before.

When you click on a menu item GetMenuString is used to get the menu item text. It is then displayed in a message.

I have attached both x32 and x64 this time. Apologies for the previous compile mistake!

Spoiler

Declare.i WinProc(hWnd, Msg, wParam, lParam)
Declare.i SetMenuItemBold(MenuNum)
Declare.i DrawTextColoured(hColour, *lpd.DRAWITEMSTRUCT, oldright, hText.s)
Declare.i AddMenuItem(hMenu, uIDNewItem, MenuText.s)
Declare.i AddMenuIcon(*lpd.DRAWITEMSTRUCT, OICicon)

; Create a text array for the menu item text and hMenu.

Global hMenu
Global Dim menutext.s(0)

#OIC_SAMPLE   = 32512
#OIC_HAND     = 32513
#OIC_QUES     = 32514
#OIC_BANG     = 32515
#OIC_NOTE     = 32516

If OpenWindow(0, 0, 0, 250, 100,"Right click in the window...", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  If SetWindowCallback(@WinProc())
    
    hMenu = CreatePopupMenu(0)
    
    If hMenu
      AddMenuItem(hMenu, GetMenuItemCount_(hMenu), " 0 AddMenuItem")
      AddMenuItem(hMenu, GetMenuItemCount_(hMenu), " 1 Change to Bold")
      AddMenuItem(hMenu, GetMenuItemCount_(hMenu), " 2 Tuts 4 You")
      AddMenuItem(hMenu, GetMenuItemCount_(hMenu), " 3 End")
    EndIf
    
    ; PureBasic window event loop.
    
    Repeat
      Event = WaitWindowEvent()
      
      Select Event
        Case #PB_Event_RightClick
          DisplayPopupMenu(0, WindowID(0))
          
      EndSelect
      
    Until Event = #PB_Event_CloseWindow
    
  EndIf
EndIf

Procedure.i WinProc(hWnd, uMsg, wParam, lParam)
  Static hbrush
  
  Select uMsg
    Case #WM_COMMAND
      Select wParam >> 16 & $ffff
        Case #BN_CLICKED
          Select (wParam & $ffff)
            Case 0
              uIDNewItem = GetMenuItemCount_(hMenu)
              AddMenuItem(hMenu, uIDNewItem, " " + Str(uIDNewItem) + " MenuItem")
            Case 1
              SetMenuItemBold((wParam & $ffff))
            Case 2
              SetMenuItemBold((wParam & $ffff))
            Case 3
              End
          EndSelect
          
          ; Use GetMenuString and present this information in a message...
          
          cchMax = GetMenuString_(hMenu, (wParam & $ffff), #Null, #Null, #MF_BYCOMMAND)
          cchMax = cchMax + 1
          lpString.s = Space(cchMax)
          GetMenuString_(hMenu, (wParam & $ffff), @lpString.s, cchMax, #MF_BYCOMMAND)
          
          MessageRequester("A Random Title...", "You selected : " + lpString.s, #PB_MessageRequester_Ok)
      EndSelect  
      
    Case #WM_DESTROY
      
      ; Delete created objects once the window is destroyed.
      
      DeleteObject_(hbrush)
      
    Case #WM_MEASUREITEM
      
      ; lParam - Pointer to a MEASUREITEMSTRUCT structure that contains the dimensions of the owner-drawn control or menu item.
      
      *lpm.MEASUREITEMSTRUCT = lParam
      
      ; Define the width and height for the menu item to be created.
      
      *lpm\itemWidth = 200
      *lpm\itemHeight = 30
      
    Case #WM_DRAWITEM:
      
      ; lParam - Pointer to a DRAWITEMSTRUCT structure containing information about the item to be drawn and the type of drawing required.
      
      *lpd.DRAWITEMSTRUCT = lParam
      
      ; If a menu item is selected, use #COLOR_MENUHILIGHT.
      
      If *lpd\itemState & #ODS_SELECTED
        hbrush = CreateSolidBrush_(GetSysColor_(#COLOR_MENUHILIGHT))
        SelectObject_(*lpd\hDC, hbrush)
      EndIf
      
      ; Set the background mix mode of the specified device context to #TRANSPARENT.
      ; This sets the text background to #TRANSPARENT (otherwise its background will be filled a different colour from that of the menu).
      
      SetBkMode_(*lpd\hDC, #TRANSPARENT)
      
      ; Set the device context boundary pen colour, the null pen draws nothing.
      
      SelectObject_(*lpd\hDC, GetStockObject_(#NULL_PEN))
      
      ; A rectangle that defines the boundaries of the control to be drawn.
      ; When drawing menu items, the owner window must not draw outside the boundaries of the rectangle defined by the rcItem member.
      
      Rectangle_(*lpd\hDC, *lpd\rcItem\left, *lpd\rcItem\top, *lpd\rcItem\right, *lpd\rcItem\bottom)
      
      ; Set the offset any menu text is to start - after the placement of the icon.
      
      *lpd\rcItem\left = 30
      
      ; Look for specific menu items to customise the menu text colour and icon placement.
      
      Select *lpd\itemID
        Case 1
          
          AddMenuIcon(lParam, #OIC_NOTE)
          SetTextColor_(*lpd\hDC, #Red)
          DrawText_(*lpd\hDC, menutext(*lpd\itemID), -1, @*lpd\rcItem, #Null)
          
        Case 2
          
          AddMenuIcon(lParam, #OIC_SAMPLE)
          
          ; pID - tracks the character position in the menu string.
          ; RGB - tracks the RGB colour for the characters to be used.
          ; Len - Length of the menu string.
          ; oldright - tracks position of the last right position used by DrawText.
          
          pID = 1
          RGB = #Red
          Len = Len(menutext(*lpd\itemID))
          oldright = *lpd\rcItem\left
          
          ; DrawText each character in the menu string whilst cycling through RGB colours.
          
          For pID = 1 To Len
            pCCR.s = Mid(menutext(*lpd\itemID), pID, 1)
            
            Select RGB
              Case #Red
                RGB = #Green
              Case #Green
                RGB = #Blue
              Case #Blue
                RGB = #Red
            EndSelect
            
            oldright = DrawTextColoured(RGB, *lpd.DRAWITEMSTRUCT, oldright, pCCR.s)
          Next
          
        Case 3
          AddMenuIcon(lParam, #OIC_HAND)
          DrawText_(*lpd\hDC, menutext(*lpd\itemID), -1, @*lpd\rcItem, #Null)
        Default
          AddMenuIcon(lParam, #OIC_QUES)
          DrawText_(*lpd\hDC, menutext(*lpd\itemID), -1, @*lpd\rcItem, #Null)
      EndSelect
      
  EndSelect
  
  ProcedureReturn #PB_ProcessPureBasicEvents
EndProcedure

Procedure.i SetMenuItemBold(hMenuNumSel)
  
  bold.MENUITEMINFO
  bold\cbSize = SizeOf(bold)
  bold\fMask = #MIIM_STATE
  bold\fState = #MFS_DEFAULT
  
  SetMenuItemInfo_(hMenu, hMenuNumSel, #True, bold)
  
EndProcedure

Procedure.i DrawTextColoured(hColour, *lpd.DRAWITEMSTRUCT, hOldright, hText.s)
  Protected hLength
  
  ; Calculate the length of the required text rectangle.
  
  DrawText_(*lpd\hDC, hText.s, -1, @*lpd\rcItem, #DT_CALCRECT)
  
  ; Save the length of the text rectangle, we will need to add this to correct the new right position.
  
  hLength = (*lpd\rcItem\right - *lpd\rcItem\left)
  
  ; Set the new left position, offset this from the old saved right position (if there was one).
  
  *lpd\rcItem\left = hOldRight
  
  ; Set the new right position, this is offset from the new left position.
  
  *lpd\rcItem\right = (*lpd\rcItem\left + hLength)
  
  ; Set the menu item text colour and then draw it.
  
  SetTextColor_(*lpd\hDC, hColour)
  DrawText_(*lpd\hDC, hText.s, -1, @*lpd\rcItem, #Null)
  
  ; Return with the last right position in the menu.
  
  ProcedureReturn *lpd\rcItem\right
EndProcedure

Procedure.i AddMenuItem(hMenu, uIDNewItem, lpNewItem.s)
  
  ; Append a new menu item at the end of hMenu
  
  If AppendMenu_(hMenu, #MF_STRING, uIDNewItem, lpNewItem.s)
    
    ; If we successfully appended the new menu item add the entry to our menu text array.
    
    ReDim menutext.s(uIDNewItem)
    
    menutext(uIDNewItem) = lpNewItem.s
    
    ; Set the new menu item to be owner drawn.
    
    With tag.MENUITEMINFO
      \cbSize = SizeOf(MENUITEMINFO)
      \fMask = #MIIM_TYPE
      \fType = #MFT_OWNERDRAW
      \dwTypeData = @menutext(uIDNewItem)
      SetMenuItemInfo_(hMenu, uIDNewItem, #True, @tag)
    EndWith
  EndIf
  
EndProcedure

Procedure AddMenuIcon(*lpd.DRAWITEMSTRUCT, OICicon)
  
  ; Load a shared system icon and draw it at the beginning of each menu.
  
  hImage = LoadImage_(0, OICicon, #IMAGE_ICON, 0, 0, #LR_SHARED)
  DrawIconEx_(*lpd\hDC, 2, *lpd\rcItem\top+2, hImage, 26, 26, 0, #Null, #DI_NORMAL)
  
EndProcedure

 

Ted.

Appended Menu Items x64.exe Appended Menu Items x32.exe

  • Like 2
Posted

Hi Ted,

thanks again for the example code / file.So I still dont check why you do call AddMenuItem on WM_COMMAND / BN_CLICKED one time at case 0.You just called AddMenuItem before already with AppendMenu (normal) + SetMenuItemInfo with Ownerdraw ftype.

So what is if I only use AppendMenu (normal) + SetMenuItemInfo with OD.....does then GetMenuString not work again?Or whats the diffrent between using AppendMenu with OD flag and SetMenuItemInfo with OD ftype?

Bye the way,so did you check whether your method has some char limits of any menu entry (199 - 256 bytes etc)?

greetz

Teddy Rogers
Posted

In this post you stated you were having issues with AppendMenu function. My example(s) were to show you a method of using AppendMenu in owner drawn scenarios with icons. The BN_CLICKED example is simply an elaboration of this to show how you could apply AppendMenu in your code.

Providing menu strings are saved in the menu structure GetMenuString function should not have a problem finding your strings. My previous example shows this, even when it is owner drawn with coloured text. Have a read through the developer documents regarding menus.

I am not entirely sure what the string size limit the OS has set for a menu item. The last time I wrote code for a dynamic menu that could add a string of an unknown length in to the menu I opted to set a maximum character limit rather than risk a buffer overrun. You could choose to do something similar...

Ted.

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
×
×
  • Create New...