Delphi的IDE是本身就是一个非常精彩的软件,其中涵含了许多非常宝贵的软件知识。IDE中有一个窗体设计器,控件放在里面,就可以随意移动,以及调整大小,如果能够自己实现一个类似于这样的窗体设计器,那真是一件非常美妙事情。本文实现的就是窗体设计器中最重要的部分,一个移动控件的类,控件要求从TControl继承下来,在介绍如何实现之前,先说说这个类的用法:
其中有两个类:
TDragClass就是实现拉动的类
TDragPoint是控件周围出现的拉动点的类
用法很简单:
创建一个TDragClass对象
将要实现拉动的控件传进去就行了
比如:
myDrag.addControl(Edit1);
这样Edit1就能实现拉动和移动了。
另外有两个属性来控制移动的方式
isMoveStep:boolean
指定移动的方式,True为跳跃式,False为连续式,默认情况下是False,即连续式。
所谓跳跃式,即移动或拉动控件时,控件是以离散的方式在改变自己的位置和大小的,这个对窗体设计器中的控件对齐有帮助。而连续式,当然就是以连续的方式使控件的位置和大小得到改变。
MoveStep :integer
当移动方式为跳跃式时,该属性指定跳跃的大小,范围在5-20之间
另外还有一个方法:SetPointVisible(value:Boolean);用于指定移动点的可见性。在Delphi中,当你点击窗口时,控件周围的八个小点就消失了,即用此原理。
现在开始进入到具体实现的部分了,当你点击Delphi的窗体设计器中的控件时,控件周围出现了八个小点,这八个小点其实也是窗口类:TGrabHandle。预想中要实现控件移动,得有一个标识你正在移动或拉动的东西,这八个小点正是,Delphi的这种做法可以借鉴。于是我实现了一个移动点类:TDragPoint,该的对象将作为TDragClass的成员之一,具体等一下再讲。现在来看它的实现,其实非常简单,因为VCL给了我们一个有自绘能力的类TCustomControl,只要从这里继承下来,再重载其中的Paint方法,自己来画这个移动点就行了。
代码非常简单,这里就不多说了:
//---------TDragPoint--------------------------
unit UDragPoint;
interface
uses Windows, Messages,Controls,Classes,Graphics;
type
TDragPoint=class(TCustomControl)
protected
procedure Paint;override;
public
//处理移动时用变量
isDown:Boolean;
PrevP,NextP:TPoint;
constructor Create(AOwner: TComponent); override;
procedure CreateWnd; override;
published
property OnMouseMove;
property OnMouseDown;
property OnMouseUp;
end;
implementation
{ TDragPoint }
constructor TDragPoint.create;
begin
inherited Create(AOwner);
isDown:=False;
Width:=6;
Height:=6;
end;
procedure TDragPoint.CreateWnd;
begin
inherited;
//使该类位窗口最前
BringWindowToTop(self.Handle);
end;
procedure TDragPoint.Paint;
begin
Canvas.Brush.Color:=clBlack;
Canvas.Brush.Style:=bsSolid;
Canvas.Rectangle(0,0,width,Height);
end;
end.
这里有必须谈到的一点是该类重载了WndCreate,并在其中写入
BringWindowToTop(self.Handle);这样做目的是让这些移动点控件能够位于窗口的最前位置。另外在其中显化了三个鼠标事件:
property OnMouseMove;
property OnMouseDown;
property OnMouseUp;
目的是为了在TDragClass中实现移动这些点。
现在可以进入主题,来说明TDragClass的实现了。
其中有一个保存传进来的控件的列表类:FConList:TList;还有一个标识当前正在被移动或拉动的控件在FConList中的索引FCurActiveCon:Integer;
还有控件事件相关的成员
FConMouseDown:TMouseEvent;
FConMouseMove:TMouseMoveEvent;
FConMouseup:TMouseEvent;
这三个事件方法指针指向所有传进来的控件的鼠标事件的处理函数,在Create中将得到赋值。而所有控件的鼠标处理函数将在类中实现。
接下来就到了最重要的成员了:FPointRec:TPointRec;这是一个记录类型,其定义为:
TPointRec=record
LeftTop:TDragPoint;
LeftBottom:TDragPoint;
RightTop:TDragPoint;
RightButton:TDragPoint;
LeftMid:TDragPoint;
TopMid:TDragPoint;
RightMid:TDragPoint;
ButtonMid:TDragPoint;
end;
这正是当前被移动控件边缘的八个点。这八个点会粘在被移动控件的边缘。
上面说过该类可以实现跳跃式移动或拉动则必定有相关的成员: FisMoveStep:Boolean;
FMoveStep:integer;
MoveX,MoveY:integer;
FisMoveStep指定是否为跳跃式,FMoveStep为跳跃的幅度,MoveX,MoveY标识控件移动或拉动的距离是否达到了FMoveStep,是就改变控件位置和大小,如此重复
除了上面那些成员,类中还定义了一些相类的方法,大概如下:
//-------对移动点类的操作—
//创建移动点类
procedure CreateDragPoint(PointParent:TWinControl);
//设定移动点类的位置
procedure SetPointPos(posRect:TRect);
//指定移动点类的父窗口
procedure SetPointParent(PointParent:TWinControl);
//设置移动点类的鼠标事件
procedure SetPointEvent;
//设置移动点类的可见性
procedure SetPointVisible(Visibled:Boolean);
//三个控件事件处理函数,所有控件的鼠标处理函数都将是这个,主要是解决控件的移动
//以及移动点类的位置,当你点击某一个控件的时候,移动点类会附着到这个控件的边缘
procedure ConMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure ConMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
procedure ConMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
//移动点类的鼠标处理事件,解决移动点类的移动,以及当前控件的大小改变
procedure PointMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure PointMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
procedure PointMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
最后一个重要方法是function addControl(AddCon:Pointer):Boolean;
控件从这里加入,就可以实现移动和拉动了。
下面就将类实现比较重要的几点略说一下吧(主要还是看代码吧)
在类的构造函数中,将上面的三个控件处理函数指定给三个指针成员:
FConMouseDown:=ConMouseDown;
FConMouseMove:=ConMouseMove;
FConMouseup:=ConMouseUp;
现在这三个成员就指定了三个处理函数的地址了,等一下就可以看到那些控件的鼠标消息是怎么和这三个处理函数联系在一起的,实现就在AddControl函数中。
AddControl是一个非常重要的方法,在控件加入之前,它要先判断控件是否有Parent值,没有则不能加入,更重要的一点是,在FConList是否已经有这个控件了,即该控件已经加入过了,如果已经加入了,则不能再加一次,代码如下:
//如果该控件已经在列表中了,则加入失败
for i:=0 to FConList.Count-1 do
if Integer(AddCon)=Integer(FConList.Items[i]) then
begin
result:=false;
exit;
end;
如果可以加入则先加入列表类中,再指定当前活动控件:
FConList.Add(AddCon);
FCurActiveCon:=FConList.Count-1;
而AddControl中还有一个比较重要的TempCon.Parent.DoubleBuffered:=True;
即加入的控件的父窗口设为双缓冲模式,这样在移动控件或拉动控件大小的时候,不会出现闪烁现象。
接着就是为加入的控件指定鼠标处理函数了,但加入的是TControl,而他的鼠标事件指针被设为保护类型,因此无法获得,但他的子类把他们显化出来了。这里用了一种折衷的方案:
TButton(TempCon).OnMouseDown:=FconMouseDown;
TButton(TempCon).OnMouseMove:=FconMouseMove;
TButton(TempCon).OnMouseUp:=FconMouseUp;
这样做并不会出错,但显得怪怪的,但不理他了,能实现功能就行了。现在加入控件的鼠标事件都将会在这里的三个处理函数中处理了。
最后,将移动点类移动该控件的边缘去。
说得够杂的,各位可以和第二部分的原代码对照着看,这样会更好一些。
再稍微讲一下跳跃式移动或拉动控件的实现,FMoveStep指定跳跃的幅度,MoveX,MoveY:integer;用在移动点类和控件的鼠标事件中,累加鼠标移动的距离,当达到FMoveStep时,就移动控件,或改变控件的大小,然后将MoveX,MoveY变为0,又继续累加,如此循环
至于其他的就没有什么好说的了,各位还是看看源代码吧,也并不是很难理解。代码在第二部分给出。