Working with Child Controls


In this portion of the chapter, we're going to use the API application template developed at the beginning of the chapter to create an API application that allows the user to view icons stored in executable files and dynamic link libraries. While creating this application, you'll learn how to do the following:

  • Create resource files

  • Specify a custom icon for the executable file

  • Create a main menu

  • Create child controls

  • Use common dialog boxes

  • Properly draw to the window in response to the WM_PAINT message

  • Extract icons from other executables and dynamic link libraries

  • Use an external manifest to enable child controls to use XP visual styles

Before we start creating the application, you should take a look at the final product. It is displayed in Figure 23-3.

image from book
Figure 23-3: The Icon Viewer

Creating the Main Menu

It's actually easy to create a menu in an API application. You only have to do these three simple things:

  1. Create a resource file that contains the menu.

  2. Link the aforementioned resource into the executable file.

  3. Display the menu on the main window by assigning the menu's name to the lpszMenuName field of the WNDCLASS record.

The resource file that contains the menu (or other resources) can be created in any text editor, as long as you save the resource file with the .rc extension. The resource file that contains the main menu of the Icon Viewer is called image from book Menu.rc and its contents are displayed in Listing 23-3.

Listing 23-3: The Menu.rc resource file

image from book
Main_Menu MENU {    POPUP "&File"    {       MENUITEM "&Open...", 101       MENUITEM SEPARATOR       MENUITEM "E&xit", 102    } }
image from book

It is clearly visible that the statements in the image from book Menu.rc file define a simple File menu that has two commands (Open and Exit) and a separator between them. The numbers 101 and 102 at the end of the MENUITEM statements are ID numbers, which are used in the window procedure to identify the selected menu item.

Now that you've created the resource file, you have to compile it into a .res file so that it can be linked into the executable file. To compile the .rc file, you have to use the Resource Compiler (brcc32.exe). To avoid typing the command to compile the resource file over and over again, you should create a batch file to perform the compilation of the resource. Here are the contents of the image from book CompileMenu.bat file that can be used to compile the image from book Menu.rc resource file:

brcc32 Menu.rc

To use the menu from the compiled image from book Menu.res file, you have to include the image from book Menu.res file into the executable using the $R compiler directive and you have to assign the menu's name to the lpszMenuName field of the WNDCLASS:

{$R Menu.res}  wc.lpszMenuName := 'Main_Menu'; { load the menu from the resource }

If you've done everything correctly and recompiled the application, you should be able to see the File menu on your main window.

image from book
Figure 23-4: The main menu loaded from the resource file

Creating Buttons

Buttons, like all other windows, can be created with the CreateWindow function. Actually, there are several predefined classes that can be created with the CreateWindow function. These are: BUTTON, COMBOBOX, EDIT, LIST- BOX, RICHEDIT_CLASS, SCROLLBAR, and STATIC (label).

When you're creating buttons (or other windows), you have to declare an HWND variable that will hold the button's handle and preferably declare a constant with the unique ID value for the button. To create a standard button, you have to call the CreateWindow function and pass the following parameters:

  • Pass "BUTTON" as the lpClassName parameter.

  • Pass a WS_CHILD, WS_VISIBLE, BS_PUSHBUTTON, or BS_TEXT combination as the dwStyle parameter to create a child window that is initially visible, looks like a standard button, and displays text on its surface.

  • Pass the MainWnd variable as the hWndParent parameter to create the button as a child window of the main window.

  • Pass the unique ID value of the button as the hMenu parameter.

The following listing shows how to create the Previous and Next buttons shown in Figure 23-3.

Listing 23-4: Creating the Previous and Next buttons

image from book
const   PREV_BUTTON = 103;   NEXT_BUTTON = 104; var   prevButton: HWND; { handle of the Previous button }   nextButton: HWND; { handle of the Next button } ...   prevButton := CreateWindow('BUTTON', 'Previous', WS_CHILD or     WS_VISIBLE or BS_PUSHBUTTON or BS_TEXT, 10, 115, 75, 25,     MainWnd, PREV_BUTTON, HInstance, nil);   nextButton := CreateWindow('BUTTON', 'Next', WS_CHILD or     WS_VISIBLE or BS_PUSHBUTTON or BS_TEXT, 160, 115, 75, 25,     MainWnd, NEXT_BUTTON, HInstance, nil);
image from book

Handling the WM_COMMAND Message

To implement the functionality of the File menu commands and the two buttons, we have to handle the WM_COMMAND message in the application's window procedure. The control that sends the WM_COMMAND message is specified in the wParam parameter of the message. Thus, to close the application when the user selects File ® Exit, you have to write something like this:

const   FILE_EXIT = 102;   {this must match the menu item ID specified in the .rc file } ... function WindowProc(Wnd: HWND; Msg, WParam, LParam: LongInt): LongInt; stdcall; begin   Result := 0;   case Msg of     WM_COMMAND: begin       case WParam of         FILE_EXIT: SendMessage(MainWnd, WM_CLOSE, 0, 0);       end;        // case WParam     end;          // WM_COMMAND   end;            // case end;

Displaying the Open Dialog Box

To enable the user to extract icons from an EXE file or a DLL, we first have to display the Open dialog box to enable the user to select a file. The API way to display an Open dialog box is somewhat harder than using the TOpenDialog component. To display the Open dialog box in an API application, you have to do the following:

  1. Add the CommDlg unit to the application's uses list to access the necessary records and functions.

  2. Fill an OPENFILENAME record with the appropriate data.

  3. Call the GetOpenFileName function to display the Open dialog box and retrieve the file name.

The following listing contains a function that displays the Open dialog box and returns the selected file name if the user selects OK or an empty string if the user selects Cancel.

Listing 23-5: Displaying the open dialog box

image from book
function OpenFileDialog: string; var   ofn: OPENFILENAME;   fileName: array[0..MAX_PATH] of Char; const   FILTER = 'Executable Files' + #0 + '*.exe' + #0 +     'Dynamic Link Libraries' + #0 + '*.dll' + #0#0; begin   fileName := '';   ZeroMemory(@ofn, SizeOf(OPENFILENAME));   ofn.lStructSize := SizeOf(OPENFILENAME);   ofn.hWndOwner := MainWnd;   ofn.lpstrFilter := FILTER;   ofn.lpstrFile := fileName;   ofn.nMaxFile := MAX_PATH;   GetOpenFileName(ofn);   Result := string(fileName); end;
image from book

The OPENFILENAME record is pretty big and has a lot of fields that are not needed in this application. The ZeroMemory function is used here to set these unused fields to 0.

When displaying the Open dialog box you must specify a valid buffer in the lpstrFile parameter that will receive the file name and properly set the hWndOwner field. To hide unneeded items in the dialog box, you should also specify a valid filter string.

If you look at the local FILTER constant, you'll notice that the filter string in an API application differs from the filter string used by the VCL components. Each part of the filter has to be separated by a null character, not the vertical bar character. Also, the filter string in an API application must be terminated by two null characters.

The above code will only be able to display the Open dialog on NT 5.0 (Windows 2000) and later operating system versions because the OPENFILENAME record in the CommDlg unit contains three NT 5.0-specific fields:

pvReserved: Pointer; dwReserved: DWORD; FlagsEx: DWORD;

If you want to target Windows 98 as well, you cannot pass SizeOf(OPENFILENAME) to the lStructSize field. To have the Open dialog work on Windows 98, discard the size of these three fields from the lStructSize field:

ofn.lStructSize := SizeOf(OPENFILENAME) - SizeOf(ofn.pvReserved)    SizeOf(ofn.dwReserved) - SizeOf(ofn.FlagsEx);

Extracting Icons

The easiest way to extract icons from an executable file is to use the ExtractIcon function:

function ExtractIcon(hInst: HINST; lpszExeFileName: PChar;   nIconIndex: UINT): HICON; stdcall;

The ExtractIcon function returns a handle to the icon, which must be released with a call to DestroyIcon. So, to prevent the possibility of a memory leak, we should create a function that automatically releases the extracted icon before extracting a new one. Since we're going to draw the extracted icon, the function should also refresh the window when a new icon is extracted. The window can be refreshed with a call to the InvalidateRect function.

The following listing displays the entire function that extracts icons from an executable file. Note that the fileName and the icon variables used in the function are global variables. The icon variable contains the handle of the extracted icon, and the fileName variable contains the path and file name of the selected executable.

Listing 23-6: Extracting icons

image from book
procedure DisplayIcon(IconIndex: Integer); begin   if icon <> 0 then     DestroyIcon(icon); { remove the old icon from memory }   if fileName <> '' then     icon := ExtractIcon(HInstance, PChar(fileName), IconIndex);   { refresh the window to display the newly extracted icon }   InvalidateRect(MainWnd, nil, True); end;
image from book

Now that you've created the necessary utility functions, you can implement the File ® Open command, as shown in Listing 23-7.

Listing 23-7: The File Ø Open command

image from book
FILE_OPEN: begin   fileName := OpenFileDialog;   { select a new EXE or DLL }   if fileName <> '' then             { if the user clicked OK }   begin     { pass -1 as the last parameter to get the icon count }     iconCount := ExtractIcon(HInstance, PChar(fileName), Cardinal(-1));     { if there are icons in the EXE, display the first one }     if iconCount > 0 then     begin       currIcon := 0;       DisplayIcon(currIcon);     end else     begin       DisplayIcon(0); { call this to remove the old icon }       MessageBox(MainWnd, 'The application or library ' +         'contains no icons!', 'Message', MB_OK or MB_ICONINFORMATION);     end;        // if iconCount   end;          // if fileName end;            // FILE_OPEN
image from book

The only thing left to do now is handle the WM_PAINT message and draw the extracted icon on the window.

Handling the WM_PAINT message

The WM_PAINT message is sent to the window when all or part of the window should be repainted. If you want to draw in response to the WM_PAINT message, you should not use the GetDC and ReleaseDC functions but rather the BeginPaint and EndPaint functions:

function BeginPaint(hWnd: HWND; var lpPaint: TPaintStruct): HDC; stdcall;  function EndPaint(hWnd: HWND; const lpPaint: TPaintStruct): BOOL; stdcall;

The BeginPaint function prepares the window for drawing and returns a device context handle. When you call BeginPaint to begin drawing on a window, you must call EndPaint to finish drawing.

The following listing shows the WM_PAINT message handler of the Icon Viewer application.

Listing 23-8: Drawing icons and text in response to the WM_PAINT message

image from book
var   selMessage: string;   dc: HDC;   ps: PAINTSTRUCT; ...     WM_PAINT: begin       dc := BeginPaint(MainWnd, ps);       if icon <> 0 then { if we have a valid icon }       begin         SetBkMode(dc, TRANSPARENT);         { select the default system font into the device context }         SelectObject(dc, GetStockObject(DEFAULT_GUI_FONT));         selMessage := Format('Icon %d of %d', [Succ(currIcon), iconCount]);         { display the message using the previously selected system font }         TextOut(dc, 10, 10, PChar(selMessage), Length(selMessage));         { display the icon }         DrawIcon(dc, 10, 40, icon);       end;       EndPaint(MainWnd, ps);     end;       // WM_PAINT
image from book

The Icon Viewer Application

To fully understand how the Icon Viewer application works and where the previously described pieces of code go, you should take a closer look at Listing 23-9 since it contains the fully commented source code of the application.

Listing 23-9: The Icon Viewer

image from book
program Generic; uses   Windows, Messages, SysUtils, CommDlg, ShellAPI; {$R Menu.res} const   MY_CLASS = 'DelphiAPIWindow';   APP_CAPTION = 'Icon Viewer';   FILE_OPEN = 101;   FILE_EXIT = 102;   PREV_BUTTON = 103;   NEXT_BUTTON = 104; var   Msg: TMsg;   MainWnd: HWND;            { the main window's handle }   wc: WNDCLASS;             { the window class }   prevButton: HWND;         { handle of the Previous button }   nextButton: HWND;         { handle of the Next button }   icon: HIcon;              { the selected icon's handle }   iconCount: Integer;       { the number of icons in an EXE }   currIcon: Integer;        { the currently selected item }   fileName: string;         { the file name of the EXE we're viewing }   screenWidth: Integer;   screenHeight: Integer; function OpenFileDialog: string; var   ofn: OPENFILENAME;   fileName: array[0..MAX_PATH] of Char; const   FILTER = 'Executable Files' + #0 + '*.exe' + #0 +     'Dynamic Link Libraries' + #0 + '*.dll' + #0#0; begin   fileName := '';   ZeroMemory(@ofn, SizeOf(OPENFILENAME));   ofn.lStructSize := SizeOf(OPENFILENAME);   ofn.hWndOwner := MainWnd;   ofn.lpstrFilter := FILTER;   ofn.lpstrFile := fileName;   ofn.nMaxFile := MAX_PATH;   GetOpenFileName(ofn);   Result := string(fileName); end; procedure DisplayIcon(IconIndex: Integer); begin   if icon <> 0 then     DestroyIcon(icon); { remove the old icon from memory }   if fileName <> '' then     icon := ExtractIcon(HInstance, PChar(fileName), IconIndex);   { refresh the window to display the newly extracted icon }   InvalidateRect(MainWnd, nil, True); end; function WindowProc(Wnd: HWND; Msg, WParam, LParam: LongInt): LongInt; stdcall; var   selMessage: string;   dc: HDC;   ps: PAINTSTRUCT; begin   Result := 0;   case Msg of     WM_COMMAND: begin       case WParam of         FILE_EXIT: SendMessage(MainWnd, WM_CLOSE, 0, 0);         FILE_OPEN: begin           fileName := OpenFileDialog; { select a new EXE or DLL }           if fileName <> '' then               { if the user clicked OK }           begin             { pass -1 as the last parameter to get the icon count }             iconCount := ExtractIcon(HInstance, PChar(fileName), Cardinal(-1));             { if there are icons in the EXE, display the first one }             if iconCount > 0 then             begin              currIcon := 0;              DisplayIcon(currIcon);             end else             begin              { call DisplayIcon to remove                the old icon and to refresh the screen }              DisplayIcon(0);              MessageBox(MainWnd, 'The application or library ' +                'contains no icons!', 'Message', MB_OK or MB_ICONINFORMATION);             end;        // if iconCount           end;          // if fileName         end;            // FILE_OPEN         NEXT_BUTTON: begin           if currIcon < Pred(iconCount) then           begin             Inc(currIcon);             DisplayIcon(currIcon);           end;         end;             // NEXT_BUTTON         PREV_BUTTON: begin           if currIcon > 0 then           begin             Dec(currIcon);             DisplayIcon(currIcon);           end;         end;          // PREV_BUTTON       end;           // case WParam     end;             // WM_COMMAND     WM_PAINT: begin       dc := BeginPaint(MainWnd, ps);       if icon <> 0 then { if we have a valid icon }       begin         SetBkMode(dc, TRANSPARENT);         { select the default system font into the device context }         SelectObject(dc, GetStockObject(DEFAULT_GUI_FONT));         selMessage := Format('Icon %d of %d', [Succ(currIcon), iconCount]);         { display the message using the previously selected system font }         TextOut(dc, 10, 10, PChar(selMessage), Length(selMessage));         { display the icon }         DrawIcon(dc, 10, 40, icon);       end;       EndPaint(MainWnd, ps);     end;            // WM_PAINT     WM_CLOSE: begin       if icon <> 0 then DestroyIcon(icon);   { release the icon }       DestroyWindow(prevButton);             { destroy the Prev button }       DestroyWindow(nextButton);             { destroy the Next button }     end;            // WM_CLOSE     WM_DESTROY: PostQuitMessage(0); { terminate the app }   else     Result := DefWindowProc(Wnd, Msg, WParam, LParam);   end;             // case end; begin   wc.style := CS_DROPSHADOW;   wc.lpfnWndProc := @WindowProc;   wc.cbClsExtra := 0;   wc.cbWndExtra := 0;   wc.hInstance := HInstance;   wc.hCursor := LoadCursor(0, IDC_ARROW);   wc.hbrBackground := COLOR_BTNFACE + 1;   wc.lpszMenuName := 'Main_Menu'; { load the menu from the resource }   wc.lpszClassName := MY_CLASS;   if RegisterClass(wc) = 0 then Exit;   { create the window and emulate the poDesktopCenter form position }   screenWidth := GetSystemMetrics(SM_CXSCREEN);   screenHeight := GetSystemMetrics(SM_CYSCREEN);   MainWnd := CreateWindow(MY_CLASS, APP_CAPTION,     WS_BORDER or WS_SYSMENU, screenWidth div 2 - 125,     screenHeight div 2 - 100, 250, 200, 0, 0, HInstance, nil);   prevButton := CreateWindow('BUTTON', 'Previous', WS_CHILD or     WS_VISIBLE or BS_PUSHBUTTON or BS_TEXT, 10, 115, 75, 25,     MainWnd, PREV_BUTTON, HInstance, nil);   nextButton := CreateWindow('BUTTON', 'Next', WS_CHILD or     WS_VISIBLE or BS_PUSHBUTTON or BS_TEXT, 160, 115, 75, 25,     MainWnd, NEXT_BUTTON, HInstance, nil);   { use system font on the buttons }   SendMessage(prevButton, WM_SETFONT, GetStockObject(DEFAULT_GUI_FONT), 0);   SendMessage(nextButton, WM_SETFONT, GetStockObject(DEFAULT_GUI_FONT), 0);   { display the main window }   ShowWindow(MainWnd, SW_SHOWNORMAL);   UpdateWindow(MainWnd);  { message loop }   while GetMessage(Msg, 0, 0, 0) do   begin     TranslateMessage(Msg);     DispatchMessage(Msg);   end; end.
image from book

Custom Application Icon and the Manifest File

Once you've finished the application, you should take care of two user interface details: replacing the default application icon with a custom one, and creating the external manifest file to enable the child controls to use XP visual styles.

To change the main icon of the application, open the image from book Menu.rc file and write the following statement to define the MAINICON resource:

MAINICON ICON "Factory.ico"

The above statement uses the Factory icon that can be found in the X:\Pro- gram Files\Common Files\Borland Shared\Images\Icons directory.

After you've added the above statement to the image from book Menu.rc resource file, you should recompile both the resource file and the application. The Delphi compiler will replace the default white application icon with your custom one, but the custom icon won't be displayed on the title bar of the main window. To display the icon on the main window's title bar, you have to use the LoadIcon function to load the icon from the resource file and assign the result of the LoadIcon function to the hIcon field of the WNDCLASS record:

wc.hIcon := LoadIcon(HInstance, 'MAINICON'); 

By passing the global HInstance variable as the first parameter in the LoadIcon function, you're telling the function that the requested resource is stored in the executable file.

The final step in the creation of the Icon Viewer is the manifest file that will render the child controls using the XP visual styles. You actually don't have to create the manifest because the sample manifest file already exists in the X:\Program Files\Borland\BDS\4.0\source\Win32\vcl directory under the name sample.manifest. You only have to copy it to the application directory and rename it as follows:

ApplicationName.exe.manifest

Since the Icon Viewer's executable is image from book Generic.exe, you must rename the manifest file to image from book Generic.exe.manifest, and you're done. The child controls will be rendered using XP visual styles.



Inside Delphi 2006
Inside Delphi 2006 (Wordware Delphi Developers Library)
ISBN: 1598220039
EAN: 2147483647
Year: 2004
Pages: 212
Authors: Ivan Hladni

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net