Site hosted by Angelfire.com: Build your free website today!

Home
DelphiZeus
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 result
The 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.
MF_BITMAPUses a bitmap as the menu item. The lpNewItem parameter contains the handle to the bitmap to be displayed as the menu item.
MF_CHECKEDPlaces a check mark next to the menu item. This flag displays the check mark bitmap next to the menu item.
MF_UNCHECKEDDoes not place a check mark next to the item (default). This flag will display the unchecked bitmap (if one is specified, the default is to display nothing) next to the menu item.
MF_DISABLEDDisables the menu item so it cannot be selected, but the flag does not gray it.
MF_ENABLEDEnables the menu item so it can be selected, (default) and restores it from its grayed state.
MF_GRAYEDDisables the menu item so it cannot be selected, and grays it to give it the standard "Disabled" look.
MF_MENUBREAKPlaces the item on a new line (for a menu bar) or in a new column (for a drop-down menu, submenu, or shortcut menu) without separating columns.
MF_OWNERDRAWSpecifies that the item is an owner-drawn item. Before the menu is displayed for the first time, the window that owns the menu receives a WM_MEASUREITEM message to retrieve the width and height of the menu item. The WM_DRAWITEM message is then sent to the window procedure of the owner window whenever the appearance of the menu item must be updated.
MF_POPUPSpecifies that the menu item opens a drop-down menu or submenu. The uIDNewItem parameter specifies the handle to the drop-down menu or submenu. This flag is used to add a menu name to a menu bar, or a menu item that opens a submenu to a drop-down menu, submenu, or shortcut menu.
MF_SEPARATORDraws a horizontal dividing line. This flag is used only in a drop-down menu, submenu, or shortcut menu. The line cannot be grayed, disabled, or highlighted. The lpNewItem and uIDNewItem parameters are ignored.
MF_STRINGSpecifies that the menu item is a text string; the lpNewItem parameter points to the PChar.

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 result
It 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 result
The 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 = MENUITEMINFOA
Please 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 result
an 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.
  1. function   CheckMenuItem (hMenu: HMENU; uIDCheckItem, uCheck: Cardinal): Cardinal;
    - - checks and unchecks a menu item in hMenu
  2. function   EnableMenuItem (hMenu: HMENU; uIDEnableItem, uEnable: Cardinal): Boolean;
    - - enables, grays, and disables a menu item
  3. function   GetMenuState (hMenu: HMENU; uId, uFlags: Cardinal): Cardinal
    - - get's the state of a menu item, if it is checked, grayed, enabled
  4. function   GetMenuItemCount (hMenu: HMENU): Integer;
    - - gets the number of items in a menu
  5. function   GetMenuString (hMenu: HMENU; uIDItem: Cardinal; lpString: PChar; nMaxCount: Integer; uFlag: Cardinal): Integer;
    - - gets the PChar string for a menu item
  6. function   CheckMenuRadioItem hMenu: HMENU; First, Last, Check, Flags: Cardinal): Boolean;
    - - checks a menu item group as a radio button
  7. function   GetMenuItemID (hMenu: HMENU; nPos: Integer): Cardinal;
    - - gets the ID number of a menu item
GetMenuItemInfo and SetMenuItemInfo
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 result
This 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.




Menu an Listbox program


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
       

Lesson -     One  Two  Three  Four  Five  Six  Seven  Eight  Nine  Ten  Eleven  Twelve  Thirteen  Fourteen




H O M E