Best workaround for SetMenuItemState?

Just starting out? Need help? Post your questions and find answers here.
dige
Addict
Addict
Posts: 1430
Joined: Wed Apr 30, 2003 8:15 am
Location: Germany
Contact:

Best workaround for SetMenuItemState?

Post by dige »

Hello everyone,

since PureBasic 6.30 switched PopupMenu() over to the Windows API, MenuItemState = #True no longer shows a checkmark when the menu item also has an icon. viewtopic.php?t=87965

In my app I’d really like to keep both: icons for clarity and a visible “active/on” indicator for selected items. So I’m looking for the cleanest, most user-friendly workaround.

Right now I’m considering two approaches:

Text-based marker: override/wrap SetMenuItemState (e.g., via a macro) and prefix/suffix the menu text with a ✔ when the state is #True.

Icon-based solution: use SetMenuItemInfo_() to swap/update the icon to reflect the checked state (example approach below / from ChatGPT).

Which option would you recommend in practice—especially regarding UX consistency, Windows menu conventions, and maintainability? If you’ve implemented this already, I’d really appreciate a short snippet or guidance.

Thanks!

Code: Select all

; Example CreatePopupImageMenu ()

UsePNGImageDecoder() 

  If CreateImage(0,16,16,32)     
    StartDrawing(ImageOutput(0))
    Box(0,0,15,15,RGB(255,255,128))
    DrawRotatedText(-5,3, "+", 45, RGB(255,0,128))
    StopDrawing()
  EndIf
  OpenWindow(0, 200, 200, 200, 120, "Image Popup-Menu Example")
  If LoadImage(1,#PB_Compiler_Home + "examples/sources/Data/ToolBar/Properties.png")
    If CreatePopupImageMenu(0)
      MenuItem(1, "Filter 1", ImageID(1))
      MenuItem(2, "Filter 2")
    EndIf
    SetMenuItemState(0, 1, 1) ; ✔️ Not visible
    SetMenuItemState(0, 2, 1) ; ✔️ still visible
    Repeat
      Event = WaitWindowEvent()
      Select Event
        Case #PB_Event_RightClick  ; rechte Maustaste wurde gedrückt =>
          DisplayPopupMenu(0, WindowID(0))  ; zeige jetzt das Popup-Menü an
      EndSelect
    Until Event = #PB_Event_CloseWindow
  EndIf

Code: Select all

; Workaround with SetMenuItemInfo_()
EnableExplicit

Enumeration
  #Win
  #Canvas
EndEnumeration

Enumeration
  #Pop
EndEnumeration

Enumeration 1000
  #MI_Toggle
EndEnumeration

; --- WinAPI: MENUITEMINFO (minimal, damit SetMenuItemInfo_() sauber klappt)
Structure MENUITEMINFO_PB Align #PB_Structure_AlignC
  cbSize.l
  fMask.l
  fType.l
  fState.l
  wID.i
  hSubMenu.i
  hbmpChecked.i
  hbmpUnchecked.i
  dwItemData.i
  dwTypeData.i
  cch.l
  hbmpItem.i
EndStructure

#MIIM_BITMAP = $00000080

Procedure SetMenuItemBitmap(Menu, ItemID, hBitmap.i)
  Protected mii.MENUITEMINFO_PB
  mii\cbSize   = SizeOf(MENUITEMINFO_PB)
  mii\fMask    = #MIIM_BITMAP
  mii\hbmpItem = hBitmap
  SetMenuItemInfo_(MenuID(Menu), ItemID, #False, @mii) ; by command (ID), nicht by position
EndProcedure

Procedure.i MakeCheckedIcon(SrcImg, DstImg = #PB_Any)
  Protected out = CopyImage(SrcImg, DstImg)

  If StartDrawing(ImageOutput(out))
    DrawingMode(#PB_2DDrawing_AlphaBlend)

    ; kleines “Badge” links oben (optional, hilft Kontrast)
    Box(0, 0, 8, 8, RGBA(0, 0, 0, 110))

    ; Häkchen (2px “dick” durch doppelte Linien)
    Protected c = RGBA(255, 255, 255, 235)
    LineXY(1, 4, 3, 6, c) : LineXY(1, 5, 3, 7, c)
    LineXY(3, 6, 7, 2, c) : LineXY(3, 7, 7, 3, c)

    StopDrawing()
  EndIf

  ProcedureReturn out
EndProcedure

; --- Demo-Icons (16x16)
Procedure.i MakeBaseIcon(Color.l, Img = #PB_Any)
  Protected out = CreateImage(Img, 16, 16, 32, RGBA(0,0,0,0))
  If StartDrawing(ImageOutput(out))
    DrawingMode(#PB_2DDrawing_AlphaBlend)
    Box(0,0,16,16, RGBA(0,0,0,0))
    Box(1,1,14,14, Color)
    StopDrawing()
  EndIf
  ProcedureReturn out
EndProcedure

; ---------------- Main ----------------
If OpenWindow(#Win, 0, 0, 360, 220, "PopupImageMenu – Checked Workaround", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  CanvasGadget(#Canvas, 0, 0, WindowWidth(#Win), WindowHeight(#Win))
  If StartDrawing(CanvasOutput(#Canvas))
    DrawText(12, 12, "Rechtsklick hier -> Popup-Menü. Item toggelt + Icon zeigt Häkchen.", RGB(30,30,30))
    StopDrawing()
  EndIf

  ; Images: normal + checked (mit Overlay)
  Define imgNormal = MakeBaseIcon(RGBA(0,120,215,255))
  Define imgChecked = MakeCheckedIcon(imgNormal)

  ; Popup-Menu
  CreatePopupImageMenu(#Pop)
  MenuItem(#MI_Toggle, "Option (sichtbar checked)", ImageID(imgNormal))

  ; initial checked
  SetMenuItemState(#Pop, #MI_Toggle, 1)
  SetMenuItemBitmap(#Pop, #MI_Toggle, ImageID(imgChecked))

  Repeat
    Select WaitWindowEvent()
      Case #PB_Event_Gadget
        If EventGadget() = #Canvas And EventType() = #PB_EventType_RightClick
          DisplayPopupMenu(#Pop, WindowID(#Win))
        EndIf

      Case #PB_Event_Menu
        Select EventMenu()
          Case #MI_Toggle
            Define newState = Bool(GetMenuItemState(#Pop, #MI_Toggle) = 0)
            SetMenuItemState(#Pop, #MI_Toggle, newState)

            If newState
              SetMenuItemBitmap(#Pop, #MI_Toggle, ImageID(imgChecked))
            Else
              SetMenuItemBitmap(#Pop, #MI_Toggle, ImageID(imgNormal))
            EndIf
        EndSelect

      Case #PB_Event_CloseWindow
        Break
    EndSelect
  ForEver
EndIf


"Daddy, I'll run faster, then it is not so far..."
RASHAD
PureBasic Expert
PureBasic Expert
Posts: 5017
Joined: Sun Apr 12, 2009 6:27 am

Re: Best workaround for SetMenuItemState?

Post by RASHAD »

Hi dige
Quick hack adapt it for your needs
Use state? to set the menu item status
SetMenuItemBitmaps_() = SetMenuItemState :D

Code: Select all


UsePNGImageDecoder()
LoadImage(1,#PB_Compiler_Home + "examples/sources/Data/ToolBar/Open.png")
LoadImage(2,#PB_Compiler_Home + "examples/sources/Data/ToolBar/Save.png")
LoadImage(3,#PB_Compiler_Home + "examples/sources/Data/ToolBar/Help.png")


OpenWindow(0, 0, 0, 600, 400, "Change PopUp Menu Icons", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
If CreatePopupMenu(0)
  MenuItem(1, "Open")
  MenuItem(2, "Save")
  MenuItem(3, "Help")
EndIf

SetMenuItemBitmaps_(MenuID(0),1, #MF_BYCOMMAND, ImageID(1), 0)
SetMenuItemBitmaps_(MenuID(0),2, #MF_BYCOMMAND, ImageID(2), 0)
SetMenuItemBitmaps_(MenuID(0),3, #MF_BYCOMMAND, ImageID(3), 0)
DrawMenuBar_(WindowID(0))

LoadFont(0,"Marlett",12)
CreateImage(0,16,16,24)
StartDrawing(ImageOutput(0))
DrawingFont(FontID(0))
DrawText(0,0,Chr($62),$0000FF,$FFFFFF)
StopDrawing()

Repeat
  Select WaitWindowEvent(1)
    Case #PB_Event_CloseWindow
      Quit = 1
      
    Case #PB_Event_RightClick
      DisplayPopupMenu(0, WindowID(0))
      
    Case #PB_Event_Menu
      Select EventMenu()
        Case 1
          If state1 = 0
            SetMenuItemBitmaps_(MenuID(0), 1, #MF_BYCOMMAND, ImageID(0), 0)
          Else
            SetMenuItemBitmaps_(MenuID(0), 1, #MF_BYCOMMAND, ImageID(1), 0)
          EndIf
          state1 ! 1
        Case 2
          If state2 = 0
            SetMenuItemBitmaps_(MenuID(0), 2, #MF_BYCOMMAND, ImageID(0), 0)
          Else
            SetMenuItemBitmaps_(MenuID(0), 2, #MF_BYCOMMAND, ImageID(2), 0)
          EndIf
          state2 ! 1
        Case 3
          If state3 = 0
            SetMenuItemBitmaps_(MenuID(0), 3, #MF_BYCOMMAND, ImageID(0), 0)
          Else
            SetMenuItemBitmaps_(MenuID(0), 3, #MF_BYCOMMAND, ImageID(3), 0)
          EndIf
          state3 ! 1
      EndSelect
  EndSelect
Until Quit = 1
Last edited by RASHAD on Wed Dec 10, 2025 3:56 pm, edited 1 time in total.
Egypt my love
dige
Addict
Addict
Posts: 1430
Joined: Wed Apr 30, 2003 8:15 am
Location: Germany
Contact:

Re: Best workaround for SetMenuItemState?

Post by dige »

RASHAD my friend! Thats great! Thank you very much!! :D
"Daddy, I'll run faster, then it is not so far..."
Axolotl
Addict
Addict
Posts: 905
Joined: Wed Dec 31, 2008 3:36 pm

Re: Best workaround for SetMenuItemState?

Post by Axolotl »

well, I borrowed RASHAD's code and added my idea.
Hint: This may not work on every computer, or may not look good on other computers due to other screen settings.
Assumption: The font used is Segoe UI.

Code: Select all

UsePNGImageDecoder()
LoadImage(1,#PB_Compiler_Home + "examples/sources/Data/ToolBar/Open.png")
LoadImage(2,#PB_Compiler_Home + "examples/sources/Data/ToolBar/Save.png")
LoadImage(3,#PB_Compiler_Home + "examples/sources/Data/ToolBar/Help.png")

#CheckMark      =     $2713     ; Font: Segoe UI 
#CheckMark$     = Chr($2713) 
#CheckMarkSpace =         4     ; see below 

Procedure ToggleMenuItemState(Menu, MenuItem, State=#PB_Ignore)  
  Protected text$ 

  If State = #False Or State = #True 
    ; State = #False or #True :) 
  Else 
    State = 1 - GetMenuItemState(Menu, MenuItem)  ; toggle -> the new state ?? 
  EndIf 
  text$ = GetMenuItemText(Menu, MenuItem) 
  text$ = LTrim(Mid(text$, 2)) 
  If state ; state is helpful in other parts of the app. 
    text$ = #CheckMark$ + " " + text$ 
  Else
    text$ = Space(#CheckMarkSpace) + text$ 
  EndIf 
  SetMenuItemState(Menu, MenuItem, state) 
  SetMenuItemText(Menu, MenuItem, text$) 
EndProcedure


OpenWindow(0, 0, 0, 600, 400, "Toggle PopUp Menu Items", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
If CreatePopupMenu(0)
  MenuItem(1, Space(#CheckMarkSpace) + "Open", ImageID(1))   ; make some space for checkmark ?? 
  MenuItem(2, Space(#CheckMarkSpace) + "Save", ImageID(2))
  MenuItem(3, Space(#CheckMarkSpace) + "Help", ImageID(3))
EndIf 

ToggleMenuItemState(0, 1, 1)  ; set the first menu item checked 

Repeat
  Select WaitWindowEvent()
    Case #PB_Event_CloseWindow
      Quit = 1
      
    Case #PB_Event_LeftClick, #PB_Event_RightClick
      DisplayPopupMenu(0, WindowID(0))
      
    Case #PB_Event_Menu
      ToggleMenuItemState(0, EventMenu()) 
  EndSelect
Until Quit = 1
Just because it worked doesn't mean it works.
PureBasic 6.04 (x86) and <latest stable version and current alpha/beta> (x64) on Windows 11 Home. Now started with Linux (VM: Ubuntu 22.04).
dige
Addict
Addict
Posts: 1430
Joined: Wed Apr 30, 2003 8:15 am
Location: Germany
Contact:

Re: Best workaround for SetMenuItemState?

Post by dige »

Also very nice - thx Axolotl 👍
"Daddy, I'll run faster, then it is not so far..."
dige
Addict
Addict
Posts: 1430
Joined: Wed Apr 30, 2003 8:15 am
Location: Germany
Contact:

Re: Best workaround for SetMenuItemState?

Post by dige »

Mayby its better to use Chr($2800)?

Code: Select all

; Orig by Axolotl, edit by Dige use 'safe spaces'

UsePNGImageDecoder()
LoadImage(1,#PB_Compiler_Home + "examples/sources/Data/ToolBar/Open.png")
LoadImage(2,#PB_Compiler_Home + "examples/sources/Data/ToolBar/Save.png")
LoadImage(3,#PB_Compiler_Home + "examples/sources/Data/ToolBar/Help.png")

#CheckMark$     = Chr($2713) 
#CheckSpace$    = Chr($2800)

Procedure ToggleMenuItemState(Menu, MenuItem, State=#PB_Ignore)  
  Protected text$ 

  If State <> #False And State <> #True 
    State = 1 - GetMenuItemState(Menu, MenuItem)
  EndIf 
  
  text$ = GetMenuItemText(Menu, MenuItem) 
  text$ = LTrim(Mid(text$, 3)) 
    
  If state
    text$ = #CheckMark$ + #CheckSpace$ + text$ 
  Else
    text$ = #CheckSpace$ + #CheckSpace$ + text$ 
  EndIf 
  
  SetMenuItemState(Menu, MenuItem, state) 
  SetMenuItemText(Menu, MenuItem, text$) 
EndProcedure


OpenWindow(0, 0, 0, 600, 400, "Toggle PopUp Menu Items", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)

If CreatePopupMenu(0)
  MenuItem(1, #CheckSpace$ + #CheckSpace$ + "Open", ImageID(1))
  MenuItem(2, #CheckSpace$ + #CheckSpace$ + "Save", ImageID(2))
  MenuItem(3, #CheckSpace$ + #CheckSpace$ + "Help", ImageID(3))
EndIf 

ToggleMenuItemState(0, 1, 1)  ; set the first menu item checked 

Repeat
  Select WaitWindowEvent()
    Case #PB_Event_CloseWindow
      Quit = 1
      
    Case #PB_Event_LeftClick, #PB_Event_RightClick
      DisplayPopupMenu(0, WindowID(0))
      
    Case #PB_Event_Menu
      ToggleMenuItemState(0, EventMenu()) 
  EndSelect
Until Quit = 1
"Daddy, I'll run faster, then it is not so far..."
breeze4me
Enthusiast
Enthusiast
Posts: 648
Joined: Thu Mar 09, 2006 9:24 am
Location: S. Kor

Re: Best workaround for SetMenuItemState?

Post by breeze4me »

The check mark with the theme applied can be drawn as shown below.

Code: Select all

#MENU_POPUPCHECK = 11

#MC_CHECKMARKNORMAL = 1
#MC_CHECKMARKDISABLED = 2

#TS_TRUE = 1
#TS_DRAW = 2

Procedure.i MakeCheckedIcon(SrcImg, DstImg = #PB_Any)
  Protected Image = CopyImage(SrcImg, DstImg)
  Protected hDC, hDCWnd, hThemeMenu, rt.RECT, sz.SIZE
  
  If IsImage(Image)
    hThemeMenu = OpenThemeData_(WindowID(#Win), "Menu")
    If hThemeMenu
      hDCWnd = GetDC_(WindowID(#Win))
      If hDCWnd
        If GetThemePartSize_(hThemeMenu, hDCWnd, #MENU_POPUPCHECK, #MC_CHECKMARKNORMAL, 0, #TS_DRAW, @sz) = #S_OK
          
          ResizeImage(Image, sz\cx, sz\cy, #PB_Image_Raw)
          hDC = StartDrawing(ImageOutput(Image))
          If hDC
            rt\right = sz\cx
            rt\bottom = sz\cy
            
            DrawThemeBackground_(hThemeMenu, hDC, #MENU_POPUPCHECK, #MC_CHECKMARKNORMAL, @rt, 0)
            
            StopDrawing()
          EndIf
        EndIf
        ReleaseDC_(WindowID(#Win), hDCWnd)
      EndIf
      
      CloseThemeData_(hThemeMenu)
    EndIf
  EndIf
  
  ProcedureReturn Image
EndProcedure
Full code.
Edit: Modified the code to support DPI awareness.

Code: Select all

; Workaround with SetMenuItemInfo_()
EnableExplicit

Enumeration
  #Win
  #Canvas
EndEnumeration

Enumeration
  #Pop
EndEnumeration

Enumeration 1000
  #MI_Toggle
EndEnumeration

; --- WinAPI: MENUITEMINFO (minimal, damit SetMenuItemInfo_() sauber klappt)
Structure MENUITEMINFO_PB Align #PB_Structure_AlignC
  cbSize.l
  fMask.l
  fType.l
  fState.l
  wID.i
  hSubMenu.i
  hbmpChecked.i
  hbmpUnchecked.i
  dwItemData.i
  dwTypeData.i
  cch.l
  hbmpItem.i
EndStructure

#MIIM_BITMAP = $00000080

Procedure SetMenuItemBitmap(Menu, ItemID, hBitmap.i)
  Protected mii.MENUITEMINFO_PB
  mii\cbSize   = SizeOf(MENUITEMINFO_PB)
  mii\fMask    = #MIIM_BITMAP
  mii\hbmpItem = hBitmap
  SetMenuItemInfo_(MenuID(Menu), ItemID, #False, @mii) ; by command (ID), nicht by position
EndProcedure


#MENU_POPUPCHECK = 11

#MC_CHECKMARKNORMAL = 1
#MC_CHECKMARKDISABLED = 2

#TS_TRUE = 1
#TS_DRAW = 2

Procedure.i MakeCheckedIcon(SrcImg, DstImg = #PB_Any)
  Protected Image = CopyImage(SrcImg, DstImg)
  Protected hDC, hDCWnd, hThemeMenu, rt.RECT, sz.SIZE
  
  If IsImage(Image)
    hThemeMenu = OpenThemeData_(WindowID(#Win), "Menu")
    If hThemeMenu
      hDCWnd = GetDC_(WindowID(#Win))
      If hDCWnd
        
        ; The check mark image is selected differently depending on the DPI, so determine the image size that displays clearly without blurring.
        If GetThemePartSize_(hThemeMenu, hDCWnd, #MENU_POPUPCHECK, #MC_CHECKMARKNORMAL, 0, #TS_DRAW, @sz) = #S_OK
          hDC = StartDrawing(ImageOutput(Image))
          If hDC
            rt\left = (sz\cx - ImageWidth(Image)) / 2
            rt\top = (sz\cy - ImageHeight(Image)) / 2
            If rt\left < 0 : rt\left = 0 : EndIf
            If rt\top < 0 : rt\top = 0 : EndIf
            
            rt\right = rt\left + sz\cx
            rt\bottom = rt\top + sz\cy
            
            DrawThemeBackground_(hThemeMenu, hDC, #MENU_POPUPCHECK, #MC_CHECKMARKNORMAL, @rt, 0)
            
            StopDrawing()
          EndIf
        EndIf
        ReleaseDC_(WindowID(#Win), hDCWnd)
      EndIf
      
      CloseThemeData_(hThemeMenu)
    EndIf
  EndIf
  
  ProcedureReturn Image
EndProcedure

; --- Demo-Icons (16x16)
Procedure.i MakeBaseIcon(Color.l, Img = #PB_Any)
  Protected out = CreateImage(Img, DesktopScaledX(16), DesktopScaledY(16))
  If StartDrawing(ImageOutput(out))
    Box(0,0,ImageWidth(out), ImageHeight(out), #Green)
    StopDrawing()
  EndIf
  ProcedureReturn out
EndProcedure

; ---------------- Main ----------------
If OpenWindow(#Win, 0, 0, 360, 220, "PopupImageMenu – Checked Workaround", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  CanvasGadget(#Canvas, 0, 0, WindowWidth(#Win), WindowHeight(#Win))
  If StartDrawing(CanvasOutput(#Canvas))
    DrawText(12, 12, "Rechtsklick hier -> Popup-Menü. Item toggelt + Icon zeigt Häkchen.", RGB(30,30,30))
    StopDrawing()
  EndIf

  ; Images: normal + checked (mit Overlay)
  Define imgNormal = MakeBaseIcon(RGBA(0,120,215,255))
  Define imgChecked = MakeCheckedIcon(imgNormal)

  ; Popup-Menu
  CreatePopupImageMenu(#Pop)
  MenuItem(#MI_Toggle, "Option (sichtbar checked)", ImageID(imgNormal))

  ; initial checked
  SetMenuItemState(#Pop, #MI_Toggle, 1)
  SetMenuItemBitmap(#Pop, #MI_Toggle, ImageID(imgChecked))

  Repeat
    Select WaitWindowEvent()
      Case #PB_Event_Gadget
        If EventGadget() = #Canvas And EventType() = #PB_EventType_RightClick
          DisplayPopupMenu(#Pop, WindowID(#Win))
        EndIf

      Case #PB_Event_Menu
        Select EventMenu()
          Case #MI_Toggle
            Define newState = Bool(GetMenuItemState(#Pop, #MI_Toggle) = 0)
            SetMenuItemState(#Pop, #MI_Toggle, newState)

            If newState
              SetMenuItemBitmap(#Pop, #MI_Toggle, ImageID(imgChecked))
            Else
              SetMenuItemBitmap(#Pop, #MI_Toggle, ImageID(imgNormal))
            EndIf
        EndSelect

      Case #PB_Event_CloseWindow
        Break
    EndSelect
  ForEver
EndIf
Post Reply