Monday, March 16, 2020

Click and Drag a Delphi Form Without the Caption Bar

Click and Drag a Delphi Form Without the Caption Bar The most common way to move a window is to drag it by its title bar. Read on to find out how you can provide dragging capabilities for Delphi forms without a title bar, so the user can move a form by clicking anywhere on the client area. For example, consider the case of a Windows application that doesnt have a title bar, how can we move such a window?  In fact, its 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 WM_NCHitTest Windows Message The Windows operating system is heavily based on handling 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. Code to Input 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 youre actually clicking on the title bar of a form. Heres what you have to do: 1. Insert the following line into your forms 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 forms unit (where Form1 is the  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 windows 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 forms 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. No More Mouse Events By changing the default behavior 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. Captionless-Borderless Window If you want a captionless borderless window similar to a floating toolbar, set the Forms Caption to an empty string, disable all of the BorderIcons, and set the BorderStyle to bsNone. A form can be changed in various ways by applying custom code in the CreateParams method. More WM_NCHitTest Tricks If you look more carefully at the wm_NCHitTest message youll 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 the Close button. 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; Having Components On a Form In most cases, well have some components on a form. Lets 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? Its 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.