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

Home
DelphiZeus
13. MakeApp Unit
A Form and Control Unit,
with Font and Menu creation

Home



MakeApp  Unit

In this lesson I will have code for a Unit called MakeApp, this is a Unit that I have used many times when doing API programs. This is a "One Size Fits All" or universal type of unit, meant to be used in many different programs as a container for re-usable code that will do the main form window creation, with a main-menu. I do not have this as a "Delphi TObject" type of creation code, but try and use some functions in this to simplify some often-used common tasks, like control and font creation. This is an expansion and improvement of the ApiFormU.pas unit you saw in the last lesson, the MakeForm( ) function in that unit is the same as the one in this MakeApp unit. This also includes the SetWinClass( ), RunMsgLoop( ), MakeFont( ), and MakeButton( ) functions from the ApiFormU, of the last lesson. .
Some new functions are included, one for making the Main-Menu sub-menus called MakeSubMenu( ) and one for List Boxes called MakeListBox( ), and one for Combo Boxes MakeComboBox( ), and one to create a container window, like the ever popular TPanel called MakePanel( ).

MakeApp.pas  Unit
The MakeApp Unit will contain many of the functions you saw in the ApiFormU unit of the last lesson, but the labels have been taken out and moved to the GrafCtrls.pas file (for graphic controls, covered in a later lesson).

These are the five functions from the APIFormsU.pas Unit, in this MakeApp unit -

  1. function     SetWinClass
  2. function     MakeForm
  3. procedure RunMsgLoop
  4. function     MakeFont
  5. function     MakeButton
If you need information about these functions then see the ApiFormU.pas unit information on the  InUnits  page for the previous lesson 11. You may notice that this MakeFont( ) function has been changed to allow more font "Looks".

This MakeApp.pas unit has the MakeButton function, and adds two other control creation functions, List Box creation is added as the "MakeListBox( )" function and Combo Box Creation has been added with the "MakeComboBox( )" function. I use alot of menus, so I added the Sub-Menu Creation function for the "Main Menu" of the main Form, with the "MakeSubMenu( )" function. I also added code to make a "Container" window, modeled after the popular Delphi TPanel window.

You should notice that I do NOT attempt to make these controls have all of the creation or usage options that delphi does in it's VCL, I do not even try and write any control modification functions or any message handling code. Since I know the API code methods for options, messages, or control changes, I just use the API function code in my programs. I only wanted some reusable code for things that I do many times, like MakeForm or MakeFont, and keep the code as simple as I could. . . Also, the more functions and code you add to make a "One Size Fits All" control functions, it will increase the byte size of your compiled program. If you look at the MakeComboBox( ) function, you will see that I have included code to correctly resize a Simple style Combo-Box, if you never use a Simple Combo-Boxes, then this is wasted (unused) code and just adds to the size of the program without being used. So you might try to consider the trade off between having "fast and easy" code writing, and having "small and efficient" code methods.
You can read about these new control creation functions after the Code for the MakeApp.pas Unit below.

I present this MakeApp unit code as a possible Help for you, but you should create your own units with your own constant values, creation and modification function code for the types of windows, controls and methods, that YOU frequently use, , , adding the default properties that are most common in your programs. Also add any code that you see and use over and over again.

Code for the MakeApp.pas Unit -

unit MakeApp;
{this MakeApiForm 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 Menu and a simple MakeFont function.
there are 4 control creation functions}

interface

uses    // I have NO Units from any Program in the Uses clause
  Windows;

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

type
{the TPanelFunc if the function that will be called by the PanelMessFunc when
that panel's Windows message proc is called, you can use the three message
parameters to provide the methods (like WM_PAINT or button clicks) for that panel}
  PPanelFunc = ^TPanelFunc;
  TPanelFunc = function(iMsg, wParam, lParam: Integer): Integer;

{there are six styles of Panel creation, with a raised Edge, a Border, and a 
 "Tab Stop child control message handling" a psNone has none of these styles.
 a psEdge has only the raised Edge, a psTabEdge has both the Edge and the Tab 
 Stop, the psBorder has the WS_BORDER style, psTabBorder adds the Tab Stop.
 psTab has only the child control Tab Stop with WS_EX_CONTROLPARENT}

  TPanelStyle = (psEdge, psTabEdge, psBorder, psTabBorder, psTab, psNone);

{For Font Creation, there are four font settings in the TFontLook for the way
 a font will look on screen, flBold is for a Bold font, and the other three
 will give the font the look of their name}

  TFontLook = (flBold, flItalic, flUnderLine, flStrikeOut);
  TFontLooks = Set of TFontLook; // Used in th MakeFont( ) function


var
hMainMenu: Integer = Zero;
VarFont: Integer = Zero;
ShowCmdd: Integer = SW_SHOWDEFAULT;
{if you need to alter the show command in ShowWindow( ), you can change
this ShowCmdd before calling the  RunMsgLoop  procedure}



function SetWinClass(const 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; const 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; const FontName: String;
                 Look: TFontLooks = []; 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}

function MakeListBox(Left, Top, Width, Height, Parent: Integer;
                     const ListItems: String; WinStyle: Integer = DEF): Integer;
  {the MakeListBox function will create a ListBox control and place the Items
  in the ListItems string in the list box}

function MakeComboBox(Left, Top, Width, Height, Parent: Integer;
                     const ListItems: String; WinStyle: Integer = DEF): Integer;
  {the MakeComboBox function will create a ComboBox control and place the Items
  in the #0 delimited ListItems string in the list box}

function MakePanel(Left, Top, Width, Height, hParent: Integer; WndFunc: TPanelFunc;
                   ID_Number: Cardinal; Style: TPanelStyle = psTabEdge): Integer;
  {the MakePanel function will create a container window like the Delphi TPanel,
   it can have one of six panel styles, with a raised Edge, a Border or Tab Stop}

function MakeSubMenu(const ItemList: String; ID1, ID2: Cardinal;
                     hMenu: Integer = Zero): Integer;
  {the MakeSubMenu function is used to create a Main Menu for the MakeForm,
   with sub menus and menu Items}

function id4menu(a, b: Byte; c: Byte = Zero; d: Byte = Zero): Cardinal;
  {the id4menu function is used to set a Cardinal value to have 4 separate
   byte values for the two ID numbers in the MakeSubMenu function}


implementation

uses
  Messages, CommCtrl, SmallUtils;


type
  TPanelRec = record
    Handle: Integer;
    WndFunc: TPanelFunc;
    end;

const
One = 1;
Two = 2;
n8 = 8;
n13 = 13;
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_MakeMenu: PChar = 'ERROR - in MakeSubMenu - ItemList string is Incorrect';

E_TitleWinClas: PChar = 'SetWinClass  function  ERROR';
E_TitleMF: PChar = 'MakeForm  function  ERROR';
E_TitleButton: PChar = 'MakeButton  function  ERROR';
E_TitleMenu: PChar = 'MakeSubMenu  function  ERROR';
E_TitlePanel: PChar = 'MakePanel  function  ERROR';


var
wClassEx, PanelClass: TWndClassEx;
Atom1: Word = Zero;
FirstForm: Integer = Zero;
LBnum: Integer = Zero;
CBnum: Integer = Zero;
aryPanel: Array of TPanelRec;



procedure ErrorMsgBox(pText, pTitle: PChar);
begin
MessageBox(Zero, PChar(pText+#10+SysErrorMessage(GetLastError)),
           pTitle, MB_ICONERROR);
end;


function MakeFont(Height, Width: Integer; const FontName: String;
                 Look: TFontLooks = []; 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 flBold in Look then
    lfWeight := FW_BOLD;
  if flItalic in Look then
    lfItalic := One;
  if flUnderline in Look then
    lfUnderline := One;
  if flStrikeOut in Look then
    lfStrikeOut := One;
  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
  DEF: hFont := VarFont;
  -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 WS_CLIPSIBLINGS
                         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;
if hFont <> Zero then
  SendMessage(Result, WM_SETFONT, hFont, Zero);
end;



function MakeListBox(Left, Top, Width, Height, Parent: Integer;
                     const ListItems: String; WinStyle: Integer = DEF): Integer;
var
Lim: Integer;
pGet: PChar;
begin
if WinStyle < Zero then
  WinStyle := WS_VISIBLE or WS_CHILD or LBS_NOTIFY
              or WS_VSCROLL or WS_CLIPSIBLINGS or WS_TABSTOP;

Result := CreateWindowEx(WS_EX_CLIENTEDGE,'LISTBOX',PChar('LB '+Int2Str(LBnum)),
            Cardinal(WinStyle),Left,Top,Width,Height,Parent,Zero,hInstance,nil);
if Result = Zero then Exit;
Inc(LBnum);
SendMessage(Result,WM_SETFONT,VarFont,Zero);

if (ListItems = '') or (ListItems[One] <> #255) or (Length(ListItems) < Two) then
  Exit;

pGet := @ListItems[Two];
Lim := Zero;
while (Lim < 256) do
  begin
  Inc(Lim);
  SendMessage(Result, LB_ADDSTRING, Zero, Integer(pGet));
  pGet := StrEnd(pGet);
  Inc(pGet);
  if pGet^ = #0 then Break;
  end;
end;


function MakeComboBox(Left, Top, Width, Height, Parent: Integer;
                     const 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(CBnum)),
        Cardinal(WinStyle),Left,Top,Width,Height,Parent,Zero,hInstance,nil);
{the ID of the Combo Box is set to the ID_Combo0+ ComboNumber}
if Result = Zero then Exit;
Inc(CBnum);
SendMessage(Result,WM_SETFONT,VarFont, Zero);

  {if you create a Simple Combo Box, that is NOT in a Dialog window,
   it WILL NOT size and paint correctly, The following code will 
   correctly "Size" a simple combo box.}

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

  hChild := ChildWindowFromPoint(Result, pnt);
  if hChild <> Zero then
    begin
    GetWindowRect(hChild, eRect);
    pnt.x := n8;
    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[One] = #255) then
  begin
  pGet := @ListItems[Two];
  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}
    end;
  SendMessage(Result, CB_SETCURSEL, Zero, Zero);
  end;
end;


function PanelMessFunc(hWnd, Msg, wParam, lParam: Integer): Integer; stdcall;
var
i: Integer;
begin
Result := -2;
for i := Zero to High(aryPanel) do
  if (aryPanel[i].Handle = hWnd) and (@aryPanel[i].WndFunc <> nil) then
    Result := aryPanel[i].WndFunc(Msg, wParam, LParam);
if Result = -2 then
  Result := DefWindowProc(hWnd,Msg,wParam,lParam);
end;

function MakePanel(Left, Top, Width, Height, hParent: Integer; WndFunc: TPanelFunc;
                   ID_Number: Cardinal; Style: TPanelStyle = psTabEdge): Integer;
var
ExStyle, wStyle: Cardinal;
begin
{this function creates a window control container like the delphi VCL TPanel.
In order to get the messages for buttons and other controls on this container
you will need a TPanelFunc and place that function in the WndFunc parameter}
if Length(aryPanel) = Zero then
  if RegisterClassEx(PanelClass) = Zero then
  begin
  Result := Zero;
  ErrorMsgBox('ERROR - in Makepanel - Failure Register Panel Class', E_TitlePanel);
  Exit;
  end;

wStyle := WS_CHILD or WS_CLIPSIBLINGS or WS_CLIPCHILDREN or WS_VISIBLE;
ExStyle := Zero;

case Style of
  psEdge: ExStyle := WS_EX_DLGMODALFRAME;
  psTabEdge: ExStyle := WS_EX_CONTROLPARENT or WS_EX_DLGMODALFRAME;
  psBorder: wStyle := wStyle or WS_BORDER;
  psTabBorder: begin
               ExStyle := WS_EX_CONTROLPARENT;
               wStyle := wStyle or WS_BORDER;
               end;
  psTab: ExStyle := WS_EX_CONTROLPARENT;
  end;

Result := CreateWindowEx(ExStyle,PanelClass.lpszClassName,
                        PChar('panel '+Int2Str(Length(aryPanel))), wStyle, Left,
                        Top, Width, Height, hParent, ID_Number, hInstance, nil);
if Result = Zero then
  begin
  ErrorMsgBox('ERROR - in MakePanel - Panel Window not Created', E_TitlePanel);
  Exit;
  end;
SetLength(aryPanel, Length(aryPanel)+One);
aryPanel[High(aryPanel)].Handle := Result;
aryPanel[High(aryPanel)].WndFunc := WndFunc;
end;


function id4menu(a, b: Byte; c: Byte = Zero; d: Byte = Zero): Cardinal;
begin
{this function will join 4 bytes into a DWord for a MakeSubMenu ID container}
Result := a or (b shl n8) or (c shl 16) or (d shl 24);
end;


function MakeSubMenu(const ItemList: String; ID1, ID2: Cardinal;
                     hMenu: Integer = Zero): Integer;
var
Lim, Flags, Enab, mID: Integer;
pGet, pSub: PChar;
begin
{this MakeSubMenu function is a way to make Menu creation have less lines
of code. But to keep it simple, it has Limited functionality. . .
You can not add more than 8 String type menu Items, And only ONE item
can have a sub-Menu. After the submenu is created, and you need more Items,
then you can use the menu Handle and API functions like  AppendMenu  or
InsertMenu to have more than 8 string items or more items with sub-menus}
Result := Zero;
if (ItemList = '') or (Length(ItemList) < 4) or
  (not ((ItemList[One] = #250) or (ItemList[One] = #251))) then
  begin
  SetLastError(n13);
  MessageBox(Zero, E_MakeMenu, E_TitleMenu, MB_ICONERROR);
  Exit;
  end;

pSub := nil;

{if ItemList[1] is #250 , then This sub-menu is added to the Main Menu
if ItemList[1] is #251 , then a separate, sub-menu is created
and NOT added to the main menu}
Enab := MF_STRING or MF_POPUP;
if ItemList[One] = #250 then
  begin
  pSub := @ItemList[Two];
  if pSub^ = #202 then
    begin
    Enab := Enab or MF_GRAYED;
    Inc(pSub);
    end;

  pGet := StrEnd(pSub);
  Inc(pGet);
  end else
  pGet := @ItemList[Two];

if pGet^ = #0 then
  begin
  SetLastError(n13);
  ErrorMsgBox('ERROR - in MakeSubMenu - No Menu Item Text Charaters', E_TitleMenu);
  Exit;
  end;

Result := CreateMenu;
if Result = Zero then
  begin
  ErrorMsgBox('ERROR - in MakeSubMenu - CreateMenu = FAILED', E_TitleMenu);
  Exit;
  end;

Lim := Zero;
while (Lim < 9) do
  begin
  if Lim = n8 then
    begin
    SetLastError(n13);
    ErrorMsgBox('ERROR - in MakeSubMenu - More Than 8 Menu Items', E_TitleMenu);
    Break;
    end;

  Flags := MF_STRING;
 {test for the menu Item options}
  case pGet^ of
   #200: begin
         Flags := MF_SEPARATOR;
         Inc(pGet);
         end;
   #201: begin
         Flags := Flags or MF_CHECKED;
         Inc(pGet);
         end;
   #202: begin
         Flags := Flags or MF_GRAYED;
         Inc(pGet);
         end;
   #203: begin
         Flags := Flags or MF_POPUP;
         Inc(pGet);
         end;
   end;

  mID := Zero;
  if Flags = MF_STRING or MF_POPUP then
    begin
    if hMenu = Zero then
      mID := DEF else
      mID := hMenu;
    end else
    if Flags = MF_SEPARATOR then mID := $200
    else
    case Lim of
      Zero: mID := ID1 and $FF;
      One: mID := (ID1 and $FF00) shr n8;
      Two: mID := (ID1 and $FF0000) shr 16;
      3: mID := ID1 shr 24;
      4: mID := ID2 and $FF;
      5: mID := (ID2 and $FF00) shr n8;
      6: mID := (ID2 and $FF0000) shr 16;
      7: mID := ID2 shr 24;
      {separate the bytes out of the ID numbers}
      end;

  if mID <> DEF then
    begin
    if not AppendMenu(Result, Flags, mID, pGet) then
      begin
      ErrorMsgBox('ERROR - in MakeSubMenu - AppendMenu = FAILED', E_TitleMenu);
      DestroyMenu(Result);
      Result := Zero;
      Exit;
      end;
    end else
    begin
    SetLastError(n13);
    ErrorMsgBox('ERROR - in MakeSubMenu - SubMenu handle is Zero', E_TitleMenu);
    end;

  pGet := StrEnd(pGet);
  Inc(pGet);
  if pGet^ = #0 then Break;
  if not ((Flags = MF_SEPARATOR) or (Flags = MF_STRING or MF_POPUP)) then
    Inc(Lim);
  end;

if pSub <> nil then
  begin
  if hMainMenu = Zero then
    hMainMenu := CreateMenu;
  AppendMenu(hMainMenu, Enab, Result, pSub);
  end;
end;


function SetWinClass(const 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) < Two) or (pMessFunc = nil) then
  begin
  SetLastError(n13); // 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+One;
  GetMem(lpszClassName, Length(ClassName)+One);
  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; const Caption: String;
                  {hMenu: Integer;} 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(n13); // 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;


if Width < Zero then
  begin
  Width := -Width;
  SetRect(Rect1, Zero, Zero,Width,Height);
  end else
  begin
  SetRect(Rect1, Zero, Zero,Width,Height);
  if not AdjustWindowRect(Rect1, WinStyle,False) then
  SetRect(Rect1, Zero, Zero, Width+6, Height+26);
  end;

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

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

Result := CreateWindow(wClassEx.lpszClassName, PChar(Caption), WinStyle,
                Left, Top, Rect1.Right-Rect1.Left,
                Rect1.Bottom-Rect1.Top, Zero, hMainMenu, 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
  begin
  SetLastError(n13);
  ErrorMsgBox('ERROR - in RunMsgLoop - FirstForm is Zero',
              'ERROR, No Form has been created');
  Exit;
  end;
{this RunMsgLoop procedure will run the GetMessage Loop to keep
this program running, I have included a Show parameter, if False the
Main Window will not be visible}
if Show then
  ShowWindow(FirstForm, ShowCmdd);

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

procedure DoInit;
begin
{I will fill the wClassEx and PanelClass with Zeros}
ZeroMemory(@wClassEx, SizeOf(wClassEx));
ZeroMemory(@PanelClass, SizeOf(PanelClass));
VarFont := GetStockObject(ANSI_VAR_FONT);

with PanelClass do
  begin
  cbSize := SizeOf(PanelClass);
  Style := CS_BYTEALIGNWINDOW;
  hInstance := SysInit.hInstance;
  lpfnWndProc := @PanelMessFunc;
  hbrBackground := COLOR_BTNFACE+One;
  lpszClassName := 'hPanel Class';
  hCursor := LoadCursor(Zero, IDC_ARROW);
  end;
InitCommonControls;
// I will get the ComControl library in this process
end;


initialization
DoInit

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


end.

Many of the functions in this unit are the same as in the ApiFormU.pas unit of the last lesson. Including, SetWinClass( ), MakeForm( ), RunMsgLoop( ), MakeFont( ), and MakeButton( ), Please look at the last lesson for explanations of these functions, that were introduced there. You have seen the methods I use for the MakeComboBox( ) and MakeListBox( ) functions in the ComboBoxU.pas file of the last lesson, so you can look at the explanation for MakeComboBox( ) in ComboBoxU.pas, in the last lesson, at   InUnits, ComboBoxU. . . With both of these functions, I use a single string parameter called "ListItems" as a Zero delimted string, to load all of the "Items" into that control's list boxes. An example of code for a list box creation -
hListBox1 := MakeListBox(7, 7, 136, 104, hForm1, #255'First Item'+
             #0'Next Item'#0'Item 3'#0'Item4'#0'Item 5'#0+
             'Item 6'#0'Item 7'#0'Last Item'#0);
I do not include any Owner draw code for these controls, to keep down the code used, but if you use owner drawn listboxes or comboboxes alot, you might take some of the owner-draw code form ComboBoxU.pas and put it in this MakeApp unit, or make a separate unit for those owner-drawn controls.

There are two New functions in this, including MakePanel( ) and MakeSubMenu( ).

MakePanel fuction
I have a MakePanel fuction to create a "Container" window control, much like the Delphi TPanel,

function MakePanel(
  Left, Top, Width, Height, // standard position parameters
  hParent: Integer; // handle of Parent Window 
  WndFunc: TPanelFunc; // a TPanelFunc function used for messages
  ID_Number: Cardinal; // standard control ID number 
  Style: TPanelStyle = psTabEdge): // Style is set to a TPanelStyle
  Integer; // result is the system window Handle for panel
the MakePanel( ) function is much like the other "Make" functions and has the standard "Left, Top, Width, Height, hParent, and ID_Number" parameters. It also has the "Style" parameter (TPanelStyle), which can be set to one of six panel Styles, to have the panel have an Edge, Border or Tab Stop processing for child windows, See these styles listed below -

TPanelStyle = (psEdge, psTabEdge, psBorder, psTabBorder, psTab, psNone);

psEdgeWill make a Raised Edge with the WS_EX_DLGMODALFRAME window ExStyle
psTabEdgeWill make a Raised Edge and add the WS_EX_CONTROLPARENT window ExStyle for child control Tab Stop processing
psBorderWill make a window Border with the WS_BORDER window Style
psTabBorderWill make a window Border and Tab Stops for child controls
psTabWill have Tab Stops for child controls with the WS_EX_CONTROLPARENT window ExStyle
psNoneDoes not add any edge, border or tab stop

Because this is a "Container" control, it is nessary to get the event and change messages of child controls (like buttons). So there is a parameter called "WndFunc" of Type "TPanelFunc", you will need a function of type -

      function(iMsg, wParam, lParam: Integer): Integer;

to handle the messages of this Panel. When the system calls the Window Proc with a message for a panel, then the TPanelFunc (in the WndFunc parameter) is called, passing the message, wParam and lParam. You may notice that this function is like the standard Window Proc, except that I do not have a hWnd, since this function is suppose to be used for only ONE panel window that you should know it's handle. So From the MakePanel function, you use the Panel's handle as the hWnd handle if you need it. If you need the hWnd paramter included in the TPanelFunc for your coding style, then you can add it to the TPanelFunc in this MakeApp unit. . .
Next is some code to make a Panel with a button, notice the PanelFunc function, which is used to process the Window Proc messages and do the button click for the Panel -

const
ID_PanelBut = 100;

var
hPanel1: Integer;


function PanelFunc(iMsg,wParam,lParam:Integer):Integer;
begin
{the Result of this will be passed back to the windows system,
UNLESS it is -2, then the DefWindowProc is called and it's Result
goes back to the system. So if you want DefWindowProc, use -2,
otherwise DefWindowProc will NOT be called}
Result := -2;
case iMsg of
  WM_COMMAND: if LOWORD(wParam) = ID_PanelBut then
    MessageBox(hForm1, 'Panel Button Click', 
               'Click Button', MB_ICONINFORMATION);

  end;
end;

// - - - Panel and button creation below

hPanel1 := MakePanel(30,50, 138, 93, hForm1, PanelFunc, 0, True);

MakeButton(25,16,76, 22, 'Panel Button', hPanel1, ID_PanelBut);

    IMPORTANT - If you have the panel Style with the TabStop (psTab, psTabEdge, psTabBorder) and that panel is a CHILD of another CHILD window, you must make sure that the panel's parent also has the WS_EX_CONTROLPARENT ExStyle. If it's parent window does not also have the WS_EX_CONTROLPARENT ExStyle, then your program can Freeze Up.

I do not have a function for a Panel's destruction and removal, since I did not need this sort of thing, however if you find that you need this, you can add that code.

You can see some code examples for these Panel methods in the next page at
MakeApp Unit Example Code.

function MakeSubMenu( )
I wanted an easier way to create a Main Menu for my programs, and this MakeSubMenu( ) function will reduce the code written to make a main menu. But since the menu creation can have many options for the menu and it's menu items, this function is not so simple. I wanted to have menu Item options and menu Item ID numbers, but I did NOT want to have 20 paramters in a single function, so I Combined 8 single Byte ID numbers into 2 cardinal values, and added character "Switch" ON option in the ItemList string. The function looks like this -

function MakeSubMenu(const ItemList: String; ID1, ID2: Cardinal;
                     hMenu: Integer = Zero): Integer;
This function will create a system sub menu and it's Result will be the handle for that new Sub-Menu. You may want to look at the code in the MakeApp unit above for the MakeSubMenu function. You will see that the ItemList string is searched for the special menu Item Characters (#250, #251, #200, #201, #202, #203). There is no function or setting in this MakeApp.pas to place a "Main Menu" on the main Form. If you call this MakeSubMenu( ) function with the #250 in the ItemList string, and it succesfully makes a Sub Menu, the Main Menu is automaticly created and added to the main Form window. If you look at this function's parameters, the first is the ItemList, string, like the MakeComboBox( ) function, this is a Zero delimited string, with a sub-string for each added menu item. The ID1 and ID2 cardinal parameters are the "menu Item ID" containers, each 4 byte Cardinal value will contain 4 Byte values, each byte having an ID number for a single menu Item ID. (so the maximum menu-Items with ID is eight, the separator does not use an ID, so there can be more than 8 Items). The hMenu Integer parameter is the menu handle for an added Sub-Menu, and set to Zero if there are no added sub-menus.

In these parameters, you will NOT see any for menu Item creation options, I use a method of including a single charater in the ItemList string to indicate a Menu Item Creation option or type of menu Item. This is my own method to have menu Item options without function parameters for those options. I use a method of string "Charater Switch" because this is a #0 delimited string, so I can add a special "Switch" charater after a #0 to turn ON an Item option. This is unlike any methods used by the Delphi VCL, and you may not understand it or like it, if so, you can skip over this method or just delete the MakeSubMenu( ) function code.

The very First charater in the ItemList string must be a #250 or a #251 charater or the function will fail. The #250 will tell the function to make a main-menu sub-menu, and automaticly add it to the Main-menu. The #251 will tell the function to make a sub-menu and NOT add it to the main-menu. You always need to have either a #250 or #251 charater as the first charcter in this parameter string. Use the #250 if you want the sub-menu to be automatically added to the Main-Menu. If you use the #251 charater, the submenu will be created, but NOT added to the Main menu, so you will need to get the result as a menu Handle and add this sub-menu to another menu yourself. The sub-menu Handle returned by the MakeSubMenu( ) function is a System menu Handle, which can be used in any of the API menu functions needing a menu Handle.

There are four special "Switch" charaters for item options or types, they are the #200, #201, #202, #203 charaters. You will need to place these "Switch" chatacters after the #0 and before capton "text" of that menu Item. The menu Item option these "Switch" charaters will turn ON is listed below -

#200  will make a "Separator" menu-item, and with this type NO ID number (byte) will be used.

#201  will make the menu-item Checked, with a menu check mark.

#202  will make the menu-item Greyed, Disabled.

#203  will have that menu-item add a Sub-Menu for this menu, and you must have a sub-menu handle in the hMenu parameter to add to this menu. And with this type NO ID number (byte) will be used. You can only have ONE #203, sub-menu in a ItemList string.

The 2 ID parameters, ID1 and ID2, are Cardinal valuse that can hold 4 ID numbers each as single bytes of the cardinal value, so you can NOT have more than 8 menu items with ID in a ItemList string. I did not want to have Eight parameteres for 8 Byte values in this function, so I use two Cardinal parameters, (4 bytes in each). Since the IDs are Byte values, then your IDs are limited to 0 to 255, which is less than the menu Items can have, but more than enough for my personal menu Item ID use in this function. I have included a function to build one Cardinal value from 4 byte values, called id4menu( ) and looks like this -

function id4menu(a, b: Byte; c: Byte = Zero; d: Byte = Zero): Cardinal;
If you have just one menu item for the cardinal value, you can just place that byte value in the Cardinal parameter. If you need more than one ID value in the Cardinal ID then you can use the id4menu( ) function.

Making the ItemList string
The ItemList string is a zero delimited string, this first example will make a Main Menu Sub-menu, and I will put the #250 as the first charater in the string. The next text charaters are the "Name" to appear on the main menu, and then there must be a #0 , to show the End of that name, next there will be the text charaters for each sub-menu item, separated by a #0, with a #0 at the end of the ItemList string -

var
LS: String;

LS := #250'&File'#0'Top Item'#0'Menu Item2'#0'menu item 3'#0;
MakeSubMenu(LS, id4menu(0,1,2), 0, 0);

or

MakeSubMenu(#250'&File'#0'Top Item'#0'Menu Item2'#0'menu item 3'#0,
            id4menu(0,1,2), 0, 0);
This will make a three Item Sub-Menu for the Main-Menu, the Main-Menu name will be "File".

If you need a menu "Sepatator" you add the #200 before the menu Item, although the separator does not have any text, you must include at least ONE text charater, so there is not a double #0#0 . (all menu additions require at least ONE charater for the text, if you do not want any Text to show then use a single space character)

  LS := #250'&File'#0'Top Item'#0'Menu Item2'#0#200'.'#0'menu item 3'#0;

If you want a sub-menu Item to be checked, then place a #201 before it's text string, this will check the first menu item -

LS := #250'&File'#0#201'Top Item'#0'Menu Item2'#0'menu item 3'#0;

If you want a sub-menu Item to be Disabled, then place a #202 before it's text string, this will disable and grey the first menu item -

LS := #250'&File'#0#202'Top Item'#0'Menu Item2'#0'menu item 3'#0;

If you need a sub-menu item in this sub-menu, then you would use the #203 before the text characters. But with an added sub-menu, you will need to have created a sub-menu and place it's Handle in the hMenu parameter of the MakeSubMenu( ) function.

LS := #251'Top Item'#0'Menu Item2'#0'menu item 3'#0;
{the first charater of the LS is a #251, which means that
 this Sub-menu will NOT be added to the main-menu}
hSubMenu := MakeSubMenu(LS, id4menu(16,17,18), 0, 0);
// you must get the sub-menu handle, I use hSubMenu
LS := #250'&File'#0'Top Item'#0#203'Sub Menu Item2'#0'menu item 3'#0;
MakeSubMenu(LS, id4menu(0,1), 0, hSubMenu);
{if you have the #203 in the LS, then you MUST  have a menu
 handle in the hMenu parameter, Also notice that the id4menu only
 has two ID numbers, a Sub-Menu does NOT use an ID}

You can see some example code for using the MakeSubMenu function on the next MakeApp lesson at MakeApp Unit Example Code

This should give you some ideas on how to get some of the code that you use many times into a reuseable unit. You should create your own "One Size Fits All" code container units and then try to structure those units with code that is reusable to save you time and effort for tasks that you offen need.

More lessons about MakeApp and GraphCtrls
The following three lessons will give you more information about creating and using your own code unit containers.

13A. MakeApp Unit Example Code
Has code to show you how to use this MakeApp unit.

13b. Graphic Control Creation
Shows you ways to create non-window graphic (just an image) controls.

13C. Using GrafCtrls Program
Has code examples for using the GraphCtrl.pas unit.



                           

Next
The following lesson shows you how to create the system Open and Save Dialog Boxes.
  14. Open and Save Dialogs


       

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




H O M E