Visual C# 2005程序開發與界面設計祕訣——章立民作品


條款1  如何生成可執行文件.EXE

您可以採用下列兩種方式來生成可執行文件.EXE:
  • 方法一:在Visual Studio 2005的集成開發環境中,從“生成”菜單中選擇“生成解決方案”,即會在項目的\bin文件夾中產生.EXE文件。
  • 方式二:在SDK命令提示符窗口下,執行csc命令來編譯.EXE文件。
 條款2  如何指定.EXE的輸出位置

雖然可執行文件.EXE默認會保存在項目的\bin文件夾中,但是您可以依下列步驟來指定其輸出位置:
  1. 將您的Visual C# 2005項目在Visual Studio 2005的集成開發環境中啓動。
  2. 在“解決方案資源管理器”中執行下列操作之一:
    • 用鼠標左鍵雙擊Properties選項。
    • 在資源管理器窗口中單擊鼠標右鍵,在快捷菜單中選中“屬性”選項。
    鼠標單擊左側的“生成”索引標籤。在“輸出路徑”文字框中鍵入您所希望的輸出位置,或是單擊“瀏覽”按鈕來選擇其他輸出位置。值得一提的是,如果您希望將.EXE輸出至項目的根目錄下,可以將此文字框中的內容置空。  單擊右上角的“關閉”按鈕。           

條款3 如何設定啓動對象

啓動對象就是當加載應用程序時所要調用的進入點(Entry Point)。一般來說,我們會將啓動對象設定成應用程序的主窗體,或是當應用程序激活時所會執行的Main程序。值得注意的是,類庫項目與 ASP.NET Web應用程序項目都沒有進入點,因此沒有啓動對象。
第1章應用程序的基礎設置技巧要給一個Visual C# 2005的Windows應用程序項目設定啓動對象,您必須修改Programcs文件,基本的程序代碼如下所示:using System;
using SystemCollectionsGeneric;

using SystemWindowsForms;

namespace CH1

{

staticclass Program

{

///<summary>/// 應用程序的主要進入點。

///</summary>
[STAThread]

staticvoid Main()

{

ApplicationEnableVisualStyles();

ApplicationSetCompatibleTextRenderingDefault(
false);

ApplicationRun(
new StartUpForm());

}

}

}

條款4關閉窗體與結束應用程序

如果您只是要關閉窗體,請調用該窗體的Close方法。因此我們經常在窗體中的“關閉”按鈕的Click事件處理函數中編寫下列程序代碼:
this.Close();
如果您將應用程序項目的啓動窗體設定成某一個窗體,則調用該啓動窗體的Close方法時,就會結束應用程序。
一般來說,無論在何時結束應用程序,僅調用ApplicationExit方法即可。ApplicationExit方法會結束所有處在運行狀態的進 程,並關閉應用程序的所有窗口。ApplicationExit方法並不一定會強制結束應用程序。ApplicationExit方法一般是在消息隊 列中調用,並強制ApplicationRun返回。若只是要結束當前線程,則調用ApplicationExitThread方法即可。
ApplicationExit方法會引發下列事件,並執行與之相關聯的條件動作:
  • 每一個以OpenForms屬性表示的窗體都會引發FormClosing事件。您可以通過將事件的FormClosingEventArgs參數的Cancel屬性設定爲True,取消這個事件。
  • 如果是一個或多個處理例程取消事件,就會傳回ApplicationExit而不再執行進一步動作。否則,每一個處於活動狀態的窗體都會引發FormClosed事件,然後關閉所有正在運行的消息循環和窗體。

條款117如何在窗體加載時讓某一個控件取得焦點

在此我們將說明如何在窗體加載時,讓某一個控件取得焦點(Focus)——即使該控件成爲作用控件。我們將示範如何使用下列3種方式來完成此項操作。
  • 窗體的ActiveControl屬性能夠用來取得或設定窗體上的作用控件。因此,您只需將窗體的ActiveControl屬性設定成 窗體上的某一個控件,該控件就會成爲活動控件。如圖8.1所示是程圖8.1使Text屬性爲空字符串的TextBox控件成爲作用控件序範例 CH8_DemoForm001.cs的運行畫面,它會在窗體加載後,將Text屬性爲空字符串的TextBox控件設定成活動控件(也就是取得焦點)。 程序代碼列示如下:

圖8.1使Text屬性爲空字符串的TextBox控件

privatevoid CH4_DemoForm054_Load(object sender, EventArgs e)

{

int nCount =this.Controls.Count;



for (int i =0; i <= nCount -1; i++)

{

if (this.Controls[i] is System.Windows.Forms.TextBox)

{

// 找出 Text 屬性爲空字符串的 TextBox 控件。if (this.Controls[i].Text =="")

{

// 使沒有任何文字的 TextBox

// 控件成爲作用控件。this.ActiveControl =this.Controls[i];



// 跳離 For 循環。break;

}

}

}

}


  • 調用控件的Select方法即可啓動該控件並使其取得焦點。程序範例CH8_DemoForm002.cs的功能與上一個程序範例CH8_DemoForm001cs完全相同,只不過它如下所示,改用Select方法來使控件取得焦點:

this.Controls[i].Select();

  • 調用控件的Focus方法即可使該控件取得焦點。不過由於我們是在窗體加載時要使控件取得焦點,因此您必須先將窗體的Visible屬性設定成True。 程序範例CH7_DemoForm003.cs的功能與前面兩個程序範例完全相同,只不過它如下所示,改用Focus方法來使控件取得焦點:

this.Visible =true;
...

this.Controls[i].Focus();

...

條款118如何在控件中捕捉按鍵

如何於Windows Form控件中捕捉按鍵向來是許多程序設計師所關心的課題,基本上,標準的KeyUp、KeyDown與KeyPress事件就足以去捕捉並處理按鍵。然而問題在於,並非所有的控件會在所有的情況下爲所有的按鍵操作產生這些事件。

圖8.2CH8_DemoForm004.cs運行畫面

第8 章探討重要的人機界面設計技巧如果您希望不管控件的狀況如何,都能夠順利地捕捉WindowsForm控件中的按鍵,必須根據該控件的類派生出一個新的類 並重寫ProcessCmdKey方法,並在此重寫方法中編寫程序代碼來捕捉並處理您所需的按鍵。系統會傳遞兩個參數給ProcessCmdKey方法: msg與keyData。msg參數含有所要處理的窗口信息(例如:WM_KEYDOWN),此窗口信息是以傳址方式傳遞的。keyData參數則會含有 被按下的按鍵的按鍵碼,也就是其中一個Keys值(注意,keyData參數的類型是Keys枚舉類型)。如果CTRL或ALT鍵也被按下, keyData參數還會含有輔助按鍵(Modifier Key)信息。
您並非一定要使用msg參數,也就是說,您可以忽略它。不過利用msg參數來檢測窗口信息倒是一項不錯的選擇。在稍後的程序範例中,我們會去檢測窗口信息 是否爲WM_KEYDOWN,以便確認這是一個按鍵事件。此外,我們也會去檢測窗口信息是否爲WM_SYSKEYDOWN,以便確認按鍵組合是否包含輔助 按鍵。
由於DataGridView控件的狀況最爲複雜,因而在此我們就要示範如何在DataGridView控件中捕捉按鍵,請切記,您可以將同樣的方法應用 在其他的控件中。圖82所示是程序範例CH8_DemoForm004.cs的運行畫面,從窗體的標題欄變化可以看出,只要DataGridView控 件取得焦點,不論它是否顯示數據,都能夠捕捉用戶在DataGridView控件中的按鍵。顯而易見地,本程序範例的關鍵在於必須根據 DataGridView控件派生出一個新的類並重寫其ProcessCmdKey方法。這裏將派生類MyDataGridView的程序代碼列示如下 (編寫在MyDataGridView.cs中):
publicpartialclass MyDataGridView :

System.Windows.Forms.DataGridView

{

...

...

protectedoverridebool ProcessCmdKey(

ref Message msg, Keys keyData)

{

constint WM_KEYDOWN =0x100;

constint WM_SYSKEYDOWN =0x104;



if ((msg.Msg == WM_KEYDOWN) ||

(msg.Msg
== WM_SYSKEYDOWN))

{

switch (keyData)

{

case Keys.Down:

this.Parent.Text ="向下鍵已經被捕捉";

break;

case Keys.Up:

this.Parent.Text ="向上鍵已經被捕捉";

break;

case Keys.Left:

this.Parent.Text ="向左鍵已經被捕捉";

break;

case Keys.Right:

this.Parent.Text ="向右鍵已經被捕捉";

break;

case Keys.Home:

this.Parent.Text ="Home 鍵已經被捕捉";

break;

case Keys.End:

this.Parent.Text ="End 鍵已經被捕捉";

break;

}

}



returnbase.ProcessCmdKey(ref msg, keyData);

}

}

條款 119剪貼板的數據擷取與存入

有許多應用程序都會使用剪貼板(Clipboard)作爲數據的暫存處,而此項需求通常與用戶的操作相關聯。舉例來說,當我們在文字處理軟件中進行剪切、 複製與粘貼等操作時就會使用到剪貼板。由此可知,學會如何將數據存入剪貼板以及如何從剪貼板擷取數據便成爲一項非常重要的課題。
將數據存入剪貼板要在Windows應用程序中將數據存入剪貼板必須分兩方面來討論:用戶操作與程序控制方式。所謂的用戶操作就是當用戶進行復制或剪切操 作時,會將數據存入剪貼板中。所謂程序控制方式就是如何通過程序代碼將數據存入剪貼板中,顯然此作法纔是值得我們討論的。
要以程控方式將數據存入剪貼板中,應該通過Clipboard類的SetDataObject方法來完成。SetDataObject方法會使用 IDataObject接口將數據以“多重格式”保存在剪貼板中,而此舉最大的好處是,以後可以採用各種不同的格式從剪貼板中擷取數據。畢竟當我們將數據 存入剪貼板時,可能無法確定未來會採用哪一種格式從剪貼板中擷取數據,爲了提高從剪貼板擷取數據的機會,以多重格式將數據保存在剪貼板中是非常恰當的做 法。
SetDataObject方法共提供如圖8.3所示的3個重載版本。語法中的data參數即是您要存入剪貼板中的數據,第二個參數copy用來決定在結 束應用程序之後是否要保留剪貼板中的數據。如果您沒有指定第二個參數或是將第二個參數設定成False,則當應用程序結束時,數據會從剪貼板中刪除;如果 您將第二個參數設定成True,則當應用程序結束時,數據仍然會保留在剪貼板中。
值得注意的是,如果剪貼板忙於運行其他進程或應用程序,則嘗試將數據加入到剪貼板時有可能會失敗。如果要在經常使用剪貼板的環境中解決這個問題,第三個重 載版本的SetDataObject方法就顯得非常有用。您可以使用第3個參數retryTimes來設定嘗試將數據放置於剪貼板上的次數,並使用第4個 參數retryDelay來設定在每一次嘗試之間暫停的毫秒數。
<;P align="center"><IMG src="/BookFiles/133/00/image003.jpg"  border=0 /></P>
以下面的簡例而言,表示將名稱爲TextBox1的TextBox控件中的數據存入剪貼板中:
Clipboard.SetDataObject(TextBox1.Text);
事情就這樣結束了嗎?當然還沒有。上面這一個簡例其實是一種非常簡單的情況,因爲位於TextBox控件中的數據是由用戶所輸入的。然而,您可曾想過一個 問題,假設我希望將某一個圖片文件(.bmp、.jpg或.gif)的圖像數據存入剪貼板,或是希望將某一個文字文件中的文字數據存入剪貼板的話,該怎麼 做呢?諸如此類情況,還必須藉助於DataObject類才能順利取得要存入剪貼板中的數據,並將其作爲SetDataObject方法的data參數。
DataObject類實現IDataObject接口,它的各種方法提供了不受格式影響的數據傳輸機制。DataObject主要是使用於剪貼板和拖放 操作的相關處理中。DataObject類提供IDataObject接口的建議實現,也就是說,您應該使用DataObject而不要自己去實現 IDataObject。
您可以將不同格式的多項數據存儲在DataObject對象中。這樣做的最大好處是,以後可以採用各種不同的格式從DataObject中擷取數據。畢竟 當我們將數據存入DataObject對象時,可能無法確定未來會採用哪一種格式從DataObject對象中擷取數據,爲了提高從DataObject 對象擷取數據的機會,以多重格式將數據存儲在DataObject對象中是最恰當的做法。
如果您要將數據存儲在DataObject對象中,請將數據傳遞給DataObject的構造函數(Constructor)或是在創建 DataObject對象之後再調用其SetData方法。要想從DataObject對象中以特定格式擷取數據,請調用DataObject對象的 GetData方法。
稍後的程序範例將會示範如何使用DataObject對象,請稍安勿躁。
從剪貼板擷取數據如果您要從剪貼板中擷取數據,請依下列步驟進行:
1.首先,請調用Clipboard類的GetDataObject方法。GetDataObject方法會將剪貼板中的數據以一個實現IDataObject接口的對象返回。例如:
IDataObject data = Clipboard.GetDataObject();
2.接下來,請調用被GetDataObject方法返回的對象(也就是實現IDataObject接口的對象)的GetDataPresent方法,以便檢測數據是否含有您所需的格式。
3.如果GetDataPresent方法返回True表示存在您所需的格式,最後的工作就是調用被返回對象的GetData方法,以便以指定的格式取得數據。例如:
if (data.GetDataPresent(DataFormats.Text))
{
TextBox1.Text = data.GetData(DataFormats.Text).ToString();
}

圖8.4CH8_DemoForm005.cs運行畫面

程序範例1

圖8.4所示是程序範例CH8_DemoForm005.cs的運行畫面,它示範如何使用DataObject與 Clipboard類將圖形文件中的圖像數據複製到剪貼板,然後再將剪貼板中的圖像數據複製到文件中。完成本程序的執行操作後,剪貼板中的圖像數據會分別 被複制到C磁盤的Test.bmp、Test.gif與Test.jpeg等3個圖形文件中。

相關程序代碼如下所示:
privatevoid btnCopyImageToClipboard_Click(

object sender, EventArgs e)

{

try

{

// 創建一個 DataObject 對象。
DataObject myDataObject
=new DataObject();



// 將項目的圖像資源存入 DataObject 對象中,

// 並設定圖像數據可以被轉換成其他格式。
myDataObject.SetData(

DataFormats.Bitmap,
true

Resources.章立民的大頭照);



// 將持有圖形文件的圖像數據的 DataObject 對象

// 存入剪貼板中。
Clipboard.SetDataObject(myDataObject,
true);

btnSaveClipboardToFile.Enabled
=true;

}

...

}



// 將剪貼板中的圖像數據複製到文件中。privatevoid btnSaveClipboardToFile_Click(

object sender, EventArgs e)

{

try

{

// 將剪貼板中的數據以一個實現 IDataObject接口的對象返回。
IDataObject oDataObj
= Clipboard.GetDataObject();



if (oDataObj !=null)

{

// 檢測從剪貼板所返回的數據是否存在Windows位圖的格式。if (oDataObj.GetDataPresent(DataFormats.Bitmap))

{

// 以Windows位圖格式取得影像數據。
System.Drawing.Image oImgObj
=

(Image)(oDataObj.GetData(

DataFormats.Bitmap,
true));



// 存儲成Bitmap。
oImgObj.Save(
@"C:\Test.bmp"

System.Drawing.Imaging.ImageFormat.Bmp);



// 存儲成JPEG。
oImgObj.Save(
@"C:\Test.jpeg"

System.Drawing.Imaging.ImageFormat.Jpeg);



// 存儲成GIF。
oImgObj.Save(
@"C:\Test.gif"

System.Drawing.Imaging.ImageFormat.Gif);

}

}

Process.Start(
"explorer.exe"@"C:\");

}

...

}


程序範例2
程序範例CH8_DemoForm006.cs詳細示範如何以各種格式將數據存入剪貼板並且從剪貼板中以特定格式擷取數據。首先,我們來查看其功能特性:
  • 您可以將文本字符串“From Microsoft Community!”以特定的格式或所有的格式(即多重格式)複製到剪貼板中,然後再以特定格式從剪貼板中擷取出來並貼入RichTextBox或TextBox控件中。
以 圖8.5所示的操作而言,我們將文本字符串“From Microsoft Community!”以所有的格式(即多重格式)複製到剪貼板中,然後以Rich TextFormat從剪貼板中擷取出來。請注意,由於我們以多重格式將數據存入剪貼板,因此在“粘貼爲”的子菜單項目中會列出剪貼板中所有可用的格式。
以圖8.6所示的操作而言,我們將文本字符串“From Microsoft Community!”以HTM格式複製到剪貼板中,然後以HTML格式從剪貼板中擷取出來。請注意,由於我們以單一格式將數據存入剪貼板,因此在“粘貼爲”的子菜單項目中只會列出一種格式。

圖8.5將文本字符串以所有格式進行復制,以Rich Text格式擷取

圖8.6將文本字符串以HTM格式進行


 

複製與擷取操作

  • 本程序範例還可以將項目的圖像資源“章立民_01”複製到剪貼板,然後再從剪貼板中取出並粘貼到RichTextBox或PictureBox控件中(如圖8.7所示)。
  • 當然,您也可以在其他應用程序中將數據複製到剪貼板,然後再於本程序範例中進行粘貼操作;反之亦然。例如,假設您已經在Microsoft Word中複製文字,則“粘貼爲”菜單將會如圖8.8所示,列出剪貼板中所有可用的格式。
充分了解了本程序範例的功能特性之後,我們要研究本程序範例的編寫技巧,說明如下:
  • 首先,您必須創建各個變量來持有文本字符串“From Microsoft Taiwan Community!”以及項目的圖像資源“章立民_01.JPG”:// 下列文本字符串會以各種不同格式來存儲文字
// "From Microsoft Taiwan Community!"。

privatestring strText ="From Microsoft Community!";

privatestring strHTML ="<P>From <B><FONT

size
=' 4'><U>Microsoft</U></FONT></B>" +

"<FONT size=' 5' >Community!</FONT></P>";

圖8.7將項目的圖像資源進行復制與粘貼操作

圖8.8顯示剪貼板中所有可用格式

// 將文本字符串 "From Microsoft Taiwan!" 以標準的 ANSI 文字格式複製到剪貼板。privatevoid tsmiCopyTextAsText_Click(object sender, EventArgs e)

{

Clipboard.SetDataObject(strText,
true);

}


  • 當您從“編輯”菜單中選取“將文字複製爲/Text”命令時,將會執行以下的程序代碼:
// 將文本字符串 "From Microsoft Taiwan!" 以標準的 ANSI 文字格式複製到剪貼板。privatevoid tsmiCopyTextAsText_Click(object sender, EventArgs e)

{

Clipboard.SetDataObject(strText,
true);

}


  • 當您從“編輯”菜單中選取“將文字複製爲/HTML”命令時,將會運行以下的程序代碼:
// 將文本字符串 "From Microsoft Taiwan!" 以HTML 格式複製到剪貼板。privatevoid tsmiCopyTextAsHTML_Click(

object sender, EventArgs e)

{

DataObject myDataObject
=new DataObject();



myDataObject.SetData(DataFormats.Html, strHTML);

Clipboard.SetDataObject(myDataObject,
true);

}


  • 當您從“編輯”菜單中選取“將文字複製爲/RTF”命令時,將會執行以下的程序代碼:
// 將文本字符串 "From Microsoft Taiwan!" 以 XML 格式複製到剪貼板。

// 請注意,由於 XML 並不是剪貼板所自帶的格式,因而這代表

// 一種獨有的格式。因爲此格式在本應用程序之外可能不具有意

// 義,所以我們將 SetDataObject 方法的第二個參數設定成 False,

// 來要求在結束本程序之後不保留剪貼板上的數據,以便不讓其他應

// 用程序使用。privatevoid tsmiCopyTextAsXML_Click(object sender, EventArgs e)

{

DataObject myDataObject
=new DataObject();

myDataObject.SetData(
"MyInternalXmlFormat", strXML);

Clipboard.SetDataObject(myDataObject,
false);

}


  • 當您從“編輯”菜單中選取“將文字複製爲/XML”命令時,將會執行以下的程序代碼:
// 將文本字符串 "From Microsoft Taiwan!" 以 XML 格式複製到剪貼板。

// 請注意,由於 XML 並不是剪貼板所自帶的格式,因而這代表

// 一種獨有的格式。因爲此格式在本應用程序之外可能不具有意

// 義,所以我們將 SetDataObject 方法的第二個參數設定成 False,

// 來要求在結束本程序之後不保留剪貼板上的數據,以便不讓其他應

// 用程序使用。privatevoid tsmiCopyTextAsXML_Click(object sender, EventArgs e)

{

DataObject myDataObject
=new DataObject();

myDataObject.SetData(
"MyInternalXmlFormat", strXML);

Clipboard.SetDataObject(myDataObject,
false);

}


  • 當您從“編輯”菜單中選取“將文字複製爲/所有的格式”命令時,將會執行以下的程序代碼:
// 將文本字符串 "From Microsoft Taiwan!" 以所有可用的格式複製到剪貼

// 板。您只能使用 DataObject 對象來完成此項操作。我們會爲每一種

// 格式調用 DataObject 對象的 SetData 方法以便將文本字符串以該格式

// 存入 DataObject 對象中。privatevoid tsmiCopyTextAsAllFormats_Click(object sender, EventArgs e)

{

// 創建一個 DataObject 對象。
DataObject myDataObject
=new DataObject();



// 將文本字符串以標準的ANSI 文字格式存入 DataObject 對象中。
myDataObject.SetData(DataFormats.Text, strText);



// 將文本字符串以標準的 Windows Unicode 文字格式存入 DataObject 對象中。
myDataObject.SetData(DataFormats.UnicodeText, strText);



// 將文本字符串以 HTML 格式存入 DataObject 對象中。
myDataObject.SetData(DataFormats.Html, strHTML);



// 將文本字符串以 RTF 格式存入 DataObject 對象中。
myDataObject.SetData(DataFormats.Rtf, strRTF);



// 將文本字符串以 XML 格式存入 DataObject 對象中。
myDataObject.SetData(
"MyInternalXmlFormat", strXML);



// 將DataObject對象與其包含的所有格式數據存入剪貼板中。Clipboard.SetDataObject(myDataObject, true);
}


當您從“編輯”菜單中選取“將圖像複製爲/Bitmap”命令時,將會執行以下的程序代碼:
' 將圖形文件中的圖像數據複製到剪貼板。privatevoid tsmiCopyImageAsBitmap_Click(

object sender, EventArgs e)

{

try

{

// 創建一個 DataObject 對象。
DataObject myDataObject
=new DataObject();



// 將 myImage 中的圖像數據存入 DataObject 對象中,

// 並設定圖像數據可以被轉換成其他格式。
myDataObject.SetData(DataFormats.Bitmap,
true, myImage);



// 將持有圖形文件的圖像數據的 DataObject對象存入剪貼板中。
Clipboard.SetDataObject(myDataObject,
true);

}

...

}


  • 請大家注意,“粘貼爲”菜單是動態產生的。我們將產生“粘貼爲”菜單及其子菜單項目標程序代碼編寫在“編輯”菜單項目的 DropDownOpening事件處理函數中。之所以如此做,是爲了能夠動態地根據剪貼板中數據的可用格式來創建“粘貼爲”菜單的各個子菜單。相關程序 代碼如下所示:
privatevoid PasteAsMenuEventHandler(object sender, System.EventArgs e)

{

string strType; // 持有格式的值。object obj; // 被用來持有要粘貼的數據。



// 清除 RichTextBox。this.rtbPaste.Clear();



// 清除 TextBox。this.txtPaste.Clear();



// 清除 PictureBox。this.picturePaste.Image =null;



// 取得用戶所選取之格式的文字。
strType
= ((ToolStripMenuItem)(sender)).Text;



// 確保剪貼板支持所選取的格式。if (Clipboard.GetDataObject().GetDataPresent(strType))

{

// 依所要求的格式從剪貼板中取得數據並賦給obj。
obj
= Clipboard.GetDataObject().GetData(strType);



if (obj !=null)

{

// 使用 RichTextBox 控件的 Paste方法以便以指定的剪貼板格式

// 將剪貼板的內容粘貼到 RichTextBox控件中。this.rtbPaste.Paste(

DataFormats.GetFormat(strType));



// 將純文字表示粘貼到 TextBox 控件中。if (obj.GetType().ToString() =="System.String")

{

this.txtPaste.AppendText((string)(obj));

}

else

{

this.txtPaste.AppendText(obj.GetType().ToString());

}



// 嘗試粘貼到PictureBox控件中。

// 如果失敗的話,表示PictureBox不支持此格式,

// 因此會將 Image 設定成 Null 以便清除圖像。try

{

picturePaste.Image
= (Image)(obj);

}

...

}

}

}


  • 以下所示則是事件處理函數PasteAsMenuEventHandler的程序代碼,它會負責處理所有的粘貼菜單項目的Click事件,而不管哪一種格式被選取:
privatevoid PasteAsMenuEventHandler(object sender, System.EventArgs e)

{

string strType; // 持有格式的值。object obj; // 被用來持有要粘貼的數據。



// 清除 RichTextBox。this.rtbPaste.Clear();



// 清除 TextBox。this.txtPaste.Clear();



// 清除 PictureBox。this.picturePaste.Image =null;



// 取得用戶所選取之格式的文字。
strType
= ((ToolStripMenuItem)(sender)).Text;



// 確保剪貼板支持所選取的格式。if (Clipboard.GetDataObject().GetDataPresent(strType))

{

// 依所要求的格式從剪貼板中取得數據並賦給obj。
obj
= Clipboard.GetDataObject().GetData(strType);



if (obj !=null)

{

// 使用 RichTextBox 控件的 Paste方法以便以指定的剪貼板格式

// 將剪貼板的內容粘貼到 RichTextBox控件中。this.rtbPaste.Paste(

DataFormats.GetFormat(strType));



// 將純文字表示粘貼到 TextBox 控件中。if (obj.GetType().ToString() =="System.String")

{

this.txtPaste.AppendText((string)(obj));

}

else

{

this.txtPaste.AppendText(obj.GetType().ToString());

}



// 嘗試粘貼到PictureBox控件中。

// 如果失敗的話,表示PictureBox不支持此格式,

// 因此會將 Image 設定成 Null 以便清除圖像。try

{

picturePaste.Image
= (Image)(obj);

}

...

}

}

}


程序範例3
圖8.9所示是程序範例CH8_DemoForm007.cs的運行畫面,它示範如何使用DataObject與Clipboard類將項目的音頻數據複製到剪貼板,然後再播放剪貼板中的音頻數據。相關程序代碼如下所示:

圖8.9複製與播放音頻數據

privatevoid btnCopyWavAudioToClipboard_Click(

object sender, EventArgs e)

{

try

{

// 創建一個 DataObject 對象。
DataObject myDataObject
=new DataObject();



// 將項目的音頻數據GoTop存入DataObject對象中,

// 並設定音頻數據可以被轉換成其他格式。
myDataObject.SetData(

DataFormats.WaveAudio,
true, Resources.GoTop);



// 將持有音頻數據的 DataObject 對象存入剪貼板中。
Clipboard.SetDataObject(myDataObject,
true);



btnPlayClipboardWavAudio.Enabled
=true;

}

...

}



privatevoid btnPlayClipboardWavAudio_Click(object sender, EventArgs e)

{

try

{

// 將剪貼板中的數據以一個

// 實現 IDataObject 接口的對象返回。
IDataObject oDataObj
= Clipboard.GetDataObject();



if (oDataObj !=null)

{

// 檢測從剪貼板所返回的數據是否存在 Wav Audio 的格式。if (oDataObj.GetDataPresent(

DataFormats.WaveAudio))

{

// 以 Wave Audio 格式取得音頻數據並進行播放。
SoundPlayer player
=new SoundPlayer(

(System.IO.Stream)(

oDataObj.GetData(

DataFormats.WaveAudio,
true)));



player.Play();

}

}

}

...

條款120.NET Framework 2.0對剪貼板存取操作做了哪些強化

爲了讓剪貼板的數據存取操作更加便利且單純化,.NET Framework 2.0替Clipboard類新增了下列方法:
  • 您現在可以調用Clipboard.Clear方法來清除剪貼板中的所有數據。不過由於剪貼板並非是單一程序所使用的,而是多個進程所共享的,因此在調用Clear方法之前,請先確認此舉不會對其他的進程造成不良影響。
  • 我 們在前一節中一再強調,Clipboard類的SetDataObject方法會使用IDataObject接口將數據以“多重格式”存儲在剪貼板中,而 此舉最大的好處是,以後可以採用各種不同的格式從剪貼板中擷取數據。但是或許您就是想以特定的單一數據格式來將數據存儲在剪貼板中,而不需要使用多重格 式,爲了讓您以更直接的方式來完成此類操作,Clipboard類現在新增了下列數個前綴爲Set的方法來讓您將數據以特定格式存入剪貼板中,而不再需要 通過DataObject對象來完成:
    • Clipboard.SetText方法能夠將文字數據存入剪貼板中。
    • Clipboard.SetImage方法能夠將一個Image以Bitmap格式存入剪貼板中。
    • Clipboard.SetAudio方法能夠將數據以WaveAudio格式存入剪貼板中。
    • Clipboard.SetFileDropList方法能夠將一個文件名稱集合以FileDrop 格式存入剪貼板中。
    • Clipboard.SetData方法能夠將數據以特定的格式存入剪貼板中。
       
  • 在我們從剪貼板提取數據之前,通常會先確認是否存在所需格式的數據。爲了幫助您以更直接且便利的方式完成此類操作,Clipboard類現在新增了下列數個前綴爲Contains的方法,而不再需要通過IDataObject接口來完成:
  • Clipboard.ContainsText方法能夠判斷剪貼板中是否存在文字數據。
    •     Clipboard.ContainsImage方法能夠判斷剪貼板中是否存在Bitmap格式的數據或是數據能否轉換成Bitmap格式。
    • Clipboard.ContainsFileDropList方法能夠判斷剪貼板中是否存在FileDrop格式的數據或是數據能否轉換成FileDrop格式。
    • Clipboard.ContainsAudio方法能夠判斷剪貼板中是否存在WaveAudio格式的數據。
    • Clipboard.ContainsData方法能夠判斷剪貼板中是否存在指定格式的數據或是數據能夠轉換成所指定的格式。
       
  • 確認剪貼板中存在所指定格式的數據後,接下來就是要將該數據提取出來。Clipboard類現在新增了下列數個前綴爲Get的方法,來讓您直接提取剪貼板中特定格式的數據,而不再需要通過IDataObject接口來完成:
    • Clipboard.GetText方法能夠從剪貼板中提取文字數據。
    • Clipboard.GetImage方法能夠從剪貼板中提取圖像數據。
    • Clipboard.GetFileDropList方法能夠從剪貼板中提取文件名稱的集合。
    • Clipboard.GetAudioStream方法能夠從剪貼板中提取音頻數據流。
    • Clipboard.GetData方法能夠從剪貼板中提取特定格式的數據。
       
顯而易見地,藉助於Clipboard類新增的這些方法,將使得剪貼板的數據存取操作變得更加容易與直觀。現在,我們就要用這些方法,來改寫前一節的程序範例。
程序範例1
程序範例CH8_DemoForm008.cs的功能用途與前一節的第一個程序範例CH8_DemoForm005.cs完全相同,只不過本程序範例 CH8_DemoForm008.cs改用Clipboard類所新提供的SetImage、ContainsImage與GetImage方法來進行剪 貼板的圖像數據的存取操作。從以下的程序代碼可以看出,新的方法可以讓程序代碼更爲精簡且容易瞭解:
privatevoid btnCopyImageToClipboard_Click(object sender, EventArgs e)

{

try

{

// 將項目的圖像資源存入剪貼板中。
Clipboard.SetImage(Resources.章立民的大頭照);



btnSaveClipboardToFile.Enabled
=true;

}

...

}



privatevoid btnSaveClipboardToFile_Click(object sender, EventArgs e)

{

try

{

// 判斷剪貼板中是否存在圖像數據。if (Clipboard.ContainsImage())

{

// 使用GetImage方法取得剪貼板中的圖像數據。
System.Drawing.Image oImgObj
=

Clipboard.GetImage();



// 存儲成 Bitmap。
oImgObj.Save(
@"C:\Test.bmp"

System.Drawing.Imaging.ImageFormat.Bmp);



// 存儲成 JPEG。
oImgObj.Save(
@"C:\Test.jpeg"

System.Drawing.Imaging.ImageFormat.Jpeg);



// 存儲成 GIF。
oImgObj.Save(
@"C:\Test.gif"

System.Drawing.Imaging.ImageFormat.Gif);



Process.Start(
"explorer.exe"@"C:\");

}

}

...

}


程序範例2
程序範例CH8_DemoForm009.cs的功能用途與前一節的第二個程序範例CH8_DemoForm006.cs完全相同,只不過本程序範例 CH8_DemoForm009.cs改用Clipboard類所新提供的SetText、SetImage與SetData方法來將文本字符串與圖像數 據存入剪貼板中。從以下的程序代碼可以看出,新的方法可以讓程序代碼更爲精簡且容易瞭解:
// 將文本字符串 "From Microsoft !" 以標準的 ANSI 文字格式複製到剪貼板。privatevoid tsmiCopyTextAsText_Click(object sender, EventArgs e)

{

Clipboard.SetText(strText, TextDataFormat.Text);

}



// 將文本字符串 "From Microsoft !" 以 HTML格式複製到剪貼板。privatevoid tsmiCopyTextAsHTML_Click(object sender, EventArgs e)

{

Clipboard.SetText(strHTML, TextDataFormat.Html);

}



// 將文本字符串 "From Microsoft !" 以 RTF格式複製到剪貼板。privatevoid tsmiCopyTextAsRTF_Click(object sender, EventArgs e)

{

Clipboard.SetText(strRTF, TextDataFormat.Rtf);

}



// 將文本字符串 "From Microsoft !" 以 XML 格式複製到剪貼板。

// 請注意,由於 XML 並不是剪貼板所自帶的格式,因而這代表

// 一種獨有的格式。privatevoid tsmiCopyTextAsXML_Click(object sender, EventArgs e)

{

Clipboard.SetData(
"MyInternalXmlFormat", strXML);

}



// 將項目的圖像資源存入剪貼板中。privatevoid tsmiCopyImageAsBitmap_Click(object sender, EventArgs e)

{

try

{

Clipboard.SetImage(Resources.章立民_01);

}

...

}


仔細比較程序範例CH8_DemoForm009.cs與CH8_DemoForm006.cs之後可以發現,CH8_DemoForm009.cs仍然 保留了CH8_DemoForm006.cs極大部分的寫法,尤其是在將數據以“多重格式”存儲在剪貼板中以及判斷剪貼板中擁有哪些格式的操作方面,仍然 必須藉助於DataObject對象以及IDataObject接口。因此大家可別以爲有了新的就忘了舊的,應該學會如何綜合運用所有的技巧纔是上策。
程序範例3
程序範例CH8_DemoForm010.cs的功能用途與前一節的第三個程序範例CH8_DemoForm007.cs完全相同,只不過本程序範例 CH8_DemoForm010.cs改用Clipboard類所新提供的SetAudio、ContainsAudio與GetAudioStream 方法來進行剪貼板的音頻數據的存取操作。從以下的程序代碼可以看出,新的方法可以讓程序代碼更爲精簡且容易瞭解:
privatevoid btnCopyWavAudioToClipboard_Click(

object sender, EventArgs e)

{

try

{

// 將項目的音頻資源 GoTop 存入剪貼板中。
Clipboard.SetAudio(Resources.GoTop);



btnPlayClipboardWavAudio.Enabled
=true;

}

...

}



privatevoid btnPlayClipboardWavAudio_Click(object sender, EventArgs e)

{

try

{

// 判斷剪貼板中是否存在音頻數據。if (Clipboard.ContainsAudio())

{

// 使用 GetAudioStream 方法取得剪貼板中的音頻數據以便加以播放。
SoundPlayer player
=new SoundPlayer(Clipboard.GetAudioStream());

player.Play();

}

}

...

}

條款121如何執行拖放操作

就操作習慣而言,我們可以將Windows應用程序的用戶分爲兩大類,第一類是偏好使用鍵盤的用戶,第二類是偏好使用鼠標的用戶。衆多的實踐經驗讓程序設 計師充分瞭解到,務必提供熱鍵(會顯示出下劃線的快速字符鍵)與快捷鍵(例如:Ctrl+某字符的組合按鍵)給大量運用鍵盤的用戶,但是卻反而常常忽略鼠 標用戶的需求。由於程序設計師本身就比較傾向於鍵盤用戶,因此特別強調鍵盤導向的功能是可以理解的,但是每一位程序設計師也應該好好顧及鼠標的完整支持才 是。
其實鼠標用戶所最期盼的就是對拖放操作的充分支持。仔細端詳大多數的Windows應用軟件或Windows操作系統本身,我們會發現拖放能力是無處不在 的。舉例來說,用戶早已非常習慣在Windows資源管理器中拖曳和置放文件,並且在Microsoft Word中拖曳和置放文字。
令人遺憾的是,只有極少數的VisualC#程序設計師會在他們所開發的應用程序中提供完善的拖放功能,當然,造成此現象的原因之一,就是要實現拖放功能 確實有其困難度與複雜度。本節將讓您知道要利用Visual C#2003~2005以後的版本來實拖現放功能是多麼簡單的一件事情。我們將實際展現如何在窗體內、在窗體之間,以及在應用程序之間移動和複製文字、圖 片以及文件。
拖放操作是如何運作的
拖放操作其實與剪切與粘貼(或複製與粘貼)沒有什麼不同,只不過它是使用鼠標而不是使用鍵盤。在這兩類操作中,您都會擁有一個來源(也就是您剪切或複製的 對象)以及一個目標(也就是您所粘貼之處)。不論是哪一種操作,在操作期間,都會在內存中存在數據的一份副本。剪切與粘貼會使用到剪貼板,而拖放則會使用 到一個DataObject對象,其實DataObject對象就好比是一個私有剪貼板。
在一個典型的拖放操作中,將會依序引發下列事件:
1.您可以通過調用源控件的DoDragDrop方法來初始化拖曳操作。DoDragDrop方法的語法如下所示:
DragDropEffects DoDragDrop(

Object data,


DragDropEffects allowedEffects)DoDragDrop方法會接受下列兩個參數:
  • data參數用來指定所要拖曳(傳遞)的數據。
  • allowedEffects參數用來指定哪些操作(“複製”和/或“移動”)是被允許的。
一個新的DataObject對象會自動被創建。
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鍵被按下時所拖曳的數據通常要進行復制。
拖曳文字 拖曳操作最簡單的實現就是將某一個TextBox控件中的文字移動或複製到另一個TextBox控件中。當然,您也可以使用複製或剪切以及粘貼操作在兩個TextBox控件間複製或移動數據,然而使用拖放操作來完成此類操作絕對會更有效率。
程序範例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,以便允許用戶移動或複製。
我們會於置放目標(也就是右側下方的TextBox控件)的DragEnter事件處理函數中執行下列處理:
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鍵沒有被按下,表示要執行移動操作,此時會移除來源文字以便營造出移動文字的效果。

拖曳一個圖片

拖放操作當然並非只限於文字,有許多應用程序都會提供拖放圖片的功能,以便提升操作的便利性。事實上不管是拖放哪一種類型的數據,其間的方法都沒有太大的差異。
程序範例CH8_DemoForm012.cs示範如何在兩個PictureBox控件間拖曳圖片,其功能特性如下所示:
  • 如圖8.13所示,您可以使用拖放方式將左側PictureBox控件中的圖片移動至右側的PictureBox控件中,反之亦然;即左右兩個PictureBox控件都可以作爲拖放來源與置放目標。
  • 值得一提的是,如果您持續按Ctrl鍵,則可以使用拖放方式將左側PictureBox控件中的圖片複製到右側的PictureBox控件中(如圖8.14所示),反之亦然;即左右兩個PictureBox控件都可以作爲拖放來源與置放目標。

圖8.13通過拖放操作來移動圖片

圖8.14通過拖放操作來複製圖片

程序範例CH8_DemoForm012.cs在拖放操作方面的程序代碼如下所示:// 聲明一個常量以便偵測在拖曳期間 Ctrl 鍵是否被按下。
constbyte CtrlMask =8;



privatevoid CH4_DemoForm065_Load(object sender, EventArgs e)

{

// 由於目前無法在設計工具中去設定 PictureBox 控件

// 的 AllowDrop 屬性,所以必須通過程序代碼來加以設定。
picLeft.AllowDrop
=true;

picRight.AllowDrop
=true;

}

// 處理左右兩個 PictureBox 控件的 MouseDown 事件。

// 當鼠標指針位於控件的範圍內而且鼠標按鍵被按下時便會引發此事件。privatevoid PictureBox_MouseDown(System.Object sender,

System.Windows.Forms.MouseEventArgs e)

{

if (e.Button == System.Windows.Forms.MouseButtons.Left)

{

PictureBox pic
= (PictureBox)(sender);



//初始化拖放操作。if (pic.Image !=null)

{

pic.DoDragDrop(pic.Image,

DragDropEffects.Move
| DragDropEffects.Copy);

}

}

}



// 處理左右兩個 PictureBox 控件的 DragEnter 事件。

// 當某一個對象被拖曳至控件的範圍內時就會引發該控件的 DragEnter 事件。privatevoid PictureBox_DragEnter(System.Object sender,

System.Windows.Forms.DragEventArgs e)

{

// 檢查被拖曳資料的類型是否適用於目標控件。如果不適用,則拒絕置放。if (e.Data.GetDataPresent(DataFormats.Bitmap))

{

// 如果在拖曳期間按 Ctrl 鍵,則執行復制操作;反之,則執行移動操作。if ((e.KeyState & CtrlMask) == CtrlMask)

{

e.Effect
= DragDropEffects.Copy;

}

else

{

e.Effect
= DragDropEffects.Move;

}

}

else

{

e.Effect
= DragDropEffects.None;

}

}



//處理左右兩個 PictureBox 控件的 DragDrop 事件。其實只要轉換髮送者(sender)然後檢查 Name 屬性以便確認哪一個PictureBox 控件要移除影像,就可以使用同一個事件處理函數來處理兩個 PictureBox控件的 DragDrop 事件。privatevoid PictureBox_DragDrop(System.Object sender,

System.Windows.Forms.DragEventArgs e)

{

PictureBox pic
= (PictureBox)(sender);



pic.Image
= (Bitmap)(e.Data.GetData(DataFormats.Bitmap));



// 如果 Ctrl 鍵沒有被按下的話,就使另外一個 PictureBox 控件(也就是在 DragDrop 事件中並不是 sender 的那一個 PictureBox 控件)中的圖像被移除。if ((e.KeyState & CtrlMask) != CtrlMask)

{

if (pic.Name =="picLeft")

{

picRight.Image
=null;

}

else

{

picLeft.Image
=null;

}

}

}


前面這兩個關於文字與圖片的拖放操作範例都是在同一個窗體上的兩個控件間進行,其實它們也可在同一個應用程序的不同窗體上的控件間拖放。下一個程序範例將示範如何接受從另一個應用程序拖放而來的項目,在此程序範例中,將接受從Windows資源管理器拖放而來的文件。
拖放文件在Windows資源管理器中使用拖放操作來移動或複製文件是大家所慣用的方式。Windows資源管理器充分支持拖放操作,而且這也是非常多用 戶所偏愛的文件使用方式。此外,許多用戶非常習慣直接從Windows資源管理器將文件拖放至對應的應用程序中來打開它們。例如,從Windows資源管 理器將一個.doc 文檔拖放至Microsoft Word即會將該文檔在Microsoft Word中打開。

圖8.15示範如何從Windows資源管理器中拖放文件

圖8.15 所示是程序範例CH8_DemoForm013.cs的運行畫面。顯而易見地,您可以從Windows資源管理器將一個或多個文件拖放至窗體上的 ListBox控件中,而被拖放的文件的文件名會被添加到ListBox控件中。以下是CH8_DemoForm013.cs的程序代碼內容:

privatevoid ListBox1_DragEnter(object sender, DragEventArgs e)

{

if (e.Data.GetDataPresent(DataFormats.FileDrop))

{

e.Effect
= DragDropEffects.All;

}

}



privatevoid ListBox1_DragDrop(object sender, DragEventArgs e)

{

if(e.Data.GetDataPresent(DataFormats.FileDrop))

{

string[] MyFiles;

int i;



// 將文件賦給一個數組。
MyFiles
= (string[])(e.Data.GetData(DataFormats.FileDrop));



// 循環處理數組並將文件添加到列表中。for(i =0;i <= MyFiles.Length -1;i++)

{

ListBox1.Items.Add(MyFiles[i]);

}

}

}



圖8.16在兩個列表間來回拖放一個或多個文件來移動項目

請 注意我們在ListBox控件的DragEnter事件處理函數中將Effect屬性設定成DragDropEffects.All。由於文件本身實際上 並沒有被移動或複製,因此拖放源如何設定AllowedEffects將無關緊要,設定All表示對任何的FileDrop都會啓用置放。
就本範例而言,DataFormats.FileDrop格式會含有每一個被置放文件的完整路徑。本範例的操作邏輯是將所有被拖放文件的完整路徑添入 ListBox控件中,當然,您可以採用其他方法,比方說,您可以將被拖放的文件在一個MDI(多文件界面)文件窗口中打開。
在兩個列表之間來回拖放項目另外一項常見的拖放需求是,在兩個列表(ListView控件)之間來回拖放項目。事實上,我們經常會通過一組按鈕來將列表中 被選取的項目移至另外一個列表中,不過這樣的操作模式需要兩次鼠標按鍵操作(第一次選取項目,第二次單擊按鈕)。顯然,在這樣的操作需求中,拖放操作會較 受青睞,因爲它只需單一動作即可完成(選取並拖曳)。

圖8.16所示是程序範例CH8_DemoForm014.cs的運行畫面。顯而易見地,您可以在兩個列表間來回拖放一個或多個文件來移動項目。本程序範例的設計重點說明如下:

  • 由於兩個列表的ListView控件都可以作爲置放目標,因此務必將這兩個ListView控件的AllowDrop屬性設定成True。
  • 請將兩個ListView控件的MultiSelect屬性設定成True。
  • 請將兩個ListView控件的FullRowSelect屬性設定成True。
  • 以下是程序範例CH8_DemoForm014.cs的程序代碼內容。於Load事件處理函數中所調用的 PopulateListView() 程序主要是用來初始化兩個ListView控件:
privatevoid CH4_DemoForm067_Load(object sender, EventArgs e)

{

this.PopulateListView();

}



privatevoid ListView_ItemDrag(object sender,

System.Windows.Forms.ItemDragEventArgs e)

{

ListViewItem[] myItems
=new ListViewItem[((ListView)(sender)).SelectedItems.Count];

int i =0;



// 循環處理拖放來源的 SelectedItems 集合。foreach(ListViewItem myItem in

((ListView)(sender)).SelectedItems)

{

// 將ListViewItem新增至ListViewItems的數組中。
myItems[i]
= myItem;

i
= i +1;

}



// 建立一個DataObject對象來包含ListViewItem的數組。
((ListView)(sender)).DoDragDrop(
new

DataObject(
"System.Windows.Forms.ListViewItem()"

myItems), DragDropEffects.Move);

}



privatevoid ListView_DragEnter(object sender,

System.Windows.Forms.DragEventArgs e)

{

// 檢查自定義的 DataFormat ListViewItem 數組。if (e.Data.GetDataPresent(

"System.Windows.Forms.ListViewItem()"))

{

e.Effect
= DragDropEffects.Move;

}

else

{

e.Effect
= DragDropEffects.None;

}

}



privatevoid ListView_DragDrop(object sender,

System.Windows.Forms.DragEventArgs e)

{

ListViewItem[] myItems
=

(ListViewItem[])(

e.Data.GetData(
"System.Windows.Forms.ListViewItem()"));

int i =0;



foreach (ListViewItem myItem in myItems)

{

// 將項目添加到目標列表中。
ListViewItem item
=new ListViewItem(myItems[i].Text);



item.SubItems.Add(myItems[i].SubItems[
1].Text);

((ListView)(sender)).Items.Add(item);



// 從源列表移除項目。if (sender == ListView1)

{

istView2.Items.Remove(ListView2.SelectedItems[
0]);

}

else

{

ListView1.Items.Remove(ListView1.SelectedItems[
0]);

}



i
= i +1;

}

}


您或許會覺得奇怪,爲什麼不使用ListBox控件而要使用ListView控件。最主要的理由是,ListBox控件並不支持拖曳多個項目,單擊列表會使得多重選取失效。
ListView與TreeView控件都擁有一個ItemDrag事件來促進拖曳操作。在本範例中,我們使用單一個ItemDrag事件處理函數來處理兩個ListView控件的ItemDrag事件。其中的sender參數代表初始化拖曳的控件。
由於DataFormats類的成員並不包括ListViewItem類型,所以數據必須當作一個系統Type來傳遞。ItemDrag事件處理函數的程 序代碼會創建一個類型爲ListViewItem的數組並循環處理SelectedItems集合以便添入數組。在DoDragDrop方法中,一個新的 DataObject對象會被創建並以數組來添入。您可以使用相同的技巧來拖放任何的系統Type。
在DragDrop事件處理函數中,我們會將DataObject對象中的數組複製到一個新的ListViewItem數組中,而且每一個ListViewItem會被添加到目標ListView控件的Items集合中。
在兩個TreeView之間來回拖放節點
程序範例

圖8.17與圖8.18所示是程序範例CH8_DemoForm015.cs的運行畫面。顯而易見地,您可以在兩個TreeView控件間來回拖放一個節點(移動或複製)。本程序範例的程序代碼如下所示:// 聲明一個常量以便偵測在拖曳期間 Ctrl 鍵是否被按下。

constbyte CtrlMask =8;



// 處理兩個 TreeView 控件的 ItemDrag 事件。privatevoid TreeView_ItemDrag(System.Object sender,

System.Windows.Forms.ItemDragEventArgs e)

{

if (e.Button == System.Windows.Forms.MouseButtons.Left)

{

// 初始化拖放操作。
DoDragDrop(e.Item,

DragDropEffects.Move
| DragDropEffects.Copy);

}

}



// 處理兩個 TreeView 控件的 DragDrop 事件。privatevoid TreeView_DragDrop(System.Object sender,

System.Windows.Forms.DragEventArgs e)

{

// 此變量用來持有被用戶所拖曳的節點。
TreeNode OriginationNode
=

(TreeNode)(

e.Data.GetData(
"System.Windows.Forms.TreeNode"));



//爲一個TreeView控 件調用GetDataPresent方法與爲一個文字或圖像的方式一點不同,原因是TreeNode並不是DataFormats類的一個成員。也就是 說,它不是一個預先定義的類型。諸如此種狀況,您必須使用能夠接受一個字符串作爲類型的重載版本。if (e.Data.GetDataPresent(

"System.Windows.Forms.TreeNode"false))

{

Point pt;

TreeNode DestinationNode;



// 取得鼠標指針所在位置的工作區座標(Client Coordinate)。
pt
= ((TreeView)(sender)).PointToClient(

new Point(e.X, e.Y));



// 選取鼠標指針所在位置之下的節點。
DestinationNode
= ((TreeView)(sender)).GetNodeAt(pt);



// 此處的 If 語句用來確保當用戶在他們嘗試去拖曳節點的上方不小心放開鼠標按鍵的話,不會失去節點。如果您沒有去檢查目標節點是否就是源節點,將會使得該節點消失。if (DestinationNode.TreeView != OriginationNode.TreeView)

{

DestinationNode.Nodes.Add(

(TreeNode)(OriginationNode.Clone()));



// 當添加一個新的節點時展開父節點,如此纔會清楚地呈現出拖放操作的結果。如果沒有這樣做的話,將會顯示一個 + 號。
DestinationNode.Expand();



// 如果 Ctrl 鍵沒有被按下,就將原來的節點移除

// 以便實現移動節點的拖放操作。if ((e.KeyState & CtrlMask) != CtrlMask)

{

OriginationNode.Remove();

}

}

}

}



// 處理兩個 TreeView 控件的 DragEnter 事件。privatevoid TreeView_DragEnter(System.Object sender,

System.Windows.Forms.DragEventArgs e)

{

// 檢查被拖曳的數據的類型是否適用於目標控件。如果不適用,則拒絕置放。if (e.Data.GetDataPresent(

"System.Windows.Forms.TreeNode"))

{

// 如果在拖曳期間按着 Ctrl 鍵,則執行復制操作;反之,則執行移動操作。if ((e.KeyState & CtrlMask) == CtrlMask)

{

e.Effect
= DragDropEffects.Copy;

}

else

{

e.Effect
= DragDropEffects.Move;

}

}

else

{

e.Effect
= DragDropEffects.None;

}

}


圖8.17使用拖放操作來移動節點

圖8.18使用拖放操作來複制節點

條款122如何使用多重窗體

在使用窗體之前,務必先創建窗體的實例,我想這個觀念大家都非常清楚。然而,如果您要在多重位置使用相同的實例,就需要傳送指針到該實例。處理窗體指針有兩種主要的方法,第一種方法是使它可供全局使用,第二種方法是將它傳送給需要的每一個窗體、類、模塊或程序。
全局窗體在Visual C#中使用全局數據的一種特有方式就是利用Static類成員(即所謂的靜態成員)來實現。Static類成員不需要創建類的實例就可以使用,而且如果它 是屬性,還可以在整個應用程序中共享它的值。例如,您可以創建如下所示的類(請參見Log.cs):
class Log

{

privatestatic LogForm m_LogForm;



publicstatic LogForm LogForm

{

get

{

return m_LogForm;

}



set

{

m_LogForm
= value;

}

}



publicstaticvoid WriteToLogWindow(string Message)

{

StringBuilder sb
=new StringBuilder(m_LogForm.txtLogInfo.Text);



sb.Append(Environment.NewLine);

sb.Append(Message);

m_LogForm.txtLogInfo.Text
= sb.ToString();

}

}


附註 我們已經在第2章討論過如何建立與使用靜態成員,請自行參考之。在您創建窗體的實例後,就可以將窗體的實例賦值給Log類的共享屬性LogForm(請參見CH8_DemoForm016.cs):
privatevoid CH4_DemoForm069_Load(object sender, EventArgs e)

{

LogForm theLogForm
=new LogForm();



theLogForm.Show();

Log.LogForm
= theLogForm;

}


由於LogForm屬性是Log類的一個共享成員,因此在您將窗體的實例賦值給它之後,就可以在程序代碼的任何位置,通過Log類來訪問相同的實例並調用其共享方法WriteToLogWindow(請參見MyModule.cs):
struct MyModule

{

publicstaticvoid DoLogThings()

{

Log.LogForm.Text
= DateTime.Now.ToString();

Log.WriteToLogWindow(

"========================================");

Log.WriteToLogWindow(DateTime.Now.ToString());

Log.WriteToLogWindow(Environment.MachineName);

Log.WriteToLogWindow(Environment.UserName);

Log.WriteToLogWindow(Environment.StackTrace);

}

...

}


然而,我們將在何處調用DoLogThings程序呢?以本範例而言,我們在CH8_DemoForm016.cs的“使用Log窗體”按鈕的Click事件處理函數中進行此項調用:
privatevoid btnUseLogForm_Click(object sender, EventArgs e)

{

MyModule.DoLogThings();

}


基本上,就上面這一個範例而言,我們使用LogForm窗體來呈現所寫入的記錄信息,爲此,我們創建了使窗體揭露成爲一個共享成員的Log類,並且提供共 享的WriteToLogWindow方法來處理窗體的實際互動。您所有的程序代碼都應該引用WriteToLogWindow方法,而不是直接訪問窗 體。
傳送您的窗體另外一種使窗體全局化的方式是,將窗體的引用保存成窗體或類中的變量,然後將該引用傳送至需要訪問您窗體的圖8.19CH8_DemoForm017.cs 運行畫面
任何程序代碼。如圖8.19所示是程序範例CH8_DemoForm017.cs的運行畫面,當您按下“顯示第二個窗體”按鈕時,會打開第二個窗體 (Second_CH8_DemoForm017.cs),然後按下“列出第二個窗體中各個控件的名稱”按鈕則會將第二個窗體中各個控件的名稱列於第一個 窗體中。您的程序代碼應該編寫在第一個窗體(CH8_DemoForm017.cs)中,如下所示:
publicpartialclass CH8_DemoForm017 : Form

{

...

Second_CH8_DemoForm017 theSecondForm;



privatevoid btnShowSecondForm_Click(

object sender, EventArgs e)

{

theSecondForm
=new Second_CH8_DemoForm017();

theSecondForm.Show();

}



privatevoid btnCalculateSecondForm_Click(

object sender, EventArgs e)

{

txtResult.Text
=

MyModule.ListFormControl(theSecondForm);

}


}不論採用哪一種方法(全局引用或將窗體實例當作自變量傳送)都可以使用,但您應該選擇最適合您項目的方式。在創建窗體之後,如果只有少數程序需要訪問窗 體,我們會建議僅在程序中加入窗體引用,然後視需求來傳送您的窗體引用。如果項目中有許多程序都會用到該窗體,則建議採用全局引用,但是應考慮重新構造您 的應用程序,使其實際上只有單一類或程序需要訪問窗體。

訪問其他窗體的成員

到目前爲止,我們已經探討如何取得和跟蹤窗體實例,但是尚未討論如何訪問其他窗體的成員。如果您擁有窗體實例,而且您的程序代碼和窗體來自同一個項目,則 可以直接訪問其他窗體的成員,但我們不認爲這是最好的主意。與其使用窗體上的文本框、按鈕及其他控件,衷心建議您明確建立Public成員,以便讓您設定 和擷取想要訪問的值。
基本上,要在某一個窗體中去訪問聲明在其他窗體中的Public成員,有兩種主要方法,以下是我們的說明。
訪問其他窗體的Public成員的第一種作法
訪問其他窗體的Public成員的第一種作法是,在第一個窗體中創建第二個窗體的實例。如此一來,您就可以在第一個窗體中訪問第二個窗體的所有Public成員。
說得更詳細點,如果您要從第一個窗體訪問聲明在第二個窗體中的變量、屬性和方法,您必須將這些變量、屬性和方法聲明爲Public成員。然後在第一個窗體中創建第二個窗體的實例,如此一來,就可以訪問第二個窗體的所有Public成員。
程序範例1
程序範例CH8_DemoForm018.cs示範如何去存取另外一個窗體中的共享屬性與共享方法。我們先來查看其操作流程:
1.當您運行CH8_DemoForm018.cs後,會顯示圖8.20所示的窗體。請您依序輸入兩個整數之後,單擊“顯示第二個窗體”按鈕。
2.這個時候會顯示第二個窗體。請您如圖8.21所示,在文本框中任意輸入一個值,然後單擊“將輸入值賦給Form2Value屬性”按鈕。此操作會將您所輸入的值賦給第二個窗體的公有屬性Form2Value。

圖8.20示範如何訪問另外一個窗體的Public成員

圖8.21在文本框中輸入任意值

3.請單擊第二個窗體的“關閉”按鈕。此時如圖8.22所示,第一個窗體中會顯示出您剛剛在第二個窗體中賦給公有屬性Form2Value的值,而且還會調用第二個窗體的公有方法MathMultiplication來計算出您於第一個窗體所輸入的兩個整數的相乘結果。

圖8.22屬性Form2Value的值

從以上的操作說明可以瞭解,程序範例CH8_DemoForm018.cs主要在示範如何去訪問另外一個窗體中的公有屬性Form2Value與公有方法MathMultiplication。相關設計技巧說明如下:
  • 首先,您必須在第二個窗體(也就是Second_CH8_DemoForm018.cs)中編寫下列程序代碼以便聲明公有屬性Form2Value與公有方法MathMultiplication:
privatestring MyVal;



// 聲明一個公有成員。publicstring Form2Value

{

get

{

return MyVal;

}



set

{

MyVal
= value;

}

}



publicdecimal MathMultiplication(int P, int Q)

{

return P * Q;

}



privatevoid btnAssignValue_Click(object sender, EventArgs e)

{

this.Form2Value = TextBox1.Text;

}



privatevoid Second_CH8_DemoForm018_FormClosed(

object sender, FormClosedEventArgs e)

{

// 設定 DialogResult 屬性。this.DialogResult =

System.Windows.Forms.DialogResult.OK;

}


  • 接下來,請爲第一個窗體(也就是CH8_DemoForm018.cs)的“顯示第二個窗體”按鈕的Click事件處理函數編寫下列程序代碼,以便創建第二個窗體的實例並訪問其公有成員:
privatevoid btnShowSecondForm_Click(object sender, EventArgs e)

{

int p = Int32.Parse(TextBox1.Text);

int q = Int32.Parse(TextBox2.Text);



// 創建第二個窗體的實例。
Second_CH8_DemoForm018 objForm
=new Second_CH8_DemoForm017();



// 使用 ShowDialog 方法顯示第二個窗體。if (objForm.ShowDialog() ==

System.Windows.Forms.DialogResult.OK)

{

// 將名稱爲Label2的Label控件的Text屬性

// 設定成第二個窗體之Form2Value屬性的值。
Label2.Text
= objForm.Form2Value;



// 將名稱爲TextBox3的TextBox控件的Text屬性

// 設定成第二個窗體之MathMultiplication方法的返回值。
TextBox3.Text
=

objForm.MathMultiplication(p, q).ToString();

}



// 釋放第二個窗體的實例。
objForm
=null;

}


程序範例2

圖8.23所示是程序範例CH8_DemoForm019.cs的運行畫面,它示範如何利用第一個窗體中的按鈕來設定第二個窗體內的TextBox控件中的客戶姓名。相關設計重點說明如下:

圖8.23CH8_DemoForm019.cs運行畫面

  • 首先,我們必須爲第二個窗體(Second_CH8_DemoForm019.cs)創建一個名稱爲CustomerName的Public屬性:
publicpartialclass Second_CH8_DemoForm019 : Form

{

...

publicstring CustomerName

{

get

{

return TextBox1.Text;

}



set

{

TextBox1.Text
= value;

}

}

}


  • 接下來,您必須爲第一個窗體(CH8_DemoForm019.cs)編寫下列程序代碼:
publicpartialclass CH4_DemoForm072 : Form

{

...

Second_CH8_DemoForm019 myForm2
=new Second_CH8_DemoForm019();



privatevoid Button1_Click(object sender, EventArgs e)

{

this.myForm2.CustomerName ="章立民";

myForm2.Show();

}



privatevoid Button2_Click(object sender, EventArgs e)

{

MessageBox.Show(myForm2.CustomerName);

myForm2.CustomerName
="李小生";

}



privatevoid CH8_DemoForm019_FormClosed(

object sender, FormClosedEventArgs e)

{

myForm2.Close();

}

}


採用CustomerName屬性看起來與直接訪問第二個窗體上的文本框似乎沒有很大的不同,但是此種作法可以從窗體的互動中獲得一些好處。主要的好處就 是抽象,因爲您不必知道第二個窗體的控件的任何相關信息(其中甚至可能沒有文本框),而只要去設定和擷取CustomerName屬性就可以了。有了這一 個抽象層,可以讓第二個窗體更改其實現,而不會影響程序代碼的其他部分,使未來更容易進行修改。在我們的範例中,以屬性爲基礎的版本起來並不容易,但若要 處理更復雜的用戶界面,屬性背後的程序代碼可以處理所有的繁瑣細節,讓使用窗體的程序代碼仍然維持非常簡單。當然,這個模型的最後一個好處,雖然屬於比較 無形的好處,但是對許多開發人員而言,自有其價值所在:使用屬性來取代直接訪問控件,屬於比較有深度的作法,而且程序代碼也較爲易於瞭解。
訪問其他窗體的Public成員的第二種作法
第二種作法,就是在第二個窗體中聲明一個公有的對象引用。在第一個窗體中創建第二個窗體的實例,然後將您在第二個窗體所聲明的對象引用設定成第一個窗體目前的實例。如此一來,您就能夠從第二個窗體訪問第一個窗體的所有Public成員。
程序範例
程序範例CH8_DemoForm020.cs示範如何使用第二種作法來訪問其他窗體的Public成員。我們先來查看其操作流程:
1.當您執行CH8_DemoForm020.cs後,會顯示圖8.24所示的窗體,請您單擊“顯示第二個窗體”按鈕。
2.此時會顯示出第二個窗體,我們發現,第二個窗體(也就是Second_CH8_DemoForm020.cs)會比我們在設計工具中所設計的還大。接 下來,請您在文本框中鍵入任意值,然後單擊按鈕。我們發現,此時第一個窗體中會立即顯示出您在第二個窗體上的文本框中所鍵入的值(如圖8.25所示)。

圖8.24顯示第二個窗體按鈕

圖8.25第二個窗體上的文本框的值

從 以上的操作流程可以看出,本範例會在第二個窗體中去訪問第一個窗體上的Label控件。而當您顯示第二個窗體時,第二個窗體的大小之所以與我們在設計工具 中所創建的不相同,是因爲我們在第一個窗體中創建了第二個窗體的實例後,隨即調用第二個窗體自帶的公有成員SetBounds方法來設定其位置與大小。相 關設計技巧說明如下:
  • 首先,請您替第二個窗體(也就是Second_CH8_DemoForm020.cs)編寫下列程序代碼:// 聲明一個公有的對象引用。
publicobject objForm;



privatevoid Button1_Click(object sender, EventArgs e)

{

// 將第二個窗體上名稱爲 TextBox1 的 TextBox 控件的 Text 屬性

// 賦給第一個窗體上名稱爲 Label2 的 Label 控件的 Text 屬性。
((CH8_DemoForm020)(objForm)).Label2.Text
= TextBox1.Text;

}


  • 接下來,請爲第一個窗體(也就是CH8_DemoForm020.cs)的“顯示第二個窗體”按鈕的Click事件處理函數編寫下列程序代碼:
privatevoid Button1_Click(object sender, EventArgs e)

{

// 創建第二個窗體的實例。
Second_CH8_DemoForm020 MyForm
=new Second_CH8_DemoForm020();



// 將目前窗體(也就是第一個窗體)的實例賦給將您於第二個窗體中所聲明的對象引用。
MyForm.objForm
=this;



// 調用第二個窗體的公有成員 SetBounds。
MyForm.SetBounds(
400400300300);



// 顯示第二個窗體。
MyForm.Show();

}
發佈了8 篇原創文章 · 獲贊 0 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章