其實鼠標用戶所最期盼的就是對拖放操作的充分支持。仔細端詳大多數的Windows應用軟件或Windows操作系統本身,我們會發現拖放能力是無處不在的。舉例來說,用戶早已非常習慣在Windows資源管理器中拖曳和置放文件,并且在Microsoft Word中拖曳和置放文字。
令人遺憾的是,只有極少數的VisualC#程序設計師會在他們所開發的應用程序中提供完善的拖放功能,當然,造成此現象的原因之一,就是要實現拖放功能確實有其困難度與復雜度。本節將讓您知道要利用Visual C#2003~2005以后的版本來實拖現放功能是多么簡單的一件事情。我們將實際展現如何在窗體內、在窗體之間,以及在應用程序之間移動和復制文字、圖片以及文件。
拖放操作是如何運作的
拖放操作其實與剪切與粘貼(或復制與粘貼)沒有什么不同,只不過它是使用鼠標而不是使用鍵盤。在這兩類操作中,您都會擁有一個來源(也就是您剪切或復制的對象)以及一個目標(也就是您所粘貼之處)。不論是哪一種操作,在操作期間,都會在內存中存在數據的一份副本。剪切與粘貼會使用到剪貼板,而拖放則會使用到一個DataObject對象,其實DataObject對象就好比是一個私有剪貼板。
在一個典型的拖放操作中,將會依序引發下列事件:
1.您可以通過調用源控件的DoDragDrop方法來初始化拖曳操作。DoDragDrop方法的語法如下所示:
DragDropEffects DoDragDrop(
Object data,
DragDropEffects allowedEffects)DoDragDrop方法會接受下列兩個參數:
- data參數用來指定所要拖曳(傳遞)的數據。
- allowedEffects參數用來指定哪些操作(“復制”和/或“移動”)是被允許的。
2.接下來會引發源控件的GiveFeedback事件。在大多數的情況下,您并不需要去處理GiveFeedback事件,但是如果您想在拖曳期間顯示一個自定義的鼠標指針,則可以在GiveFeedback事件處理函數中編寫程序代碼來完成此項設定。
3.AllowDrop屬性被設定成True的任何控件都可以是置放目標。您可以在設計階段在“屬性”窗口中將要作為目標控件的AllowDrop屬性設定成True,或者是于運行階段在窗體的Load事件處理函數中將要作為目標控件的AllowDrop屬性設定成True。
4.當您將鼠標指針移至任何一個控件的上方時,便會引發該控件的DragEnter事件。我們通常會在目標控件的DragEnter事件處理函數中,使用GetDataPresent方法去檢測所拖曳的數據格式是否適用于目標控件,并使用DragEventArgs類型參數的Effect屬性來設定所允許的置放操作。
5.如果用戶在一個有效的置放目標上放開鼠標按鍵,將會引發目標控件的DragDrop事件。我們通常會在目標控件的DragDrop事件處理函數中編寫程序代碼,從DataObject對象擷取數據并將其顯示于目標控件中。
關于拖放操作,您還必須注意下列事項:
- 某些控件具有自定義的特定拖曳事件。例如,ListView與TreeView控件就擁有ItemDrag事件。
- 當一項拖曳操作正在執行的時候,您可以處理QueryContinueDrag事件,該事件會向系統“要求使用權限”來繼續執行拖曳操作。當以該方法處理的時候,也是一種對調用那些對拖曳操作有影響的方法非常恰當的時機。比方說,當鼠標指針停留在TreeView控件上方的時候展開一個TreeNode。
- 您也可以定義您自己的DataFormats。做法非常簡單,您只需將您的對象指定為SetData方法中的Object參數,同時請確定所指定的對象是可序列化的。
- 除此之外,您還可以使用KeyState屬性,以便根據拖放操作期間所按下的按鍵來產生特定效果。舉例來說,當Ctrl鍵被按下時所拖曳的數據通常要進行復制。
程序范例CH8_DemoForm011.cs示范如何在兩個TextBox控件間拖曳文字,其功能特性如下所示:圖8.10示范如何拖曳文字
- 如圖8.10所示,由于右側上方TextBox控件的AllowDrop屬性被設定成False,因此您無法從左側的TextBox控件中將文字拖放其中。
- 如圖8.11所示,由于右側下方之TextBox控件的AllowDrop屬性被設定成True,因此您可以使用拖放方式將左側TextBox控件中的文字移動至右側下方的TextBox控件中。
- 值得一提的是,如果您持續按Ctrl鍵,則可以使用拖放方式將左側TextBox控件的文字復制到右側下方的TextBox控件中(如圖8.12所示)。

圖8.11通過拖放操作來移動文字

圖8.12通過拖放操作來復制文字
程序范例CH8_DemoForm011.cs在拖放操作方面的程序代碼如下所示:
// 聲明一個常量以便調試在拖曳期間Ctrl鍵是否被按下。
constbyte CtrlMask =8;


// 替左側的 TextBox 控件處理 MouseDown 事件。
// 當用戶在此控件的范圍內按下鼠標按鍵時便會引發此事件。
privatevoid txtLeft_MouseDown(object sender, MouseEventArgs e)
...{
// 如果用戶按下鼠標左鍵。
if (e.Button == System.Windows.Forms.MouseButtons.Left)
...{
// 選取文本框中所有的文字。
txtLeft.SelectAll();
// 初始化拖放操作。
txtLeft.DoDragDrop(
txtLeft.SelectedText,
DragDropEffects.Move | DragDropEffects.Copy);
}
}



// 處理右側下方 TextBox 控件的 DragEnter 事件。
// 當一個對象被拖曳至目標控件的范圍內時,就會引發
// 目標控件的 DragEnter 事件。
privatevoid txtLowerRight_DragEnter(object sender, DragEventArgs e)
...{
// 檢查被拖曳的數據的類型是否適用于目標控件。如果不適用,則拒絕置放。
if (e.Data.GetDataPresent(DataFormats.Text))
...{
// 如果在拖曳期間按著 Ctrl 鍵,則執行復制操作;反之,則執行移動操作。
if ((e.KeyState & CtrlMask) == CtrlMask)
...{
e.Effect = DragDropEffects.Copy;
}
else
...{
e.Effect = DragDropEffects.Move;
}
}
else
...{
e.Effect = DragDropEffects.None;
}
}



// 處理右側下方 TextBox 控件的 DragDrop 事件。
// 當用戶放開鼠標按鍵時就會引發此事件,并終止拖放操作。
privatevoid txtLowerRight_DragDrop(object sender, DragEventArgs e)
...{
txtLowerRight.Text = e.Data.GetData(
DataFormats.Text).ToString();
// 如果 Ctrl 鍵沒有被按下,移除源文字以便營造出移動文字的效果。
if ((e.KeyState & CtrlMask) != CtrlMask)
...{
txtLeft.Text ="";
}
}
從以上的程序代碼可以看出,我們會在拖放源(也就是左側的TextBox控件)的MouseDown事件處理函數中判斷鼠標按鍵已經被按下,而且如果用戶是按下鼠標左鍵的話,便會調用DoDragDrop 方法并傳遞下列兩個參數給它以便初始化拖曳操作:
- 我們使用TextBox控件中被選取的文字作為第一個參數(即data參數)的值,也就是TextBox控件中的文字將成為被拖曳的數據。
- 我們將第二個參數(也就是allowedEffects參數)設定成DragDropEffects.Move Or DragDropEffects.Copy,以便允許用戶移動或復制。
1.先使用GetDataPresent方法來檢查被拖曳的數據是否為純文字(DataFormats.Text)。如果不是純文字的話,便將Effect屬性設定成DragDropEffects.None表示置放目標不接受數據;如果是純文字的話,則繼續進行后續處理。
2.檢查Ctrl鍵是否被按下。如果Ctrl鍵被按下的話,便將Effect屬性設定成DragDropEffects.Copy,表示復制數據到置放目標中,此時鼠標指針將會顯示成復制指針圖標;如果Ctrl鍵沒有被按下的話,便將Effect屬性設定成DragDropEffects.Move,表示移動數據到置放目標中。
我們會于置放目標(也就是右側下方的TextBox控件)的DragDrop事件處理函數中執行下列處理:
1.使用GetData方法從DataObject對象中提取被拖曳的文字并將它賦給置放目標。
2.判斷Ctrl鍵是否被按下。如果Ctrl鍵沒有被按下,表示要執行移動操作,此時會移除來源文字以便營造出移動文字的效果。

