您的位置:首页 > 编程语言 > C#

Visual C# 2005程序开发与界面设计秘诀——章立民作品

2006-12-22 11:08 525 查看
条款1 如何生成可执行文件.EXE

您可以采用下列两种方式来生成可执行文件.EXE:

方法一:在Visual Studio 2005的集成开发环境中,从“生成”菜单中选择“生成解决方案”,即会在项目的\bin文件夹中产生.EXE文件。

方式二:在SDK命令提示符窗口下,执行csc命令来编译.EXE文件。

条款2 如何指定.EXE的输出位置

虽然可执行文件.EXE默认会保存在项目的\bin文件夹中,但是您可以依下列步骤来指定其输出位置:

将您的Visual C# 2005项目在Visual Studio 2005的集成开发环境中启动。

在“解决方案资源管理器”中执行下列操作之一:

用鼠标左键双击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(400, 400, 300, 300);

// 显示第二个窗体。
MyForm.Show();

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐