Another Way to Drag a Window
Step aside, junior
For example, consider the case of a Windows application that doesn't have a title bar, how can we move such a window?. In fact, it's possible to create windows with a nonstandard title bar and even non rectangular forms. In this case, how could Windows know where the borders and the corners of the window are?
The Windows operating system is heavily based on messages. For example, when you click on a window or a control, Windows sends it a wm_LButtonDown message, with additional information about where the mouse cursor is and which control keys are currently pressed. Sounds familiar? Yes, this is nothing more than an OnMouseDown event in Delphi.
Similarly, Windows sends a wm_NCHitTest message whenever a mouse event occurs, that is, when the cursor moves, or when a mouse button is pressed or released. If the mouse is not captured, the message is sent to the window beneath the cursor. Otherwise, the message is sent to the window that has captured the mouse.
If we can make Windows think that the user is dragging (has clicked on) the title bar rather than the client area, then the user could drag the window by clicking in the client area. The easiest way to do this is to "fool" Windows into thinking that you're actually clicking on the title bar of a form. We will do this by handling the WM_NCHitTest windows message. Here's what you have to do:
1. insert the following line into your form's "Private declarations" section (message handling procedure declaration):
procedure WMNCHitTest(var Msg: TWMNCHitTest); message wm_NCHitTest;
2. add the following code into the "implementation" section of your form's unit (where Form1 is assumed form name):
procedure TForm1.WMNCHitTest(var Msg: TWMNCHitTest); begin inherited; if Msg.Result = htClient then Msg.Result := htCaption; end;
The first line of code in the message handler calls the inherited method to obtain the default handling for the wm_NCHitTest message.
The if part in the procedure intercepts and changes your window's behavior. This is what actually happens: when the operating system sends a wm_NCHitTest message to the window, together with the mouse coordinates, the window returns a code that states which portion of itself has been hit. The important piece of information, for our task, is in the value of the Msg.Result field. At this point, we have an opportunity to modify the message result.
This is what we do: if the user has clicked in the form's client area we make Windows to think the user clicked on the title bar. In Object Pascal "words": if the message return value is HTCLIENT, we simply change it to HTCAPTION.
Be carefull: mouse events, no more!
By changing the default behaviour of our forms we remove the ability of Windows to notify you when the mouse is over the client area. One side effect of this trick is that your form will no longer generate events for mouse messages.
If you want a captionless borderless window similar to a floating toolbar, set the Form's Caption to an empty string, disable all of the BorderIcons, and set the BorderStyle to bsNone.
Show window contents while dragging
To show the window contents while dragging the window we can use the next API procedure calls:
SystemParametersInfo (SPI_SETDRAGFULLWINDOWS, 1, nil, 0); SystemParametersInfo (SPI_SETDRAGFULLWINDOWS, 0, nil, 0);
More wm_NCHitTest tricks
If you look more carefully at the wm_NCHitTest message you'll see that return value of the function indicates the position of the cursor hot spot. This enables us to play some more with the message to create strange results.
. The following code fragment will prevent users to close your forms by clicking on their Close buttons.
if Msg.Result = htClose then Msg.Result := htNowhere;
. If the user is trying to move the form by clicking on the caption bar and dragging, the code replaces the result of the message with a result which indicates the user clicked on the client area. This prevents the user from moving the window with the mouse (opposite to what we were doing in the begging of the article).
if Msg.Result = htCaption then Msg.Result := htClient;
. A "funny" peace of code, minimize is maximize and maximize is minimize.
if Msg.Result = htMaxButton then Msg.Result := htMaxButton else if Msg.Result = htMinButton then Msg.Result := htMinButton;
But, I have components on a form!?
In most cases we'll have some components on a form. Let's say, for example, that one Panel object is on a form. If Align property of a panel is set to alClient, the Panel fills the entire client area so that it is impossible to select the parent form by clicking on it. The code above will not work! Why? The code above will not work because the mouse is always moving over the Panel component not the form.
To move our form by dragging a panel on the form we have to add few lines of code in the OnMouseDown event procedure for the Panel component:
procedure TForm1.Panel1MouseDown (Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin ReleaseCapture; SendMessage(Form1.Handle, WM_SYSCOMMAND, 61458, 0); end;
Note: this code will not work with non-window controls such as TLabel components.
Moving, moving... That's it. If you have some discussion ideas on this topic, please post to the Delphi Programming Forum.