Delphi的IDE是本身就是一個(gè)非常精彩的軟件,其中涵含了許多非常寶貴的軟件知識。IDE中有一個(gè)窗體設(shè)計(jì)器,控件放在里面,就可以隨意移動(dòng),以及調(diào)整大小,如果能夠自己實(shí)現(xiàn)一個(gè)類似于這樣的窗體設(shè)計(jì)器,那真是一件非常美妙事情。本文實(shí)現(xiàn)的就是窗體設(shè)計(jì)器中最重要的部分,一個(gè)移動(dòng)控件的類,控件要求從TControl繼承下來,在介紹如何實(shí)現(xiàn)之前,先說說這個(gè)類的用法:
 
其中有兩個(gè)類:
TDragClass就是實(shí)現(xiàn)拉動(dòng)的類
TDragPoint是控件周圍出現(xiàn)的拉動(dòng)點(diǎn)的類
用法很簡單:
創(chuàng)建一個(gè)TDragClass對象
將要實(shí)現(xiàn)拉動(dòng)的控件傳進(jìn)去就行了
比如:
myDrag.addControl(Edit1);
這樣Edit1就能實(shí)現(xiàn)拉動(dòng)和移動(dòng)了。
另外有兩個(gè)屬性來控制移動(dòng)的方式
isMoveStep:boolean
指定移動(dòng)的方式,True為跳躍式,F(xiàn)alse為連續(xù)式,默認(rèn)情況下是False,即連續(xù)式。
所謂跳躍式,即移動(dòng)或拉動(dòng)控件時(shí),控件是以離散的方式在改變自己的位置和大小的,這個(gè)對窗體設(shè)計(jì)器中的控件對齊有幫助。而連續(xù)式,當(dāng)然就是以連續(xù)的方式使控件的位置和大小得到改變。
MoveStep :integer
當(dāng)移動(dòng)方式為跳躍式時(shí),該屬性指定跳躍的大小,范圍在5-20之間
另外還有一個(gè)方法:SetPointVisible(value:Boolean);用于指定移動(dòng)點(diǎn)的可見性。在Delphi中,當(dāng)你點(diǎn)擊窗口時(shí),控件周圍的八個(gè)小點(diǎn)就消失了,即用此原理。
 
現(xiàn)在開始進(jìn)入到具體實(shí)現(xiàn)的部分了,當(dāng)你點(diǎn)擊Delphi的窗體設(shè)計(jì)器中的控件時(shí),控件周圍出現(xiàn)了八個(gè)小點(diǎn),這八個(gè)小點(diǎn)其實(shí)也是窗口類:TGrabHandle。預(yù)想中要實(shí)現(xiàn)控件移動(dòng),得有一個(gè)標(biāo)識你正在移動(dòng)或拉動(dòng)的東西,這八個(gè)小點(diǎn)正是,Delphi的這種做法可以借鑒。于是我實(shí)現(xiàn)了一個(gè)移動(dòng)點(diǎn)類:TDragPoint,該的對象將作為TDragClass的成員之一,具體等一下再講。現(xiàn)在來看它的實(shí)現(xiàn),其實(shí)非常簡單,因?yàn)閂CL給了我們一個(gè)有自繪能力的類TCustomControl,只要從這里繼承下來,再重載其中的Paint方法,自己來畫這個(gè)移動(dòng)點(diǎn)就行了。
代碼非常簡單,這里就不多說了:
//---------TDragPoint--------------------------
unit UDragPoint;
 
interface
    uses Windows, Messages,Controls,Classes,Graphics;
type
  TDragPoint=class(TCustomControl)
    procedure Paint;override;
  public
    //處理移動(dòng)時(shí)用變量
    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.
 
這里有必須談到的一點(diǎn)是該類重載了WndCreate,并在其中寫入BringWindowToTop(self.Handle);這樣做目的是讓這些移動(dòng)點(diǎn)控件能夠位于窗口的最前位置。另外在其中顯化了三個(gè)鼠標(biāo)事件:
property OnMouseMove;
    property OnMouseDown;
    property OnMouseUp;
目的是為了在TDragClass中實(shí)現(xiàn)移動(dòng)這些點(diǎn)。
 
現(xiàn)在可以進(jìn)入主題,來說明TDragClass的實(shí)現(xiàn)了。
其中有一個(gè)保存?zhèn)鬟M(jìn)來的控件的列表類:FConList:TList;還有一個(gè)標(biāo)識當(dāng)前正在被移動(dòng)或拉動(dòng)的控件在FConList中的索引FCurActiveCon:Integer;
還有控件事件相關(guān)的成員
      FConMouseDown:TMouseEvent;
      FConMouseMove:TMouseMoveEvent;
      FConMouseup:TMouseEvent;
這三個(gè)事件方法指針指向所有傳進(jìn)來的控件的鼠標(biāo)事件的處理函數(shù),在Create中將得到賦值。而所有控件的鼠標(biāo)處理函數(shù)將在類中實(shí)現(xiàn)。
接下來就到了最重要的成員了:FPointRec:TPointRec;這是一個(gè)記錄類型,其定義為:
TPointRec=record
      LeftTop:TDragPoint;
      LeftBottom:TDragPoint;
      RightTop:TDragPoint;
      RightButton:TDragPoint;
      LeftMid:TDragPoint;
      TopMid:TDragPoint;
      RightMid:TDragPoint;
      ButtonMid:TDragPoint;
    end;
這正是當(dāng)前被移動(dòng)控件邊緣的八個(gè)點(diǎn)。這八個(gè)點(diǎn)會(huì)粘在被移動(dòng)控件的邊緣。
上面說過該類可以實(shí)現(xiàn)跳躍式移動(dòng)或拉動(dòng)則必定有相關(guān)的成員:      FisMoveStep:Boolean;
      FMoveStep:integer;
      MoveX,MoveY:integer;
FisMoveStep指定是否為跳躍式,F(xiàn)MoveStep為跳躍的幅度,MoveX,MoveY標(biāo)識控件移動(dòng)或拉動(dòng)的距離是否達(dá)到了FMoveStep,是就改變控件位置和大小,如此重復(fù)
除了上面那些成員,類中還定義了一些相類的方法,大概如下:
//-------對移動(dòng)點(diǎn)類的操作—
//創(chuàng)建移動(dòng)點(diǎn)類
      procedure CreateDragPoint(PointParent:TWinControl);
//設(shè)定移動(dòng)點(diǎn)類的位置
      procedure SetPointPos(posRect:TRect);
//指定移動(dòng)點(diǎn)類的父窗口
      procedure SetPointParent(PointParent:TWinControl);
//設(shè)置移動(dòng)點(diǎn)類的鼠標(biāo)事件
      procedure SetPointEvent;
//設(shè)置移動(dòng)點(diǎn)類的可見性
      procedure SetPointVisible(Visibled:Boolean);
//三個(gè)控件事件處理函數(shù),所有控件的鼠標(biāo)處理函數(shù)都將是這個(gè),主要是解決控件的移動(dòng)
//以及移動(dòng)點(diǎn)類的位置,當(dāng)你點(diǎn)擊某一個(gè)控件的時(shí)候,移動(dòng)點(diǎn)類會(huì)附著到這個(gè)控件的邊緣
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);
//移動(dòng)點(diǎn)類的鼠標(biāo)處理事件,解決移動(dòng)點(diǎn)類的移動(dòng),以及當(dāng)前控件的大小改變
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);
最后一個(gè)重要方法是function addControl(AddCon:Pointer):Boolean;
控件從這里加入,就可以實(shí)現(xiàn)移動(dòng)和拉動(dòng)了。
 
下面就將類實(shí)現(xiàn)比較重要的幾點(diǎn)略說一下吧(主要還是看代碼吧)
在類的構(gòu)造函數(shù)中,將上面的三個(gè)控件處理函數(shù)指定給三個(gè)指針成員:
FConMouseDown:=ConMouseDown;
FConMouseMove:=ConMouseMove;
FConMouseup:=ConMouseUp;
現(xiàn)在這三個(gè)成員就指定了三個(gè)處理函數(shù)的地址了,等一下就可以看到那些控件的鼠標(biāo)消息是怎么和這三個(gè)處理函數(shù)聯(lián)系在一起的,實(shí)現(xiàn)就在AddControl函數(shù)中。
 
AddControl是一個(gè)非常重要的方法,在控件加入之前,它要先判斷控件是否有Parent值,沒有則不能加入,更重要的一點(diǎn)是,在FConList是否已經(jīng)有這個(gè)控件了,即該控件已經(jīng)加入過了,如果已經(jīng)加入了,則不能再加一次,代碼如下:
//如果該控件已經(jīng)在列表中了,則加入失敗
  for i:=0 to FConList.Count-1 do
    if Integer(AddCon)=Integer(FConList.Items[i]) then
    begin
      result:=false;
      exit;
    end;
如果可以加入則先加入列表類中,再指定當(dāng)前活動(dòng)控件:
FConList.Add(AddCon);
FCurActiveCon:=FConList.Count-1;
而AddControl中還有一個(gè)比較重要的TempCon.Parent.DoubleBuffered:=True;
即加入的控件的父窗口設(shè)為雙緩沖模式,這樣在移動(dòng)控件或拉動(dòng)控件大小的時(shí)候,不會(huì)出現(xiàn)閃爍現(xiàn)象。
接著就是為加入的控件指定鼠標(biāo)處理函數(shù)了,但加入的是TControl,而他的鼠標(biāo)事件指針被設(shè)為保護(hù)類型,因此無法獲得,但他的子類把他們顯化出來了。這里用了一種折衷的方案:
  TButton(TempCon).OnMouseDown:=FconMouseDown;
  TButton(TempCon).OnMouseMove:=FconMouseMove;
  TButton(TempCon).OnMouseUp:=FconMouseUp;
這樣做并不會(huì)出錯(cuò),但顯得怪怪的,但不理他了,能實(shí)現(xiàn)功能就行了。現(xiàn)在加入控件的鼠標(biāo)事件都將會(huì)在這里的三個(gè)處理函數(shù)中處理了。
最后,將移動(dòng)點(diǎn)類移動(dòng)該控件的邊緣去。
說得夠雜的,各位可以和第二部分的原代碼對照著看,這樣會(huì)更好一些。
 
再稍微講一下跳躍式移動(dòng)或拉動(dòng)控件的實(shí)現(xiàn),F(xiàn)MoveStep指定跳躍的幅度,MoveX,MoveY:integer;用在移動(dòng)點(diǎn)類和控件的鼠標(biāo)事件中,累加鼠標(biāo)移動(dòng)的距離,當(dāng)達(dá)到FMoveStep時(shí),就移動(dòng)控件,或改變控件的大小,然后將MoveX,MoveY變?yōu)?,又繼續(xù)累加,如此循環(huán)
 
至于其他的就沒有什么好說的了,各位還是看看源代碼吧,也并不是很難理解。代碼在第二部分給出。