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

Home
DelphiZeus
16. More Form Windows
More Kinds of Top-Level Windows

Home

Some Other Methods for Top Level Windows

In earlier lessons of DelphiZeus, you have seen some ways to create windows as a Top Level window (Form in a Delphi term). Here I will give some more methods for creating and using some windows that are not a child window on your form's client area. I have shown you how to make a window with the WS_POPUP style in Lesson-6 "Brushes and Pens", and how to create the standard system "Dialog" windows from a templete in Lesson-8 "Using Windows Dialogs". In this lesson I will show some more ways to use windows with the WS_POPUP style, like a "User Information" window. I will also create another main window (Form) that is not a Pop-Up and has it's own Task-Bar button. There are ways to make a "Tool" window, and you can have your program's "Tools" and options on this window. I also show you how to "Dock" three top-level windows, so they move together, as if they are attatched to each other. There are also examples for two API animation effects.

I have explanations and information about several methods for top level windows here. You can read through these below, and you may see something that you would like to try and use. The program code to use for this lesson is in the TopWnd.dpr program code, and the TopWndU.pas unit code below. There are code examples there to show you Pop-Up window creation, docking windows, window animation, and other methods. Pop-Up windows can be very useful, and have many ways that you can configure them for different looks and different actions. In Lesson-6 a pop-up was used for a "Splash" opening window. You can have pop-up information windows, user dialog and "Tool" windows, and docking windows.

Make a Top Level Window
You can create a window after you create your main form window, that does not have a WS_CHILD or WS_POPUP style flag, and has the hWndParent parameter as zero, just like the main form window. This new "Top Level" window will have a different task-bar button than the Main Form. You might use code like this -

hTopForm := CreateWindow(topClass.lpszClassName, 'Top Level Form',
    WS_CAPTION or WS_MINIMIZEBOX or WS_SYSMENU or WS_VISIBLE,
    50, 6, 348, 264, 0, 0, hInstance, nil);
This window is NOT owned, and will act much like the main form window, it is not hidden when the main form is minimized and can be behind (hidden) by the main form. I will call this kind of window an "UnOwned Top-Level" window. If you need to have separate forms (windows) for user interaction, each with it's own task-bar button, you can use this method. However - please Notice that this window does NOT have an Owner (Parent) window, so if the main form is closed and destroyed, it does NOT call for this Top level window to be destroyed, so you should add code that will destroy this Non-Owned top-level window when the main form is destroyed, or when the program stops execution. You can see an example of this in the TopWndU.pas unit code below, in the procedure ShowTopLevel;

Pop-Up Windows

If you include the WS_POPUP style when you create a window and set the hWndParent parameter to your main Form's Handle, then you will get a window that "Pops Up" off of the owner window's client area, so it is Not a child window of the Main Form (placed on it's window area), , but is shown as a top level (Form) window on the screen (you should NOT have the WS_CHILD style flag, if you have a WS_POPUP flag bit). . you saw this done with the Splash window in lesson 6 - Brushes and Pens. -

hSplash := CreateWindow(wClass.lpszClassName,
             'Pop Up Form',WS_POPUP or WS_VISIBLE, 
             100, 50, 300, 230, hForm1, 0, hInstance, nil);
All Pop-Up windows should be "Owned", so you should have a valid window handle in the hParent parameter of CreateWindow( ), , hForm1 in the code above. You can create this Pop-Up window with the "Style" flags you need, like WS_CAPTION and WS_SYSMENU, to make the type of window you want to work with. You should read the Win32 API Help for "CreateWindow" about the WS_POPUP style. All of the Forms (TForms) created with the Delphi VCL have the "Application" window as their parent (owner) and they have the WS_POPUP style. You can see code for this in the procedure ShowPopUpWnd( ) in the TopWnd.pas unit below. The windows system will keep all Pop-Up windows (templete Dialogs also) in Front of the Owner window (window Z order). You can NOT move the Owner window to be in Front of (hide) any of it's Pop-Ups. The system will also handle Pop-Up window visibility if the owner window is minimized, when the owner window is minimized the pop-ups will be hidden, and they will be made visible when the owner is restored. You should notice that the WS_POPUP form windows do NOT normally have a task bar button (but can be set to have one, see below). Also the system will "group together" the owner window and all of it's top-level Pop-Up windows, when the user input focus changes between top-level windows. If any of the Pop-Up windows are clicked or tabed to and get focus, then the whole group of windows (owner and pop-ups) goes to the top of the Z-Order for screen view.

    Technical Note - Top Level windows use the hMenu parameter in the CreateWindow( ) function as a "Menu Handle", , NOT as a control ID number. So if you have the WS_POPUP in your style parameter, you will need the hMenu as zero, or a Handle to a "Main Menu". If you place an ID number there (hMenu), it will NOT create any window, the CreateWindow( ) function will fail, and return zero.

Pop-Up Windows in the Task bar
If you need to have a Pop-Up window with a button on the user's task bar, then you can use the CreateWindowEx( ) function with WS_EX_APPWINDOW in the Ex-Style parameter, (the Win32 Help description of this may not be clear). Code like this -

hTaskBarPopUp := CreateWindowEx(WS_EX_APPWINDOW,fClass.lpszClassName,
      'Task Bar PopUp', WS_OVERLAPPEDWINDOW or WS_POPUP or WS_VISIBLE,
      68, 100, 220, 140,hForm1, Zero, hInstance, nil);
You can see code for this in the procedure ShowPopUpWnd( ) in the TopWnd.pas unit below. Even though this PopUp form has a task bar button, it will be hidden when the owner is minimized, and other wize behave like other PopUps. If you need a "Separate" top-level window, that is not hidden by minimize then you will need to create an "UnOwned" top-level window.

  Top-Level WS_POPUP Windows Minimize - When your program with pop-up windows is running, you should be sure to "Minimize" these pop-ups to see where the small "Iconic" minimized window blocks are placed. The system will do all of the graphical display for this minimize action, a "Main Form" (first top-level created) goes to the task-bar button, a normal pop-up goes to the lower left corner of the work area on minimize, if there is a WS_EX_APPWINDOW in the ExStyle then it will go to it's task-bar button.

Tool Windows
If you use the CreateWindowEX( ) function, you can also include the WS_EX_TOOLWINDOW ex-style and get a window with a smaller "Tool Window" caption, like this code -

hToolForm := CreateWindowEx(WS_EX_TOOLWINDOW,formClass.lpszClassName,
          'Tool', WS_CAPTION or WS_SYSMENU or WS_POPUP or WS_VISIBLE,
          100, 60, 228, 150, hForm1, 0, hInstance, nil);
You can see some example code for this in the procedure ShowToolWnd; for the program code below. You can create these Pop-Up windows as "Helper" windows or "Tool" access windows, however, the templete Dialogs can be better for this sometimes.

  • You should experiment and use this WS_POPUP style to see what you can do with it.


IsDialogMessage( ) with more than one Top-Level
In the past lessons I did not create any Pop-Up windows that were on screen when the Main Form window was visible (a splash screen). And in those programs I had the line of code -
    if not IsDialogMessage(hForm1, MainMsg) then
in the GetMessage loop to allow Tab key navigation of the controls. Please notice that this code has hForm1 as the "handle of dialog box" so it only processes messages that are for the main Form. But what does it do if you have other "Top-Level" form windows? It does NOT process the dialog key input for any other window, so the other top-level windows will NOT have Tab-Key navigation. What to do? You could place another IsDialogMessage(hPopUp1, MainMsg) for every PopUp and top-level window you make. But this is not efficient coding. You also might have a variable to set to the handle when ever a top-level window is activated, and place that in the hWnd parameter. But this can get tricky and not so easy. . . What I would recommend, is to add the API GetActiveWindow function to the hWnd parameter, like this -

while GetMessage(mainMsg,Zero,Zero,Zero) do
  if not IsDialogMessage(GetActiveWindow, mainMsg) then
    begin
    TranslateMessage(mainMsg);
    DispatchMessage(mainMsg);
    end;
This GetActiveWindow function will get the handle of the top-level window (in that thread) that currently has keyboad focus, just what you need! And this is a simple and effective way to have Tab-Key navigation in all top-level windows.


Docking Top-Level Windows
You may not ever need this, but if you start to create Pop-Up and Tool windows, at some point you will want to keep some sort of "Order" to the arrangement of the Pop-Ups to the Main Form. Maybe "Dock" or "Attatch" a pop-up to the main form. You could get the WM_MOVING message (or WM_WINDOWPOSCHANGING message) and then move the other windows according to the message parameters. But this might result in chopy, jerky, uncorordinated visual movement, since the message processing for many forms may get slightly off timing. As the window position for each form is changed by your code, and the screen will be updated (painted) several times. There are some API functions that help to "Smooth" out visual appearence of top-level syncronized movement.The API functions -
  BeginDeferWindowPos( )
  DeferWindowPos( )
  EndDeferWindowPos( )
can help improve visual aspects of several form's movement at the same time, by changing all the windows positions before updating the screen only once. To use this method, you will need to call the the BeginDeferWindowPos( ) function first, and get the handle for this system move operation. In the TopWnd.pas unit code below, I have this move operation in the WM_MOVING message of the main form. Next you must call the DeferWindowPos( ) function once for each window that will be moved, you will need to get the handle returned by this function, and use it in the next call to this function. Once you have entered the window information for all of your windows with the DeferWindowPos( ) function, you must call the EndDeferWindowPos( ) function to have the system move all of the windows.
Below is some example code that might be used, I have 2 forms, hForm1 and hPopForm, and there is an "OffLeft" and "OffTop" integer variable, which has the OffSet of the hPopForm to the hForm1. These two OffSets must be set to the plus or minus position that you want the hPopForm to be relative to hForm1. This is in the WM_MOVING message of hForm1. -

var
pRect1: PRect;
hDwp1: Cardinal;

WM_MOVING: begin
  pRect1 := PRect(LParam);
  hDwp1 := BeginDeferWindowPos(2); // set for two windows movement
  hDwp1 :=DeferWindowPos(hDwp1,	// handle to system Defer structure
    hForm1,   // handle to first window to position
    HWND_BOTTOM, // Z-order handle, NOT used since SWP_NOZORDER
    pRect1.Left, // new horizontal position
    pRect1.Top,  // new vertical position
    1,	// width, NOT used here because of SWP_NOSIZE
    1,	// height
    SWP_NOSIZE or SWP_NOZORDER or SWP_NOACTIVATE // flags
   );

  //you need to call DeferWindowPos once for each window allocated
  //in the BeginDeferWindowPos(2) function
  hDwp1 := DeferWindowPos(hDwp1, hPopForm, HWND_BOTTOM,
  pRect1.Left-OffLeft, // use an OffSet position for the "Other" form
  pRect1.Top-OffTop, 
  1, 1,SWP_NOSIZE or SWP_NOZORDER or SWP_NOACTIVATE);
  // be sure to get the NEW hDwp1 handle from DeferWindowPos above for
  EndDeferWindowPos(hDwp1);
  Result := 1;
  Exit; // you must exit so DefWindowProc is NOT called
  end;
In my code below I have two windows docked to the main form. These windows are created in the DoDocking procedure, since I want these two windows to be "Display Only" and never get keyboard focus, I have added code for this. The doMoving( ) function has the Defer Window functions, and uses an "Array of Records" with window information for all three windows that are moved together, an array called aryDock.

A System Control as a Pop-Up Window
You can add the WS_POPUP flag to the creation style of a system control (an leave out the WS_CHILD flag). This will make a control that is a Top Level window, and will NOT be on the client area of a window. Like this code the create a Pop-Up List Box -

hListBox := CreateWindow('LISTBOX', 'LB 1',
                       WS_POPUP or WS_VSCROLL or WS_BORDER,
                       100, 100, 46, 70, hForm1, 0, hInstance, nil);
This will allow you to "Pop Up" a separate temporary system control as a window, that can hover 'over or beside' some other control, item or object that needs user input from that pop-up control. You see this control behaviour with the Combo-Box "Drop Down" List-Box, and the "List View" control (as used in the window's Explorer) when you are changing the display name of a "List View" item, (a system "EDIT" control, "Pops Up" as a top level window over the List-View item, and is used to get user text input to re-name item). Most of the time you will need to Sub-Class this pop-up system control, also many times you will need this to be a very "Temporary" input window, that will vanish if you click on anything else on screen.

Temporary Pop-Up Windows
There may be times when you want a "Temporary" pop-up window, which vanish if something else is clicked or focus is moved, like a menu, I call it the "Menu Vanish" action. To have this Menu Vanish action, does NOT require you to get mouse input for all other windows. All Top-Level windows get notification when they get or loose activation (keyboard focus) with the WM_ACTIVATE message. So if you add code in the window's Window Proc function for the WM_ACTIVATE message, and test for the WA_INACTIVE in the LOWORD(wParam), this will tell you that this top-level window is loosing keyboard focus and needs to be closed and destroyed. You can add code needed to finalize the user input from this top-level window. Like this code -

case Msg of
  WM_ACTIVATE: if LOWORD(wParam) = WA_INACTIVE then
    PostMessage(hWnd, WM_CLOSE, 0, 0);
This is a very useful method for many kinds of "Temporary" windows, like pop-up user information and reminder windows and quick input dialog windows. You can see methods for these in the TopWndU unit code below, a Pop-Up system List Box in the procedure DoPopListBox; and that list box's sub-classed Window Proc in the function LBFunc( );. . In the DoDocking procedure a system STATIC control is shown as a "Reminder", it is sub-classed so it is a temporary menu vanish action window.

Another way to Sub-Class a System Control
In Lesson-7 "Button and Edit Controls" , you were shown how to Sub-Class a system control with the SetWindowLong( ) function, which returned a pointer to the system's Window Proc function for that class of control. This is a very useful method for control message handling. But there is another way to "Sub-Class" a system control, and this method sets the "Windows Class" for that control as a "Local Class" in your program. This method does not really create a "System" control window, but creates a "Local" class window that will act like the control. This is the method that Delphi VCL uses to Sub-Class all of it's windows system controls. The API GetClassInfo( ) function will fill a TWndClass variable with the Class information for any registered windows class. You can call GetClassInfo( ) with the system class name for the control you want to Sub-Class, like 'LISTBOX'. Now you will need to change several elements of this windows Class record to be used by your code. First, you MUST change the TWndClass.hInstance to the hInstance of your program. Next you will need to get the control's system "Window Proc function", the TWndClass.lpfnWndProc memory address, into a Pointer variable, , and then set this lpfnWndProc to the address of your local message handling function. You will need to remove the CS_GLOBALCLASS style flag from the system TWinClass.style ( and remove other style flags that are not needed ). And it is a good idea to change the Class name in the TWinClass.lpszClassName, so if you call that system class name later, it will get the system class, and not the local class. Now you can register this win class with the system as a local class with the RegisterClass( ) function. You will have your local Window Proc function for this control with the default Result return of -
    Result := CallWindowProc(pSystemFunc, hWnd, Msg, wParam, lParam);
(just like the Sub-Class of Lesson-7) , so the control will get all of it's functioning from the system message handling and act like that kind of control. Some code to do this -

var
lbClass: TWinClass;
pSysFunc: Pointer;

  begin
  GetClassInfo(0, 'LISTBOX', lbClass);
    // use zero for system hInstance above
  lbClass.hInstance := SysInit.hInstance;
    // you MUST change the lbClass.hInstance from zero
  lbClass.style := lbClass.style and not CS_GLOBALCLASS;
    // remove Global class from the style flags
  pSysFunc := lbClass.lpfnWndProc;
  lbClass.lpfnWndProc := @LBFunc;
  lbClass.lpszClassName := 'LB CLass';
    // rename Class name from 'LISTBOX'
  RegisterClass(lbClass);

  hListBox := CreateWindow(lbClass.lpszClassName, 'LB 1', 
                           WS_VISIBLE or WS_VSCROLL, 8, 8, 82, 130,
                           hForm, Zero, hInstance, nil);
  end;
For most controls that you may want to Sub-Class, you will not need to use this extra method, however, if you need to change some property of the control's "Win Class", (which the system does not allow for a system Win Class, like the Style), then you may want to try this method. You can see the some code used for this method of Sub-Class, if you look at the procedure DoPopListBox; in the TopWndU unit code below.

Invisible Message Windows
The Delphi VCL will create invisible top-level windows (never seen by the user), the "Application" window (Delphi's "Main Window") is an invisible top level window. These unseen windows are used to get system messages and process these messages. In Delphi, other non-visible top level "Message Windows" are created for certain tasks (for instance to get menu messages). If you have a need for an invisible window that is used to receive system message, you can just create one and have it be hidden. There may be times that this window needs to be "Visible" to the system, but invisible to the user. In that case you can leave out the WS_CAPTION style flag and have the window width and height as zero, since the width and height are zero, the user can never see the window. (If the WS_CAPTION flag is there, the system will automatically give the window minimum width and height, so it will bee seen). If you need your main form or an invisible top-level window to NOT have a task-bar button, you can include the WS_EX_TOOLWINDOW flag in the Ex-Style parameter of CreateWindowEx( ). I show you an example of this in the procedure MakeInvisible; in the TopWndU.pas unit code below.

API Animation Effects for Windows
I will tell you about two window motion effects (animation) that the system makes availible as the API functions. These functions give you movement effects without any code work to do the timer and movement sequences.

DrawAnimatedRects Function -
The first is the DrawAnimatedRects( ) function, which will make a growing or shrinking "Moving Rectangle" on the screen, it draws a wire-frame rectangle and animates it to indicate the opening of an icon or the minimizing or maximizing of a top level window. This animation effect is what the system shows if you minimize or 'restore from the taskbar' a top level window, the window will look like it is moving to or from the taskbar. This effect is sometimes useful for "Pop-Up" information windows from a button or image click event, but is not generally useful, or used much.

function DrawAnimatedRects(
   hWnd: HWND; // handle of top-level window with caption text
   idAni: Integer; // must be IDANI_CAPTION
   const rectFrom: TRect; // rectangle location for begin animation
   const rectTo: TRect // rectangle location for end of animation
   ): BOOL; // returns true if successful
The parameters for this function are -
  1. hWnd: • This is the handle for the window that has the "Window Text" to be used. This is the window that the Caption bar text will be shown in the rectangle animation.
  2. idAni: • No Use Parameter, must be set to IDANI_CAPTION.
  3. rectFrom: • A TRect that gives the screen location for where the animation will start.
  4. rectTo: • A TRect that gives the screen location for where the animation will finish.
The idAni flag has three values listed, but only the IDANI_CAPTION seems to work. The other values are - IDANI_OPEN and IDANI_CLOSE - which do not do anything. The two TRect parameters are for screen co-ordinants, the height (TRect.Bottom) of these rectangles do not seem to matter much, but the Left-Top position, and the Right will make a difference.

For top level windows, use the IDANI_CAPTION flag, and have the rectTo or rectFrom TRect as the window Rectangle for that window. This function does NOT affect the window in the hWnd parameter, it does NOT hide or show that window, or move that window. You will almost always need to show or hide this window with your own code. Code example below -

var
  hPopForm: Integer; // hidden form window handle
  hButton: Integer; // button handle
  toRect, fromRect: TRect;

begin
GetWindowRect(hPopForm, toRect);
GetWindowRect(hButton, fromRect);
DrawAnimatedRects(hPopForm, IDANI_CAPTION, fromRect, toRect);
ShowWindow(hPopForm, SW_SHOW);
end;
You can see an examole for this in the TopWndU.pas unit code below, in the procedure ShowTopLevel;

Technical Note - The IDANI_OPEN and IDANI_CLOSE flags, used alone, would not ever work for me, it would show nothing that I could see on screen. The window's API help indicates that the hWnd has something to do with a "Clipping" area, but I saw NO evidence of this clipping. Also it says that you can use zero as the hWnd parameter, but this did NOT work for me, there was nothing visible on screen. Also, this DrawAnimatedRects( ) function did not work for me if I placed a WS_CHILD window handle in the hWnd parameter (like a button handle). If you want this animation effect for a child window, you can use your main form handle (hForm1) in the hWnd parameter, since this function has NO effect on that window and is not clipped to that window.


AnimateWindow Function -
The next API moving effects function is AnimateWindow( ) function, which is used to show or hide a window (top level or control) by using animation : to have a slide, roll, or collapse movement effect (or a fading "blend" effect). The system will do all of the animation sequence image production, and all of the screen image placement calculations, and execute the "Timer" sequence to show these images. This means that you can have an animation effect and not create a single image, not calculate a single movement position, and not execute a timer to run the animation. You can have an animation effect for a window with a single line of code. The function definition is -

function AnimateWindow(
   hWnd: HWND; // handle of window to show or hide
   dwTime: DWORD; // speed of the animation
   dwFlags: DWORD // flags for animation type and direction
   ): BOOL; // returns True if successful
This function will return True is it is succesful.

The hWnd parameter is the handle of the window that will be made visible or hidden.

The dwTime parameter sets the "Speed" of the animation movement. The API help documents say this value is for microseconds to play the animation. I am not sure about that, but I do know that the lower this value is, the faster the animation effect. I would suggest you begin with a default value of 300 for dwTime, and run the animation so you can see it, and then increase or decrease this value until the speed of the effect is what you like to see. I use a dwTime value of 320 in my TopWndU.pas program code below. This movement speed seems to be consistant (look the same speed) with different operating systems (Win 98, Win 2000, Win XP), and different hardware (CPU speed, Video card). But for movement effects (not blend-fade effects), the size of the window that is animated makes a real difference in how you set this dwTime, smaller windows use smaller time of movement (the blend-fade effect is different).

The dwFlags parameter is set for the kind of effect (movement, direction, fade) shown. Please note that unlike some other functions, this function has Two default settings that are NOT listed in the flag values, the first is AW_SHOW, so by default this will show a window. The next is AW_ROLL, by default this will use the "Roll" animation. This means that if you want to show the window or use the roll animation, you place nothing for these in the the dwFlags parameter, it will show the window with a roll effect. Please Note - that if the AW_ROLL or AW_SLIDE are used, you MUST have a direction flag, if there is no direction flag the function fails. Also, the direction flags are ignored for the AW_CENTER and AW_BLEND effects.

The listed dwFlags values for this are -

AW_HIDE - Hides the window. By default, the window is shown. You must include this when you want to hide the window.

AW_ACTIVATE - Activates the window when shown. Do not use this value with AW_HIDE.

AW_SLIDE - Uses the slide animation. By default, roll animation is used. This flag is ignored when used with AW_CENTER.

The Follwing Four Flags Are Only read for the AW_ROLL and AW_SLIDE

AW_HOR_POSITIVE - Animates the window with a horizontal "left to right" movement.

AW_HOR_NEGATIVE - Animates the window with a horizontal "right to left" movement.

AW_VER_POSITIVE - Animates the window with a vertical "top to bottom" movement.

AW_VER_NEGATIVE - Animates the window with a vertical "bottom to top" movement.

AW_CENTER - Makes the window appear to expand outward from the window center point when shown, or collapse inward if AW_HIDE is used. The various direction flags have no effect.

Newer Effect
  AW_BLEND - Uses a fade blending effect. This flag can be used ONLY if the window is a top-level window, AND, the operating system is windows 2000 or newer, supporting the top level window alpha blend grahpic functions.


Using this function can add some animation effects to your program for windows that you show or hide, and the system will usually produce effects that look good. For any "System" control (Button, listBox, Edit), you will see the full complete window in the effect. However, one visually "Bad" thing for these effects on "Non-System" windows, is that the system does NOT do any WM_PAINT message drawing on a "non-system" window, so any text, rectangles, polygons, or bitmaps in your WM_PAINT operation will NOT be seen on the effects window screen display, even when the animation is finished and that window is in full view.



TopWnd Program

Here is the code for this TopWnd program. First I have the .DPR program code in the program TopWnd; . It is like the dpr code you have seen before here at DelphiZeus. The unit code for TopWndU.pas is next.

program TopWnd;

uses
  TopWndU in 'TopWndU.pas';

{$R *.RES}
{$R Theme.RES}
  // Theme.RES for XP Themes, you can leave it out 
begin
if MakeForm then
  DoMsgLoop;
end.

In this TopWnd.pas code, I create the main form in the function MakeForm: Boolean;, , and I create the main form controls (buttons) in the procedure MakeControls;. The form and the buttons on it are created with methods I have used in lessons before, so I will not say anything about that.

The first four functions are used for the docking of 2 pop-up windows to the main form. The DoDocking procedure creates two pop-up windows on the left and right sides of hForm1, it fills the aryDock array with the docking information. The window's handles and position offSets are put in the array. Since I want these to be "Display Only" top-level windows, I must add code to be sure that they NEVER get keyboard focus, so I use the SetWindowPos( ) function to show them, without giving them focus. In DoDocking a user "Reminder" window is created that is a system STATIC control, this is sub-classed into the STFunc function, so it will be destroyed when it looses focus.
The doMoving( ) function is where the docked windows are syncro-moved with the DeferWindowPos function, this function is called from the WM_MOVING message of hForm1.

The DoPopListBox procedure will create a window that acts like a system List-Box control, here I show you a way to make a local window that acts like a system control with another way to "Sub-Class" using the GetClassInfo( ) function. This is one of several places I have code to animate a window with the AnimateWindow( ) function.

The other functions and procedures are mentioned in the explanations above, but you should be able to tell what they do from their names. You should notice that I use the same window class formClass for many of the top-level windows created here, so the Window-Proc in the MsgFunc( ) function has code for several different windows.

  Code for the TopWndU.pas unit -

unit TopWndU;
 {this TopWndU unit has code examples for creating differen Top-Level windows.
  Like pop-up windows, tool windows, and docked windows}

interface

function MakeForm: Boolean;
  // the MakeForm function creates the main form and all of it's controls

procedure DoMsgLoop;
  // the DoMsgLoop procedure runs the GetMessage Loop

implementation

uses
  Windows, Messages, CommCtrl, SmallUtils;


type
  {TDock is the record that is used to store information used to
   dock 3 windows together in the doMoving( ) function}
  TDock = Record
    Count, hWnd: Cardinal; // docked window Count and Handle for window
    OffLeft, OffTop: Integer; // offset of the window position
    end;


const
Zero = 0;

// below are ID numbers for control windows
ID_PopUpBut = 100;
ID_ToolBut = 101;
ID_TopBut = 102;
ID_DockBut = 103;
ID_SliderBut = 104;
ID_EditBut = 105;


ID_NewToolBut = 110;
ID_TaskBarBut = 111;
ID_Edit = 112;
ID_PopListBut = 113;

ID_Combo = 300;
ID_Radio1 = 301;
ID_Radio2 = 302;
ID_ListBox = 303;

dockRect: TRect = (Left: 5;Top: 24;Right: 47; Bottom: 38);
{dockRect has the size of the Invalidate area for the two docked windows}


var
formClass, topClass: TWndClass;
VarFont, hForm1, hPopUpForm, hToolForm, hTopForm, hNoShowForm, hAnimate,
hListBox: Integer;

aryDock: Array[Zero..2] of TDock;
  // aryDock is used for the Docking windows in the doMoving function
fmLeft, fmTop: Integer;
  // fmLeft and fmTop record the position of the main form, to be text on docked forms
ComboText: Array[Zero..2] of Char;
  // ComboText is the 2 text characters shown in the combo box
pListBoxFunc: Pointer = nil;
  // pListBoxFunc has the system List Box Window Proc
pStaticFunc: Pointer = nil;
  // pStaticFunc has the system Static Window Proc
fromRect, toRect: TRect;
  // fromRect, toRect are TRect used for window animation


// // / / /  the four functions below are for the Docking windows

function doMoving(pRect: PRect): Boolean;
var
hDwp1, i: Integer;
begin
{this function is called by the WM_MOVING message of hForm1.
 if the aryDock[Zero].Count is zero then the windows are NOT docked}
if aryDock[Zero].Count <> Zero then
  begin
  Result := True;
  hDwp1 := BeginDeferWindowPos(3);
   { to move windows in the same "refresh" paint, you can use
   the BeginDeferWindowPos( ) function }
  for i := Zero to aryDock[Zero].Count-1 do
    begin
  {the pRect comes from the WM_MOVING message, and has the new hForm1 position.
   You must call DeferWindowPos( ) for each window to be moved. Be sure to
   update the hDwp1 with the result of DeferWindowPos}
    with aryDock[i] do
    hDwp1 := DeferWindowPos(hDwp1, hWnd, HWND_BOTTOM,
        pRect.Left+OffLeft,
        pRect.Top+OffTop, 1, 1,
        SWP_NOSIZE or SWP_NOZORDER or SWP_NOACTIVATE);
    end;
  EndDeferWindowPos(hDwp1);
  { when the EndDeferWindowPos( ) is called, the windows are supposed
    to be moved together , and repainted in the same cycle}
  fmLeft := pRect.Left;
  fmTop := pRect.Top;
  // fmLeft and fmTop are used to display X an Y position on docked windows
  for i := 1 to 2 do
    InvalidateRect(aryDock[i].hWnd, @dockRect, True);
  end else 
  Result := False;
{I use the result of this function in the WM_MOVING message, in order to have
 the EndDeferWindowPos function work correctly, you MUST BLOCK the WM_MOVING message}
end;


function DockMsgFunc(hWnd,Msg,wParam,lParam:Integer): Integer; stdcall;
var
PaintS: TPaintStruct;
aStr: String;
pPos: PChar;
begin
{this is the Window Proc for the two docked windows}
Result := Zero;
case Msg of
{If you want to have "Display Only" windows you must get and process the
 WM_ACTIVATE and WM_MOUSEACTIVATE messages. I want the two docked windows
 to be for display of user information ONLY, these windows should NEVER have
 keyboard focus. If they are mouse clicked or tabed to, then they must set
 the keyboard focus to the main form hForm1}
  WM_ACTIVATE:
    if (LOWORD(wParam) = WA_ACTIVE) or (LOWORD(wParam) = WA_CLICKACTIVE)then
      begin
    {test to see if the WM_ACTIVATE message is for WA_ACTIVE, and then call for
     focus to go to hForm1 with the SetActiveWindow}
      SetActiveWindow(hForm1);
      Exit; // be sure to exit and block the WM_ACTIVATE message
      end;

  WM_PAINT: begin
    //for user information I display the Top and Left position of hForm1
    BeginPaint(hWnd, PaintS);
    SetBkMode(PaintS.hDC, TRANSPARENT);
    SelectObject(PaintS.hDC, VarFont);
    if hWnd = Integer(aryDock[1].hWnd) then
      begin
      pPos := 'Left';
      aStr := Int2Str(fmLeft);
      end else
      begin
      pPos := 'Top ';
      aStr := Int2Str(fmTop);
      end;
    TextOut(PaintS.hDC, 6, 8, pPos, 4);
    TextOut(PaintS.hDC, 6, 24, PChar(aStr), Length(aStr));
    EndPaint(hWnd, PaintS);
    Exit;
    end;

  WM_MOUSEACTIVATE: begin
   {if you set the Result to MA_NOACTIVATEANDEAT, then the mouse click does
    NOT set the focus}
    Result := MA_NOACTIVATEANDEAT;
    SetActiveWindow(hForm1); // you still have to set hForm1 to keyboard focus
    Exit; // be sure to exit and block the WM_ACTIVATE message
    end;
  end;

Result := DefWindowProc(hWnd,Msg,wParam,lParam);
end;


function STFunc(hWnd,iMsg,wParam,lParam: Integer):Integer; stdcall;
begin
{ this is the Window Proc for the pop-up STATIC control.
  this needs to be a "Temporary" window, so I close it when it looses focus}
if (iMsg = WM_ACTIVATE) and (LOWORD(wParam) = WA_INACTIVE) then
  PostMessage(hWnd, WM_CLOSE,Zero,Zero);

Result := CallWindowProc(pStaticFunc,hWnd,iMsg,wParam,lParam);
end;


procedure DoDocking;
var
dockClass: TWndClass;
fmRect: TRect;
i, hInfoForm: Integer;
mMsg: TMSG;
begin
{the DoDocking procedure will create or destroy the two docking windows.
 First I test the aryDock[Zero].Count for zero, if zero then no dock windows exist}
if aryDock[Zero].Count = Zero then
  SetDlgItemText(hForm1, ID_DockBut, 'Hide Dock Forms')
  else begin
  // if count is more than zero then I need to destroy the windows
  SetDlgItemText(hForm1, ID_DockBut, 'Show Dock Forms');
  for i := 1 to 2 do
    DestroyWindow(aryDock[i].hWnd);
  UnRegisterClass('Docker Class',hInstance);
  aryDock[Zero].Count := Zero; // set count to zero for no docking
  Exit;
  end;

// I now Register the Class and create two dock windows
ZeroMemory(@dockClass, SizeOf(dockClass));
with dockClass do
  begin
  Style := CS_PARENTDC or CS_BYTEALIGNCLIENT;;
  hInstance := SysInit.hInstance;
  hIcon := LoadIcon(hInstance,'MAINICON');
  lpfnWndProc := @DockMsgFunc;
  hbrBackground := GetStockObject(WHITE_BRUSH);
  lpszClassName := 'Docker Class';
  hCursor := LoadCursor(Zero,IDC_ARROW);
  end;

if RegisterClass(dockClass) = Zero then
  begin
  MessageBox(hForm1,'ERROR - Dock Class could NOT be registered','No Class',MB_ICONERROR);
  Exit;
  end;

GetWindowRect(hForm1,fmRect);
// I use the position of hForm1 in fmRect to set where the two dock windows go
fmLeft := fmRect.Left;
fmTop := fmRect.Top;

{the aryDock has three elements, the first one is used for hForm1, index 1 and 2
 are used for the 2 dock windows, their handles and position OffSets}
aryDock[1].hWnd := CreateWindow(dockClass.lpszClassName, 'DFL',
             WS_POPUP or WS_BORDER, fmRect.Left-48,fmRect.Top + 37,
             48, 48,hForm1, Zero, hInstance, nil);
// do NOT create these windows with the WS_VISIBLE, you do NOT want them to get focus

aryDock[2].hWnd := CreateWindow(dockClass.lpszClassName, 'DFR',
             WS_POPUP or WS_BORDER, fmRect.Right,fmRect.Top + 87,
             48, 100,hForm1, Zero, hInstance, nil);

aryDock[1].OffLeft := -48;
aryDock[1].OffTop := 37;
// set the offSets to the relative position to hForm1

aryDock[2].OffLeft := fmRect.Right-fmRect.Left;
aryDock[2].OffTop := 87;

aryDock[Zero].Count := 3; // set count to a non-zero to have the docking executed

{these 2 docked windows are for user info ONLY, they should never get keyboard focus.
 So I use the SetWindowPos( ) to show these window, and they are not activated}
for i := 1 to 2 do
SetWindowPos(aryDock[i].hWnd, hForm1, 1, 1,1,1,
        SWP_NOSIZE or SWP_NOMOVE or SWP_NOACTIVATE or SWP_NOCOPYBITS or SWP_SHOWWINDOW);
while PeekMessage(mMsg, Zero, Zero, Zero, PM_REMOVE) do
  DispatchMessage(mMsg);  // allows painting of the dock windows

// the next code will make a temporary pop-up STATIC information window
GetWindowRect(GetDlgItem(hForm1, ID_DockBut),fmRect);
hInfoForm := CreateWindow('STATIC', 'Move Main Form to'#10'see Docking effect',
             WS_POPUP or WS_BORDER or SS_CENTER, fmRect.Left,fmRect.Bottom,
             fmRect.Right-fmRect.Left, 31, hForm1, Zero, hInstance, nil);
SendMessage(hInfoForm,WM_SETFONT,VarFont,Zero);
pStaticFunc := Pointer(SetWindowLong(hInfoForm, GWL_WNDPROC, Integer(@STFunc)));
// subClass this static control to get the WM_ACTIVATE message to close it
AnimateWindow(hInfoForm, 240, AW_SLIDE or AW_VER_POSITIVE or AW_ACTIVATE);
end;

// // / / /  the four functions above are for the Docking windows


function LBFunc(hWnd,iMsg,wParam,lParam: Integer):Integer; stdcall;
var
Index1: Integer;
aText: Array[Zero..7] of Char;

  procedure ChangeEdit;
  begin
  // the ChangeEdit procedure gets text from list box, and puts it in the edit
  Index1 := CallWindowProc(pListBoxFunc,hWnd,LB_GETCURSEL,Zero,Zero);
  if not (Index1 in [Zero..10]) then Index1 := Zero;
  aText := '100'#0;
  CallWindowProc(pListBoxFunc,hWnd,LB_GETTEXT,Index1, Integer(@aText));
  SetDlgItemText(hTopForm, ID_Edit, aText);
  SetFocus(GetDlgItem(hTopForm, ID_Edit)); //will close this list box
  end;


begin
// this is the Window Proc for the Pop-Up List Box
case iMsg of
  WM_ACTIVATE: if LOWORD(wParam) = WA_INACTIVE then PostMessage(hWnd, WM_CLOSE,Zero,Zero);
    { I want this list box to have the "Menu Effect" and Close, if it loses
      keyboard focus. I test for WA_INACTIVE in the WM_ACTIVATE message
      (happens when keyboard focus is lost) and poat the WM_CLOSE message}

  {I also want this List Box to change the edit and close if the user
     clicks on a section, like a combo Box List Box does. This is
     done in the WM_LBUTTONUP with ChangeEdit procedure}
  WM_LBUTTONUP: ChangeEdit;

  WM_KEYDOWN: if wParam = VK_RETURN then ChangeEdit; // close with Return Key

  WM_GETDLGCODE: begin
    // needs all keys for VK_RETURN above
    Result :=  DLGC_WANTALLKEYS;
    Exit;
    end;

  end;

Result := CallWindowProc(pListBoxFunc,hWnd,iMsg,wParam,lParam);
end;


procedure DoPopListBox;
var
fRect: TRect;
c: Cardinal;
lbClass: TWndClass;
begin
{this procedure will create a Pop-Up List Box control, that is NOT a Child
 control on a form window, I show you a method for system control creation
 that is another way to "Sub-Class" a system control using the GetClassInfo( )
 function to get the system Window Proc and Class parameters}
GetWindowRect(GetDlgItem(hTopForm, ID_Edit), fRect);

{below is another way to "Sub-Class" a system control, this is the method the
 Delphi VCL uses to subclass it's system controls. The sub-class method below
 makes a control that is NOT a system control window, but will fuction and act
 just like a system control. You have a few more options in changing this control
 than you would have with a system control}

if pListBoxFunc = nil then // test to see if the class has already been registred
  begin
  {first use GetClassInfo( ) to get the win Class properties for the
   system control, 'LISTBOX' in this case, into lbClass}
  ZeroMemory(@lbClass, SizeOf(lbClass));
  if not GetClassInfo(Zero, 'LISTBOX', lbClass) then
    begin
    MessageBox(hTopForm, 'ERROR - GetClassInfo', 'ERROR', MB_ICONERROR);
    Exit;
    end;

  { unlike a system control, you CAN change some of the "Class" parameters. Next
    you MUST change the elements of the lbClass to fit a Class for this hInstance.
    You can NOT change the Class "Style" in system controls,
    but you can change this lbClass.style}
  lbClass.hInstance := hInstance; // ALWAYS change the hInstance
  lbClass.Style := lbClass.style and not (CS_CLASSDC or CS_PARENTDC or CS_GLOBALCLASS);
  // you MUST remove style flages that can cause problems or are un-needed
  pListBoxFunc := lbClass.lpfnWndProc;
  // record the system window proc in the pListBoxFunc pointer variable for the LBFunc
  lbClass.lpfnWndProc := @LBFunc;
  // now set the lpfnWndProc to your local Window Proc function
  lbClass.lpszClassName := 'LB CLass';
  { be sure to change the Class name from 'LISTBOX' to your own name, or you will
    have problems with the standard control class name of 'LISTBOX' later}
  lbClass.hCursor := LoadCursor(Zero,IDC_HAND);
  // I have changed the class Cursor to the system Hand cursor
  RegisterClass(lbClass);
  end;

hListBox := CreateWindow('LB CLass', 'LB 1', WS_POPUP or WS_VSCROLL or WS_BORDER,
               fRect.Left, fRect.Bottom, 46, 70, hTopForm, Zero, hInstance, nil);
// remember, you can NOT have a control ID number in the hMenu parameter of Pop-Up

// code below could be used to make a pop-up list box, but this is a system window -
{hListBox := CreateWindow('LISTBOX', 'LB 1', WS_POPUP or WS_VSCROLL or WS_BORDER,
            fRect.Left, fRect.Bottom,46,70,hTopForm,Zero,hInstance,nil);
pListBoxFunc := Pointer(SetWindowLong(hListBox, GWL_WNDPROC, Integer(@LBFunc))); }

if hListBox = Zero then
  begin
  MessageBox(hTopForm, 'ERROR - CreateWindow for PopUp List Box FAILED',
             'ERROR List Box Create', MB_ICONERROR);
  Exit;
  end;

SendMessage(hListBox,WM_SETFONT,VarFont,Zero);
// next items are added to list box
for c := 1 to 6 do
  SendMessage(hListBox, LB_ADDSTRING, Zero, Integer(PChar(Int2Str(c*100))));

AnimateWindow(hListBox, 320, AW_SLIDE or AW_VER_POSITIVE or AW_ACTIVATE);
{AnimateWindow is used by the system to show ComboBox, drop down list boxs.
 It can give your program some graphical "Movement" effects with one line of code}
end;


function TopMsgFunc(hWnd,Msg,wParam,lParam:Integer):Integer; stdcall;
var
PaintS: TPaintStruct;
begin
// this is the Window Proc function for the hTopForm
case Msg of
  WM_CLOSE: begin
    GetWindowRect(hTopForm, fromRect);
    GetWindowRect(GetDlgItem(hForm1, ID_TopBut), toRect);
    ShowWindow(hTopForm, SW_HIDE); // hide window before calling DrawAnimatedRects
    RedrawWindow(Zero, @fromRect, Zero, RDW_INVALIDATE or RDW_UPDATENOW or RDW_ALLCHILDREN);
    Sleep(0);
    // system may not refresh screen until After the DrawAnimatedRects, I call RedrawWindow
    DrawAnimatedRects(hTopForm, IDANI_CAPTION, fromRect, toRect);
    {DrawAnimatedRects( ) is the animation effect used by system to do the main
     window minimize and restore movement effects}
    end;

  WM_PAINT:
    begin
    BeginPaint(hWnd, PaintS);
    SetBkMode(PaintS.hDC, TRANSPARENT);
    SelectObject(PaintS.hDC, VarFont);
    TextOut(PaintS.hDC, 10, 10, 'This is a Top Level Window', 26);
    TextOut(PaintS.hDC, 10, 30, 'Some text to read here', 22);
    EndPaint(hWnd, PaintS);
    end;

  WM_COMMAND: case LOWORD(wParam) of
    IDOK: PostMessage(hWnd,WM_CLOSE,Zero,Zero);
    IDCancel: begin
      MessageBox(hTopForm, 'A Message',
             'Read Me', MB_ICONINFORMATION);
     end;
    ID_PopListBut: DoPopListBox;
    end;

  end; // Msg case

Result := DefWindowProc(hWnd,Msg,wParam,lParam);
end;


procedure ShowTopLevel;
var
hBut: Cardinal;
begin
// this procedure will make an independent (not owned) top level window
if IsWindow(hTopForm) then Exit;
hTopForm := CreateWindowEx(WS_EX_OVERLAPPEDWINDOW, topClass.lpszClassName,
    'Top Level Un-Owned Form', WS_CAPTION or WS_MINIMIZEBOX or WS_SYSMENU,
    50, 6, 348, 264, Zero, Zero, hInstance, nil);
// notice that the hParent parameter is zero, like the main form

CreateWindow('BUTTON', 'Show Msg',
    WS_VISIBLE or WS_CHILD or BS_PUSHBUTTON or WS_TABSTOP,
    8,48,90,22,hTopForm,IDCancel,hInstance,nil);

{the Edit and hBut below (in some ways) will act like a system combo box.
 If the button is clicked a drop down list box will slide into view below
 the edit control. If you click the listbox that selected value will
 be placed in the edit control, and list box vanishes}
SendMessage(CreateWindowEx(WS_EX_CLIENTEDGE,'EDIT','100',
    WS_VISIBLE or WS_CHILD or WS_TABSTOP,
    250,34,46,21,hTopForm,ID_Edit,hInstance,nil),
    WM_SETFONT, VarFont, Zero);

hBut := CreateWindow('BUTTON', 'o',
    WS_VISIBLE or WS_CHILD or BS_PUSHBUTTON or BS_BITMAP or WS_TABSTOP,
    296,35,18,18, hTopForm,ID_PopListBut,hInstance,nil);

SendMessage(hBut,BM_SETIMAGE, IMAGE_BITMAP,
            LoadBitmap(Zero,MAKEINTRESOURCE(OBM_DNARROWD)));

SetFocus(CreateWindow('BUTTON', 'Close Me',
    WS_VISIBLE or WS_CHILD or BS_PUSHBUTTON or WS_BORDER or WS_TABSTOP,
    160,6,80,24,hTopForm,IDOk,hInstance,nil));

{below I create a window as a child of the fTopForm, but this window has
 the WS_OVERLAPPEDWINDOW style, so it has a Caption and Title bar, so it looks
 like a top level window, but it is NOT set up as a MDI, so when it has focus
 the title bar stays grey}
CreateWindow(formClass.lpszClassName, 'Chlid Form',
    WS_OVERLAPPEDWINDOW or WS_CHILD or WS_VISIBLE or WS_TABSTOP or WS_CLIPSIBLINGS,
    28,60,200,120,hTopForm,33,hInstance,nil);

{code below is to have an animated movement graphical effect}
GetWindowRect(hTopForm, toRect);
GetWindowRect(GetDlgItem(hForm1, ID_TopBut), fromRect);
DrawAnimatedRects(hTopForm, IDANI_CAPTION, fromRect, toRect);
// DrawAnimatedRects does NOT affect the hTopForm, so you must call ShowWindow
ShowWindow(hTopForm, SW_SHOW);
end;


procedure ShowPopUpWnd(TaskBar: Boolean = False);
begin
// the TaskBar parameter is set to True for a pop-up Form with a Taskbar button
if TaskBar then
  begin
  {here I have combined three API functions, SetFocus, CreateWindow and CreateWindowEx.
   I do NOT record ANY windows handles, for the button or the form}
  SetFocus(CreateWindow('BUTTON', 'Close Me',
    WS_VISIBLE or WS_CHILD or BS_PUSHBUTTON or WS_BORDER or WS_TABSTOP,
    68,80,80,24,

    CreateWindowEx(WS_EX_APPWINDOW,formClass.lpszClassName,
             'Pop Up in Task Bar', WS_OVERLAPPEDWINDOW or WS_POPUP or WS_VISIBLE,
             (GetSystemMetrics(SM_CXSCREEN) div 2)-110,
             (GetSystemMetrics(SM_CYSCREEN) div 2)-5,
             220, 140,hForm1, Zero, hInstance, nil),

      IDOk,hInstance,nil));

  { in the CreateWindowEx( ) function above I have the WS_EX_APPWINDOW flag,
    using this flag will give this PopUp a button in the TaskBar}
  Exit;
  end;

if IsWindow(hPopUpForm) then Exit; // allow only one Popup to be created

{in the CreateWindow function below, I have the WS_POPUP flag, and the parent as
 hForm1, with the WS_POPUP the window will be a Top-Level window (Form), not a
 Child window on the parent client area}
hPopUpForm := CreateWindowEx(Zero, formClass.lpszClassName, 'Pop Up Window',
             WS_OVERLAPPEDWINDOW or WS_POPUP or WS_VISIBLE,
             (GetSystemMetrics(SM_CXSCREEN) div 2)-110,
             (GetSystemMetrics(SM_CYSCREEN) div 2)-175,
             220, 170,hForm1, Zero, hInstance, nil);

SendMessage(CreateWindow('BUTTON', 'PopUp Window'#10'with Task Bar',
    WS_VISIBLE or WS_CHILD or BS_PUSHBUTTON or WS_TABSTOP or BS_MULTILINE,
    12,30,88,36,hPopUpForm,ID_TaskBarBut,hInstance,nil),
    WM_SETFONT,VarFont,Zero);

SetFocus(CreateWindow('BUTTON', 'Close Me',
    WS_VISIBLE or WS_CHILD or BS_PUSHBUTTON or WS_BORDER or WS_TABSTOP,
    68,110,80,24,hPopUpForm,IDOk,hInstance,nil));
end;


procedure ChangeButton(pCaption: PChar = nil);
begin
if pCaption = nil then
  pCaption := 'Show the'#10'Invisible Tool';
SetDlgItemText(hToolForm, ID_NewToolBut, pCaption);
end;


procedure MakeInvisibleTool;
begin
{there are times you may need a top level window created to just process
 messages or be a container for some system object, the user should not see
 or use this window, if you set the width and height of a Tool Window to zero
 it will be visible to the system, but the user can not see it}

{if you do not need the window to be visible in the system settings, you can have
 a window with a width and height, and just set it as hidden}

{first I test to see if the hNoShowForm exists with IsWindow , if it does not exist
 I create it. If it exists then I show or hide it}
if IsWindow(hNoShowForm) then
  begin
  if GetWindowLong(hNoShowForm, GWL_USERDATA) = 2 then
    begin
    if IsWindowVisible(hNoShowForm) then
      begin
      ShowWindow(hNoShowForm, SW_HIDE);
      ChangeButton;
      end else
      begin
      ShowWindow(hNoShowForm, SW_SHOW);
      ChangeButton('Hide the'#10'Invisible Tool');
      end;

    end else
    begin
    {if the width and height are zero, I change them to make the window visible}
    SetWindowPos(hNoShowForm, Zero, 1, 1,200, 100, SWP_NOMOVE or SWP_NOCOPYBITS or
              SWP_NOREPOSITION or SWP_NOZORDER or SWP_SHOWWINDOW);
    SetWindowLong(hNoShowForm, GWL_USERDATA, 2);
    ChangeButton('Hide the'#10'Invisible Tool');
    end;

  Exit;
  end;

hNoShowForm := CreateWindowEx(WS_EX_TOOLWINDOW,formClass.lpszClassName, 'Invisible',
             WS_CAPTION or WS_SYSMENU {or WS_POPUP} or WS_VISIBLE,
             10, 10, Zero, Zero, Zero{hForm1}, Zero, hInstance, nil);
{to have an invisible window you need the WS_EX_TOOLWINDOW flag with the width
 and height set to zero}

if hNoShowForm <> Zero then
  begin
  SetWindowLong(hNoShowForm, GWL_USERDATA, 1);
  ChangeButton;
  InvalidateRect(hForm1, nil, True);
  // hForm1 has text on it to tell you that the invisible form exists
  end;

end;


procedure ShowToolWnd;
var
pButCap: PChar;
begin
// this procedure makes forms with the smaller caption bar for tool windows
if IsWindow(hToolForm) then Exit;
// you place the WS_EX_TOOLWINDOW flag in the style Ex parameter for a tool window
hToolForm := CreateWindowEx(WS_EX_TOOLWINDOW,formClass.lpszClassName, 'Tool Form',
             WS_CAPTION or WS_SYSMENU or WS_POPUP or WS_VISIBLE,
             (GetSystemMetrics(SM_CXSCREEN) div 2)-110,
             (GetSystemMetrics(SM_CYSCREEN) div 2)-75,
             228, 150,hForm1, Zero, hInstance, nil);

if IsWindow(hNoShowForm) then
  pButCap := 'Show-Hide the'#10'Invisible Tool'
  else
  pButCap := 'Invisible Top'#10'Tool Window';

SendMessage(CreateWindow('BUTTON', pButCap,
    WS_VISIBLE or WS_CHILD or BS_PUSHBUTTON or WS_TABSTOP or BS_MULTILINE,
    64,30,88,36, hToolForm, ID_NewToolBut, hInstance, nil),
    WM_SETFONT,VarFont,Zero);

SetFocus(CreateWindow('BUTTON', 'Close Me',
    WS_VISIBLE or WS_CHILD or BS_PUSHBUTTON or WS_BORDER or WS_TABSTOP,
    68,90,80,24, hToolForm, IDOk, hInstance, nil));
end;


procedure DoAnimate;
var
fRect: TRect;
c, hCombo: Cardinal;
begin
// this procedure will create and animate a temporary dialog window
if IsWindow(hAnimate) then Exit;

GetWindowRect(hForm1, fRect);
// the hAmimate wildow will be placed on the left edge of hForm1
hAnimate := CreateWindowEx(Zero, formClass.lpszClassName, 'Animate',
             WS_POPUP or WS_DLGFRAME, fRect.Left-49, fRect.Bottom -120,
             50, 120, hForm1, Zero, hInstance, nil);

// for a dialog window I have a combo box and radio buttons
hCombo := CreateWindow('COMBOBOX','cb1',WS_VISIBLE or WS_CHILD or
                 CBS_DROPDOWNLIST or WS_CLIPSIBLINGS or WS_TABSTOP or WS_VSCROLL,
                 4,20,38,110,hAnimate,ID_Combo,hInstance,nil);
SendMessage(hCombo,WM_SETFONT,VarFont, Zero);

for c := 1 to 6 do
  SendMessage(hCombo,CB_ADDSTRING, Zero, Integer(PChar(Int2Str(c*10))));

SendMessage(hCombo, CB_SETCURSEL, Zero, Zero);
ComboText := '10'#0; // ComboText is used to store the text for the hAmimate Paint

SendMessage(CreateWindow('BUTTON','Yes',
    WS_VISIBLE or WS_CHILD or BS_AUTORADIOBUTTON or BS_TEXT or WS_TABSTOP or WS_GROUP,
    2, 44, 40, 25, hAnimate, ID_Radio1, hInstance, nil),
    WM_SETFONT, VarFont, Zero);

SendMessage(CreateWindow('BUTTON','No',
    WS_VISIBLE or WS_CHILD or BS_AUTORADIOBUTTON or BS_TEXT,
    2, 64, 40, 25, hAnimate, ID_Radio2, hInstance, nil),
    WM_SETFONT, VarFont, Zero);
  SendDlgItemMessage(hAnimate, ID_Radio1, BM_SETCHECK, BST_CHECKED, Zero);

CreateWindow('BUTTON','O K', WS_VISIBLE or WS_CHILD or BS_TEXT,
    4, 90, 36, 21, hAnimate, IDOK, hInstance, nil);

AnimateWindow(hAnimate, 320, AW_HOR_NEGATIVE or AW_ACTIVATE);
// AnimateWindow will roll out this window
SetFocus(hCombo);

>// AnimateWindow function does NOT call the WM_PAINT for non-system control windows
// to show the text draw in the WM_PAINT message, you will need to invalidate, below
//InvalidateRect(hAnimate, nil, False);
end;


function MsgFunc(hWnd,iMsg,wParam,lParam:Integer):Integer; stdcall;
var
PaintS: TPaintStruct;
aFlags: Cardinal;

begin
{this Window Proc fuction is used by several different top level windows.
 So I have several tests for the hWnd parameter, to see which window to code for.
 The main form hForm1, and the "Pop-Up windows and the tool windows and hAmimate
 all use this Window Proc}
Result := Zero;
case iMsg of
  WM_DESTROY: if hWnd = hForm1 then PostQuitMessage(Zero)
    else if hWnd = hToolForm then hToolForm := Zero
    else if hWnd = hNoShowForm then
      begin
      if IsWindow(hToolForm) then
        ChangeButton('Invisible Top'#10'Tool Window');
      InvalidateRect(hForm1, nil, True);
      end;

  WM_ACTIVATE: if (hWnd = hAnimate) and (LOWORD(wParam) = WA_INACTIVE) then  // $0006;
    PostMessage(hWnd, WM_CLOSE,0,0); // hAnimate closes when it loses focus

  WM_PAINT: if hWnd <> hNoShowForm then
    begin
    BeginPaint(hWnd, PaintS);
    SetBkMode(PaintS.hDC, TRANSPARENT);

    if hWnd = hAnimate then
      begin
      SetTextColor(PaintS.hDC, $FF);
      // The hAmimate form does NOT paint this text when it is animated
      TextOut(PaintS.hDC,13,2,ComboText,2);
      end else
    if hWnd = hForm1 then
      begin
      TextOut(PaintS.hDC, 23, 2, 'Buttons to Create Forms', 23);
      if IsWindow(hNoShowForm) then
        begin
        SelectObject(PaintS.hDC, VarFont);
        TextOut(PaintS.hDC, 30, 162, 'Invisible Tool Window Exists', 28);
        end;
      end else
      if (hWnd = hToolForm) then
      begin
      SelectObject(PaintS.hDC, VarFont);
      TextOut(PaintS.hDC, 38, 2, 'Place User Tool Buttons Below', 29);
      end else
      begin
      SelectObject(PaintS.hDC, VarFont);
      TextOut(PaintS.hDC, 1, 1, 'Minimize this Form to see where it goes', 39);
      end;

    EndPaint(hWnd, PaintS);
    Exit;
    end;

  WM_CLOSE: if (hWnd = hForm1) and
    IsWindow(hTopForm) then
      SendMessage(hTopForm, WM_CLOSE, Zero,Zero);

  WM_COMMAND:
    case LOWORD(wParam) of
      IDOK: PostMessage(hWnd,WM_CLOSE,Zero,Zero);
      ID_PopUpBut: ShowPopUpWnd;
      ID_ToolBut: ShowToolWnd;
      ID_TopBut: ShowTopLevel;
      ID_DockBut: DoDocking;
      ID_NewToolBut: MakeInvisibleTool;
      ID_TaskBarBut: ShowPopUpWnd(True);
      ID_SliderBut: DoAnimate;
      ID_EditBut: begin // button to slide out the EDIT control
        PaintS.hDC := GetDlgItem(hWnd, ID_Edit);
        if IsWindowVisible(PaintS.hDC) then // test to see is edit is visible
          begin
          // set flags for hide or show
          aFlags := AW_SLIDE or AW_HOR_NEGATIVE or AW_HIDE;
          SetDlgItemText(hWnd, ID_EditBut, '>');
          end else
          begin
          aFlags := AW_SLIDE or AW_HOR_POSITIVE;
          SetDlgItemText(hWnd, ID_EditBut, 'X');
          end;
        AnimateWindow(PaintS.hDC, 400, aFlags);
        if aFlags and AW_HIDE = Zero then
          SetFocus(PaintS.hDC); // set focus if showing
        end;
      ID_Combo: if (HIWORD(wParam) = CBN_SELENDOK) and
        (SendMessage(LParam, CB_GETLBTEXT, SendMessage(LParam,
               CB_GETCURSEL, Zero, Zero), Cardinal(@ComboText)) <> CB_ERR) then
        begin
        PaintS.hDC := GetDC(hWnd);
        SetBkColor(PaintS.hDC, GetSysColor(COLOR_3DFACE));
        SetTextColor(PaintS.hDC, $FF);
        TextOut(PaintS.hDC,13,2,ComboText,2);
        ReleaseDC(hWnd, PaintS.hDC);
        end;
      end;

  WM_CTLCOLORSTATIC: if hWnd = hForm1 then
    begin
    //The pop-up Static will be white with red text
    SetBkColor(wParam,$FFFFFF);
    SetTextColor(wParam,$D6);
    Result := GetStockObject(WHITE_BRUSH);
    Exit;
    end;

  WM_MOVING: if (hWnd = hForm1) and doMoving(PRect(LParam)) then
    begin
    // test docking with doMoving, and block the DefWindowProc
    Result := 1;
    Exit;
    end;

  end;

Result := DefWindowProc(hWnd,iMsg,wParam,lParam);
end;


procedure MakeButtons;
const
ButText: Array[Zero..3] of PChar =
         ('PopUp Form', 'Tool Form', 'Show Top Form','Show Dock Forms');
var
i: Integer;
begin
// this procedure makes all the buttons on hForm1
CreateWindow('BUTTON', '>', WS_VISIBLE or WS_CHILD or WS_TABSTOP,
    1, 3, 18, 21, hForm1, ID_EditBut, hInstance, nil);
        
for i := Zero to High(ButText) do
  SendMessage(CreateWindow('BUTTON', ButText[i],
    WS_VISIBLE or WS_CHILD or BS_PUSHBUTTON or WS_TABSTOP,
    47,30+(i*33),104,26,hForm1,ID_PopUpBut+i,hInstance,nil),
    WM_SETFONT,VarFont,Zero);

CreateWindow('BUTTON', '<', WS_VISIBLE or WS_CHILD or WS_TABSTOP,
    1, 94, 15, 76, hForm1, ID_SliderBut, hInstance, nil);
end;


procedure MakeControls;
begin
hNoShowForm := Zero;
hTopForm := Zero;

// set the Count in the aryDock[Zero] to zero so there is no docking
aryDock[Zero].Count := Zero;
aryDock[Zero].hWnd := hForm1;
// this first aryDock if for the main form and has zero OffSets
aryDock[Zero].OffLeft := Zero;
aryDock[Zero].OffTop := Zero;

// topClass is used for the "Top" window (form)
with topClass do
  begin
  Style := CS_PARENTDC or CS_BYTEALIGNCLIENT;
  hInstance := SysInit.hInstance;
  hIcon := LoadIcon(Zero, IDI_EXCLAMATION);
  lpfnWndProc := @TopMsgFunc;
  hbrBackground := formClass.hbrBackground;
  lpszClassName := 'Top Class';
  hCursor := formClass.hCursor;
  cbClsExtra := Zero;
  cbWndExtra := Zero;
  lpszMenuName := nil;
  end;

RegisterClass(topClass);

MakeButtons;
SendMessage(CreateWindowEx(WS_EX_CLIENTEDGE,'EDIT','Sliding Edit',
    WS_CHILD, 21, 3, 80, 21,hForm1,ID_Edit,hInstance,nil),
    WM_SETFONT, VarFont, Zero);
// the Edit above is created hidden, and slides out when ID_EditBut is clicked
end;


function MakeForm: Boolean;
begin
// main form creation
ZeroMemory(@formClass, SizeOf(formClass));
with formClass do
  begin
  Style := CS_PARENTDC or CS_BYTEALIGNCLIENT;
  hInstance := SysInit.hInstance;
  hIcon := LoadIcon(hInstance,'MAINICON');
  lpfnWndProc := @MsgFunc;
  hbrBackground := COLOR_BTNFACE+1;
  lpszClassName := 'Forms Class';
  hCursor := LoadCursor(Zero,IDC_ARROW);
  end;

RegisterClass(formClass);

hForm1 := CreateWindow(formClass.lpszClassName, 'Multi Forms',
    WS_CAPTION or WS_MINIMIZEBOX or WS_SYSMENU or WS_VISIBLE,
    (GetSystemMetrics(SM_CXSCREEN) div 2)-102,
    (GetSystemMetrics(SM_CYSCREEN) div 2)-140,
    204, 216, Zero, Zero, hInstance, nil);
Result := hForm1 <> Zero;
if Result then
  begin
  InitCommonControls;
  MakeControls;
  end;
end;

procedure DoMsgLoop;
var
MainMsg: TMSG;
begin
{You should Notice that the IsDialogMessage( ) now has the
   GetActiveWindow function for the hWnd paramater. You need this because
   there are many top-level windows in this program}
while GetMessage(mainMsg,Zero,Zero,Zero) do
  if not IsDialogMessage(GetActiveWindow, mainMsg) then
    begin
    TranslateMessage(mainMsg);
    DispatchMessage(mainMsg);
    end;
// be sure to Destroy un-owned windows with DestroyWindow
DestroyWindow(hNoShowForm);
DestroyWindow(hTopForm);
end;

initialization
VarFont := GetStockObject(ANSI_VAR_FONT);

end.

I hope you can take time to try and experiment with the different methods presented in the code above.
Having pop-up windows can give you a more flexible way to give an get information for the user.
You may have seen in some of your computer programs different ways to use extra windows, not on the client area.

                           

Next Page
The next page shows you how to create pop-up windows that are "Modal", and prevent
user from using any program window except the modal window, and how to block code progression.
  16A. Modal Windows


       

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




H O M E