1. Technology
Zarko Gajic your About Delphi Guide About Delphi Programming
From Zarko Gajic, your Guide to Delphi Programming
http://delphi.about.com

Sticky Windows
How to dock your Delphi forms to the edges of your desktop screen.
http://delphi.about.com/library/weekly/aa070301a.htm

In this article, we'll see how to write some message handlers for your form so that it will be able to "snap" to the screen edge when moving toward it.

Of course, there is no built-in feature of the Win API that allows windows to snap to the screen edge - we will have to deal with Windows messages. As stated in previous articles, Delphi makes message handling easy through its use of events, an event is usually generated in response to a Windows message being sent to an application.

Even though many Window messages are handled by Delphi events, some messages are left for us to handle. For example, we know if a form was resized with the Resize event (OnResize) - Delphi handles the WM_SIZE message, but how do we know if a form has moved? The Delphi form can get the message, but initially does not do anything with it.

The WM_MOVING message is sent to a window that the user is moving. By processing this message, an application can monitor the size and position of the drag rectangle and, if needed, change its size or position.

The WM_WINDOWPOSCHANGING message is sent to a window whose size, position, or place in the Z order is about to change as a result of a call to the SetWindowPos function or another window-management function.

Most of the time a simple message is not enough, we have to have parameters to tell us additional information. For example, the WM_MOVE message tells us that our Form has changed position but it is the LPARAM parameter that gives us the X and Y position.

With the WM_WINDOWPOSCHANGING message we "only" get one parameter - the one that points to a WindowPos structure that contains information about the window's new size and position. This is how the WindowPos structure is defined:

TWindowPos = packed record
  hwnd: HWND; {Identifies the window.}
  hwndInsertAfter: HWND; {Window above this one}
  x: Integer; {Left edge of the window}
  y: Integer; {Right edge of the window}
  cx: Integer; {Window width}
  cy: Integer; {Window height}
  flags: UINT; {Window-positioning options.}
end;

Our task is simple: we want a Delphi form to stick to the screen edge when the edge of the form is within a certain distance (say 20 pixels) of the edge of the screen.

   The code
On a new Delphi form add a Label, one Edit control and four Check boxes. Change the name of the Edit control to edStickAt. Change the names of check boxes to chkLeft, chkTop, etc... We use the edStickAt to set the number of pixels that our form uses to snap to nearest edge if closer than edStickAt pixels and not docked.
The form should look something like:

Docking at run time

The only message we are interested in is the WM_WINDOWPOSCHANGING. The declaration is placed in the private part of the form declaration. I'll give you here the entire code for the "sticking" procedure along with the code descriptions. Note that you can prevent the form from snapping at the certain edge by unchecking the appropriate check box.

SystemParametersInfo called with the SPI_GETWORKAREA as the first parameter retrieves the size of the working (desktop) area. We use it to determine the useable area of the screen minus appbars, taskbars, IE toolbars, etc.

...

  private
   procedure WMWINDOWPOSCHANGING
            (Var Msg: TWMWINDOWPOSCHANGING);
             message WM_WINDOWPOSCHANGING;

...

procedure TfrMain.WMWINDOWPOSCHANGING
          (var Msg: TWMWINDOWPOSCHANGING);
const
  Docked: Boolean = FALSE;
var
  rWorkArea: TRect;
  StickAt : Word;
begin
  StickAt := StrToInt(edStickAt.Text);
  
  SystemParametersInfo
     (SPI_GETWORKAREA, 0, @rWorkArea, 0);

  with Msg.WindowPos^ do begin
    if chkLeft.Checked then
     if x <= rWorkArea.Left + StickAt then begin
      x := rWorkArea.Left;
      Docked := TRUE;
     end;

    if chkRight.Checked then
     if x + cx >= rWorkArea.Right - StickAt then begin
      x := rWorkArea.Right - cx;
      Docked := TRUE;
     end;

    if chkTop.Checked then
     if y <= rWorkArea.Top + StickAt then begin
      y := rWorkArea.Top;
      Docked := TRUE;
     end;

    if chkBottom.Checked then
     if y + cy >= rWorkArea.Bottom - StickAt then begin
      y := rWorkArea.Bottom - cy;
      Docked := TRUE;
     end;

    if Docked then begin
      with rWorkArea do begin
      // no moving out of the screen
      if x < Left then x := Left;
      if x + cx > Right then x := Right - cx;
      if y < Top then y := Top;
      if y + cy > Bottom then y := Bottom - cy;
      end; {with rWorkArea}
    end; {if Docked}
  end; {with  Msg.WindowPos^}

  inherited;
end;
end.

Now simply run the project and move the form to any screen edge to dock it.

That's it. If you have any questions please post them on the Delphi Programming Forum!

Just to give you some hints for discussion:

  • how to add the code to check whether the form can move past the screen edge or not?
  • how to add the (something like) StickToWindow property that will make the form dock to the form defined by the StickToWindow property.
  • then when the StickToWindow moves our form gets moved too.
  • etc...
    {
    Zarko Gajic, BSCS
    About.com Guide to Delphi Programming
    http://delphi.about.com
    email: delphi@aboutguide.com
    free newsletter: http://delphi.about.com/library/blnewsletter.htm
    forum: http://forums.about.com/ab-delphi/start/
    }
  • ©2014 About.com. All rights reserved.