後一頁
前一頁
回目錄
回首頁
第九章 Delphi拖放程式設計

  拖放(DragDrop)是Windows提供的一種快捷的操作方式。作為基於Windows的開發工具,Delphi同樣支援拖放操作,而且開發應用系統的拖放功能十分方便,真正體現了Delphi的強大功能和方便性。

  Delphi提供的所有控件(Control,即能獲得輸入焦點的部件)都支援拖放操作,並有相應的拖放屬性、拖放事件和拖放方法。下面我們先介紹控件的拖放支援,而後再給出開發拖放操作的一般步驟和應用實例。 

9.1 控件的拖放支援 

  拖放操作中控件可以分為源控件和目標控件兩類。絕大部分控件既可以作為源控件也可以作為目標控件。但也有一部分控件只能支援其中的一種。 

9.1.1 拖放屬性 

  拖放屬性主要有兩個:

  ● DragMode : 拖動模式

  ● DragCursor : 拖動遊標 

  它們都是在拖放的源控件中設定。DragMode控制用戶在執行時間內當在控件上按下滑鼠時控件如何反應。如果DragMode置為dmAutomatic,那麼當用戶在控件上按下滑鼠時拖動自動開始;如果DragMode置為dmManual(這是缺省值),則將通過處理滑鼠事件來判斷一個拖動是否可以開始。

  DragCursor用於選擇拖動時顯示的遊標,缺省值是CrDrag,一般不要去修改它。在程式設計過程中一般的的介面規範應該得到開發者的尊重。但有時候為了特定的目的,開發者也可以把自己設計的遊標賦給DragCursor。 

9.1.2 拖放事件 

  拖放事件主要有三個:

  ●OnDragOver:拖動經過時激發

  ●OnDragDrop:拖動放下時激發

  ●OnEndDrop :拖動結束時激發 

  前兩個事件由目標控件響應,後一個事件由源控件響應。

  OnDragOver事件最主要的功能是確定當用戶就地放下拖動時控件是否可以接受。它的參數包括: 

Source : TObject;  {源控件}

X,Y : Integer; {遊標位置}

State : TDragState; {拖動狀態}

var Accept : Boolean {能否接受} 

  TDragState是一個枚舉型式,表示拖放項目與目標控件的關係。 

   type

TDragState = (dsDragEnter, dsDragLeave, dsDragMove);

  不同取值的意義如下表:

表9.1 DragState 的取值與意義

───────────────────────────

  取 值 意 義

———————————————————————————

dsDragEnter 拖動物件進入一個允許拖動物件放下

的控件中。為缺省狀態。

dsDragLeave 拖動物件離開一個允許拖動物件放下

的控件。

dsDragMove 拖動物件在一個允許拖動物件放下的

控件內移動。

───────────────────────────  

  用戶可以利用提供的參數來確定放下的拖動是否可被接受,如:

  ● 判斷源控件型式: 

   Accept := Source is TLabel;

  ● 判斷源控件物件: 

   Accept := (Source = TabSet1);

  ● 判斷遊標位置:

見(9.2),(9.3)中的例程。 

● 判斷拖動狀態: 

   If (Source is TLabel) and (State = dsDragMove) then

   begin

source.DragIcon := ' New.Ico ';

Accept := True;

   end

   else

   Accept := False;

  當Accept=True時,目標控件可以響應OnDragDrop事件,用於確定拖動被放下後程式如何進行處理。

  OnDragDrop事件處理過程的參數包括源控件和遊標位置。這些資訊可用於處理方式的確定。

  OnEndDrag事件是在拖動操作結束後由源控件來進行響應的,用於源控件進行相應的處理。拖動操作結束既包括拖動放下被接受,也包括用戶在一個不能接受放下的控件上釋放了滑鼠。該事件處理過程的參數包括目標控件(Target)和放下位置的坐標。如果Target=nil, 表示拖動項目沒有被任何控件接受。

  在第3節將介紹的文件拖放移動、拖放拷貝操作中,如果操作成功,則文件列示方塊應更新顯示內容。下面這段程式用於實現這一功能。 

procedure TFMForm.FileListEndDrag(Sender, Target: TObject; X, Y: Integer);

begin

if Target <> nil then FileList.Update;

end;

  除以上介紹的三個事件外,還有一個事件OnMouseDown 也常用於拖放操作的響應。OnMouseDown雖然不是一個專門的拖放事件,但在人工模式下拖動的開始是在這一事件的處理過程中實現的。 

9.1.3 拖放方法 

  拖放方法有三個:

  ●BeginDrag : 人工方式下開始一個拖動

  ●EndDrag : 結束一個拖動

  ●Dragging : 判斷一個控件是否正被拖動 

  這三個方法都被源控件使用。

  當DragMode置為dmManual時,拖動必須呼叫控件的BeginDrag方法才能開始。BeginDrag有一個布爾參數Immediate。如果輸入參數為True,拖動立即開始,遊標改變到DragCursor的設定。如果輸入參數為False,直到用戶將遊標移動了一定的距離(5個像素點)後才改變遊標,開始拖動。這就允許控件接受一個OnClick事件而並不開始拖動操作。

  EndDrag方法中止一個物件的被拖動狀態。它有一個布爾參數Drop。如果Drop設定為True,被拖動的物件在目前位置放下(能否被接受由目標控件決定);如果Drop設定為False,則拖動就地被取消。

  下面一段程式表明當拖動進入一控制面板時拖動被取消。     

procedure TForm1.Panel1DragOver(Sender, Source: TObject; X, Y: Integer;

State: TDragState; var Accept: Boolean);

begin

Accept := False;

if (Source is TLabel) and (State = dsDragEnter) then

(Source as TLabel).EndDrag(False);

end;

  Draging方法判斷一個控件是否正被拖動。在下面的例子中當用戶拖動不同的檢查框時視窗改變為不同的顏色。 

procedure TForm1.FormActivate(Sender: TObject);

begin

CheckBox1.DragMode := dmAutomatic;

CheckBox2.DragMode := dmAutomatic;

CheckBox3.DragMode := dmAutomatic;

end; 

procedure TForm1.FormDragOver(Sender, Source: TObject; X, Y: Integer;

State: TDragState; var Accept: Boolean);

begin

if CheckBox1.Dragging then

Color := clAqua;

if CheckBox2.Dragging then

Color := clYellow;

if CheckBox3.Dragging then

Color := clLime;

end; 

9.2 開發拖放功能的一般步驟 

  拖放作為Windows提供的一種方便操作物件的功能,在Delphi中可以很容易地開發出來。根據拖放操作的過程可以把開發步驟劃分為四個階段,即:

  ● 開始拖動操作

  ● 接收拖動項目

  ● 放下拖動項目

  ● 終止拖動操作 

  在介紹過程中我們將結合一個TabSet(標籤集)的拖放操作實例。介面設計如圖。在執行時當用戶把一個標籤拖動到另一個標籤的位置時,該標籤將移動到該位置並引起標籤集的重新布置。

9.2.1 開始拖動操作 

  當拖動模式(DragMode)設定為dmAutomatic時,用戶在源控件上按下滑鼠時拖動自動開始;當設定為dmManual時通過處理滑鼠事件來決定拖動是否開始。如果想開始拖動呼叫BeginDrag方法。

  在TabSet拖放中,我們用下面的MouseDown事件處理過程來開始一個標籤的拖動。首先判斷按下的是否是左鍵,而後再判斷項目是否合法。 

procedure TForm1.TabSet1MouseDown(Sender: TObject; Button: TMouseButton;

Shift: TShiftState; X, Y: Integer);

var

DragItem: Integer;

begin

if Button = mbLeft then

begin

DragItem := TabSet1.ItemAtPos(Point(X, Y));

if (DragItem > -1) and (DragItem < TabSet1.Tabs.Count) then

TabSet1.BeginDrag(False);

end;

end; 

9.2.2 接收拖動項目 

  一個控件能否接收拖動項目是由該控件的OnDragOver事件決定的。在TabSet拖動中,主要是利用滑鼠的位置進行判斷。  

procedure TForm1.TabSet1DragOver(Sender, Source: TObject; X, Y: Integer;

State: TDragState; var Accept: Boolean);

var

DropPos: Integer;

begin

if Source = TabSet1 then

begin

DropPos := TabSet1.ItemAtPos(Point(X, Y));

Accept := (DropPos > -1) and (DropPos <> TabSet1.TabIndex) and

(DropPos < TabSet1.Tabs.Count);

end;

else

Accept := False;

end; 

9.2.3 放下拖動項目 

  當OnDragOver事件處理過程返回的Accept為True且項目被放下時,由OnDragDrop事件處理過程來完成拖動放下後的響應。在TabSet拖放實例中是改變標籤的位置。 

procedure TForm1.TabSet1DragDrop(Sender, Source: TObject; X, Y: Integer);

var

OldPos: Integer;

NewPos: Integer;

begin

if Source = TabSet1 then

begin

OldPos := TabSet1.TabIndex;

NewPos := TabSet1.ItemAtPos(Point(X, Y));

if (NewPos > -1) and (NewPos <> OldPos) then

TabSet1.Tabs.Move(OldPos, NewPos);

end;

end; 

9.2.4 結束拖動操作 

  結束拖動操作的方式有兩種:或者是用戶釋放了滑鼠鍵或者是程式用EndDrag方法強行中止拖動。結束拖動操作的後果有兩種:放下被接受或放下被忽略。

  拖動操作結束後源控件都要收到一條訊息響應拖動結束事件OnEndDrag。 

9.3  拖放應用實例:文件管理器的拖放支援 

  在第六章最後開發的文件管理器應用實例,雖然功能上已初具規模,但在操作上與Windows的文件管理器相比還有很大不足。其中最大的缺陷是它不支援文件的拖放移動和拖放拷貝。在這一章結束的時候,我們可以來彌補這一缺陷了。

  文件拖放移動指的是當用戶把一個文件拖動到目錄樹下的某一目錄並放下時,文件將自動移動到該目錄中;文件拖放拷貝指的是當用戶把一個文件拖動到某個磁碟機標籤上並放下時,文件將自動拷貝到該磁碟機的目前工作目錄下。作為源控件的文件列示方塊和作為目標控件的目錄樹、磁碟機標籤可以位於不同的子視窗。磁碟機的目前工作目錄是任一子視窗的最新操作結果,而不論這一子視窗與拖動源、拖動目標是否有關係。

  為了實現上述功能,有兩個問題必須首先解決:

  1.如何記錄每一磁碟機的目前工作目錄?

  為此我們定義了一個全局變數: 

  var

CurentDirList: Array[0...25] of string[70]; 

在DirectoryOutline的OnChange事件中: 

procedure TFMForm.DirectoryOutlineChange(Sender: TObject);

begin

CreateCaption;

FileList.clear;

FileList.Directory := DirectoryOutline.Directory;

FileList.Update;

CurrentDirList[DriveTabSet.TabIndex] := DirectoryOutline.Directory;

FileManager.DirectoryPanel.Caption := DirectoryOutline.Directory;

end;  

  由於DriveTabSet在響應OnDragDrop事件前先響應OnClick事件,並由該事件激發DirectoryOutline的Onchange事件,因而可保證在任何時候OnDragDrop事件中用到的CurrentDirList數群群組項不為空字元串。

 2.如何保證移動、拷貝與子視窗的無關性?

  在這�堣@個關鍵問題是我們判斷源控件時是用is操作符進行型式檢查: 

If Source is TFileList then

…

  如果我們用下面的語句: 

  If Source = FileList then

   …

  則移動、拷貝操作將限制在本子視窗範圍內。

  當解決了上述問題後我們的工作就只是遵循拖放的一般開發步驟,按步就班來完成了。

  1.FileList開始拖動操作 

procedure TFMForm.FileListMouseDown(Sender: TObject; Button: TMouseButton;

Shift: TShiftState; X, Y: Integer);

begin

if Button = mbLeft then

with Sender as TFileListBox do

begin

if ItemAtPos(Point(X, Y), True) >= 0 then

BeginDrag(False);

end;

end;

  ItemAtPos用來檢查目前是否有文件存在。而BeginDrag方法傳遞參數False, 允許FileList單獨處理滑鼠事件而並不開始拖動。事實上這種情況是大量存在的。 

  2.DirectoryOutline、DriveTabSet決定是否能接受拖動的就地放下。  

procedure TFMForm.DirectoryOutlineDragOver(Sender, Source: TObject; X,

Y: Integer; State: TDragState; var Accept: Boolean);

begin

if Source is TFileListBox then

Accept := True;

end; 

procedure TFMForm.DriveTabSetDragOver(Sender, Source: TObject; X,

Y: Integer; State: TDragState; var Accept: Boolean);

var

PropPos: Integer;

begin

if Source is TFileListBox then

with DriveTabSet do

begin

PropPos := ItemAtPos(Point(X,Y));

Accept := (PropPos > -1) and (PropPos < Tabs.Count);

end;

end;

  DirectoryOutline是無條件的接受,而DriveTabSet需檢查是否是合法的標籤。 

  3.拖動放下的響應

  DirectoryOutline的拖動放下用於實現文件移動功能。程式中呼叫ConfirmChange事件處理過程,目標路徑由DirctoryOutline.Items[GetItem(X,Y)].FullPath來得到。  

procedure TFMForm.DirectoryOutlineDragDrop(Sender, Source: TObject; X,

Y: Integer);

begin

if Source is TFileListBox then

with DirectoryOutline do

begin

ConfirmChange('Move',FileList.FileName, Items[GetItem(X, Y)].FullPath);

end;

end;

  DriveTabSet的拖動放下用於實現文件拷貝功能。程式中把目前位置轉化為相應的磁碟機號,目標路徑由CurrentDirList[DriveTabSet.TabIndex]獲得。 

procedure TFMForm.DriveTabSetDragDrop(Sender, Source: TObject; X,Y: Integer);

var

APoint: TPoint;

begin

APoint.X := X; APoint.Y := Y;

DriveTabSet.TabIndex := DriveTabSet.ItemAtPos(APoint);

if Source is TFileListBox then

with DriveTabSet do

begin

if CurrentDirList[TabIndex] <> '' then

ConfirmChange('Copy',TheFilename,CurrentDirList[TabIndex]);

end;

end; 

4.FileList響應拖動結束,更新文件清單 

procedure TFMForm.FileListEndDrag(Sender, Target: TObject; X, Y: Integer);

begin

if Target <> nil then FileList.Update;

end; 

到目前為止,我們的文件管理器功能已足夠強大。 不過還有許多問題值得讀者去進

一步探討,如:

  1.文件與應用程式關聯的建立;

  2.在文件列示方塊中顯示更多的文件資訊;

  3.文件列示方塊中的文件按後綴各排序等。

  文件管理器是一個真正的綜合例程,對它的鑽研會使您更進一步模到Delphi程式設計的精髓。



後一頁
前一頁
回目錄
回首頁