Post by Walt Decker on Mar 14, 2021 13:52:04 GMT -5
While developing the demo for the listview control I recently uploaded I wanted to put a seperator between some of the menu sub-items. Looking through the LB PRO help I did not find a native way to seperate sub-items so I used the underscore character and made a note to delve into the menus and devise a way to include a seperator. I have not done much with menus for a long while since the coding language I generally use has most of the functions I need to create menus and those it does not have I developed include files to address them.
A menu is a strange duck. It is kind of a cross between a treeview control and a list. The menu itself has a handle. Each of the items on the meun bar has a handle (there are exceptions). Items relating to a menu bar item do not have a handle but do have a programmer defined ID number. Knowing a litte about how callback functions work I reasoned that I could stuff a seperator without disturbing the LB system. I also looked into adding a check mark. Those two things are exactly what the below code does.
One might wonder why bother. Here are three reasons:
1. I gives your app a more professional look
2. If you begin programming in a language that gives you access to the window callback you will have a little more knowledge of manipulating menus
3. With a little research you may discover a method to produce sub-sub-menus for LB 4x without disrupting the LB flow control
A menu is a strange duck. It is kind of a cross between a treeview control and a list. The menu itself has a handle. Each of the items on the meun bar has a handle (there are exceptions). Items relating to a menu bar item do not have a handle but do have a programmer defined ID number. Knowing a litte about how callback functions work I reasoned that I could stuff a seperator without disturbing the LB system. I also looked into adding a check mark. Those two things are exactly what the below code does.
To run the below code you will need this LISTVIEW_DLL.ZIP (53.04 KB)
One might wonder why bother. Here are three reasons:
1. I gives your app a more professional look
2. If you begin programming in a language that gives you access to the window callback you will have a little more knowledge of manipulating menus
3. With a little research you may discover a method to produce sub-sub-menus for LB 4x without disrupting the LB flow control
[REFERENCE]
MF.BYCOMMAND = HEXDEC("&H00000000")
MF.BYPOSITION = HEXDEC("&H00000400")
MF.SEPARATOR = HEXDEC("&H00000800")
MF.ENABLED = HEXDEC("&H00000000")
MF.GRAYED = HEXDEC("&H00000001")
MF.DISABLED = HEXDEC("&H00000002")
MF.UNCHECKED = HEXDEC("&H00000000")
MF.CHECKED = HEXDEC("&H00000008")
MF.USECHECKBITMAPS = HEXDEC("&H00000200")
MF.STRING = HEXDEC("&H00000000")
MF.BITMAP = HEXDEC("&H00000004")
MF.OWNERDRAW = HEXDEC("&H00000100")
MF.POPUP = HEXDEC("&H00000010")
MF.MENUBARBREAK = HEXDEC("&H00000020")
MF.MENUBREAK = HEXDEC("&H00000040")
MF.UNHILITE = HEXDEC("&H00000000")
MF.HILITE = HEXDEC("&H00000080")
MF.DEFAULT = HEXDEC("&H00001000")
MF.SYSMENU = HEXDEC("&H00002000")
MF.HELP = HEXDEC("&H00004000")
MF.RIGHTJUSTIFY = HEXDEC("&H00004000")
MF.MOUSESELECT = HEXDEC("&H00008000")
[DECLARATIONS]
OPEN "User32.dll" FOR DLL AS #USER
OPEN "LvCtrl.dll" FOR DLL AS #LV
[GLOBALS]
GLOBAL DmoHndl, _
LvHndl
GLOBAL SelItem, _
SubItems
DIM MnuBar$(0)
DIM SubMnu$(0, 0)
[INITIALIZE]
DmoHndl = 0
LvHndl = 0
SelItem = 0
SubItems = 0
[MENUS]
MENU #MNU, "MENU", "Insert Separator", INSERT.SEP, "Add Check", ADD.CHECK, _
"Get Check", GET.CHECK
MENU #MNU, "M1", "A", D1, "B", D1, "C", D1, "D", D1, "E", D1
MENU #MNU, "M2", "D", D2, "E", D2, "F", D2
[STATIC]
STATICTEXT #MNU.INFO, "Select MENUBAR Name", 0, 5, 300, 25
[LIST]
LISTBOX #MNU.BAR, MnuBar$(), SEL.BAR, 0, 30, 300, 200
OPEN "MENUS" FOR WINDOW AS #MNU
[START.WIN]
DmoHndl = HWND(#MNU)
A = FN.MenuBar()
WAIT
'----------------------------------------------------------------
'----------------------------------------------------------------
SUB SEL.BAR Hndl$
MF.BYPOSITION = HEXDEC("&H00000400")
MnuHndl = 0
SubHndl = 0
MnuCnt = 0
NumChrs = 0
MnuId = 0
Flag = MF.BYPOSITION
I = 0
TxtIn$ = ""
PRINT #MNU.BAR, "selectionindex? SelItem"
SelItem = SelItem - 1
CALLDLL #USER, "GetMenu", DmoHndl AS ULONG, MnuHndl AS ULONG
CALLDLL #USER, "GetSubMenu", MnuHndl AS ULONG, SelItem AS LONG, SubHndl AS ULONG
CALLDLL #USER, "GetMenuItemCount", SubHndl AS ULONG, MnuCnt AS LONG
MnuCnt = MnuCnt - 1
SubItems = MnuCnt
REDIM SubMnu$(MnuCnt, 2)
FOR I = 0 TO MnuCnt
SubMnu$(I, 2) = STR$(SubHndl)
StrIn$ = SPACE$(60)
CALLDLL #USER, "GetMenuStringA", SubHndl AS ULONG, _
I AS LONG, _
StrIn$ AS PTR, _
60 AS SHORT, _
Flag AS ULONG, _
NumChrs AS LONG
SubMnu$(I, 0) = LEFT$(StrIn$, NumChrs)
CALLDLL #USER, "GetMenuItemID", SubHndl AS ULONG, I AS LONG, MnuId AS ULONG
SubMnu$(I, 1) = STR$(MnuId)
NEXT I
SelItem = 0
I = FN.SelectSub()
END SUB
'----------------------------------------------------------------
'----------------------------------------------------------------
FUNCTION FN.SelectSub()
LVS.EX.CHECKBOXES = HEXDEC("&H00000004")
I = 0
J = 0
STRUCT LvType, _
Ulx AS LONG, _
Uly AS LONG, _
Wide AS LONG, _
High AS LONG, _
LvStyle AS ULONG, _
LvStyleEx AS ULONG, _
Asort AS LONG, _
FntHndl AS ULONG, _
ChrClr AS LONG, _
BkgColor AS LONG
PRINT #MNU.BAR, "HIDE"
PRINT #MNU.INFO, "Select a SUBMENU name then select ONE of Insert Separator," + _
" Add Check, Get Check from the 'MENU' menu bar item."
LvType.Ulx.struct = 0
LvType.Uly.struct = 35
LvType.LvStyleEx.struct = LVS.EX.CHECKBOXES
LvType.ChrClr.struct = -7
LvType.BkgColor.struct = -7
CALLDLL #LV, "FN_InitListView", DmoHndl AS ULONG, LvType AS STRUCT, LvHndl AS ULONG
CALLDLL #LV, "AddLvColumn", LvHndl AS ULONG, 1 AS LONG, _
75 AS LONG, 0 AS LONG, "MENU" AS PTR, RetVal AS VOID
CALLDLL #LV, "AddLvColumn", LvHndl AS ULONG, 2 AS LONG, _
75 AS LONG, 0 AS LONG, "MENU ID" AS PTR, RetVal AS VOID
CALLDLL #LV, "AddLvColumn", LvHndl AS ULONG, 3 AS LONG, _
75 AS LONG, 0 AS LONG, "HANDLE" AS PTR, RetVal AS VOID
J = -1
FOR I = 1 TO SubItems + 1
J = J + 1
Txt$ = SubMnu$(J, 0)
CALLDLL #LV, "InsertLvLine", LvHndl AS ULONG, I AS LONG, _
0 AS LONG, Txt$ AS PTR, RetVal AS VOID
Txt$ = SubMnu$(J, 1)
CALLDLL #LV, "AddLvText", LvHndl AS ULONG, I AS LONG, _
2 AS LONG, Txt$ AS PTR, RetVal AS VOID
Txt$ = SubMnu$(J, 2)
CALLDLL #LV, "AddLvText", LvHndl AS ULONG, I AS LONG, _
3 AS LONG, Txt$ AS PTR, RetVal AS VOID
NEXT I
END FUNCTION
'----------------------------------------------------------------
'----------------------------------------------------------------
FUNCTION FN.MenuBar()
MF.BYPOSITION = HEXDEC("&H00000400")
NumMnuBar = 0
NumSubMnu = 0
MnuHndl = 0
SubHndl = 0
RetVal = 0
I = 0
Flag = MF.BYPOSITION
StrIn$ = ""
'/================================================================================/'
' Get the handle to the menu bar
'=================================================================================/'
CALLDLL #USER, "GetMenu", DmoHndl AS ULONG, MnuHndl AS ULONG
'/================================================================================/'
' Get tje number of items on the menu bar
'/================================================================================/'
CALLDLL #USER, "GetMenuItemCount", MnuHndl AS ULONG, NumMnuBar AS LONG
REDIM MnuBar$(NumMnuBar - 1)
'/================================================================================/'
' Parse the item on the menu bar BY POSITION because those items do not have an ID
' AND even if they did we would not know what it is.
'/================================================================================/'
FOR I = 0 TO NumMnuBar - 1
StrIn$ = SPACE$(60)
CALLDLL #USER, "GetMenuStringA", MnuHndl AS ULONG, _
I AS LONG, _
StrIn$ AS PTR, _
60 AS SHORT, _
Flag AS ULONG, _
RetVal AS LONG
MnuBar$(I) = LEFT$(StrIn$, RetVal)
NEXT I
'/================================================================================/'
' Put the names in the listbox
'/================================================================================/'
PRINT #MNU.BAR, "RELOAD"
END FUNCTION
'----------------------------------------------------------------
'----------------------------------------------------------------
SUB INSERT.SEP
MF.BYPOSITION = HEXDEC("&H00000400")
MF.SEPARATOR = HEXDEC("&H00000800")
RetVal = 0
SubHndl = 0
LineCnt = 0
NumChrs = 0
Selected = 0
Flags = MF.BYPOSITION OR MF.SEPARATOR
Fnd = 0
Txt$ = ""
'/================================================================================/'
' Find the number of lines in the list view
'/================================================================================/'
CALLDLL #LV, "FN_GetLvLineCount", LvHndl AS ULONG, LineCnt AS LONG
'/================================================================================/'
' Parse the list view until we find the selected item
'/================================================================================/'
FOR I = 1 TO LineCnt
CALLDLL #LV, "FN_LvGetSelect", LvHndl AS ULONG, I AS LONG, Selected AS LONG
IF Selected <> 0 THEN
Fnd = I
GOTO [OK]
END IF
NEXT I
[OK]
IF Fnd = 0 THEN EXIT SUB
[GETLV.VALUES]
REDIM SubMnu$(0, 2)
TxtIn$ = SPACE$(60)
CALLDLL #LV, "FN_GetLvText", LvHndl AS ULONG, Selected AS LONG, _
1 AS LONG, TxtIn$ AS PTR, NumChrs AS LONG
SubMnu$(0, 0) = LEFT$(TxtIn$, NumChrs)
TxtIn$ = SPACE$(60)
CALLDLL #LV, "FN_GetLvText", LvHndl AS ULONG, Selected AS LONG, _
2 AS LONG, TxtIn$ AS PTR, NumChrs AS LONG
SubMnu$(0, 1) = LEFT$(TxtIn$, NumChrs)
TxtIn$ = SPACE$(60)
CALLDLL #LV, "FN_GetLvText", LvHndl AS ULONG, Selected AS LONG, _
3 AS LONG, TxtIn$ AS PTR, NumChrs AS LONG
SubMnu$(0, 2) = LEFT$(TxtIn$, NumChrs)
[INSERT.SEPARATOR]
SubHndl = VAL(SubMnu$(0, 2))
Selected = Selected - 1
'/================================================================================/'
' Insert the seperator BEFORE the item selected
'/================================================================================/'
CALLDLL #USER, "InsertMenuA", SubHndl AS ULONG, Selected AS LONG, Flags AS ULONG, _
0 AS LONG, "" AS PTR, RetVal AS LONG
CALLDLL #USER, "DrawMenuBar", DmoHndl AS ULONG, RetVal AS VOID
[CLEANUP.SEP]
PRINT #MNU.INFO, "Select MENUBAR Name"
CALLDLL #LV, "LvClose", LvHndl AS ULONG, RetVal AS VOID
PRINT #MNU.BAR, "SHOW"
END SUB
'----------------------------------------------------------------
'----------------------------------------------------------------
SUB ADD.CHECK
[ADD.REF]
MF.BYCOMMAND = HEXDEC("&H00000000")
MF.ENABLED = HEXDEC("&H00000000")
MF.UNCHECKED = HEXDEC("&H00000000")
MF.CHECKED = HEXDEC("&H00000008")
[ADD.INIT]
RetVal = 0
MnuHndl = 0
LineCnt = 0
NumChrs = 0
Selected = 0
MnuId = 0
Flags = MF.COMMAND OR MF.ENABLED OR MF.CHECKED 'OR MF.USECHECKBITMAPS
Txt$ = ""
'/================================================================================/'
' Get the number of lines in the list view
'/================================================================================/'
CALLDLL #LV, "FN_GetLvLineCount", LvHndl AS ULONG, LineCnt AS LONG
'/================================================================================/'
' Parse the listview until the selected item is found
'/================================================================================/'
FOR I = 1 TO LineCnt
CALLDLL #LV, "FN_LvGetSelect", LvHndl AS ULONG, I AS LONG, Selected AS LONG
IF Selected <> 0 THEN
Fnd = I
GOTO [OK]
END IF
NEXT I
[OK]
IF Fnd = 0 THEN EXIT SUB
REDIM SubMnu$(0, 2)
'/================================================================================/'
' Get the ID of the sub-menu item selected
'/================================================================================/'
TxtIn$ = SPACE$(60)
CALLDLL #LV, "FN_GetLvText", LvHndl AS ULONG, Selected AS LONG, _
2 AS LONG, TxtIn$ AS PTR, NumChrs AS LONG
'/================================================================================/'
' Get the handle of the menu bar. Because we are going to use the sub-item ID
' we need this rather than the sub-menu handle
'/================================================================================/'
CALLDLL #USER, "GetMenu", DmoHndl AS ULONG, MnuHndl AS ULONG
MnuId = VAL(TxtIn$)
'/================================================================================/'
' Set the sub-menu state as CHECKED
'/================================================================================/'
CALLDLL #USER, "CheckMenuItem", SubHndl AS ULONG, _
MnuId AS ULONG, _
Flags AS ULONG, _
RetVal AS LONG
CALLDLL #USER, "DrawMenuBar", DmoHndl AS ULONG, RetVal AS VOID
[ADDCHECK.CLEANUP]
PRINT #MNU.INFO, "Select MENUBAR Name"
CALLDLL #LV, "LvClose", LvHndl AS ULONG, RetVal AS VOID
PRINT #MNU.BAR, "SHOW"
END SUB
'----------------------------------------------------------------
'----------------------------------------------------------------
SUB GET.CHECK
MF.BYCOMMAND = HEXDEC("&H00000000")
MF.CHECKED = HEXDEC("&H00000008")
Selected = 0
Checked = 0
MnuHndl = 0
MnuId = 0
Flag = MF.BYCOMMAND
Fnd = 0
Txt$ = ""
'/================================================================================/'
' Get the number of lines in the listview
'/================================================================================/'
CALLDLL #LV, "FN_GetLvLineCount", LvHndl AS ULONG, LineCnt AS LONG
'/================================================================================/'
' Parse the listview for the item selected
'/================================================================================/'
FOR I = 1 TO LineCnt
CALLDLL #LV, "FN_LvGetSelect", LvHndl AS ULONG, I AS LONG, Selected AS LONG
IF Selected <> 0 THEN
Fnd = I
GOTO [CHECK.OK]
END IF
NEXT I
[CHECK.OK]
IF Fnd = 0 THEN EXIT SUB
'/================================================================================/'
' Get the ID of the sub-menu item
'/================================================================================/'
TxtIn$ = SPACE$(60)
CALLDLL #LV, "FN_GetLvText", LvHndl AS ULONG, Selected AS LONG, _
2 AS LONG, TxtIn$ AS PTR, NumChrs AS LONG
MnuId = VAL(TxtIn$)
'/================================================================================/'
' Get the handle of the menu bar
'/================================================================================/'
CALLDLL #USER, "GetMenu", DmoHndl AS ULONG, MnuHndl AS ULONG
'/================================================================================/'
' Determine if the sub-item state is checked
'/================================================================================/'
CALLDLL #USER, "GetMenuState", MnuHndl AS ULONG, MnuId AS ULONG, _
Flag AS ULONG, Checked AS ULONG
IF (Checked AND MF.CHECKED) = MF.CHECKED THEN
NOTICE "The item with menu id " + STR$(MnuId) + " is checked"
ELSE
NOTICE "The item with menu id " + STR$(MnuId) + " is not checked"
END IF
END SUB
'----------------------------------------------------------------
'----------------------------------------------------------------
SUB D1
PRINT "D1"
END SUB
'----------------------------------------------------------------
'----------------------------------------------------------------
SUB D2
PRINT "D2"
END SUB