1. Technology

Docking Delphi Forms to Screen Edges

By

Docking Delphi Forms to Screen Edges

Screen Docking Delphi Form

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. 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 Windows 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:

 type
 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 WM_WindowPosChanginh message

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 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.

 ... //interface
 
   private
    procedure WMWINDOWPOSCHANGING(var Msg: TWMWINDOWPOSCHANGING) ; message WM_WINDOWPOSCHANGING;
 
 ...//implementation
 
 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;
 
 
 
Now simply run the project and move the form to any screen edge to dock it.

Note: this article was written *before* the "ScreenSnap" and "SnapBuffer" were introduced as standard Delphi form properties.

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

Here are some discussion ideas:

  • 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...

©2014 About.com. All rights reserved.