Componentes - Movendo componentes e redimensionado em runtime

Top  Previous  Next

{

After some experimentation with Captionless Forms and my custom panel components, I have found a (relatively) easy way to

make your component movable at runtime. That is, when a user clicks and drags on your component, a "frame" appears just

like when you move a window, so they can drag the component around. 

 

The method involves catching a Windows Message called WM_NCHITTEST, which is passed to windows whenever a mouse

operation (like moving the pointer or clicking) occurs on them. Let's go through the steps: 

 

1. In the protected section of your component, add the following line: 

 

     procedure NCHitTest(var Msg: TWMNCHitTest); message WM_NCHITTEST;

 

2. In the implementation section of the unit, define the method thusly: 

 

     procedure TMyComponent.NCHitTest(var Msg: TWMNCHitTest); 

     begin 

        inherited; 

        Msg.Result := HTCAPTION; 

     end;

 

That's it! Now when you click on the component at runtime, you can drag it around the form. The next thing to do is test

whether the mouse has been clicked on one particular section of your component (like a "title bar" area, for example). Redefine

your definition like this: 

}

     procedure TMyComponent.NCHitTest(var Msg: TWMNCHitTest); 

     var 

       rc: TRect; 

       pt: TPoint; 

     begin 

       inherited; 

       pt.x := Msg.XPos; 

       pt.y := Msg.YPos; 

       pt := ScreenToClient(pt); 

       rc := Rect(00, Width, 16); 

       if PtInRect(rc, pt) then Msg.Result := HTCAPTION; 

     end;

 

{

This will check that the user clicked somewhere within the top 16 pixels of your component. Otherwise, the usual stuff will

happen (OnClick or whatever). 

 

Keep coding! 

  

 

Notes from the Editor: 

 

When I first got this, I was reading through it and decided that no, it wasn't going to work. But sure enough... It did!

Basically, this is just tricking the component into thinking that you are over its window banner area (HTCAPTION). All

controls are basically "windows", it's just that most don't have banners like a form does. By overridding the

WM_NCHitTest message, you are just telling the control "the mouse is now over your banner" and then all the default

windows behavior kicks in for this condition. 

 

For example, I tried this with a TEdit and it basically worked as advertised... but with a few undesireable exceptions.

First, since it eats the mouse message, you can't click on such a control to give it focus (unless you modify the

NCHitTest to shrink the size of the "caption" area as he did above). Also, since the component was now thinking that

you were over its "banner", I wondered what would happen if I double-clicked on it. Sure enough... It maximizes the

control to fill the form! Definitely got to stop that one... but it does open up some interesting possibilities. 

 

I reviewed this message again and decided to try all the other possible message results (i.e. those other than

HTCAPTION). If you open up the WIN32.HLP file and search on WC_NCHITTEST, you will see the other result flags

that can be managed. For example, if you change HTCAPTION in the first example to HTBOTTOM, when the mouse

moves into the control, it thinks that you are now on the bottom of the "window's" resize border and the

double-headed arrow shows up. Any click and drag process within the control then resizes the bottom edge of the

control. It is obvious that with just a few lines of code, you could enable all the behavior of a standard resizable

window into just about any control. 

 

For those of you who need a little help setting this up as a custom component, here is the entire code for what I did to

experiment with his technique. 

}

     unit Edit1; 

 

     interface 

 

     uses 

       Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, 

       StdCtrls; 

 

     type 

       TEdit1 = class(TEdit) 

       protected 

         procedure NCHitTest(var Msg: TWMNCHitTest); message WM_NCHITTEST; 

       end

 

     procedure Register; 

 

     implementation 

 

     procedure Register; 

     begin 

       RegisterComponents('Win32', [TEdit1]); 

     end

 

     procedure TEdit1.NCHitTest(var Msg: TWMNCHitTest); 

     begin 

        inherited; 

        Msg.Result := HTCAPTION; 

     end

 

     end.

 

{

If you wanted to go a little further with this technique, you could add a property to a custom component that allows

you to turn on and off movement and/or resizing. Then in your overridden NCHitTest method, you just test to see if it is

enabled and only return the result if it is. You could then use this technique to allow users of your application to design

the size and placement of controls on the form. Simply turn on a "design-mode" (which sets the Movable/Resizable

property of the form's controls), and then let the user move and resize all the controls on the form. When he is done, he

turns off the design-mode and your application can save the positions of the control to the registry or an INI file. 

 

The possibilities are endless. I would love to see additional work on this by some enterprising reader out there. In fact, I

have a few ideas of my own... stay tuned! 

}