Home |
10. Menus and List Boxes Creating and using Menus, and List Boxes |
Home |
Menus Menus are a fundamental part of Windows programing, the use of menus can greatly improve user convience and give them a familiar interface to use. Menus got their name from restaurant menus, which have a list of items availible there. A Windows Menu is a list of items availible to the user, which can be clicked to open a submenu or cause your program to to do something. The Main Menu is the menu bar; sub menus drop down from this menu bar, and these can have other sub menus. This Menu Bar is sometimes called a top-level menu, and the submenus are also known as pop-up menus. The menu button names on a Menu Bar should give the main categories of commands that a program provides. Windows also provides Shortcut menus, which are not attached to the menu bar, they can appear anywhere on the screen. These menus are also called "context menus" and "pop-up menus" which appear with a Right mouse click.List Boxes A list box is a control window that contains a list of items from which the user can choose. List box items can be represented by text strings, bitmaps, or both. If the list box is not large enough to display all the list box items at once, the list box can provide a scroll bar. The user maneuvers through the list box items, scrolling the list when necessary, and selects the items they want. Selecting a list box item changes its visual appearance, usually by changing the text and background colors to the colors specified by the operating system metrics for selected items. When the user selects an item or removes the selection from an item, Windows sends a notification message to the parent window of the list box. The notification messages can be processed to add additional funtionality to the lisy box. The Win32 API provides two general styles of list box: single-selection (the default style) and multiple-selection.
Menus A Menu may be one of the most recognizable and familar aspects of the computer interface. It can be helpful to present a consistant menu interface to your users. A window's menu bar (main menu) is shown just below the title bar; sub-menus drop down from the menu bar, and other submenus can come from these drop down menus. A menu bar is sometimes called a Main-Menu or Top-Level menu, and the drop-down menus and submenus are also known as pop-up menus. A menu item can either carry out a command or open a submenu. An item that carries out a command is called a command item or a command. An item on the Main-Menu bar almost always opens a sub-menu. Main-Menu bars rarely contain command items, it is common practice to have all the Main-Menu items open a sub-menu. A sub-menu opened from the Main-Menu bar, drops down from the menu bar and is sometimes called a drop-down menu. A Main-Menu item on the menu bar that opens a drop-down menu is also called a menu name. Each menu must have an owner window, where Windows sends a messages to a menu's owner window when the user selects the menu or chooses an item from the menu. Creating Menues There are two ways to create your menues, by using the window's menu creation functions, or by making a resource templete (using a .RC resource file to compile a .RES file with brcc32.exe). The most common (and easiest) way is using a resource templete, but first I will show you how to use the menu creation functions, so you can create menus at run time. Menus need to be destroyed after they are created, if a menu or submenu is in the program's Main Menu, then it will be destroyed when the owner window is destroyed. If a menu is not part of a Main menu, then you will need to destroy it. One of the simpleist API functions is the call to CreateMenu, it takes no parameters and returns a handle to the new menu. hMenu := CreateMenu;The new menu is completly empty and can not be used until you put something in it. If you use an Empty menu's Handle to set a menu or in menu creation fuctions, nothing happens. There are several functions used to add items to a menu, the InsertMenuItem( ) is more powerful, but the AppendMenu( ) and the InsertMenu( ) are still used if you don't need the extra features of the InsertMenuItem. I'll start with AppendMenu( ). AppendMenu( ) function function AppendMenu( hMenu: THandle; // handle of menu uFlags: Cardinal; // menu item flags uIDNewItem: Cardinal; // menu-item identifier or // handle of submenu lpNewItem: PChar // Text of Item ): Boolean; //boolean resultThe first parameter hMenu is the handle of the menu that you are adding an Item. The second parameter uFlags are the creation flag bits for this item and are listed below. They have the MF_ prefix for "Menu Flag", and are used in many of the Menu creation and modification functions.
Because of conflicting menu display results the following 4 groups of flags can NOT be used together. 1: MF_CHECKED and MF_UNCHECKED 2: MF_DISABLED, MF_ENABLED, and MF_GRAYED 3: MF_BITMAP, MF_STRING, and MF_OWNERDRAW 4: MF_MENUBARBREAK and MF_MENUBREAK The next Parameter is the uIDNewItem which specifies either the ID number of the new menu item or, if the uFlags parameter is set to MF_POPUP, the handle to a submenu. And the last parameter is the lpNewItem which is defined as a PChar in the windows.pas unit and usually contains the text in this item. However, if the uFlag bit is set to MF_BITMAP then this has the handle of the bitmap to be displayed here and you have to typecast the THandle to a PChar. And if the uFlag bit is set to MF_OWNERDRAW then this is read as an Integer value that is passed in the itemData of the lParam when the WM_MEASURE or WM_DRAWITEM message is sent when the menu is created or its appearance is updated. Again you must typecast the interger to a PChar. Adding Access Keys (HotKeys) The standard keyboard interface for menus can be enhanced by adding access keys (hotkeys) on the keyboard to menu items. An access key is the underlined letter in the text of a menu item. When a menu is active, the user can select a menu item by pressing the key that corresponds to the item's underlined letter. The user makes the main menu bar active by pressing the ALT key to highlight the first item on the menu bar. A sub-menu is active when it is displayed. To create an access key for a menu item, precede any character in the item's text string with an ampersand "&". For example, the text string "&Move" causes Windows to underline the letter "M" and automatically set the HotKey to the "m" key. When you use the MF_STRING flag and you can place the "&" charater before the charater you want to be the "HotKey" of the text. If you need to place a "&" in your text then use 2 "&" together, like this '&Bread && Butter' which will display "Bread & Butter" . Some Examples - - To add a Text Item with "101" as the ID and "New" as the text use - AppendMenu(hMenu, MF_STRING, 101, '&New'); To add a Separator line to the menu use ( the last 2 parameters are not used) AppendMenu(hMenu, MF_SEPARATOR, 0,nil); To add a Sub-Menu Item with "Folders" as the text add the sub-menu handle in the third parameter and use - AppendMenu(hMenu, MF_POPUP or MF_STRING, hSubMenu, 'Folders'); To add a Bitmap Item with "102" as the ID use, ( you must typecast the Bitmap Handle to a PChar) AppendMenu(hMenu, MF_BITMAP, 102, PChar(hBitmap)); InsertMenu( ) function The InsertMenu( ) function is very simalar to the AppendMenu( ) fuction and is used to add a menu Item at a certain "Position" on a pre-existing menu. InsertMenu( ) has an extra parameter for the 0 based position where the menu item is to be inserted. It is defined in window.pas as - function InsertMenu( hMenu: THandle; // handle of menu uPosition, Cardinal, // item position uFlags: Cardinal; // menu item flags uIDNewItem: Cardinal; // menu item flags lpNewItem: PChar // Text of Item ): Boolean; //boolean resultIt has the same parameters as the AppendMenu( ) function except for the uPosition parameter. This uPosition, the second parameter, is the Zero based position you want the item to be placed at, if you give a larger number than the positons availible the it is added to the end of the menu items. The other parameters are used the same as the AppendMenu( ) function. An Example - - To Insert a Text Item with "203" as the ID and "Undo" as the text in the Fourth Position use - InsertMenu(hMenu, 3, MF_STRING, 203, '&Undo'); InsertMenuItem( ) function The InsertMenuItem( ) function uses a TMenuItemInfo Record to get information about the menu Item creation. Since the TMenuItemInfo Record has 11 items in it, it can contain more information than the 5 parameters of InsertMenu( ), so InsertMenuItem( ) has more creation options than AppendMenu or InsertMenu. InsertMenuItem is defined in windows.pas as function InsertMenuItem(hMenu: THandle; // handle of menu uItem: Cardinal; // ID or position of Item fByPosition: Boolean; // ID or position in uItem const lpmii: TMenuItemInfo // a TMenuItemInfo record ): Boolean; //boolean resultThe first parameter, hMenu, is the Menu Handle of the menu that the item will go in. The second paramter, uItem, will either have the ID number or the Zero based position number of the Item. The meaning of this second parameter is set in the Third paramerter, fByPosition, which is True for Position number in uItem, and False for ID number in uItem. The fourth parameter, lpmii, is a TMenuItemInfo record and is defined in windows.pas as - - TMenuItemInfo PMenuItemInfoA = ^TMenuItemInfoA; PMenuItemInfo = PMenuItemInfoA; tagMENUITEMINFOA = packed record cbSize: Cardinal; fMask: Cardinal; fType: Cardinal; { used if MIIM_TYPE} fState: Cardinal; { used if MIIM_STATE} wID: Cardinal; { used if MIIM_ID} hSubMenu: HMENU; { used if MIIM_SUBMENU} hbmpChecked: HBITMAP; { used if MIIM_CHECKMARKS} hbmpUnchecked: HBITMAP; { used if MIIM_CHECKMARKS} dwItemData: Cardinal; { used if MIIM_DATA} dwTypeData: PAnsiChar; { used if MIIM_TYPE} cch: Cardinal; { used if MIIM_TYPE} hbmpItem: HBITMAP; { used if MIIM_BITMAP} end; tagMENUITEMINFO = tagMENUITEMINFOA; TMenuItemInfoA = tagMENUITEMINFOA; TMenuItemInfo = TMenuItemInfoA; MENUITEMINFO = MENUITEMINFOAPlease look at the Win32 API Help for index "MENUITEMINFO" for the meaning of the members of this TMenuItemInfo record. Using this TMenuItemInfo takes some time to get to know how (and when) the members are used (or not used) depending on the fMask and fType flags set. . . There are six fMask flag bits, , , MIIM_CHECKMARKS, MIIM_DATA, MIIM_ID, MIIM_STATE, MIIM_SUBMENU, MIIM_TYPE, if you include the flag in the fMask, then that flag's information will be used from the TMenuItemInfo record. If the flag is not set then those members are ignored. For instance if the MIIM_STATE is included in the fMask, then the fState member is used, otherwise it is ignored. With this TMenuItemInfo you can set the menu item's State during creation, which you can not do with the AppendMenu( ) or InsertMenu( ) functions. Here are some examples of code used to place Items in a menu with InsertMenuItem( ) - - This first code will place a Text Item in the menu, hMenu1, with the ID number of 700 in the first menu position. var MenuInfo1: TMenuItemInfo; begin MenuInfo1.cbSize := SizeOf(TMenuItemInfo); MenuInfo1.fMask := MIIM_ID or MIIM_TYPE; MenuInfo1.fType := MFT_STRING; MenuInfo1.wID := 700; MenuInfo1.dwTypeData := '&Menu Item1'; {everything below is ignored since the MIIM_ID or MIIM_TYPE flags were used, so you do not need to set them at all} MenuInfo1.cch := 0; (the cch is NOT used if the dwTypeData is used to set text, it is used if dwTypeData is used to get text} MenuInfo1.fState := 0; MenuInfo1.hSubMenu := 0; MenuInfo1.hbmpChecked := 0; MenuInfo1.hbmpUnchecked := 0; MenuInfo1.hbmpItem := 0; InsertMenuItem(hMenu1, 0, True, MenuInfo1); end;Whenever you use a TMenuItemInfo record you Must set the cbSize to the size of the TMenuItemInfo, before you pass this record in a function. Now look at the fMask member and see that the MIIM_ID or MIIM_TYPE bits are set. This will tell the InsertMenuItem( ) function to use the wID and fType members and ignore the rest. The wID is set to the ID number of 700, (although this record member is defind as a Cardinal, only 2 bytes (a Word type) are read so you need to use number values below 65535). Since the MIIM_TYPE flag is set in fMask, the fType is read, here is is set to MFT_STRING, which will cause the dwTypeData member to be read as a PChar. The code above will produce the same Menu Item as - InsertMenu(hMenu1, 0, MF_STRING, 700, '&Menu Item1''); The next code will make a menu separator bar MenuInfo1.cbSize := SizeOf(TMenuItemInfo); MenuInfo1.fMask := MIIM_TYPE; MenuInfo1.fType := MFT_SEPARATOR; {you do not need to define any other members since they will be ignored} InsertMenuItem(hMenu1, 1, True, MenuInfo1); The next code will make a Text Menu item that is Checked and Grayed begin MenuInfo1.cbSize := SizeOf(TMenuItemInfo); MenuInfo1.fMask := MIIM_ID or MIIM_TYPE or MIIM_STATE; MenuInfo1.fType := MFT_STRING; MenuInfo1.wID := 302; MenuInfo1.dwTypeData := '&Word Wrap'; MenuInfo1.fState := MFS_CHECKED or MFS_GRAYED; MenuInfo1.hbmpChecked := 0; MenuInfo1.hbmpUnchecked := 0; {you do not need to set any others} InsertMenuItem(hMenu1, 3, True, MenuInfo1); end; Default Menu Items A submenu can contain one default menu item which will use Bold Type instead of the normal menu type. When someone opens a submenu by double-clicking, Windows will send a command message to the menu's owner window and then close the menu as if the default menu item had been clicked. If there is no default command item, nothing happends and the submenu remains open. You can set the Default item with - SetMenuDefaultItem(hMenu: THandle; //menu handle uItem: Cardinal //position or ID fByPos: Cardinal // 0 for ID, 1 for position ): BOOL; // Boolean resultan example to set menu ID 105 to the default item SetMenuDefaultItem(hMenu, 105, 0); if you use -1 as the uItem then no item will be the Default, since uItem is defined as a cardinal and no negative values are allowed then you will have to use $FFFFFFFF. You can use the function - GetMenuDefaultItem(hMenu: THandle; fByPos, gmdiFlags: Cardinal): Cardinal; to get the Default Item of that sub-menu, , , if $FFFFFFFF is returned then there is no Default Item. API Menu Functions Here is a list of API Menu functions used in the Menu Listbox Program below. Most of these functions are named for what they do to a menu, for instance CheckMenuItem( ) will check and uncheck a menu item. You should look up each function in the Win32 API help and read about it's parameters. Also look in the Code for the Menu and Listbox Program below to see an example of how to use the function.
The GetMenuItemInfo( ) and SetMenuItemInfo( ) functions can do most of the things that the above menu functions can do. These 2 function use the TMenuItemInfo record to get and set menu item charteristics. GetMenuItemInfo(HMENU hMenu, // handle of menu uItem: Cardinal, // item ID or position fByPosition: Boolean, // true for position, false for ID lpmii: TMenuItemInfo // fills record with requested info ); SetMenuItemInfo(HMENU hMenu, // handle of menu uItem: Cardinal, // item ID or position fByPosition: Boolean, // true for position, false for ID lpmii: TMenuItemInfo // sets items with record info );As with the InsertMenuItem( ) function, you need to set the TMenuItemInfo fMask and fType so the function will know what information to get or set. The next code will get the string and ID for a menu Item - var MenuInfo1: TMenuItemInfo; Buffer: Array[0..1024] of Char; begin MenuInfo1.cbSize := SizeOf(TMenuItemInfo); MenuInfo1.fMask := MIIM_ID or MIIM_TYPE; MenuInfo1.fType := MFT_STRING; MenuInfo1.cch := 1023; MenuInfo1.dwTypeData := @Buffer; GetMenuItemInfo(hMenu1, 1, True, MenuInfo1); //MenuInfo1.wID will now have the ID Number //MenuInfo1.dwTypeData will now have the item's string Menu Messages sent to the Window Proc We have seen in previous programs that if you click a button, then a WM_COMMAND message is sent to your Window Proc with the button's Handle in the LParam. Since a Menu is considered the "Primary" user interface, a Menu Click will send a WM_COMMAND message with the LParam as Zero. No matter which MainMenu, submenu or Popup menu is clicked the LParam is 0 for all menus (EXCEPT the System Menu which does not send a WM_COMMAND message, it sends the WM_SYSCOMMAND message). If the LParam is zero then it is a menu click, the LOWORD of the WParam will have the menu item ID number. You can test for the LOWORD(wParam) to see which menu item was clicked. (See the code in the Menu and Listbox program below for examples). The WM_INITMENUPOPUP message is sent just before a menu is shown, you can use this message to update a menu before it is displayed, enable items, change check marks, add items, ect. Whenever a Menu is displayed from your program, the WM_ENTERMENULOOP message is sent to tell your program that a menu execution loop has been entered. The WM_EXITLOOP message is sent to tell your program that a menu modal loop has been exited. The WM_ENTERMENULOOP and WM_EXITLOOP are not used in the Menus and Listbox Program. Shortcut or Pop Up Menus A Shortcut or Popup menu is not associated with the Programs main menu, but pops up in a location away from the main menu, usually with a mouse Right Click. These can also be called context menus. You can create a Popup menu when your program begins and then show that menu with the TrackPopupMenu( ) function. Or you can create and destroy the Popup menu whenever you need to show it, which is what I do in this program. To display a Popup or Shortcut menu you should call the TrackPopupMenu( ) function, which is definded in windows.pas as - TrackPopupMenu(hMenu: HMENU; // handle of shortcut menu uFlags: Cardinal; // screen-position and mouse-button flags x: Integer; // horizontal position, in screen coordinates y: Integer; // vertical position, in screen coordinates nReserved: Integer; // reserved, must be zero hWnd: HWND; // handle of owner window prcRect: PRect // pointer to RECT for no-dismissal area ): Boolean; // boolean resultThis function works with pop-up menus because it does not return (finish it's function) until the menu has been dismissed (no longer visible). So no other code in that thread is able to be executed while the menu is visible, creating a menu modal effect. Look at the Win32 API Help for more info about the parameters. The uFlags can be set so the position of the menu is placed relative to the X cordinate, TPM_CENTERALIGN will center it on the X position, TPM_LEFTALIGN will place the left side on the X position, and TPM_RIGHTALIGN will place the Right side on the X position. Look at the code in the procedure ShowPopMenu(X, Y: Integer); in the Menu and Listbox Program below. the function TrackPopupMenuEx( ) can also be used, it is a newer version of TrackPopupMenu. |
List Boxes A list box is a control window that has a vertical list of items from which the user can select. If the list box is not tall enough to display all the items at once, the list box can have a scroll bar so the user can scroll all the items into view. When a list box item is selected it changes color to the system select color and Windows sends a notification message to the parent window of the list box. There are two general styles of list box, single-selection (the default style) and multiple-selection. The CreateWindow( ) function provides many other list box and window styles that control the look and operation of a list box. These styles determine whether list box items are sorted, arranged in multiple columns, can be selected, and others. Look at the Win32 API Help for index "CreateWindow" and look at the style flags for a listbox. You may also want to read the API help for index "List Boxes" and click the arrows at the top to read the sequence of pages about list boxes. You could use the following code to create a "Standard" list box, which uses strings, is Sorted, has a single line border and the parent window gets Notification messages - hListBox1 := CreateWindow('LISTBOX', nil, WS_VISIBLE or WS_CHILD or LBS_STANDARD, 8,30,150,220,hForm1,101,hInstance,nil);However, to get the 3D look, we will need the CreateWindowEx( ) with the WS_EX_CLIENTEDGE flag - hListBox1 := CreateWindowEx(WS_EX_CLIENTEDGE,'LISTBOX', nil, WS_VISIBLE or WS_CHILD or LBS_HASSTRINGS or LBS_NOTIFY or LBS_SORT or WS_VSCROLL, 8,30,150,220,hForm1,101,hInstance,nil);Like Edits, listboxes send Notification messages to it's parent window, LBN_DBLCLK is sent for a list box double click, LBN_ERRSPACE is sent for lack of memory for text, LBN_KILLFOCUS is sent if it loses focus, LBN_SELCANCEL is sent when the selection is canceled, LBN_SELCHANGE is sent before the selection changes, LBN_SETFOCUS is sent when the list box gets focus. These are sent in the HIWORD(wParam) and the listbox Handle is in the lParam. Messages to List Boxes There are over 30 LB_, list boxes messages, look in the Win32 API Help for index "Messages to List Boxes" to see a list of these messages and their use. Some of the ones used in this Program are, LB_ADDSTRING, LB_GETCOUNT, LB_GETCURSEL, LB_GETTEXT, and LB_RESETCONTENT. You use these messages in a SendMessage( ) function. Their names will tell you what they do. Look at the code in the Menus and Listbox program for examples of how to use these messages. List Box Notification messages When some events occur in a list box, the list box sends a notification message to the Window Proc procedure of the owner window. List box notification messages are sent when a user selects, double-clicks, or cancels a list box item; when the list box receives or loses the keyboard focus; and when the system does not have enough memory for a list box request. A notification message is sent as a WM_COMMAND message in which the low-order word of the wParam parameter contains the list box identifier, the high-order word of wParam contains the notification message, and the lParam parameter contains the list box window handle. The HIWORD(wParam) can contain , LBN_DBLCLK, LBN_ERRSPACE, LBN_KILLFOCUS, LBN_SELCANCEL, LBN_SELCHANGE, and LBN_SETFOCUS. In the program code below there is an example for the LBN_DBLCLK to get the list box double click notification message. Sub-Classing a List Box In this program, List Box 1 will change it's selection to follow the mouse movement over the list box, like the list boxes in a combo box does. The code for this is in function Listbox1Proc( ), which is the Sub-Classed list box Window Proc. Look at the code for the if (hWnd = hListBox1) and (Msg = WM_MOUSEMOVE) in the Listbox1Proc( ). . . List Box 3 has a Drag and Drop method, so the user can rearange the items by dragging them to a new location in the list box. The code for this is in the procedure DragDropListBox; and it uses the Mouse down and Mouse Up messages in the function Listbox1Proc( ). Both List boxes use the same Sub-Classed procedure, Listbox1Proc( ), if you look at the 2 sub-class SetWindowLong functions - PListbox1Proc := Pointer(SetWindowLong(hListBox1, GWL_WNDPROC, Integer(@Listbox1Proc))); PListbox3Proc := Pointer(SetWindowLong(hListBox3, GWL_WNDPROC, Integer(@Listbox1Proc)));you will see that the new proc is Listbox1Proc( ) for both listboxes. Look at this code in the program if PListbox1Proc = PListbox3Proc then SetWindowText(hEdit1, 'C:\My Documents');And this will test to be True, PListbox1Proc is equal to PListbox3Proc. The window system has a single system Window Proc for each of the standard classes like "BUTTON", "EDIT", and "LISTBOX". Now let's look at the code at the end of the Listbox1Proc( ) where we call the default system message handling with - Result:=CallWindowProc(PListBox1Proc,hWnd,Msg,wParam,lParam);You do not need to use this code - if hWnd = hListBox1 then Result:=CallWindowProc(PListBox1Proc,hWnd,Msg,wParam,lParam) else if hWnd = hListBox3 then Result:=CallWindowProc(PListBox3Proc,hWnd,Msg,wParam,lParam);Because PListbox1Proc is equal to PListbox3Proc. It is sometimes convient to use the same window proc for 2 or more controls and use the hWnd parameter to get messages for the different controls. Filling a list box with Folder file names If you send the LB_DIR message to a list box you can fill it with the Names of files and folders in the folder name you send in the LParam. You should look up LB_DIR in the Win32 API Help for more info about this. I use this method to fill List Box 1 with file names. But this method is a Left-over from the Windows 3, 16-bit days, so you will get only the "Short" Dos names in the list box. If you want the 32-bit long names in your list box you will need to use FindFirstFile( ) function. To see an example of this, look at the procedure GetFiles(FilePath: String); in the Menus and Listboxes program code below. This method is used to fill List Box 2 with file names. |
In the code below there are many examples for creating and using Menus and Listboxes. But there may be to much here to deal with all at once, so you may want to create your own GUI program with a hForm1 main window and then copy code from the program below to create a simple menu and put code in your Window Proc (MessageFunc) to get the menu click messages. (see the code after the main begin for the main menu creation code). You can copy other code and experiment with menu fuctions like CheckMenuItem( ) and GetMenuState( ) to see how to use them and the options the parameters give you. Then create a List box or 2 and use some of the List Box messages like LB_ADDSTRING and LB_GETCURSEL which you can see some examples in the code below. As you learn more about using menus and list boxes, you will add more and more functions and use more options. The Procedure and Function Names in the code below, like procedure ShowPopMenu( ) will give you an Idea of what they do.
Look at the const values, you will see things like mID_m1New = 101;, this is a way to give you an idea of which item the menu ID number is for. I only do the first 2 sub-menu ID numbers, to show you how to do it, the rest of the menu ID numbers are used as the integer value. But you will need to develop your own menu item ID naming scheme. I use a Prefix of mID_ and then a sub-menu number like m1 or m2, then the menu items text. But if you are not using any other types of ID constants then you could leave off the mID and use m1_New and m2_NewFolder. There are alot of comments in the code below to explain why the code is there and what it does. |
program MenuListB; {this program will show how to create and use menus and list boxes} uses Windows, Messages, smallutils; {$R *.RES} var wClass: TWndClass; hForm1, menuFile, menuListB1, menuListB2, menuListB3, menuMain, menuSubFolder1, menuSubFolder2, hListBox1, hListBox2, hListBox3, hEdit1, Bitmap1, hSysMenu: Integer; mainMsg: TMsg; MenuInfo: TMenuItemInfo; PListBox1Proc, PListBox3Proc: Pointer; Rect1: TRect; DlgChk, CanDrag, DoChange: Boolean; Count, CopyNum: Integer; UpSelect, DownSelect: Integer; DlgEditText: String; FindData: TWin32FindData; ErrorMode: Cardinal; FindHandle: THandle; CharBuffer: array[0..1024] of Char; TempDC, BmpDC: HDC; const About = 'This is the MenuListB program'#10'to show you how to program menus and list boxes'; {for menu item ID's you may want to make const names, to help you know which menu item the ID numbers are for} mID_m1New = 101; mID_m1Open = 102; mID_m1Save = 103; mID_m1SaveAs = 104; mID_m1Show = 105; mID_m1Exit = 106; mID_m2NewFolder = 201; mID_m2AddSel = 202; mID_m2Clear = 203; mID_m2ChangeItem = 204; mID_m2Item = 205; function Max(A,B: Integer): Integer; begin {a function from the math.pas} if A > B then Result := A else Result := B; end; procedure ShutDown; begin DeleteObject(Bitmap1); PostQuitMessage(0); end; procedure ShowPopMenu(X, Y: Integer); var hPopMenu: HMENU; begin {this procedure is called on a right click by the WM_CONTEXTMENU message. It creates and shows a Pop up Menu} hPopMenu := CreatePopupMenu; {this CreatePopupMenu fuction makes an empty menu} {AppendMenu() adds Items to a menu} AppendMenu(hPopMenu, MF_STRING, 501,'Message Box'); {the 501 is the ID number sent to the WndProc message function when menu click} AppendMenu(hPopMenu, MF_STRING, 502,'Move'); AppendMenu(hPopMenu, MF_STRING, 503,'About'); AppendMenu(hPopMenu, MF_STRING,504,'Exit'); {the TrackPopupMenu fuction is active as long as the PopUp Menu is displayed, so nothing else is happening in this thread while the menu is up} TrackPopupMenu(hPopMenu, // handle of shortcut menu TPM_LEFTALIGN or TPM_LEFTBUTTON, // screen-position and mouse-button flags X-5, // horizontal position, in screen coordinates Y-5, // vertical position, in screen coordinates 0, // reserved, must be zero hForm1, // handle of window that gets menu messages { see MessageProc at the WM_COMMAND for menu messages} nil // points to RECT that specifies no-dismissal area ); {since this menu is created each time, you must destroy the menu} DestroyMenu(hPopMenu); end; procedure DragDropListBox; {the listBox is changed after a drag and drop. you need to get the text then insert a new item and delete the old item} var Pos: Integer; begin {UpSelect, DwnSelect are from the ListBox1Proc fuction for mouse operations} Pos := UpSelect; SendMessage(hListBox3, LB_GETTEXT, DownSelect, Integer(@CharBuffer)); if DownSelect < UpSelect then Inc(UpSelect); SendMessage(hListBox3, LB_INSERTSTRING, UpSelect, Integer(@CharBuffer)); if DownSelect > UpSelect then Inc(DownSelect); SendMessage(hListBox3, LB_DELETESTRING, DownSelect, 0); SendMessage(hListBox3, LB_SETCURSEL, Pos, 0); end; function Listbox1Proc(hWnd,Msg,wParam,lParam: Integer): Integer; stdcall; {we need to get mouse click messages here for Drag and Drop and mouse move messages for changing select item} var X, Y, selNum, CurSel: Integer; begin Result := 0; if (hWnd = hListBox1) and (Msg = WM_MOUSEMOVE) then begin {this moves the selection with the mouse cursor in List Box 1} CurSel := CallWindowProc(PListBox1Proc,hWnd, LB_GETCURSEL,0, 0); selNum := CallWindowProc(PListBox1Proc,hWnd, LB_ITEMFROMPOINT,0, MAKELPARAM(LOWORD(lParam){X}, HIWORD(lParam){Y})); if CurSel <> selNum then CallWindowProc(PListBox1Proc,hWnd, LB_SETCURSEL, selNum, 0); {since this is the Message proc for this list box you do not need to call SendMessage, you can use CallWindowProc(PListBox1Proc, , instead} end; {CanDrag is set to true if Dragging in hListBox3 is allowed} if CanDrag and (hWnd = hListBox3) then case Msg of {this is the code that does the drag and drop in List box 3} WM_LBUTTONDOWN: begin {on Button down we get the X and Y and then get the list box selection. DownSelect is used to store the Selected number for the Button Up} X := LOWORD(lParam); Y := HIWORD(lParam); selNum := CallWindowProc(PListBox3Proc,hWnd, LB_ITEMFROMPOINT, 0, MAKELPARAM(X, Y)); {LB_ITEMFROMPOINT gets the item index closest to the point in listbox} if HIWORD(selNum) = 0 then {the HIWORD(selNum) will be 0 if the point is in the client area of the listbox} begin DownSelect := LOWORD(selNum); {LOWORD(selNum) has the select position in it} DoChange := True; {DoChange will prevent the Button up from being executed on a drag and drop} end; {a note - - - you could just get the Listbox current section with LB_GETCURSEL, which will be where ever the button Down happens. But I wanted to show how to use the LB_ITEMFROMPOINT} end; WM_LBUTTONUP: begin X := LOWORD(lParam); Y := HIWORD(lParam); {mouse position is given in the HI and LO Word positions of lParam} selNum := CallWindowProc(PListBox3Proc,hWnd, LB_ITEMFROMPOINT, 0, MAKELPARAM(X, Y)); if X < 186 then {X < 186 is to make sure the mouse BUTTONUP was inside the listbox you might check Y also} begin if DoChange and (DownSelect <> selNum) then begin UpSelect := selNum; DragDropListBox; {DragDropListBox rearranges the items in the list box} end; end; end; end; {if hWnd = hListBox3 then Result:=CallWindowProc(PListBox3Proc,hWnd,Msg,wParam,lParam) else if hWnd = hListBox1 then Result:=CallWindowProc(PListBox1Proc,hWnd,Msg,wParam,lParam);} {the code above is not needed because PListBox1Proc is the same as PListBox3Proc, all system processes for a certain system class like "LISTBOX", are the same} Result:=CallWindowProc(PListBox1Proc,hWnd,Msg,wParam,lParam); end; procedure GetFiles(FilePath: String); var num: Cardinal; begin {this will Get the files in a folder and put their names in List Box 2} {if not DirectoryExists(FilePath) then Exit;} num := 0; SendMessage(hListBox2, WM_SETREDRAW, 0, 0); {the WM_SETREDRAW message stops the redraw update of the listbox, so it is not redrawn untill all changes have been compleated} SendMessage(hListBox2, LB_RESETCONTENT, 0, 0); {clear the listbox with LB_RESETCONTENT} SetWindowText(hListBox2, @FilePath[1]); {I Store the Folder Path in the Text Buffer of the List Box since the TextBuffer for this Control is not Shown} FilePath := FilePath+'*.*'; ErrorMode := SetErrorMode(SEM_FailCriticalErrors); FindHandle := FindFirstFile(@FilePath[1], FindData); SetErrorMode(ErrorMode); if FindHandle <> INVALID_HANDLE_VALUE then begin if (FindData.cFileName[0] <> '.') and (FindData.cFileName[1] <> #0) then begin SendMessage(hListBox2, LB_ADDSTRING, 0, Integer(@FindData.cFileName)); Inc(num); end; while FindNextFile(FindHandle, FindData) do begin if (FindData.cFileName <> '.'#0) and (FindData.cFileName <> '..') then begin if (FindData.dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY) then begin {if a Folder is found, I add "1dir " to indicate that it is a Folder} SendMessage(hListBox2, LB_ADDSTRING, 0, Integer(PChar('1dir '+String(FindData.cFileName)))); {LB_SETITEMDATA will set an Integer value for Data. Every Item in a List box has it's own Data Integer. I set it to 12 for a Folder, so in the List Box Double Click I can tell which Items are folders. You can place a Pointer in this data, which can point to a PChar, TRect or any other variable you need} SendMessage(hListBox2, LB_SETITEMDATA, num, 12); Inc(num); end else begin SendMessage(hListBox2, LB_ADDSTRING, 0, Integer(@FindData.cFileName)); Inc(num); end; end; end; FindClose(FindHandle); end; if Num = 0 then SendMessage(hListBox2, LB_ADDSTRING, 0, Integer(PChar('No files found'))); SendMessage(hListBox2, WM_SETREDRAW, 1, 0); {WM_SETREDRAW with the wParam as 1, tells the listbox to update it's display and Redraw} end; procedure SetSubMenu(subMenu: Integer); var Num, ID, i: Integer; begin {each time the menuSubFolder2 sub-menu is displayed, it is updated with all the folders on the C drive. Unlike the menuSubFolder1, which was created when this program started and is never updated to show changes on the C drive} Num := GetMenuItemCount(subMenu); if subMenu = menuSubFolder1 then ID := 701 else ID := 801; if Num > 0 then {the subMenu may have menu Items in it, so you need to delete all the old items} for i := Num-1 downto 0 do DeleteMenu(subMenu, i, MF_BYPOSITION); Count := 0; ErrorMode := SetErrorMode(SEM_FailCriticalErrors); {set error mode to SEM_FailCriticalErrors to prevent NT systems from showing Disk not avaiable error messages} FindHandle := FindFirstFile('C:\*.*', FindData); {FindFirstFile will get the first file or folder on C:\} SetErrorMode(ErrorMode); if FindHandle <> INVALID_HANDLE_VALUE then begin {test for the FILE_ATTRIBUTE_DIRECTORY attribute to get only Folders} if (FindData.cFileName <> '.'#0) and (FindData.dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY) then begin AppendMenu(subMenu, MF_STRING, ID,FindData.cFileName); Inc(Count); end; while FindNextFile(FindHandle, FindData) do begin if (FindData.cFileName <> '.'#0) and (FindData.cFileName <> '..') and (FindData.dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY) then begin {since there can be many folders, I limit the length of the folder name to 70 Char and I limit the number of folders displayed in the menu to 64} if Count > 63 then Break; if (StrLen(FindData.cFileName) < 70) then begin AppendMenu(subMenu, MF_STRING, ID+Count, FindData.cFileName); Inc(Count); end; end; end; FindClose(FindHandle); end; end; procedure SelToLB3(LB1: Boolean); var SMResult: Integer; hLB: Integer; begin {this procedure will get the selected item text from a list box and add that text as an item in List Box 3} if LB1 then hLB := hListBox1 else hLB := hListBox2; SMResult := SendMessage(hLB, LB_GETCURSEL, 0, 0); if SMResult = LB_ERR then begin {if no item is selected then the LB_GETCURSEL will return LB_ERR} MessageBox(hForm1,'No Item is Selected in the ListBox, select an Item and try again', 'None Selected',MB_OK or MB_ICONERROR); Exit; end; SMResult := SendMessage(hLB, LB_GETTEXT, SMResult, Integer(@CharBuffer)); if SMResult < 1 then begin {the result of LB_GETTEXT will be the number of charaters in that items text} MessageBox(hForm1,'Could not get any text from the List Box Item selected', 'No Text',MB_OK or MB_ICONERROR); Exit; end; SendMessage(hListBox3, LB_ADDSTRING, 0, Integer(@CharBuffer)); end; procedure DoubleClick(LBHandle: Cardinal); var SMResult, sel: Integer; PathStr1: String; begin {this procedure is called when there is a a Double click on a List box} sel := SendMessage(LBHandle, LB_GETCURSEL, 0, 0); if sel < LB_ERR then Exit; SMResult := SendMessage(LBHandle, LB_GETTEXT, sel, Integer(@CharBuffer)); if SMResult < 1 then Exit; PathStr1 := GetWindowStr(LBHandle); {the Folder path is stored in the list box text buffer} {in hListBox2 all the Folders have the item's Data Integer set to 12. So if the item data is 12 then list the files in that folder in the list box} if SendMessage(LBHandle, LB_GETITEMDATA, sel, 0) = 12 then begin PathStr1 := PathStr1+PChar(@CharBuffer[6])+'\'; GetFiles(PathStr1); end else begin if CharBuffer = 'No files found' then PathStr1 := CharBuffer else PathStr1 := PathStr1+CharBuffer; MessageBox(hForm1, @PathStr1[1], 'File or Folder Selected', MB_OK or MB_ICONINFORMATION); end; end; procedure SortListBox; {this procedure changes List box 3 to Sorted or Un-Sorted} var TextLen, ItemLen, CurSel, Count, maxLen, Style, i: Integer; Buffer, PItems, Buf2, Pos1: PChar; procedure CreateLB; begin {you can not change the List Box Style of LBS_SORT with SetWindowLong( ) you will need to destroy the Listbox and then Create it again with the LBS_SORT flag} SetWindowLong(hListBox3, GWL_WNDPROC, Integer(PListbox3Proc)); DestroyWindow(hListBox3); hListBox3 := CreateWindowEx(WS_EX_CLIENTEDGE,'LISTBOX',Buffer, Style, 338,30,188,220,hForm1,0,hInstance,nil); SendMessage(hListBox3,WM_SETFONT,GetStockObject(ANSI_FIXED_FONT),0); PListbox3Proc := Pointer(SetWindowLong(hListBox3, GWL_WNDPROC, Integer(@Listbox1Proc))); end; begin {first I get the Style of List box 3 and test it for the LBS_SORT bit} Style := GetWindowLong(hListBox3, GWL_STYLE); if Style or LBS_SORT = Style then begin Style := Style and not LBS_SORT; {Style and not LBS_SORT will remove the LBS_SORT bit from Style} CanDrag := True; CheckMenuRadioItem(menuListB3, 0, 1, 1, MF_BYPOSITION); {radio check the menu item} end else begin Style := Style or LBS_SORT; canDrag := False; CheckMenuRadioItem(menuListB3, 0, 1, 0, MF_BYPOSITION); end; {List box 3 will be destroyed and then created again, so you need to get all the string data from the List box to put in the new list box} TextLen := SendMessage(hListBox3, WM_GETTEXTLENGTH, 0,0); if TextLen > 0 then begin GetMem(Buffer, TextLen+1); SendMessage(hListBox3, WM_GETTEXT, TextLen+1, Integer(Buffer)); end; Count := SendMessage(hListBox3, LB_GETCOUNT, 0, 0); if Count = LB_ERR then Count := 0; if Count > 0 then begin {if Count is greater than 0, then you need to get the text of Items in the old list box and put them in the new list box} CurSel := SendMessage(hListBox3, LB_GETCURSEL, 0, 0); ItemLen := 0; MaxLen := 0; for i := 0 to Count-1 do begin {I will save all the Item text strings in One PChar Variable, PItems. So I need to get the text length for each Item and add them together} TextLen := SendMessage(hListBox3, LB_GETTEXTLEN, i, 0); Inc(ItemLen, TextLen+1); // add 1 for the #0 MaxLen := Max(MaxLen, TextLen+1); {I need a Buf2 that is big enough for the longest Item string so I get the longest length into MaxLen} end; PItems := AllocMem(ItemLen+1); {get enough memory to hold all the charaters ItemLen+1 will Add an Additional byte to PItems for another #0 to singnal the repeat loop to stop, AllocMem is used because it fills PItems memory block with #0} Buf2 := AllocMem(MaxLen); try Pos1 := PItems-1; {Pos1 is used for pointer arithmatic, I start with Pos1 := PItems-1 because one is added to Pos1} for i := 0 to Count-1 do begin Pos1 := PItems-1; {this for loop copies all of the Items text into the PItems PChar by getting the text into the Buf2 and then use the StrECopy to copy Buf2 into PItems with the Pos1+1 which is updated to the end of the string so each string has a #0 delimiter} TextLen := SendMessage(hListBox3, LB_GETTEXT, i, Integer(Buf2)); if TextLen > 0 then Pos1 := StrECopy(Ptr(Integer(Pos1)+1), Buf2); {StrECopy returns the pointer to the End of the string the #0 charater} end; CreateLB; {CreateLB will destroy the old listbox and create a new one} Pos1 := PItems; repeat {this repeat loop adds a string to the new Listbox, there are several #0 terminated strings in the PItems memory block. By using StrEnd to update Pos1, the memory location of Pointer, Pos1, is moved to the charater after the #0 delimiter, which is the begining of the next string} SendMessage(hListBox3, LB_ADDSTRING, 0, Integer(Pos1)); Pos1 := StrEnd(Pos1)+1; until Pos1^ = #0; {I created PItems with a length of one byte (charater) longer than was needed for the strings and one #0 terminator, so it has 2 #0 at it's end. I used Pos1^ because the compiler will read this as a single Char at the Pos1 memory location. The loop ends if the first charater of the string is #0, which is at the end of PItems} if CurSel <> LB_ERR then SendMessage(hListBox3, LB_SETCURSEL, CurSel, 0); finally FreeMem(PItems); FreeMem(Buf2); end; end else CreateLB; if Buffer <> nil then FreeMem(Buffer); {SetWindowLong(hListBox3, GWL_STYLE, WS_VISIBLE or WS_CHILD or LBS_HASSTRINGS or LBS_NOTIFY or WS_VSCROLL or LBS_SORT);} {SetWindowLong above with LBS_SORT will have NO EFFECT on the list box because most controls do not allow style changes after creation} end; procedure CopyMenuItem; begin {this procedure will get a menu item's settings with GetMenuItemInfo and then set the last menu item in menuListB2 to be a copy of it} MenuInfo.cbSize := SizeOf(MenuInfo); MenuInfo.dwTypeData := @CharBuffer[0]; {assign a memory block to the MenuInfo.dwTypeData} MenuInfo.cch := 256; {because we are Getting a string with GetMenuItemInfo, you need to set the MenuInfo.cch} MenuInfo.fMask := MIIM_STATE or MIIM_ID or MIIM_TYPE or MIIM_DATA or MIIM_SUBMENU or MIIM_CHECKMARKS; {all 6 Mask flags are used, so no matter what the Menu Item is, this will get all the settings for it, for a string, a separator, a sub-menu, or a bitmap} GetMenuItemInfo(menuFile, CopyNum, False, MenuInfo); {GetMenuItemInfo with all 6 mask flags will put all of the menu items settings in the MenuInfo, including the Type and State} SetMenuItemInfo(menuListB2, 6, True, MenuInfo); {SetMenuItemInfo will copy all of the menuFile menu item's settings in MenuInfo to the menuListB2 menu item} if CopyNum < mID_m1Exit then Inc(CopyNum) else CopyNum := mID_m1New; {CopyNum will cycle through all the menu items with an ID number in menuFile} end; function MenuCheck(Menu, Item: Byte): Byte; var mHandle: Integer; StResult: Cardinal; begin {this function will change the check state of a menu item} Result := 2; case Menu of 1: mHandle := menuListB1; 2: mHandle := menuListB2; 3: mHandle := menuListB3; else Exit; end; {GetMenuState will have the menu State flags in it's result} StResult := GetMenuState(mHandle, Item, MF_BYPOSITION); if StResult = $FFFFFFFF then begin {if StResult is $FFFFFFFF, there is no menu item at that position, $FFFFFFFF is a UINT value of -1} Result := 2; Exit; end; if StResult = StResult or MF_CHECKED then begin Result := 0; CheckMenuItem(mHandle, Item, MF_UNCHECKED or MF_BYPOSITION); {CheckMenuItem will check and uncheck menu items} end else begin Result := 1; CheckMenuItem(mHandle, Item, MF_CHECKED or MF_BYPOSITION); end; {below is another way to get a menu's state using GetMenuItemInfo} {MenuInfo.cbSize := SizeOf(MenuInfo); MenuInfo.fMask := MIIM_STATE; GetMenuItemInfo(mHandle, Item, True, MenuInfo); if MenuInfo.fState = MenuInfo.fState or MFS_CHECKED then MessageBox(hForm1,'Item was Checked', 'Item Checked', MB_OK or MB_ICONQUESTION);} end; procedure DoMessage; begin MessageBox(hForm1,'a menu Do Message Message Box','Menu Click Message box', MB_OK or MB_ICONINFORMATION); end; function MessageFunc(hWnd: HWnd; Msg: UINT; WParam: WPARAM; LParam: LPARAM): UINT; stdcall; var MenuInfo: tagMENUITEMINFO; Cfolder: String; Buffer: Array[0..83] of Char; PaintS: TPaintStruct; Returned: Cardinal; procedure GetMenuStr(IsOne: Boolean); var mHandle: Integer; begin {this will get the string of a menu Item} if IsOne then mHandle := menuSubFolder1 else mHandle := menuSubFolder2; MenuInfo.cbSize := SizeOf(MenuInfo); MenuInfo.fMask := MIIM_TYPE; MenuInfo.fType := MFT_STRING; {set the Mask and Type for a string} MenuInfo.dwTypeData := Buffer; {set the dwTypeData to a memory block to recieve the charaters} MenuInfo.cch := 82; {use cch to tell the GetMenuItemInfo how much memory is in dwTypeData memory block} GetMenuItemInfo(mHandle, LOWORD(wParam), False, MenuInfo); {GetMenuString( ) could also be used} end; begin Result := 0; case Msg of WM_SYSCOMMAND: if wParam = 901 then DoMessage else if wParam = 902 then MoveWindow(hWnd,1,1, Rect1.Right-Rect1.Left, Rect1.Bottom-Rect1.Top, True); {all the System Menu clicks generate a WM_SYSCOMMAND message with the menu ID in the wParam, the "Added Item" will have a 901 wParam} WM_CONTEXTMENU: ShowPopMenu(LOWORD(lParam), HIWORD(lParam)); {WM_CONTEXTMENU is a Right Click event message, which is also sent for right clicks on child windows that do not have a right click event, right click a list box to see this and then right click the Edit Box, which will show it's edit Copy-Paste menu} WM_PAINT: begin BeginPaint(hWnd, PaintS); SelectObject(PaintS.hdc,GetStockObject(ANSI_VAR_FONT)); SetBkMode(PaintS.hdc,TRANSPARENT); TextOut(PaintS.hdc,7,4,'Menus and List Boxes',20); TextOut(PaintS.hdc,18,254,'Enter Folder for list boxes here',32); EndPaint(hWnd,PaintS); Result := 0; Exit; end; WM_COMMAND: if HIWORD(wParam) = LBN_DBLCLK then {HIWORD(wParam) = LBN_DBLCLK is sent for a list box double click} DoubleClick(lParam) else if lParam = 0 then begin {a menu click will have a lParam of 0} case LOWORD(wParam) of {the LOWORD(wParam) will have the menu item ID number} mID_m1Open{101}: DoMessage; mID_m1New{102}: DoMessage; mID_m1Save: DoMessage; mID_m1SaveAs: DoMessage; mID_m1Show: begin {this will place the sub-menu menuListB3 back into the Main Menu} InsertMenu(MenuMain, 3, MF_BYPOSITION or MF_POPUP or MF_STRING, menuListB3,'ListBox&3'); EnableMenuItem(menuFile, mID_m1Show, MF_BYCOMMAND or MF_GRAYED); DrawMenuBar(hForm1); end; mID_m1Exit: PostMessage(hForm1,WM_CLOSE,0,0); // - - - - - - - - - - - - - - // mID_m2NewFolder: begin CFolder := GetWindowStr(hEdit1); if Length(CFolder) > 2 then begin if CFolder[Length(CFolder)] <> '\' then CFolder := CFolder+'\'; if DirectoryExists(CFolder) then begin GetShortPathName(@Cfolder[1],Buffer,82); Cfolder := Buffer+'*.*'; SendMessage(hListBox1, LB_RESETCONTENT, 0, 0); SendMessage(hListBox1, LB_DIR, DDL_READONLY or DDL_DIRECTORY, Integer(PChar(CFolder))) end else MessageBox(hForm1,'Folder does NOT Exist','No Folder', MB_OK or MB_ICONERROR); end; end; mID_m2AddSel: SelToLB3(True); mID_m2Clear: SendMessage(hListBox1, LB_RESETCONTENT, 0, 0); mID_m2ChangeItem: begin {this uses the SetMenuItemInfo fuction to change several item properties at once} MenuInfo.cbSize := SizeOf(MenuInfo); MenuInfo.fMask := MIIM_STATE; GetMenuItemInfo(menuListB1, 205, False, MenuInfo); if MenuInfo.fState = MenuInfo.fState or MFS_CHECKED then begin MenuInfo.fMask := MIIM_STATE or MIIM_TYPE; MenuInfo.fType := MFT_STRING; MenuInfo.fState := MFS_UNCHECKED; MenuInfo.dwTypeData := 'Old Item String'; end else begin MenuInfo.fMask := MIIM_STATE or MIIM_TYPE; MenuInfo.fType := MFT_STRING; MenuInfo.fState := MFS_CHECKED or MFS_GRAYED; MenuInfo.dwTypeData := 'New Item string'; end; SetMenuItemInfo(menuListB1, 205, False, MenuInfo); {this SetMenuItemInfo does 3 changes to a menu item. it changes the Checked, the text and the Grayed} Returned := GetMenuDefaultItem(menuListB1, 0, 0); {4294967295 = $FFFFFFFF is returned if there is no Default menu item} SetWindowText(hEdit1, PChar(Int2Str(Returned))); end; mID_m2Item: DoMessage; {the menu ID's above use a Constant Name for the menu ID. All the menu ID's below use the Number of the ID, it makes it very difficult to know which menu and menu Item the numbers represent. I change the Hundered (301, 401, 501) to indicate a different sub-menu, but you will need to refer to the menu creation to see what the number like 302, will be for a menu item. Using constant Menu ID names is better, with naming conventions like mID_m1New, mID_m2NewFolder, where m1 and m2 indicate sub-menu 1 and sub-menu 2} 301: begin CFolder := GetWindowStr(hEdit1); if Length(CFolder) > 2 then begin if CFolder[Length(CFolder)] <> '\' then CFolder := CFolder+'\'; if DirectoryExists(CFolder) then GetFiles(Cfolder) else MessageBox(hForm1,'Folder does NOT Exist','No Folder', MB_OK or MB_ICONERROR); end; end; 302: SelToLB3(False); 303: SendMessage(hListBox2, LB_RESETCONTENT, 0, 0); 304: CopyMenuItem; 401..402: SortListBox; 403: SendMessage(hListBox3, LB_RESETCONTENT, 0, 0); 404: if MenuCheck(3,4) = 0 then SetWindowText(hEdit1, 'It was Checked'); 405: begin RemoveMenu(MenuMain, 3, MF_BYPOSITION); {RemoveMenu will remove a menu item, but it does not destroy it's sub-menu} EnableMenuItem(menuFile, mID_m1Show, MF_BYCOMMAND or MF_ENABLED); {the Show Menu item on the File menu is enabled} DrawMenuBar(hForm1); {DrawMenuBar will repaint the main Menu to show changes} end; 501: DoMessage; 502: MoveWindow(hWnd,1,1, Rect1.Right-Rect1.Left, Rect1.Bottom-Rect1.Top, True); 503: MessageBox(hForm1,About,'About MenuListB',MB_OK or MB_ICONINFORMATION); 504: PostMessage(hForm1,WM_CLOSE,0,0); 701..765: begin {ID numbers 701 to 765 are for C drive folders in the menuSubFolder1} GetMenuStr(True); Cfolder := 'C:\'+MenuInfo.dwTypeData; GetShortPathName(@Cfolder[1],Buffer,82); Cfolder := Buffer+'\*.*'; if MessageBox(hForm1,PChar('Do you want to show the files in the ' +MenuInfo.dwTypeData+' Folder? ?'), MenuInfo.dwTypeData, MB_YESNO or MB_ICONQUESTION) = IDYES then begin SendMessage(hListBox1, LB_RESETCONTENT, 0, 0); SendMessage(hListBox1, LB_DIR, DDL_READONLY or DDL_DIRECTORY, Integer(@Cfolder[1])); end; end; 801..865: begin {ID numbers 801 to 865 are for C drive folders in the menuSubFolder2} GetMenuStr(False); Cfolder := 'C:\'+MenuInfo.dwTypeData+'\'; DlgChk := True; GetFiles(Cfolder); end; end; // case end; WM_INITMENUPOPUP: if wParam = menuSubFolder2 then begin {just before a submenu is shown the WM_INITMENUPOPUP is sent, you can modify the submenu to fit the conditions before it is displayed} SetSubMenu(wParam); end else if wParam = hSysMenu then SetWindowText(hEdit1, PChar('Sys menu lParam is '+Int2Str(lParam))); WM_CTLCOLORLISTBOX: if lParam = hListBox3 then begin {WM_CTLCOLORLISTBOX gets the colors used for a ListBox} SetTextColor(wParam,$0000FF); SetBkColor(wParam,$FFFF00); Result := GetStockObject(LTGRAY_BRUSH); Exit; end; WM_DESTROY: ShutDown; end; Result:=DefWindowProc(hWnd,Msg,wParam,lParam); end; begin // * * * * * * * Main Program begin CopyNum := mID_m1New; wClass.hInstance := hInstance; with wClass do begin Style := 0; hIcon:= LoadIcon(hInstance,'MAINICON'); lpfnWndProc:= @MessageFunc; hbrBackground:= COLOR_BTNFACE+1; lpszClassName:= 'Text Class'; hCursor:= LoadCursor(0,IDC_ARROW); cbClsExtra := 0; cbWndExtra := 0; lpszMenuName := nil; end; RegisterClass(wClass); {I will create the main menu bar's submenus before I create the main menu, since they must be added to the Main menu. The first submenu is a "File" menu, but this program does not have any file operations so this sub-menu is just for demonstation} menuFile := CreateMenu; {the CreateMenu fuction creates an Empty menu, which must have items added to it before it can be used. There are 3 functions used here to add Items, AppendMenu, InsertMenu and InsertMenuItem} {this menuFile will have Items added to it with the AppendMenu function, there are 4 parameters. The third parameter, uIDNewItem, is the ID number which will be sent with the WM_COMMAND message in the LOWORD(wParam) with a menu click. AppendMenu adds items in the sequence that it is called, much like an "Add" procedure in delphi} AppendMenu(menuFile, MF_STRING, mID_m1New,'&New'); AppendMenu(menuFile, MF_STRING or MF_GRAYED, mID_m1Open,'&Open'); AppendMenu(menuFile, MF_STRING or MF_CHECKED, mID_m1Save,'&Save'); {the MF_CHECKED will check an item} AppendMenu(menuFile, MF_STRING, mID_m1SaveAs,'Save &As'); AppendMenu(menuFile, MF_STRING or MF_GRAYED, mID_m1Show,'Show Menu'); AppendMenu(menuFile, MF_SEPARATOR, 1,nil); {the MF_SEPARATOR adds a sparator bar to the menu, and does not use the last 2 parameters} AppendMenu(menuFile, MF_STRING, mID_m1Exit,'E&xit'); {using the MF_STRING parameter will add a Text item to the menu. The MF_BITMAP will add a bitmap and MF_OWNERDRAW will specify a Owner Drawn item} EnableMenuItem(menuFile, mID_m1New, MF_GRAYED); {I use the EnableMenuItem to Grey and Disable the "New" menu Item in the File Menu, the "Open" item will also be greyed because the MF_GRAYED is in the AppendMenu parameters} SetMenuDefaultItem(menuFile, mID_m1Exit, 0); {SetMenuDefaultItem will make the default Item have Bold type and be the default item that gets exicuted on a double click} menuSubFolder1 := CreateMenu(); {menuSubFolder1 is a sub-menu for the menuListB1, which is created later. This menuSubFolder1 will list all the folders on the C:\ drive when this program starts} SetSubMenu(menuSubFolder1); {SetSubMenu is used only during the creation of menuSubFolder1 which is never updated. In menuSubFolder2 it is updated each time the sub-menu shows} menuListB1 := CreateMenu; {this menuListB1 will have Items added to it with the AppendMenu function. Another Item will be added "Out" of order with the InsertMenu function at the end of the list} AppendMenu(menuListB1, MF_STRING, mID_m2NewFolder,'&New Folder from Edit'); AppendMenu(menuListB1, MF_STRING, mID_m2AddSel,'Add Sel to Lbox3'); AppendMenu(menuListB1, MF_STRING, mID_m2Clear,'Clear'); AppendMenu(menuListB1, MF_SEPARATOR,1,nil); AppendMenu(menuListB1, MF_STRING, mID_m2ChangeItem,'Change menu Item'); AppendMenu(menuListB1, MF_STRING, mID_m2Item,'menu Item'); {The next InsertMenu( ) will place a submenu item at the second position of the menu, uPosition 1, if Count is greater than 0. You should NOT add a submenu with no items in it} if Count > 0 then InsertMenu(menuListB1, 1, MF_BYPOSITION or MF_POPUP or MF_STRING, menuSubFolder1,'C Folders'); {you can add a Submenu to the menuListB1 by using the MF_POPUP flag and the handle of the sub-menu in the fourth parameter} menuSubFolder2 := CreateMenu(); {this menuSubFolder2 is created here with only one Item, the menu Items will be filled in with a directory just before the menu is shown, see the WM_INITMENUPOPUP message above} AppendMenu(menuSubFolder2, MF_STRING, 799, ' '); {there MUST be one item in the sub-menu, to be successfully added to to a menu item of another menu} menuListB2 := CreateMenu; {items will be added to this menuListB2 with the more powerful InsertMenuItem() function using a TMenuItemInfo record} MenuInfo.cbSize := SizeOf(MenuInfo); {Always set the cbSize before you use a TMenuItemInfo} MenuInfo.fMask := MIIM_ID or MIIM_TYPE; {this first menu item will be a standard string item with an ID of 301, so you need to set the MIIM_ID or MIIM_TYPE flags} MenuInfo.fType := MFT_STRING; {set the fType flag to MFT_STRING for a string to be placed in the menu item} MenuInfo.dwTypeData := '&New Folder from Edit'; //MenuInfo.cch := 21; {if the fType flag is MFT_STRING then the dwTypeData will be read as a PChar string, you do not need to set the MenuInfo.cch to the length of the string, because it will not read the data in MenuInfo.cch. The cch is used if charaters are written and not read to the dwTypeData} MenuInfo.wID := 301; {if the MIIM_ID flag is set then the wID is used ad the ID number. Notice that this is wID, meaning that this should be a WORD type, with a Maximum value of 65534} //MenuInfo.fState := 0; //MenuInfo.hSubMenu := 0; //MenuInfo.hbmpChecked := 0; //MenuInfo.hbmpUnchecked := 0; {you do not need to set other members of MenuInfo because they are ignored} InsertMenuItem(menuListB2,0, False,MenuInfo); {if you set the third parametr to False then the second parameter is used as an ID, but if you set the second Paramater to 0, then it acts like AppendMenu( ) and adds the item after the last item position} MenuInfo.fMask := MIIM_SUBMENU or MIIM_TYPE; MenuInfo.fType := MFT_STRING; MenuInfo.dwTypeData := 'C Folders'; MenuInfo.hSubMenu := menuSubFolder2; InsertMenuItem(menuListB2,1,False,MenuInfo); MenuInfo.fMask := MIIM_ID or MIIM_TYPE; MenuInfo.fType := MFT_STRING; MenuInfo.dwTypeData := 'Add Sel to Lbox3'; MenuInfo.wID := 302; InsertMenuItem(menuListB2,2,False,MenuInfo); {if you set the third parametr to True then the second parameter is used as an item position like InsertMenu( )} MenuInfo.fMask := MIIM_ID or MIIM_TYPE; MenuInfo.fType := MFT_STRING; MenuInfo.dwTypeData := 'Clear'; MenuInfo.wID := 303; InsertMenuItem(menuListB2,3,False,MenuInfo); MenuInfo.fMask := MIIM_TYPE; MenuInfo.fType := MFT_SEPARATOR; {when fType is MFT_SEPARATOR none of the other members are needed} InsertMenuItem(menuListB2,4,True,MenuInfo); MenuInfo.fMask := MIIM_ID or MIIM_TYPE; MenuInfo.fType := MFT_STRING; MenuInfo.dwTypeData := 'Copy Menu Item'; MenuInfo.wID := 304; InsertMenuItem(menuListB2,5,True,MenuInfo); MenuInfo.fMask := MIIM_ID or MIIM_TYPE; MenuInfo.fType := MFT_STRING; MenuInfo.dwTypeData := 'No Copy yet'; MenuInfo.wID := 305; InsertMenuItem(menuListB2,6,True,MenuInfo); TempDC := GetDC(0); BmpDC := CreateCompatibleDC(TempDC); {I will use a bitmap for a menu Item in the menuListB3 menu. The bitmap is created here with "Clear" written on it} Bitmap1 := CreateCompatibleBitmap(TempDC,39,18); SelectObject(BmpDC,Bitmap1); SetRect(Rect1,0,0,39,18); FillRect(BmpDC,Rect1,GetStockObject(BLACK_BRUSH)); SelectObject(BmpDC,GetStockObject(WHITE_BRUSH)); SetBkColor(BmpDC, $FFFFFF); Ellipse(BmpDC,0,0,39,18); SelectObject(BmpDC,GetStockObject(ANSI_VAR_FONT)); SetTextColor(BmpDC,$000000FF); TextOut(BmpDC,7,2,'Clear',5); DeleteDC(BmpDC); ReleaseDC(0,TempDC); menuListB3 := CreateMenu; AppendMenu(menuListB3, MF_STRING,401,'&Sort List'); AppendMenu(menuListB3, MF_STRING,402,'Allow Draging'); AppendMenu(menuListB3, MF_BITMAP,403,PChar(Bitmap1)); {with the MF_BITMAP you will need to TypeCast the Bitmap handle to a PChar} AppendMenu(menuListB3, MF_SEPARATOR,1,nil); AppendMenu(menuListB3, MF_STRING,404,'Check me'); AppendMenu(menuListB3, MF_STRING,405,'Hide this menu'); CheckMenuRadioItem(menuListB3, 0, 1, 1, MF_BYPOSITION); {CheckMenuRadioItem will set a group of Menu items into a Radio Check type where only one of the group is checked with a round Dot. This puts the 0 and 1 position menu items into a Radio Group} CanDrag := True; menuMain := CreateMenu; {the Main Menu is created here} {the TMenuItemInfo record will have all the information that is needed to create a menu item, there are more options availible for a menu item than in AppendMenu} MenuInfo.fMask := MIIM_SUBMENU or MIIM_TYPE; {in the fMask you will need to use the flags to tell the system which properties of the menu to set} MenuInfo.fType := MFT_STRING; MenuInfo.dwTypeData := '&File'; MenuInfo.hSubMenu := menuFile; InsertMenuItem(menuMain,0,True,MenuInfo); MenuInfo.dwTypeData := 'ListBox&1'; MenuInfo.hSubMenu := menuListB1; InsertMenuItem(menuMain,1,True,MenuInfo); MenuInfo.dwTypeData := 'ListBox&2'; MenuInfo.hSubMenu := menuListB2; InsertMenuItem(menuMain,2,True,MenuInfo); MenuInfo.dwTypeData := 'ListBox&3'; MenuInfo.hSubMenu := menuListB3; InsertMenuItem(menuMain,3,True,MenuInfo); SetRect(Rect1,0,0,536,321); if not AdjustWindowRect(Rect1,WS_CAPTION or WS_MINIMIZEBOX or WS_SYSMENU,False) then SetRect(Rect1,0,0,542,347); hForm1 := CreateWindow(wClass.lpszClassName, 'Menus and List Boxes', WS_CAPTION or WS_MINIMIZEBOX or WS_SYSMENU, (GetSystemMetrics(SM_CXSCREEN) div 2)-276, (GetSystemMetrics(SM_CYSCREEN) div 2)-212, Rect1.Right-Rect1.Left, Rect1.Bottom-Rect1.Top, 0, menuMain, // handle to main menu hInstance, nil); {SendMessage(CreateWindow('Static', 'Menus and List Boxes', WS_VISIBLE or WS_CHILD, 6, 3, 300, 20, hForm1, 0, hInstance, nil), WM_SETFONT,GetStockObject(ANSI_VAR_FONT),0);} {I do not use a Static control to display text, I draw the text in the WM_PAINT} {I use CreateWindowEx( ) with WS_EX_CLIENTEDGE to get a 3D look for these listboxes, I use the LBS_HASSTRINGS or LBS_NOTIFY flags for a listbox with Strings and Notifies it's parent with messages} hListBox1 := CreateWindowEx(WS_EX_CLIENTEDGE,'LISTBOX','C:\', WS_VISIBLE or WS_CHILD or LBS_HASSTRINGS or LBS_NOTIFY or WS_VSCROLL, 8,30,150,220,hForm1,101,hInstance,nil); SendMessage(hListBox1,WM_SETFONT,GetStockObject(ANSI_VAR_FONT),0); SendMessage(hListBox1, LB_DIR, DDL_READONLY or DDL_DIRECTORY, Integer(PChar('C:\*.*'))); {I use the old method of sending a message of LB_DIR to get folder items in this hListBox1, it displays them in the old "Short Name" format, this LB_DIR is left over from windows 3 and is not very useful for display in 32 bit long file name applications} PListbox1Proc := Pointer(SetWindowLong(hListBox1, GWL_WNDPROC, Integer(@Listbox1Proc))); {hListBox1 and hListBox3 are Sub-Classed so I can do more with them by processing their messages} hListBox2 := CreateWindowEx(WS_EX_CLIENTEDGE,'LISTBOX',nil, WS_VISIBLE or WS_CHILD or LBS_HASSTRINGS or LBS_NOTIFY or WS_VSCROLL, 168,30,160,220,hForm1,0,hInstance,nil); SendMessage(hListBox2,WM_SETFONT,GetStockObject(ANSI_VAR_FONT),0); hListBox3 := CreateWindowEx(WS_EX_CLIENTEDGE,'LISTBOX','ListBox 3 selected is ', WS_VISIBLE or WS_CHILD or LBS_HASSTRINGS or LBS_NOTIFY or WS_VSCROLL, 338,30,188,220,hForm1,0,hInstance,nil); SendMessage(hListBox3,WM_SETFONT,GetStockObject(ANSI_FIXED_FONT),0); SendMessage(hListBox3, LB_INSERTSTRING, 0, Integer(PChar('List Box 3'))); {use the LB_INSERTSTRING to add text in a ListBox, you need to TypeCast the LParam to a PChar and then to an Integer} PListbox3Proc := Pointer(SetWindowLong(hListBox3, GWL_WNDPROC, Integer(@Listbox1Proc))); {both the hListBox1 and hListBox3 SubClassing Functions are set to Listbox1Proc so that one listbox function can handle both of the list boxes messages} hEdit1 := CreateWindowEx(WS_EX_CLIENTEDGE,'Edit','Wacky', WS_VISIBLE or WS_CHILD or ES_LEFT or ES_AUTOHSCROLL, 16,272,400,21,hForm1,0,hInstance,nil); SendMessage(hEdit1,WM_SETFONT,GetStockObject(ANSI_VAR_FONT),0); if PListbox1Proc = PListbox3Proc then SetWindowText(hEdit1, 'C:\My Documents'); {if you test - if PListbox1Proc = PListbox3Proc - it will be equal, because the system has a single "ListBox" message process, which knows the different settings of different List boxes by the listbox Handle sent to that system message process} DlgChk := True; GetFiles('C:\'); {unlike hListBox1, I use FindFirstFile in GetFiles for hListBox2, to get long file names and a file list that you have more control of which files will be listed} ShowWindow(hForm1, SW_SHOWDEFAULT); {the GetSystemMenu( ) will get the Handle of the window's system menu and you can call menu functions for the System Menu} hSysMenu := GetSystemMenu(hForm1, False); InsertMenu(hSysMenu ,0,MF_BYPOSITION or MF_STRING,901,'Added Item'); InsertMenu(hSysMenu,4,MF_BYPOSITION or MF_STRING,902,'MOVE WINDOW'); {2 items above are added to the system menu} while GetMessage(MainMsg,0,0,0) do begin TranslateMessage(MainMsg); DispatchMessage(MainMsg); end; DlgEditText := ''; {since the menuListB3 can be removed, be sure to Destroy it. Once a sub-menu is removed it has no Owner to automaticly destroy it} DestroyMenu(menuListB3); end. |
You will need to use and experiment with the menu and list box creation functions to get to know what properties will be displayed. Using the menus and list boxes will take more experience to find out how the windows OS is set up for menu and list box functions and messages. I have covered many of the creation options for list boxes and menus, but I did not show any Owner Draw menus or list boxes. See if you can add the MF_OWNERDRAW flag to a menu and get the WM_MEASUREITEM and WM_DRAWITEM to draw the item yourself. |
Next
The following page shows you how to divide your code into Units and use Combo Boxes.
11. InUnits and Combo Boxes