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

Home
DelphiZeus
11. Put Code In Units, ComboBoxes
Making your code more managible

Home



Using Delphi Units

So far all of the Programs in these lessons have been coded entirely in the "Program" .DPR file, but to make your code more Organized and accessable, you may want to use the standard Unit .PAS file as your code containers. You should already be familar with using Units, as Delphi puts it's Forms in a separate Unit, one for each new form. I have introduced a Unit for use in these Lessons called SmallUtils.pas, which only has some Utility functions in it. But if you are creating an API program that has enough code in it to do something useful, your .DPR file will grow to 50 or 100 Kilobytes or more, , so it is helpful to divide up your code into Units.
Combo Boxes

A Combo Box can be a very useful way to present the user with a list of items to choose from, and it's display will be more compact than a List box. You can also have it so the user can change the selection by typing into it's Edit box. A Combo box gets it's name because it is a "Combination" of an Edit, a List Box and a Button. This combination of controls requires you to use API methods you have seen used for Listboxes, Edits and Buttons. A Combo Box can be Owner Drawn, giving you control of what the Items in the list box and edit look like. There is code here for an Owner Drawn Combo, that will place a small Icon on each list item. The methods for list item drawing can also be used in owner drawn List Boxws and Menus.



I will create Three Units in this lesson as code containers, and try and divide up the code and place it in a unit to organize it. But first I will give some Information about the syntax of the Delphi (Pascal) unit stucture and methods.

Unit Stucture
For review, here is information about a Unit File, which you should already have used. . . Delphi's Pascal language supports separately coded and compiled modules of code called "Units". A Delphi Unit (defined in its own .PAS file) consists of types, constants, variables, functions and procedures. A unit file begins with the word unit followed with unit's name (this is the heading section), which is followed by the interface, implementation, initialization, and finalization sections. The initialization and finalization sections are optional. A unit file stucture looks like this:
unit Unit1;  { Heading }

 
interface  { Public section, available to other units }
 
uses  { List of units used by interface and implementation goes here }
 
  { Interface definitions go here, no function code allowed }
 
implementation  { Private section, not available to other units }
 
uses  { List of units only for implementation goes here }
 
  { Implementation definitions and Function Code go here }
 
initialization
  { Initialization Code is here, optional }
 
finalization
  { Finalization Code is here, optional }
 
end.
The unit must conclude with the word end followed by a period.

      Section Definitions - -

Heading -
The unit Heading specifies the unit’s name. For Delphi applications, the Unit Name (identifier) must match the unit file name. Be sure to match these names, the unit heading of
unit  MainUnit;
would occur in a source file called
MainUnit.pas
and the file containing the compiled unit would be named
MainUnit.DCU
Unit names must be unique within a project, , two units with the Same Name CANNOT be used in a single program. It is Very Helpful if you can name your unit with a descriptive Heading, like CreateFonts or MakeFonts. If you begin to have alot of units in many different projects, you may find it helpful to use longer names like, MakeNormalFonts, or MakeRotatedFonts, or at least include a number identifier, like MakeFonts1, and MakeFonts2.

Interface -
The interface section of a unit begins with the reserved word interface and continues until the reserved word implementation occures. The interface section declares constants, types, variables, procedures, and functions that are available to All other units or programs that use this unit in their Uses clause, making them a "Client" of this unit. The interface is called "Public" because a client can access types, variables, or functions as if they were declared in the client itself. The interface declaration of a procedure or function includes ONLY it's heading (but NO OTHER CODE), the begin and end tags are not allowed in the function definitions here, the procedure or function definition Code is placed in the implementation section below this. So procedure and function declarations in the interface section work like forward declarations, although the forward directive isn’t used. The interface section can include its own Uses clause, which must appear immediately after the word interface.
NOTE - Units CANNOT list each other in their interface uses clauses, you will need to move one of the listings to the implementation Uses Clause of one of the Units. It is even better if you can move BOTH uses listings from the interface sections, to the implementation sections.

Implementation -
The implementation section of a unit begins with the reserved word implementation and continues until the beginning of the initialization section or, if there is no initialization section, until the end of the unit. The implementation is called "Private" because a client CAN NOT access types, variables, or functions that are only declared here. The implementation section must define procedures and functions that are declared in the interface section. Within the implementation section, these procedures and functions may be defined and called in any order. You can omit parameter lists from public procedure and function headings when you define them in the implementation section; but if you include a parameter list, it must match the declaration in the interface section exactly. In addition to definitions of public procedures and functions, the implementation section can declare constants, types (including classes), variables, procedures, and functions that are private to the unit—that is, inaccessible to clients. The implementation section can include its own uses clause, which must appear immediately after the word implementation.
    Note - Units CAN list each other in their implementation uses clauses, without complier errors. It is better for program efficientcy if you can put a Unit in the Implementation section instead of the Interface section.

Initialization -
The initialization section is optional. It begins with the reserved word initialization and continues until the beginning of the finalization section or, if there is no finalization section, until the end of the unit. The initialization section contains statements that are executed, in the order in which they appear, on program start-up. So, for example, if you have defined data structures (Records or memory blocks) that need to be initialized, you can do this in the initialization section. The initialization sections of units used by a client are executed in the order in which the units appear in the client’s uses clause

Finalization -
The finalization section is optional and can appear ONLY in units that have an Initialization section. The finalization section begins with the reserved word finalization and continues until the end of the unit. It contains statements that are executed when the main program terminates. Use the finalization section to insure that things allocated in the unit are freed and released when the program ends. Finalization sections are executed in the opposite order from initializations. For example, if your application initializes units A, B, and C, in that order, it will finalize them in the order C, B, and A. Once a unit’s initialization code starts to execute, the corresponding finalization section is guaranteed to execute when the application shuts down. The finalization section must therefore be able to handle incompletely initialized data, since, if a runtime error occurs, the initialization code might not execute completely.

Delphi's compiler will produce an intermediate compiled output file on disk from each unit's source code. The compiled version of each Unit is stored in a separate binary-format file with the same name as the Unit .PAS file, but with the extension .DCU (Delphi compiled unit). Since this .DCU file contains already compiled code, it enables faster compiling and linking when the program is made.
You should be used to coding in a unit if you have used the default Delphi program creation. But you may have missed some of the factors that units use for program creation, like the public and private nature of the Interface and Implementation sections. The Delphi compilier will NOT place any unused code from a unit (or DPR) in the programs executable file. You can look at the windows.dcu in the Delphi Lib folder, and see that it is more than 500 Kb in size, but if you place windows in your uses clause, you app will NOT automaticaly be over 500 kb, in size, since any unused code in the windows unit is Not compiled in to the program. This means that that you can make some Universal units to be used in many programs, and if you use that unit, only the functions used in your program will be placed in the executable. But you should remember that the Initialization section will always be included, so all of the code in the Initialization section will be added to your program. It may be helpful to limit your Initialization code to only what is efficient.

If you look at some of the previous program's code here, like 7-Button and Edit controls you will see that I did not create any other Units for code containers, and the code in that program was getting long. I did not want to code outside of the program file, to show you how you would code only using the DPR. But creating units as code containers, can really help you to organize your code and you may be able to create a unit or two that can do some of the common tasks (font and window creation) that you can use in many of your projects.


Three Units as Code Containers
The program of this lesson is called "In Units" and will create three units, each having sections of code that are related to the Name of the Unit. There will be almost no code in the program file this time, and the primary code for the operation of this program (hForm1) will be in a unit called "InUnitsU" I will use a Naming convention of adding a U , to the end of the name of a Unit. I call this InUnitsU because it is the unit that has the operational code for the InUnits.DPR program file ( add the U to the DPR program name). There will be a Unit called "ApiFormU", which will have code (like the MakeForm function) in it that can be used in many other programs, not just this one. This ApiFormU unit will be a type of "One Size Fits All" unit, that is meant to be used in many programs, like the SmallUtils Unit. . There is a ComboBoxU.pas unit, which will have the code used by the 4 Combo Boxes created in this example. I will place much of the programs operational code in the InUnitsU, like the MakeApp function (used in the .DPR file) and the control creation code (the MakeControls procedure) and the WindowProc (MessageFunc).

I will be using some methods that are similar to what the Delphi Forms Unit does in it's Program file. The InUnits Program file will have two units in it's uses clause, "InUnitsU" and "ApiFormU", , and there will be No functions or procedures defined in the program file this time. There are only 2 lines of code in the program file, the first line calls the MakeApp( ) function in the InUnitsU unit, for window creation in this program. And the next line of code will call the RunMsgLoop procedure in the ApiFormU unit. Let's look at this InUnits program file -

program InUnits;

uses
  InUnitsU, ApiFormU;

{$R *.RES}

begin
if MakeApp then 
  RunMsgLoop; // two lines of code
end.
In the normal Delphi Program file there is a line of code -
    Application.CreateForm(TForm1, Form1);

and this may correspond to the InUnits code -
    if MakeApp then

If the MakeApp function Returns False the RunMsgLoop procrdure will not be called and the program will close. You can see the code for the MakeApp function later. In the code examples before I have a GetMessage( ) loop at the end of the code DPR code, but now I have a RunMsgLoop procedure.

The Delphi Program file has the line of code -
    Application.Run;

and this may correspond to the InUnits code -
    RunMsgLoop

Which will call the GetMessage( ) loop to keep this program running. Please notice that unlike the Delphi program file, I do not use Objects like "Application", although using Objects can be useful, I do not want to do something that will have all of the code in "TObjects", this lesson will mostly be about making Units not making TObjects.


I will have some "Create" functions in the units, where I use the word "Make" in the function name, like "MakeApp", This MakeApp function is called in the Program file to start the window creation functions for this program. This is the MakeApp function in the InUnitsU.pas file. -
function MakeApp: Boolean;
  begin
  Result := False; 
    // Returnig False should prevent the message Loop, RunMsgLoop
  if SetWinClass('Units Class', @MessageFunc) = Zero then Exit;
    {the SetWinClass function simplifies the win Class Register,
     You must have a Class Name and a WndProc memory address.
     if it fails to Register the win Class, it returns Zero}

  hForm1 := MakeForm(DEF, DEF, 546, 351, 'Divide Code Into Units');
  {the MakeForm function will create a Main Form window}
  if hForm1 = Zero then Exit;
  Result := True;
  MakeControls;
    {the MakeControls procedure creates all 
     of the Buttons, Labels and ComboBoxes}
  end;
and for the window that is being created there will be properties in the MakeForm( ) parameters that will have a "Default" value, the coding convention I will use here (used in some of the API functions) is to set that paramter to minus One ( -1 ) to have the Default value used for that parameter. In the MakeForm function -
function MakeForm(Left, Top, Width, Height: Integer; const Caption: String;
                  WinStyle: Integer = DEF): Integer;
var
Rect1: TRect;
begin
Result := Zero;
{this function will check the Atom1 to see if the wClassEx was
registered and then Create the main Form Window with the parameters}
if Atom1 = Zero then
  begin
  SetLastError(13); // The data is invalid.
  ErrorMsgBox(E_MakeForm1, E_TitleMF);
  Exit;
  end;


{I use -1  as the Default Value in this WinStyle parameter}
if WinStyle < Zero then
  WinStyle := WS_CAPTION or WS_MINIMIZEBOX or WS_SYSMENU or WS_CLIPCHILDREN;

SetRect(Rect1, Zero, Zero,Width,Height);
if not AdjustWindowRect(Rect1, WinStyle,False) then
SetRect(Rect1, Zero, Zero, Width + 6, Height+26);

{if Top is -1 (Default) then the form is centered in the screen vertical}
if Top < Zero then
  Top := (GetSystemMetrics(SM_CYSCREEN) shr 1)- ((Rect1.Bottom-Rect1.Top) shr 1);

{if Left is -1 (Default) then the form is centered in the screen horizontal}
if Left < Zero then
  Left := (GetSystemMetrics(SM_CXSCREEN) shr 1)- ((Rect1.Right-Rect1.Left) shr 1);

Result := CreateWindow(wClassEx.lpszClassName, PChar(Caption), WinStyle,
                Left, Top, Rect1.Right-Rect1.Left,
                Rect1.Bottom-Rect1.Top, Zero, Zero, hInstance, nil);
                
if FirstForm = Zero then
  FirstForm := Result;
if Result = Zero then
  ErrorMsgBox(E_MakeForm2, E_TitleMF);
end;

The window creation methods in this MakeForm function have already been covered in previous Lessons of DelphiZeus. What this MakeForm function tries to do is have a more "General" form creation function, that I can use in many API programs, without writing new code. You could copy and paste this MakeForm function into very many projects and use it without needing to change any of the code there. The Top, Left and WinStyle parameters have "Default Values", , which are used if a negative number (-1) is placed in that parameter. This type of "General" or "One Size Fits All" coding can be helpful to speed up your programming development time. Certianly the Delphi code "Unit" is a great way to organize some "General", "Non-Specific", "Use in any Program" type of code, such as this MakeForm function. You should try and develop your own Units with code that you have commonly used in other projects.




Combo Boxes
A Combo Box control gets it's name because it is a "Combination" of controls, an Edit control, a List box control, and with the drop-down styles, a Button control. A Combo Box consists of an Edit control to show it's current selection, a list box control with selections (hidden until needed, if drop-down) and a button control to click to display the hidden list box (if drop down). These Combination controls give the user a list to select from (like a Menu, a group of Radio Buttons or List Box), and the Current Section is shown in the Edit control, where the user can type in their text, to further extend the options for Combo Box selections. This "Three in One" control will require you to use some of the methods you have seen used for Edits, Listboxes and Buttons. A Combo Box is a "Common Control" , Most common controls belong to a Window Class defined in the system's common control library (ComCtl32.DLL). So you will need to call the API fuction   InitCommonControls;   to make sure the ComCtl32.DLL has been loaded into the system, you will need to add "CommCtrl" to your Uses Clause, for that function.

Combo Box Styles - You can use the CreateWindowEx( ) function to create your combo box. In the Style parameter you include the Type of combo box you want. There are three types of combo boxes, "Simple", "Drop Down" and "Drop Down List". One thing to you will need to deal with using either of the "Drop Down" styles, is the Height parameter in CreateWindowEx( ). Unlike controls you have created before, the Height parameter does NOT change the height of the visible Edit control. The Height parameter is for the entire combo box, the edit and the drop-down list box together. The edit control will be auto sized to the font height of the combo box, you will have NO way to change the edit height in the CreateWindow function. When you first try to create a combo box you may set the creation Height to 21, thinking of the size of the edit, and then when you click the drop button, you will NOT see the list box. You will need to set the Height to something larger than the edit, maybe 100, in order to see the list box. The simple combo is an Edit with a List box Always displayed below this edit. If you use a simple combo in a Dialog, it will correctly size and paint it's self. If it is NOT in a dialog it will not size and paint correctly. This simple style is not used anymore, since a Drop Down style will give you better space saving GUI display.
    The three styles are listed below -
Style   TypeDiscription
CBS_SIMPLESimple combo boxThis simple type is "Left Over" from the old 16 bit windows dialog box, it will always display it's list box. I do not ever use this type of combo box. I would use two separate controls instead, an Edit and a List box.
CBS_DROPDOWNLISTDrop Down List BoxThis type will NOT allow the user to type anything into it's edit control. It does have a hidden drop down list box of selections to choose from, but the edit control will not accept keyboard or Paste input.
CBS_DROPDOWNDrop Down Combo BoxThis Type allows the user to type and Paste into the Edit control, this has a drop down list box. Most users are familar with this type because it is the type used in the Font Selection of WordPad and MS Word

By now you should expect me to tell you to read the Win32 API Help for "Combo Boxes". You can find more about the styles in Help's "Combo Box Types and Styles". In this program I create one of each type of combo box, I only do the Simple combo box to show you what it is, this is almost never used any more, AND if used outside of a templete Dialog box, it will not size and paint correctly. A simple combo box when not in a dialog, will resize it's list box to the Items in the list box (standard list box sizing), but it will NOT resize the combo box to the new list box size. So there can be an area below the always visible list box that is never painted.

    Note: In the Drop-Down styles, the dropped List Box has a WS_POPUP style flag, so it can go beyond the borders of it's parent, and even beyond the Main Window (Form) borders. Also, if it is at the bottom of the screen, without room to drop below the edit, it will be displayed Above the edit.


Using Combo Box Messages - The Combo Box messages are like some of the List Box Messages, and like some of the Edit messages. They have a  CB_  prefix for the constant message name. Here is a List of the combo messages used in this program, you may want to look at your Win32 API Help for these -
	CB_ADDSTRING
	CB_DELETESTRING
	CB_FINDSTRING
	CB_GETCOUNT
	CB_GETCURSEL
	CB_GETITEMDATA
	CB_GETITEMHEIGHT
	CB_GETLBTEXT
	CB_INSERTSTRING
	CB_SELECTSTRING
	CB_SETCURSEL
	CB_SETEXTENDEDUI
	CB_SETITEMDATA
	CB_SHOWDROPDOWN
These combo box message constants have names that correspond to what that message will do. The CB_ADDSTRING is just like the List box message LB_ADDSTRING, and so is the CB_GETCURSEL like the LB_GETCURSEL. You can look at the code in the in the InUnitsU.pas unit and the ComboBoxU.pas unit to see how to use these messages in the SendMessage( ) function. Like List Boxes, when you create a combo box it is empty, so you send the CB_ADDSTRING message to add a string to it's list box, but this does not put anything in the combo's edit. You can get a combo list box item into the edit with the CB_SETCURSEL message.

Using Combo Box Notification Messages - And there are Combo Notification messages sent to it's parent window for events in the combo box, you may want to look at your Win32 API Help for these -
	CBN_CLOSEUP
	CBN_DBLCLK
	CBN_DROPDOWN
	CBN_EDITCHANGE
	CBN_EDITUPDATE
	CBN_ERRSPACE
	CBN_KILLFOCUS
	CBN_SELCHANGE
	CBN_SELENDCANCEL
	CBN_SELENDOK
	CBN_SETFOCUS
You have seen the Edits and List boxes send the Notify messages, and the combos are much the same, but have some different messages. The three notify messages I use in this program are CBN_SELENDOK (notifies a user selection change when drop list goes away), the CBN_EDITCHANGE (notifies a type or paste in to the edit), the CBN_DROPDOWN (notifies that the list box is about to be dropped). I use the CBN_SELENDOK instead of the CBN_CLOSEUP or CBN_SELCHANGE message, because it is sent when the selection changes and the drop list box closes. You can look at the code in the in the InUnitsU.pas unit and the ComboBoxU.pas unit to see how to use these notify messages.

Owner Drawn Combo Box

You can create two types of Owner Drawn combo boxes, one that has a Fixed non-changing Item height, and one that has a variable Item height, where all of the Items can be different heights. I create the hComboODraw combo box with the CBS_OWNERDRAWFIXED style flag, which will make a fixed height owner drawn combo box. In the Button Edit program, there was an Owner Drawn Button, buttons have only a single thing (Item) to draw, in Owner Drawn List Boxes, Menus, and Combo Boxes, there are lists of Items to paint, each with different text, check boxes or icons. So you will need to test for the Index of the Item and draw what is needed for that Item. When you create an owner drawn combo box, you can include the CBS_HASSTRINGS style, as I did for the code here, or leave it out. If you do NOT include the CBS_HASSTRINGS in the owner draw combo, then the system will NOT accept any item text (string) data from the CB_ADDSTRING message, and will not give any string data in the CB_GETLBTEXT message request. You will need to use your own text data storage (array of string, the Item DATA) for the Item text. In all of the "List" owner drawn controls, there is the Fixed and Variable height options. The WM_MEASUREITEM message will be sent to the control's parent so you can set the Item height. If it is a Fixed Height Item List, the WM_MEASUREITEM is sent ONLY ONCE, when the control is first created, before there are any Items added to it. You will need to set the itemHeight member of the TMeasureItemStruct pointed to by the LParam of the WM_MEASUREITEM message. This TMeasureItemStruct record has the following structure -

  type
    PMeasureItemStruct = ^TMeasureItemStruct;
    tagMEASUREITEMSTRUCT = packed record
      CtlType: UINT; // type of control, ODT_COMBOBOX in this case
      CtlID: UINT; // control ID number
      itemID: UINT; // Item Index number, not used for Fixed Height
      itemWidth: UINT; // not used in ComboBoxes, only menus
      itemHeight: UINT; // you MUST set this for the height
      itemData: DWORD; // not used in Fixed height
    end;
    TMeasureItemStruct = tagMEASUREITEMSTRUCT;
    MEASUREITEMSTRUCT = tagMEASUREITEMSTRUCT;
You can test the CtlType or the CtlID members if you have more than one Owner Draw control to see which control is being painted. The WM_MEASUREITEM is only sent Once by a Fixed height control, so you will not use the itemID or the itemData members. And you MUST set the itemHeight member when this message is sent. If it is a variable height owner draw control, then the WM_MEASUREITEM will be sent each time the list is painted. And then you will need to test the itemID member for the index number of which Item to Size. The itemWidth member is only used in menus.

You can look at the code in the InUnitsU.pas for the MessageFunc and the WM_MEASUREITEM message, it calls the MeasureCombo procedure in the ComboBoxU.pas, and since there is only one Owner Draw control, the MeasureCombo procedure does not test the CtlID member, and just sets the itemHeight member to 18.

Painting this Owner Drawn ComboBox
The Owner Draw Combo Box in this program will have a list of File Names, and for file name lists, it is common to draw an Icon with the file name. So I draw a small icon in front of each file name, and for any WAV file I will draw a different color background, to HighLight it. In order to draw the small icon associated with the file extention, I will need to get the small icon's handle for that file extention. There are several ways to get these associated icons, the system small icon image list is a good way, but I do not want to deal with Image Lists now. There is a procedure in the ComboBoxU.pas unit called SetItemIcons. This procedure is called right after the creation of the hComboODraw combo box is created, and the Items are in it. In this procedure SendMessage is used with the CB_GETCOUNT , to get the number of Items in the combo box. A   for   loop is used to get the text (a file name) of each Item. To get the small icon handle, I will use the system Shell function SHGetFileInfo( ) which is a very usefull function to get all kinds of Shell information about a file. You should look at the Win32 API Help for index "SHGetFileInfo" to see some of the shell file infomation you can get with it. Here I use it with the SHGFI_ICON or SHGFI_SMALLICON or SHGFI_USEFILEATTRIBUTES flags set. The SHGFI_ICON flag, tells it you want icon information, the SHGFI_SMALLICON flag, tells it you want the Small Icon, handle for the shell icon for that file. The SHGFI_USEFILEATTRIBUTES flag tells it to use the file name's file extention to get the Icon, not the whole file name, because the files listed do not exist. The SHGetFileInfo function will fill a TSHFILEINFO Record, with info requested by the flags set. The hIcon member of the TSHFILEINFO Record will have the small icon handle I need. I coud keep an array of these icon handles, but each Item in this combo box has a 4 byte (Cardinal) DATA assoicated with it, this item DATA is there for the programmer to use to store info about each item. I use the SendMessage function with the CB_SETITEMDATA message to place this small Icon handle as the DATA for each combo box Item.

As with all Owner Drawn controls, painting is done in the WM_DRAWITEM message, like the owner drawn button I did in the Button Edit program, you will need to use the TDrawItemStruct in the LParam pointer of the WM_DRAWITEM message. Only this time with a combo box you will need to get the itemID index so you can paint the text and Icon for that list Item. If you look at the WM_DRAWITEM message in the MessageFunc of the InUnitsU.pas file it calls the DrawComboBox( ) procedure in the ComboBoxU.pas file. I use the PDrawItemStruct from the LParam of the WM_DRAWITEM message as the parameter in the DrawComboBox( ) procedure. You might review the members of this PDrawItemStruct. You should look at this DrawComboBox( ) procedure, as with the Owner Drawn Buttons, you will need to test for the ODS_SELECTED in the pDrawItem.itemState, but this time ODS_SELECTED means that the List Item is selected. I need the text to draw for each list Item, which I get using the CB_GETLBTEXT message with the pDrawItem.itemID for the Item Index. I create a color brush and use the FillRect( ) function to paint the background color for each item, using the pDrawItem.rcItem TRect, which will have the rectangle used for each different Item. I test for the ODS_DISABLED state in the pDrawItem.itemState (item is disabled), and set the background brush color to a Button face color. I use the DrawIconEx( ) function to draw the small Icon on the left of the Item. The handle for the small Icon was placed in the Item DATA by the SetItemIcons procedure. I use the CB_GETITEMDATA message to get this small Icon handle in the DATA for each Item..





Program In Units



This "In Units" program a program file called InUnits.DPR and has the following code -

program InUnits;

uses
  InUnitsU, ApiFormU;

{$R *.RES}

begin
if MakeApp then // MakeApp in InUnitsU
  RunMsgLoop; // RunMsgLoop in ApiFormU
end.

There are two functions called in this program file, these functions are in the InUnitsU and ApiFormU Units. I have explained this program file above in "Three Units as Code Containers" section.

This program has three units, ApiFormU, InUnitsU, and ComboBoxU. The first Unit I will present code for is the ApiFormU Unit, which is a Unit that is meant to be used in other projects, so it has "One Size Fits All" code methods.

Code for ApiFormU.pas
The code in this unit is intended to be used in other projects that need an API main window (Form) and font creation. Breaking with this universal unit theme, I have code in this for some simple non-windowed "Graphic-Control" controls like the Delphi "TLabels". These Labels are not "Independent" of the code needed for this InUnit program, since you will need to change the TLabelNames Enumerated type for whatever number and Label Names your current project needs. You could move this TLabelNames type out of this unit and into a project specific unit like InUnitsU, but then you would need to put the InUnitsU in the uses clause of this unit, canceling the "Universal, OneSize Fits All" unit theme. To make this a good "Universal" and "One Size Fits All" unit, you should move all of the code for Label out of this unit and into it's own separate LabelU unit. You would also need to use another method to replace the TLabelNames, Enumerated type, so you would not need to change the code in the Labels unit for each project it was used in.

There are five functions here meant to be used in programs needing a main window, fonts and buttons, they are -
SetWinClass( ), MakeForm( ), RunMsgLoop( ), MakeFont( ), and MakeButton( ).
You can look at the code below to see that they use code methods covered here before. In these Make functions, I have included a "Default" style or parameter so you can use these and write less code. Here is some example code, that makes a Font and then creates the main-Window (Form) with one button (Exit) -

hFont := MakeFont(-11, 0, 'Arial');
hForm1 := MakeForm(DEF, DEF, 321, 234, 'Main Window');
MakeButton(230, 204, 88, 40, 'EXIT', hForm1, ID_ExitBut, hFont);
The 2 DEF (default) parameters in the MakeForm function will center the form on the monitor Screen

LABELS, a graphic control

There are three procedures for the "Label",
SetLabel( ), SetLabelCaption( ), and DrawLabels( ).
These Labels do not create a "Static" window control, and only have a TLabelRec record to store data used to draw the text for each label on the main form's DC in the WM_PAINT message. The SetLabel( ) procedure will fill a TLabelRec in the AryLabel array with all of the data needed for that TLabelRec. Since the AryLabel (Array of TLabelRec) is in the public interface section, you change a Label's properties, by accessing the LabelName index of the AryLabel array and changing the Left, Top, or Visible member of that record. These changes will not be apparent until the form is repainted, so you will need to call an API function to repaint it (like InvalidatRect). Since we need these labels to be painted in the WM_PAINT message of the main form, we need a painting procedure for this, called DrawLabels( ). This takes two parameters from the WM_PAINT message BeginPaint TPaintStruct, the hDC and the PaintRect. This procedure will loop through All of the TLabelRec in the AryLabel and set the font for the DC and then use TextOut( ) to draw the Text at the location for the Left and Top members of the TLabelRec record. I included the SetLabelCaption( ) to show you a way to code a change to the TLabelRec record, but this is for you to see that this procedure is NOT nessary, since it offeres little help to you. I did not include any other label property change procedures, since you should should just change the AryLabel TLabelRec member and then call InvalidateRect. See the procedure ChangeLabels; in the InUnitsU unit.
These Labels are a simple way to have text painted on the form in different locations, and be able to change these Labels at run time. It would work just as well to have the code for labels in the InUnintsU unit, instead of this unit. But I wanted you to try and get these Labels to be more independent and have this ApiFormU more universal. What would you need to change in the code for these lables not to have any dependency on what may or may not be in this project? (Hint: take out the TLabelNames, Enumerated type, and make the AryLabel, a Dynamic Array)

see comments in code for more info

unit ApiFormU;
{this ApiFormU unit, is an example for a One Size Fits All unit, that
can be used in many other programs, that need an API Main Form creation,
a GetMessage Loop procedure, a simple MakeFont function and a Label}

interface

uses    // I have NO Units from this Program in the Uses clause
  Windows;
  {I want to be able to use this Unit in other programs, so I
   want to have only the Windows, Messages, and SmallUtils units used}

const
Zero = 0; // used so much, I made it a const
DEF = -1; // This is my DEFAULT value for Make functions

type
{I will draw text on this hForm1 with a Record's information and call
it a Label. In the WM_PAINT message the PaintLabels procedure
will run through an array of TLabelRec and draw the Text for each Record.
This TLabelRec is used to store all of the info needed to draw a Label}
  TLabelRec = Record
    Left, Top: Integer; // Position to draw text
    TextColor: Cardinal; // label text color
    Visible: Boolean; // Will only draw if true
    FontHnd: Integer; // font used to draw text
    Text: String; // text to draw on Label
    end;


  TLabelNames = (Label1, Combo2Sel, Combo3Text, Label4, WavFile, BigLabel);
 {this Enumerated type TLabelNames has 6  values, a name value for each
 of the 6 Labels used in this program, Label Names used in the  AryLabel array}


var
AryLabel: Array[TLabelNames] of TLabelRec;
{the AryLabel is an array of TLabelRec with TLabelNames number of
elements, that is used in the PaintLabels procedure, a for loop goes
through the array and draws the Text for each Label. This record array
is a simple way to have "Graphic Controls" that are just drawn on
the main form's DC, and can be changed at run time}



function SetWinClass(ClassName: String; pMessFunc: Pointer;
                  wcStyle: Integer = CS_PARENTDC or CS_BYTEALIGNCLIENT): Word;
  {the SetWinClass function will get the ClassName and Message Function
   address, so it can register a new Window System Class for the Form here.
   This function MUST be called BEFORE the MakeForm function}

function MakeForm(Left, Top, Width, Height: Integer; Caption: String;
                  WinStyle: Integer = DEF): Integer;
  {the MakeForm function will create the main window (form) using
   the class in wClassEx, this wClassEx is filled and registered in
   the SetWinClass procedure}

procedure RunMsgLoop(Show: Boolean = True);
  {the RunMsgLoop procedure will start the GetMessage loop
   to keep this program running}

function MakeFont(Height, Width: Integer; FontName: String;
                 Bold: Boolean = False; Roman: Boolean = False): Integer;
  {the MakeFont function will simplify Font creation, however you will
   loose the ability for many font create options}

function MakeButton(Left, Top, Width, Height: Integer; pCaption: PChar;
               hParent, ID_Number: Cardinal; hFont: Integer = DEF): Integer;
  {the MakeButton function will simplify Button creation, there are NO
   style parameters and there is a hFont parameter, to set the font 
   for the button. This only makes a Push Button with a Tab Stop}

procedure SetLabel(LabelName: TLabelNames; Left, Top: Integer; Text: String;
                   Color1: Cardinal = Zero; hFont: Integer = Zero);
  {the SetLabel procedure takes a TLabelNames and sets the AryLabel array
   for that Label with the parameters of this function}

procedure SetLabelCaption(LabelName: TLabelNames; Caption: String);
  {the SetLabelCaption procedure is used in the ComboBoxU unit, to change
   the Caption of a single Label}

procedure DrawLabels(hDC: Integer; PaintRect: TRect);
  { DrawLabels is called in the WM_PAINT message of the main Forms
    MessageFunc in the InUnitsU  unit}


implementation

uses
  Messages, SmallUtils;
  { just the Messages and SmallUtils units so
    I can use the unit in other programs }


const
{I have added these Error Message Text constants for text
 to show if a function fails}
E_WinClas1: PChar = 'ERROR - in SetWinClass - Class Name or pMessProc parameter Incorrect';
E_WinClas2: PChar = 'ERROR - in SetWinClass - RegisterClassEx - FAILED';
E_MakeForm1: PChar = 'ERROR - in MakeForm - wClassEx is NOT registered';
E_MakeForm2: PChar = 'ERROR - in MakeForm - CreateWindow - FAILED';
E_MakeBut: PChar = 'ERROR - in MakeEZButton - CreateWindow - FAILED';

E_TitleWinClas: PChar = 'SetWinClass  function  ERROR';
E_TitleMF: PChar = 'MakeForm  function  ERROR';
E_TitleButton: PChar = 'MakeButton  function  ERROR';


var
wClassEx: TWndClassEx;
Atom1: Word = Zero;
FirstForm: Integer = Zero;


procedure ErrorMsgBox(pText, pTitle: PChar);
begin
{this procedure will get the Text for the Last window's Error and
 add it to the pText, then display an Error Message Box}
MessageBox(Zero, PChar(pText+#10+SysErrorMessage(GetLastError)),
           pTitle, MB_ICONERROR);
end;

procedure SetLabelCaption(LabelName: TLabelNames; Caption: String);
begin
{this procedure will set the text for a single label in the AryLabel
 and call for the whole hForm1 window to be Invalidated. This is
 ineficient, since you do not need to invalidate the entire window,
 but I do not keep a width and height in the TLabelRec,
 so I just refresh the whole window}
AryLabel[LabelName].Text := Caption;
InvalidateRect(FirstForm, nil, True);
end;

procedure SetLabel(LabelName: TLabelNames; Left, Top: Integer; Text: String;
                   Color1: Cardinal = Zero; hFont: Integer = Zero);
begin
{this procedure is used to initialize the AryLabel in the
 MakeControls procedure, and will use a LabelName (Index number)
 and put Values into the AryLabel[Index] TLabelRec record}
AryLabel[LabelName].Left := Left;
AryLabel[LabelName].Top := Top;
AryLabel[LabelName].TextColor := Color1;
AryLabel[LabelName].Visible := True;
AryLabel[LabelName].FontHnd := hFont;
AryLabel[LabelName].Text := Text;
end;


procedure DrawLabels(hDC: Integer; PaintRect: TRect);
var
LN: TLabelNames;
begin
{this procedure is called in the Main Form's WM_PAINT message
 and will draw all of the Labels that are in the AryLabel array}
SelectObject(hDC, GetStockObject(ANSI_VAR_FONT));
SetBkMode(hDC, TRANSPARENT);
{I set the font to ANSI_VAR_FONT and have arranged the labels in the array
 so that all labels that use ANSI_VAR_FONT are at the begining of the array
 and have a FontHnd  of Zero}
for LN := Low(AryLabel) to High(AryLabel) do
  begin
  if (not AryLabel[LN].Visible) or // will skip drawing if Visible false
     (PaintRect.Bottom < AryLabel[LN].Top) or
  {if the PaintRect rectangle does not include label then skip draw}
     (PaintRect.Right < AryLabel[LN].Left) or
     (AryLabel[LN].Text = '') then Continue;
  {if there is no Text to draw then skip draw}

  if AryLabel[LN].FontHnd > Zero then // only set the font if above Zero
    SelectObject(hDC, AryLabel[LN].FontHnd);
  SetTextColor(hDC, AryLabel[LN].TextColor);
  TextOut(hDC, AryLabel[LN].Left, AryLabel[LN].Top,
          PChar(AryLabel[LN].Text), Length(AryLabel[LN].Text));
  {this TextOut( ) function is a simple way to get the text on the form}
  end;
end;

function MakeFont(Height, Width: Integer; FontName: String;
                 Bold: Boolean = False; Roman: Boolean = False): Integer;
var
FontLog1: TLogFont;
begin
{this function simplifies Font Creation, you can
 create a font with as few as 3 parameters}
ZeroMemory(@FontLog1, SizeOf(FontLog1));
with FontLog1 do
  begin
  lfHeight := Height;
  lfWidth := Width;
  if Bold then
    lfWeight := 700;
  lfCharSet := DEFAULT_CHARSET;
  lfOutPrecision := OUT_TT_PRECIS;
  if Roman then
    lfPitchAndFamily := VARIABLE_PITCH or FF_ROMAN
    else
    lfPitchAndFamily := VARIABLE_PITCH or FF_SWISS;
  StrLCopy(@lfFaceName[Zero], PChar(FontName), 31);
  end;

Result := CreateFontIndirect(FontLog1);
end;


function MakeButton(Left, Top, Width, Height: Integer; pCaption: PChar;
               hParent, ID_Number: Cardinal; hFont: Integer = DEF): Integer;
begin
{this function simplifies button creation by including a Font
 parameter, which is used to set the button's font}
case hFont of
  Zero: hFont := GetStockObject(SYSTEM_FONT);
  {zero or any number that is not -1, -2, or a system font handle,
   will get the standard System Font for the button}
  DEF: hFont := GetStockObject(ANSI_VAR_FONT);
  -2: hFont := GetStockObject(ANSI_FIXED_FONT);
  { -1 and -2 will get Var and Fixed Stock fonts}
  end;

Result := CreateWindow('Button', pCaption, WS_VISIBLE or WS_CHILD
                         or BS_PUSHBUTTON or BS_TEXT or WS_TABSTOP, Left, Top,
                         Width, Height, hParent, ID_Number, hInstance, nil);
   {there is NO way to adjust the style, so this only makes a BS_PUSHBUTTON}
if Result = Zero then
  begin
  ErrorMsgBox(E_MakeBut, E_TitleButton);
  Exit;
  end;
SendMessage(Result,  WM_SETFONT, hFont, Zero);
end;


function SetWinClass(ClassName: String; pMessFunc: Pointer;
                  wcStyle: Integer = CS_PARENTDC or CS_BYTEALIGNCLIENT): Word;
begin
{this function will set the wClassEx record and regsiter a new Class.
 You MUST call this function BEFORE you can call the MakeForm function}
Result := Zero;
{test the parameters}
if (Length(ClassName) < 2) or (pMessFunc = nil) then
  begin
  SetLastError(13); // The data is invalid.
  ErrorMsgBox(E_WinClas1, E_TitleWinClas);
{the ErrorMsgBox procedure takes the string constants
 and shows an Error Message Box}
  Exit;
  end;

{I have 3 wcStyle preSets, , a CS_PARENTDC or CS_BYTEALIGNCLIENT if No
 parameter is set and a -1  and  -2  alternates}
if wcStyle = DEF then
  wcStyle := CS_PARENTDC
  else
  if wcStyle = -2 then
  wcStyle := CS_PARENTDC or CS_HREDRAW or CS_VREDRAW or CS_BYTEALIGNCLIENT
  else
  if wcStyle < -2 then
  wcStyle := Zero;

{the wClassEx was Filled with Zeros in the initialization}
with wClassEx do
  begin
  cbSize := SizeOf(wClassEx);
  Style := wcStyle;
  hInstance := SysInit.hInstance;
  hIcon := LoadIcon(hInstance,'MAINICON');
  lpfnWndProc := pMessFunc;
  hbrBackground := COLOR_BTNFACE+1;
  GetMem(lpszClassName, Length(ClassName)+1);
  StrCopy(lpszClassName, PChar(ClassName));
  hCursor := LoadCursor(Zero, IDC_ARROW);
  end;

Atom1 := RegisterClassEx(wClassEx); // Zero in Atom1 means Failure

if Atom1 = Zero then
  begin
  ErrorMsgBox(E_WinClas2, E_TitleWinClas);
  Exit;
  end;
Result := Atom1;
end;


function MakeForm(Left, Top, Width, Height: Integer; Caption: String;
                  WinStyle: Integer = DEF): Integer;
var
Rect1: TRect;
begin
Result := Zero;
{this function will check the Atom1 to see if the wClassEx was
registered and then Create the main Form Window with the parameters}
if Atom1 = Zero then
  begin
  SetLastError(13); // The data is invalid.
  ErrorMsgBox(E_MakeForm1, E_TitleMF);
  Exit;
  end;


{I use -1  as the Default Value in this WinStyle parameter}
if WinStyle < Zero then
  WinStyle := WS_CAPTION or WS_MINIMIZEBOX or WS_SYSMENU or WS_CLIPCHILDREN;

SetRect(Rect1, Zero, Zero,Width,Height);
if not AdjustWindowRect(Rect1, WinStyle,False) then
SetRect(Rect1, Zero, Zero, Width + 6, Height+26);

{if Top is -1 (Default) then the form is centered in the screen vertical}
if Top < Zero then
  Top := (GetSystemMetrics(SM_CYSCREEN) shr 1)- ((Rect1.Bottom-Rect1.Top) shr 1);

{if Left is -1 (Default) then the form is centered in the screen horizontal}
if Left < Zero then
  Left := (GetSystemMetrics(SM_CXSCREEN) shr 1)- ((Rect1.Right-Rect1.Left) shr 1);

Result := CreateWindow(wClassEx.lpszClassName, PChar(Caption), WinStyle,
                Left, Top, Rect1.Right-Rect1.Left,
                Rect1.Bottom-Rect1.Top, Zero, Zero, hInstance, nil);
                
if FirstForm = Zero then
  FirstForm := Result;
if Result = Zero then
  ErrorMsgBox(E_MakeForm2, E_TitleMF);
end;



procedure RunMsgLoop(Show: Boolean = True);
var
MainMsg: TMSG;
begin
if FirstForm = Zero then Exit;
{this RunMsgLoop procedure will run the GetMessage Loop to keep
this program running, I have included a Show parameter, even though
it is not used in this program}
if Show then
ShowWindow(FirstForm, SW_SHOWDEFAULT);

while GetMessage(MainMsg,Zero,Zero,Zero) do
  begin
  if not IsDialogMessage(FirstForm, MainMsg) then
    begin
    TranslateMessage(MainMsg);
    DispatchMessage(MainMsg);
    end;
  end;
end;


initialization
{I will fill the wClassEx and AryLabel with Zeros}
ZeroMemory(@wClassEx, SizeOf(wClassEx));
ZeroMemory(@AryLabel, SizeOf(AryLabel));

finalization
if wClassEx.lpszClassName <> nil then
  FreeMem(wClassEx.lpszClassName);


end.

I have a single windowed control creation function in this unit, MakeButton( ), which makes a Button, but it does not have many creation options. If you include more creation options, then the code size for that creation function will increase, so if you try and place ALL of the posible creation options, there will be much code in the function that is not used, unless you call for each availible option. Since I know the API code to change the style flags in the CreateWindow( ) function, I can make a simple MakeButton function, and if I need more options, I can just use the CreateWindow function.

Code for InUnitsU.pas
This Unit has the operational code for this program, there is only one public interface function, MakeApp in this unit. This is called in the .DPR file to start the window and font creations for this program. The code used for creation of the main Window (Form) in now in the ApiFormU.pas unit. The procedure MakeControls is where the fonts and controls are created and initilized for this program. First, there are 2 fonts created and then four Buttons are created, I create the Buttons with the MakeButton function, in the ApiFormU unit. Next the Combo Boxs are created by calling the MakeCombo function in the ComboBoxU.pas unit. Next I make 6 "Labels", by setting the the data in the AryLabel with the SetLabel procedure in the ApiFormU unit.
The Window Proc (MessageFunc) for this program in in this unit, the methods used in this MessageFunc function are like some you have seen before in previous lessons, except there is the addition of the Combo Box Messages. In the ChangeLabels procedure, there is code that will set the members of the TLabelRec record, in the AryLabel array in order to change the way the labels are painted. The ComboODrawMsg( ) procedure is like the IsComboMsg( ) function in the ComboBoxU unit, and will process the CBN_SELENDOK combo message, and display the WavFile Label if the file extention is .wav. If you look in the constants you will see the RectInv const, which is a TRect, you can declare a Record const, by giving all of the record members a value. This RectInv is used in the
InvalidateRect(hForm1, @RectInv, True);
to invalidate just the Form's area that has the WavFile Label in it. You should notice that I have moved the two DeleteObject( ) functions for the two fonts created, from the WM_DESTROY message to the finalization section of this unit. That way the fonts are deleted even if the WM_DESTROY is not processed.

see comments in code for more info

unit InUnitsU;
{this unit has the code required to set up and run this program
 this is the "Main Unit" for this application, with a single public
 function called MakeApp . This function with code to register the
 windows class, to Create the main Window and controls needed}

interface

var
hForm1: Integer = 0; // handle of Main Window (Form)
FontCombo: Integer = 0;
txBuffer: Array[0..63] of Char;

function MakeApp: Boolean;
{the MakeApp function will call some functions to create the windows
and controls for this Application.
If there is a creation error, it returns False}



implementation

uses
  Windows, Messages, CommCtrl, ComboBoxU, ApiFormU, SmallUtils;

const
{I use ID number constants for the Four buttons, instead of handles}
ID_ExitBut = 1000;
ID_LChangeBut = 1001;
ID_MovWinBut = 1002;
ID_DropBut = 1003;
{the  RectInv  TRect is the rectangle constant for the
 rectangle used for the WavFileLabel Label Invalidate}
RectInv: TRect = (left: 50; Top: 287; Right: 230; Bottom: 307);
{you can declare Record constants by giving ALL of it's members values}


var
FontLarge: Integer = Zero;

procedure ChangeLabels;
begin
{this procedure will change and move several Labels
 just by changing the values in the Records in AryLabel}
if AryLabel[Label1].Visible then
  begin
  AryLabel[BigLabel].Left := 190;
  AryLabel[BigLabel].Top := 100;
  AryLabel[BigLabel].TextColor := $FF00FF;
  AryLabel[Label4].Text := 'This is new text for Label Four';
  end else
  begin
  AryLabel[BigLabel].Left := 290;
  AryLabel[BigLabel].Top := 250;
  AryLabel[BigLabel].TextColor := $22A7FF;
  AryLabel[Label4].Text := 'Other words in Label 4';
  end;
  
AryLabel[Label1].Visible := not AryLabel[Label1].Visible;
InvalidateRect(hForm1, nil, True);
// IvalidateRect will redraw all of the Form's DC and the labels
end;


procedure ComboODrawMsg(wParam1, LParam1: Integer);
var
doInval: Boolean;
begin
{I have placed procedures in both this InUnitsU and the ComboBoxU unit
 for combo box message handling, depending on your Code organization,
 you can place the code for a control's message handling in that control's
 unit or in this unit. See the IsComboMsg( ) function in the ComboBoxU unit}
if HIWORD(wParam1) = CBN_SELENDOK then
  begin
{combo boxs use Messages, like List Boxes do, to get selection and
 Item's Text, the CB_GETCURSEL get's the selection, and
 CB_GETLBTEXT will get an Item's text}
  if SendMessage(LParam1, CB_GETLBTEXT, SendMessage(LParam1,
                   CB_GETCURSEL, Zero, Zero), Integer(@txBuffer)) <> CB_ERR then
    begin
    doInval := AryLabel[WavFile].Visible;
{if the user picks a WAV file in this combo, then I will show the WavFile Label}
    if GetFileExt(txBuffer) = '.wav' then // test the text file extention
      AryLabel[WavFile].Visible := True // if Wav show Label lnWavFile
      else
      AryLabel[WavFile].Visible := false;
    if doInval <> AryLabel[WavFile].Visible then
      InvalidateRect(hForm1, @RectInv, True);
      {test the doInval with AryLabel[lnWavFile].Visible to
       see if you need to Invalidate}
    end;
  end;
end;


{as in Previous Lessons, messages are handled in this MessageFunc.
 There are no new methods here, except that functions from other
 Units are called.}
function MessageFunc(hWnd,Msg,wParam,lParam:Integer):Integer; stdcall;
var
PaintS: TPaintStruct;
Rect1, workRect: TRect;
begin
{some of these messages will call functions or procedures in other Units}
case Msg of
  WM_PAINT:
    begin
    BeginPaint(hWnd, PaintS);
    TextOut(PaintS.hDC, 4,120, 'Label0  with text defaults' , 26);
    DrawLabels(PaintS.hDC, PaintS.rcPaint); { in ApiFormU unit, DrawLabels
               runs a loop through AryLabel to draw all of the Labels}
    EndPaint(hWnd,PaintS);
    Result := Zero;
    Exit;
    end;
  WM_COMMAND: if LOWORD(wParam) = ID_ExitBut then
    PostMessage(hForm1, WM_CLOSE, Zero, Zero)
      else if LOWORD(wParam) = ID_LChangeBut then
        ChangeLabels
      else if LOWORD(wParam) = ID_DropBut then
        begin
        {this Drop Combo button click will show hCombo3 List Box}
 {the CB_SELECTSTRING will search the Combo Items for a match for text}
        SendMessage(hCombo2, CB_SELECTSTRING, Zero, Integer(PChar('MI')));
        SendMessage(hCombo3, CB_SHOWDROPDOWN, 1, Zero);
 {the CB_SHOWDROPDOWN will make the Combo List Box visible}
        EnableWindow(hCombo2, not IsWindowEnabled(hCombo2));
        EnableWindow(hComboODraw, not IsWindowEnabled(hComboODraw));
 {hComboODraw is disabled to show the drawing of disabled Owner Draw}
        SetLabelCaption(Label4, 'Click Drop Combo Button again to Enable hCombo2');
        end
      else if LOWORD(wParam) = ID_MovWinBut then
      begin
 {this Move Window button will put this form at the bottom of the work area
  and drop down the hComboODraw combo box, so you can see that the List Box
  will be ABOVE the Combo Edit and not below it, since there is no room below}
      GetWindowRect(hWnd, Rect1);
      if SystemParametersInfo(SPI_GETWORKAREA, Zero, @workRect,Zero) then
        MoveWindow(hWnd, Rect1.Left, workRect.Bottom - (Rect1.bottom -Rect1.Top),
                   Rect1.right - Rect1.left,Rect1.Bottom - Rect1.Top, True);
      SendMessage(hComboODraw, CB_SHOWDROPDOWN, 1, Zero);
  {the hComboODraw will show it's Drop Down List box when it get's the
   CB_SHOWDROPDOWN message, even if it is Disabled}
   
      end // the IsComboMsg function will test the LParam for a Combo Handle
      else if IsComboMsg(WParam, LParam) then
      if lParam = hComboODraw then
        ComboODrawMsg(wParam, LParam);
{see the IsComboMsg function in the ComboBoxU unit and the ComboODrawMsg above}
  WM_DESTROY: PostQuitMessage(Zero);
        {I have moved the DeleteObject( ) to the finalization clause}
  WM_MEASUREITEM:
    begin
 {Since there is Only one Owner draw control, I do not test the wParam}
    MeasureCombo(PMeasureItemStruct(LParam));
 {the MeasureCombo procedure is in the ComboBoxU unit for the hComboODraw}
    Result := 1;
    Exit; // Do NOT call DefWindowProc
    end;
  WM_DRAWITEM:
    begin
    DrawComboBox(PDrawItemStruct(LParam));
 {the DrawComboBox procedure is in the ComboBoxU unit for the Owner Draw Combo}
    Result := 1;
    Exit;
    end;
  end; // case
Result := DefWindowProc(hWnd,Msg,wParam,lParam);
end;



{the MakeControls procedure has the code to create and set up all of the
   buttons, listboxs, comboBoxes, and labels on this form}
procedure MakeControls;
var
ListStr: String;
ItemHeight: Integer;
begin
{this procedure will create Fonts and the controls on the main form}

FontLarge := MakeFont(-30, 14, 'Comic Sans MS', True);
{MakeFont has only 4 parameters for font creation}
FontCombo := MakeFont(-14, 6, 'Arial');

{the Four Buttons are created below with ID numbers, not handles.
  The MakeButton function is in the ApiFormU unit,
   it creates a Button and sets it's font}
MakeButton(442,292,88,40, 'EXIT', hForm1, ID_ExitBut, FontLarge);

MakeButton(8,146,88,26, 'Change Labels', hForm1, ID_LChangeBut);

MakeButton(8,184,80,26, 'Drop Combo', hForm1, ID_DropBut);

MakeButton(8,228,108,26, 'Move Window', hForm1, ID_MovWinBut, Zero);


InitCommonControls; {InitCommonControls is neccessary if you
                     have any Comon Controls like Combo Boxes}

{ListStr is for ListItems in the combo box, it must have a #255 in front,
 and an extra  #0  at the end, in order to work}
ListStr := #255'Simple Combo'#0'First Item'#0'Another Item'#0'More Here'#0+
           'the list goes on'#0'Five Items'#0'Sixth and last Item'#0;

{the ComboBoxU unit has functions to deal with Combo Boxes
 this MakeComboBox has a ListItems String Parameter, which uses a #0 delimited
 String to place several Items in the Combo Box with just one string}
hCombo1 := MakeComboBox(6,4, 128, 99, ListStr, WS_VISIBLE or WS_CHILD or
         CBS_SIMPLE or WS_CLIPSIBLINGS or WS_TABSTOP or WS_VSCROLL or CBS_SORT);
{just for an Example,  hCombo1  is a Simple Combo, CBS_SIMPLE, which will only
 correctly size and paint itself on a Dialog Box.
 Simple Combos are not used anymore}

{hCombo1 has the CBS_SORT style flag, which will sort all
 of the Items in the List Box alphabeticly}


{hCombo2 is a CBS_DROPDOWNLIST with no Edit Input}
hCombo2 := MakeComboBox(150,26, 132, 108, CB2Items, WS_VISIBLE or WS_CHILD or
               CBS_DROPDOWNLIST or WS_CLIPSIBLINGS or WS_TABSTOP or WS_VSCROLL);
{I have included the WS_VSCROLL style in ALL of these Combo
 Boxes so they will have a Scroll Bar if there are more Items
 in the list box than it can show}
SendMessage(hCombo2,WM_SETFONT,GetStockObject(ANSI_VAR_FONT), Zero);

{Using the CB_SETEXTENDEDUI message will change the Keyboard response for a
 Combo Box. By default, the F4 key opens or closes the combo list and
 the DOWN ARROW changes the current selection. In the extended user interface,
 the F4 key is disabled and the DOWN ARROW key opens the drop-down list}
SendMessage(hCombo2, CB_SETEXTENDEDUI, 1, Zero);


ListStr := #255'C:\Windows\'#0'C:\My Documents\'#0'C:\Some Folder\'#0;

{hCombo3 is a CBS_DROPDOWN and has Edit Input}
hCombo3 := MakeComboBox(300,26, 213, 128, ListStr); // uses Default Style
ItemHeight := SendMessage(hCombo3,CB_GETITEMHEIGHT, Zero, Zero);
SendMessage(hCombo3,CB_ADDSTRING, Zero, Integer(PChar(Int2Str(ItemHeight))));

ListStr := #255'C:\aText.txt'#0'C:\aFont.ttf'#0'C:\aSound.wav'#0'C:\aDoc.doc'+
           #0'C:\aWebPage.htm'#0'C:\aBitmap.bmp'#0'C:\aHelp.hlp'#0'C:\aWav.wav'#0;
   {this ListStr is all file names for the owner drawn}

{the hComboODraw is an Owner Drawn Combo Box with the CBS_OWNERDRAWFIXED style,
 which is Drawn in the DrawComboBox procedure, I draw small Icons with the text}
hComboODraw := MakeComboBox(46,306, 173, 128, ListStr, WS_VISIBLE or WS_CHILD or
                 CBS_DROPDOWNLIST or WS_CLIPSIBLINGS or WS_TABSTOP or WS_VSCROLL or
                 CBS_HASSTRINGS	or CBS_OWNERDRAWFIXED);

ListStr := '';

{I will use small file icons in the owner drawn hComboODraw combo box.
 The SetItemIcons procedure in the ComboBoxU unit finds and sets the Item data
 to the shell small icon handle}
SetItemIcons;

{the next six SetLabel will make 6 Labels (Text on Form)}
SetLabel(Label1, 150, 72, 'Label One', $AF3300);
SetLabel(Combo2Sel, 154, 4, 'Combo2 Selection');
  {you should place all of the Labels with the ANSI_VAR_FONT at the begining
  of this Array Initialization, Label1 and Label2 will have ANSI_VAR_FONT}
SetLabel(Combo3Text, 304, 4, 'C:\Windows\', Zero, GetStockObject(ANSI_FIXED_FONT));
SetLabel(Label4, 100, 182, 'Label Four', $FF, FontCombo);
SetLabel(WavFile, 50, 287, 'Combo now has a .WAV file', $AC0091, FontCombo);
SetLabel(BigLabel, 240, 200, 'Label Six moves', $009900, FontLarge);

AryLabel[WavFile].Visible := False; // set lnWavFile to not visible
end;


function MakeApp: Boolean;
begin
Result := False; // Returnig False should prevent the message Loop, RunMsgLoop
if SetWinClass('Units Class', @MessageFunc) = Zero then Exit;
{the SetWinClass function simplifies the win Class Register,
You must have a Class Name and a WndProc memory address.
if it fails to Register the win Class, it returns Zero}

hForm1 := MakeForm(DEF, DEF, 546, 351, 'Divide Code Into Units');
if hForm1 = Zero then Exit;
{the MakeForm function will create a Main Form window}

Result := True;
MakeControls; // finish with the control creation
end;


initialization
// need to have empty initialization, for the finalization included

finalization
{I have moved the clean Up from the WM_DESTROY message
to this finalization, even if you call Halt this
finalization will be executed}
  DeleteObject(FontLarge);
  DeleteObject(FontCombo);

end.

I have placed code to handle the Combo Box notify message of it's parent WM_COMMAND message, in this unit and the ComboBoxU unit. To show you that you will need to have your own "Code Organization" system when you use units, and place your code in the unit that makes it better for you.

Code for ComboBoxU.pas
This unit will have much of the code needed for the combo boxes in this program. It has the MakeComboBox function for the combo box creation, and the IsComboMsg function to handle three of the combo boxes messages. I have placed the fourth combo box message handeling function in the InUnitsU unit, to show how you can place them where you feel your personal code organization will benifit.
The three procedures MeasureCombo, DrawComboBox, and SetItemIcons are all for the Owner Draw Combo Box, which I talked about in the Owner Drawn Combo Box section above.

The MakeComboBox( ) function has a Default for the WinStyle parameter, you may want to change the default style to whatever combo style that you mostly want to use. To make adding combo list Items easier durring the combo creation, I have the ListItems parameter as a #0 delimited string. I use the #0 delimiter (the API also used #0 delimiter, for some shell functions), because a system string, PChar, is null (#0) terminated. I can just give a pointer to the begining character of that string section, and the system will automatically stop the character read of the next #0. You will need to have a #0 at the end of the ListItems string to signal the End of the text data in in this string. I have placed a #255 charater at the begining of the ListItems string, for a "Safety" test, to make sure you pass a ListItems type of #0 delimited string to this function. You can leave out this test for #255 and it will work just fine. I have found that the charater numbers above 128 (like #255) do not have keyboard keys for them (in english anyway) and can be used as delimiters, safety tests, or numeric data. You could use normal characters or characters above 128 (#254) at the begining of this ListItems string to set options like a sorted combo box. You should look at the
while Lim > 256 do
loop to see how to move the pointer to the end of each #0 delimited string section.

I have included but commented out, code that will re-size a simple combo outside of a Dialog box.

see comments in code for more info

unit ComboBoxU;
{this unit will have the functions, and variables used for the Combo Boxes}

{I do NOT try and make this a Unit to be used in other Programs, just this
 program. The  IsComboMsg  function will do things ONLY needed by this
 program. However, you could make some changes to make this a more
 Universal, "One Size Fits All" Combo Box Unit, to be
 used in other programs needing Combo Boxes}

interface

uses Windows;

const
CB2Items = #255'At Top'#0'Next Item'#0'Middle'#0'Lower'#0'Lowest'#0'Down'#0+
           'Way Down'#0'Way way Down'#0'Time -'#0; // needs #0 at end

var
hCombo1, hCombo2, hCombo3, hComboODraw: Integer;
{I have placed the Combo Box Handles in this unit,
 but you could also place them in the InUnitsU unit, if you are
  tring to make a One Size Fits All, Combo Unit}


{this MakeComboBox function will create a Combo Box
and set the text for it's Items with the #0 delimited string ListItems}
function MakeComboBox(Left, Top, Width, Height: Cardinal;
                     ListItems: String; WinStyle: Integer = -1): Integer;

{this IsComboMsg function is called in the WM_COMMAND message of the
MessageFunc function in the InUnitsU, it will handle the notification
messages from the Combo Boxes}
function IsComboMsg(WParam1, LParam1: Integer): Boolean;

{there is One Owner Drawn Combo Box, hComboODraw, the MeasureCombo
procedure is called in the WM_MEASUREITEM message of the MessageFunc
function and will give the Item Height for that Combo Box}
procedure MeasureCombo(pMeasureI: PMeasureItemStruct);

{the DrawComboBox procedure is called in the WM_DRAWITEM message
of the MessageFunc function, and draws the Items in the
owner drawn Combo Box}
procedure DrawComboBox(pDrawItem: PDrawItemStruct);

{I use the system's file small Icons in the DrawComboBox
for the file names of the items. So I need to get the small Icon
Handles, and put them in the Item Data for each Item in the
Owner Drawn Combo Box. The SetItemIcons procedure will get
these Icon handles}
procedure SetItemIcons;


implementation

uses
Messages, ShellApi, InUnitsU, ApiFormU, SmallUtils;
  {ShellApi is needed for the SHGetFileInfo function to get
   Icons in the DrawComboBox procedure}

const
ID_Combo0 = 300;

var
ComboNumber: Integer = Zero;
  {ComboNumber will record the number of Combo Boxes created
   in the MakeComboBox function}



procedure SetItemIcons;
var
Count, Index: Integer;
SHResult: Cardinal;
ShInfo1: TSHFILEINFO;
begin
  {the CB_GETCOUNT message will get the number of Items in the combo's list box}
Count := SendMessage(hComboODraw, CB_GETCOUNT, Zero, Zero);
{I need to loop through All of the Combo box Items, so I get
the Count of Items in that combo box}
if Count < 1 then Exit;
for Index := Zero to Count - 1 do
  begin 
  if SendMessage(hComboODraw, CB_GETLBTEXT, Index,
               Integer(@txBuffer)) = CB_ERR then Continue;

{for a File list it is often good to draw the File Icons next to the file name.
 I use the SHGetFileInfo function to get the handle of the Small Icon
 associated with that file extention, if you use the SHGFI_USEFILEATTRIBUTES
 flag, it will get the default icon, even if the file does NOT exist, as
 the file names in this combo box do not exist}
    SHResult := SHGetFileInfo(txBuffer, FILE_ATTRIBUTE_NORMAL,
             ShInfo1, SizeOf(TSHFILEINFO),
             SHGFI_ICON or SHGFI_SMALLICON or SHGFI_USEFILEATTRIBUTES);
{the ShInfo1.hIcon will have the small Icon's Handle,
 in the DrawComboBox procedure}
  if SHResult = Zero then
    ShInfo1.hIcon := Zero;
  SendMessage(hComboODraw, CB_SETITEMDATA, Index, ShInfo1.hIcon);
 {Every Item in ListBoxes and ComboBoxes have a 32-Bit value as Item Data,
  that can be set and retrieved with messages, CB_SETITEMDATA, , CB_GETITEMDATA.
  I set the Item Data to a value of the small icon handle}

  end; // for loop
end;


function IsComboMsg(WParam1, LParam1: Integer): Boolean;
var
Item1: Integer;
TimeRec: TSystemTime;
TimeStr: String;
AM: Boolean;
begin
{this IsComboMsg will handle the Notification messages in the MessageFunc
function of the InUnitsU unit, I have placed it here in the ComboBoxU
because these messages are for user input for the Combo Boxes, but you could
place it in another unit, if it offers better code organization for you}

Result := True; // a True Result will allow the WM_COMMAND message to continue

if LParam1 = hCombo1 then // test the LParam1 for the combo handle
  begin
  Result := False; // set Result to False to end the WM_COMMAMD tests
{the HiWord of the WParam is the notification message for a Combp Box
the CBN_SELENDOK notification is sent after the Selection has changed}
  if HIWORD(wParam1) = CBN_SELENDOK then
{using SendMessage with the CB_GETCURSEL message will get the current
Selection of the Combo Box, the CB_GETLBTEXT will get the Text of an Item
in the List Box of the Combo}
    if SendMessage(LParam1, CB_GETLBTEXT, SendMessage(LParam1,
               CB_GETCURSEL, Zero, Zero), Cardinal(@txBuffer)) <> CB_ERR then
      begin
      SendMessage(hCombo3, CB_ADDSTRING, Zero, Integer(@txBuffer));
    {using SendMessage with the CB_ADDSTRING message, will put
     a new Item in hCombo3  list box}
      AryLabel[Label1].TextColor := $6000B7;
      SetLabelCaption(Label1, 'Look in hCombo3 for   "'+txBuffer+'"');
      end;
  end else


if LParam1 = hCombo2 then
  begin
  Result := False;
  if HIWORD(wParam1) = CBN_SELENDOK then
  begin
  if SendMessage(hCombo2, CB_GETLBTEXT, SendMessage(hCombo2,
                 CB_GETCURSEL, Zero, Zero), Integer(@txBuffer)) <> CB_ERR then
    SetLabelCaption(Combo2Sel, txBuffer);
  end else
  if HIWORD(wParam1) = CBN_DROPDOWN then
    begin
{the CBN_DROPDOWN notify is sent BEFORE the List Box is shown, so you can
test or change the Items of the Combo Box before the List Box is shown}
    GetLocalTime(TimeRec);
{before this Combo drops down the list box, I update the "Time" item
 with the current local time and create the TimeStr}
    if TimeRec.wHour > 12 then
      begin
      AM := False;
      TimeRec.wHour := TimeRec.wHour - 12;
      end else
      AM := True;
    TimeStr := Int2Str(TimeRec.wMinute);
    if Length(TimeStr) = 1 then
      TimeStr := '0'+TimeStr;
    TimeStr := Int2Str(TimeRec.wHour)+' :'+ TimeStr +
               '  '+Int2Str(TimeRec.wSecond)+' Sec';
    if AM then
      TimeStr := TimeStr + '   AM'
      else
      TimeStr := TimeStr + '   PM';
    Item1 := SendMessage(LParam1, CB_GETCURSEL, Zero, Zero);
{there is no Combo message to "Change" or modify a Combo Item, so
you will have to Delete the Item with CB_DELETESTRING and then
Insert a New Item with CB_INSERTSTRING, in order to Change an Item}
    SendMessage(LParam1, CB_DELETESTRING, 8, Zero);
    SendMessage(LParam1, CB_INSERTSTRING, 8, Integer(PChar(TimeStr)));
    if Item1 = 8 then
      SendMessage(LParam1, CB_SETCURSEL, 8, Zero);
{when you Delete an Item that has the Selection, there will be NO SELECTION,
so you will need to use the CB_SETCURSEL message to reset the selection}
    end;
  end else


if lParam1 = hCombo3 then
  begin
  Result := False;
{the CBN_EDITCHANGE notification is sent when the text in the Edit Box
 of the Combo has been changed by Keyboard or user Paste, but Not by
 Combo box selection change}
  if HIWORD(wParam1) = CBN_EDITCHANGE then
    begin
    if SendMessage(hCombo3, WM_GETTEXT, 64, Integer(@txBuffer)) > Zero then
      SetLabelCaption(Combo3Text, txBuffer);
    {the Text in Label Combo3Text will change whenever
     you type into the Edit Box}
    end else
    if HIWORD(wParam1) = CBN_SELENDOK then
      begin
      if SendMessage(hCombo3, CB_GETLBTEXT, SendMessage(hCombo3,
               CB_GETCURSEL, Zero, Zero), Integer(@txBuffer)) <> CB_ERR then
      SetLabelCaption(Combo3Text, txBuffer);
      end;
  end;
end;

procedure MeasureCombo(pMeasureI: PMeasureItemStruct);
begin
pMeasureI^.itemHeight := 18;
{Since I will draw small Icons (16x16) into the Owner Draw Combo Box
I will set the itemHeight to 18, since this is a CBS_OWNERDRAWFIXED
Combo (fixed size items), this WM_MEASUREITEM message is only sent Once,
when the Combo Box is created}
end;

procedure DrawComboBox(pDrawItem: PDrawItemStruct);
var
BrushC: Integer;
begin
{This procedure will draw all of the Items in the combo box.
 This is called in the main form's WM_DRAWITEM message.
 the PDrawItemStruct is in the LParam of that message and has
 the information used to draw an Item.}

if pDrawItem.itemAction and ODA_FOCUS <> Zero then exit;
  // I do not have any Focus drawing

  {first I get the text of the item being painted}
if SendMessage(pDrawItem.hWndItem, CB_GETLBTEXT, pDrawItem.itemID,
               Integer(@txBuffer)) = CB_ERR then
  begin
  {if there is no Text in the Item then I place a ? in it.
   This is not nessary, but shows how to change the text to
   whatever you want}
  txBuffer[Zero] := '?';
  txBuffer[1] := #0;
  end;
{the  pDrawItem.hWndItem  has the Handle of the Combo Box being Painted
 the pDrawItem.itemID is the Item Index for the item being painted}

{the pDrawItem.itemState has the State of the Item, I test for the
 ODS_SELECTED and the ODS_DISABLED states here, there are other states
 but I do not use them here}
if (pDrawItem.itemState and ODS_SELECTED) <> Zero then
  begin
  {if the Item is selected, the Rect is filled with the system select color}
  FillRect(pDrawItem.hDC, pDrawItem.rcItem, GetSysColorBrush(COLOR_HIGHLIGHT));
  SetTextColor(pDrawItem.hDC,GetSysColor(COLOR_HighLightText));
  end else
  if (pDrawItem.itemState and ODS_DISABLED) <> Zero then
  begin
  {if the Item is Disabled, the Rect is filled with the system button color}
  FillRect(pDrawItem.hDC, pDrawItem.rcItem, GetSysColorBrush(COLOR_3DFACE));
  SetTextColor(pDrawItem.hDC, GetSysColor(COLOR_GRAYTEXT));
  {text color is set to system Grey Text , diabled, grey color}
  end else
  begin
  {the file extention for the file name is tested and if it is a WAV file
   the fill brush color is a light red color, normal is a green color}
  if UpperCase(GetFileExt(txBuffer)) = '.WAV' then
  BrushC := CreateSolidBrush($DFCFFF) else
  BrushC := CreateSolidBrush($D9FFC9);
  FillRect(pDrawItem.hDC, pDrawItem.rcItem, BrushC);
  DeleteObject(BrushC);
  SetTextColor(pDrawItem.hDC,$009000A0);
  end;

{DrawIconEx  will draw a small icon without resizing it if you have the
   DI_NORMAL flag set.}
  DrawIconEx(pDrawItem.hDC,3,pDrawItem.rcItem.Top+1,
         SendMessage(pDrawItem.hWndItem, CB_GETITEMDATA, pDrawItem.itemID, Zero),
         Zero, Zero, Zero, Zero, DI_NORMAL);
  {I use the SendMessage with CB_GETITEMDATA, to get this Item Data
   that has the handle of the small Icon}

SelectObject(pDrawItem.hDC, FontCombo);
SetBkMode(pDrawItem.hDC, Transparent);
{set the font and Background Mode for text drawing and draw the Text}
TextOut(pDrawItem.hDC,22,pDrawItem.rcItem.Top+1, txBuffer, PCharLength(txBuffer));
end;


function MakeComboBox(Left, Top, Width, Height: Cardinal;
                     ListItems: String; WinStyle: Integer = DEF): Integer;
var                                      {WinStyle defaults to Def, -1 }
Lim: Integer;
pGet: PChar;
    {the 4 variables below are used to get the correct Simple Combo Size}
//hChild: Integer;
//pnt: TPoint;
//eRect, LBRect: TRect;

begin
{a -1 (DEF) in the WinStyle will get the Default style Flags}
if WinStyle < Zero then
  WinStyle := WS_VISIBLE or WS_CHILD or CBS_DROPDOWN or
  WS_CLIPSIBLINGS or WS_TABSTOP or WS_VSCROLL;

Result := CreateWindow('COMBOBOX',PChar('cb'+Int2Str(ComboNumber)),
    WinStyle,Left,Top,Width,Height,hForm1,ID_Combo0+ ComboNumber,hInstance,nil);
{the ID of the Combo Box is set to the ID_Combo0+ ComboNumber, although I
do not use these ID's in this program}
if Result = Zero then Exit;
Inc(ComboNumber);
SendMessage(Result,WM_SETFONT,GetStockObject(ANSI_FIXED_FONT), Zero);

  {if you create a Simple Combo Box, that is NOT in a Dialog window,
   it WILL NOT size and paint correctly, I have commented out the
   following code, that will correctly "Size" a simple combo box.
   That way you can see that the simple Combo,  hCombo1, will have a
   space below that does NOT get any painting, because the List
   Box is sized to the Items in it, but the combo box is not}

{you can remove the Comment marks of the code below to get correct
Simple Combo sizing, however, simple combos are not used anymore}

{if (WinStyle and CBS_SIMPLE) <> Zero then
  begin
  pnt.x := 8;
  pnt.y := 6;

  hChild := ChildWindowFromPoint(Result, pnt);
  if hChild <> Zero then
    begin
    GetWindowRect(hChild, eRect);
    pnt.x := 8;
    pnt.y := (eRect.Bottom - eRect.Top)+6;
    hChild := ChildWindowFromPoint(Result, pnt);
    if hChild <> Zero then
      begin
      if GetWindowRect(hChild, LBRect) then
      MoveWindow(Result, Left, Top, Width,(LBRect.bottom - eRect.Top) +3, False);
      end;
    end;
  end;}


{I have a method to use One string (ListItems), as a #0 delimited string to
add all of the Items to this combo box at once. Just to show you, I have
placed the  #255  charater, at the begining of the string and test for it.
I set a PChar variable pGet to the memory address of the second charcter
in the ListItems string}
if (ListItems <> '') and (ListItems[1] = #255) then
  begin
  pGet := @ListItems[2];
  Lim := Zero; {LIM is just a "Safety" test in-case you forget to put an extra
                #0 at the end of your ListItems string}
  while Lim < 256 do
    begin
    Inc(Lim);
    SendMessage(Result,CB_ADDSTRING, Zero, Integer(pGet));
{the CB_ADDSTRING message will only read the ListItems string until the next #0}
    pGet := StrEnd(pGet);
{reset the pGet pointer to the next charater after the #0 ,
 with StrEnd and Inc(pGet)}
    Inc(pGet);
    if pGet^ = #0 then Break;
 {you will need to have a #0 at the end of your ListItems string,
  so this while loop will end, see the constant  CB2Items  to see how to
  construct a ListItems string}
    end;
  SendMessage(Result, CB_SETCURSEL, Zero, Zero);
  end;
end;

end.

You could use the MakeComboBox( ) function as a "One Size Fits All" function, but the rest of this unit has not been developed to use as Universal Combo Box code, but this unit will keep the combo box code together.

You can create your own units and then try to make some code that is reusable.



                           

Next
The next lesson shows you how to have your programs use the XP Theme.
  12. Xp Themes Manifest


       

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




H O M E