资源描述
1.方法
实现拖放效果时,C#中提供了一个系统方法DoDragDrop方法,用于实现开始拖放操作,该方法由Control类所定义,由于控件均直接或是间接派生于Control类,因此开发人员可以在任何可视化组件中调用DoDragDrop方法。DoDragDrop方法使用语法如下:
public DragDropEffects DoDragDrop ( Object data,DragDropEffects allowedEffects)
data:用户所要拖动的数据内容。必须将所要拖动的内容传入到这个方法的第一个参数位置。
allowedEffects:DragDropEffects枚举值之一,此类型包含了拖动操作的效果。DragDropEffects枚举值如表32.8所示。
表32.8 DragDropEffects枚举值
枚举值
说明
All
从拖动源复制、移除数据,并将其滚动到放置目标中
Copy
将数据复制到放置目标
Link
将拖动源中的数据链接到放置目标
Move
将拖动源的数据移动到放置目标
None
放置目标不接受该数据
Scroll
即将在放置目标中开始滚动,或当前正在滚动
开发人员在使用DoDragDrop方法时,必须指定参数allowedEffects为表**中的任何一个成员,另外,还可以使用位运算符,把其中的任何一个成员作为一个完整参数传入,以得到所需的拖动效果,实现关键代码如下:
DragDropEffects.Copy| DragDropEffects.None
2.事件
C#中提供了一个系统拖放事件,与拖放方法一起使用来达到更好的效果。常用的拖放事件如表所示。
表 拖放事件
名称
说明
DragEnter
当用户在拖放操作过程中首次将鼠标光标拖到控件上时,会引发该事件
DragDrop
在完成拖放操作时发生
GiveFeedback
在执行拖动操作期间发生
DragLeave
如果用户移出一个窗口,则引发DragLeave事件
DragOver
如果鼠标移动但停留在同一个控件中,则引发DragOver事件
QueryContinueDrag
在拖放操作过程中,当键盘或鼠标按钮状态发生变化时,会引发QueryContinueDrag 事件。QueryContinueDrag事件允许拖动源确定是否应取消拖放操作
下面对拖放事件中比较重要的事件进行详细介绍。
(1)DragEnter事件
当用户在拖放操作过程中首次将鼠标光标拖到控件上时,触发该事件。
语法:
public event DragEventHandler DragEnter
该事件为DragEventHandler委托类型,该委托是专门设计用以处理控件的DragEnter、DragDrop、GiveFeedback、DragLeave和DragOver等相关事件的方法。DragEventHandler委托类型的定义语法如下:
public delegate void DragEventHandler(object sender,DragEventArgs e)
该委托封装的方法必须接受两个参数,第一个是object类型对象,该对象用来指定拖放目标对象;第二个为DragEventArgs 类型参数e,它主要包含拖动操作的相关数据。DragEventArgs 类型参数e的相关属性值及说明如表所示。
表 DragEventArgs类型参数e的属性值及说明
名称
说明
AllowedEffect
获取拖动事件的发起方(或源)所允许的拖放操作
Data
获取IDataObject,它包含与此事件关联的数据
Effect
获取或设置拖放操作中目标放置效果
KeyState
获取Shift、Ctrl 和 Alt键的当前状态以及鼠标按钮的状态
X
获取鼠标指针在屏幕坐标系中的x坐标
Y
获取鼠标指针在屏幕坐标系中的y坐标
(2)QueryContinueDrag事件
在拖放操作过程中,当键盘或鼠标按钮状态发生变化时,触发该事件。
语法:
public event QueryContinueDragEventHandler QueryContinueDrag
委托定义语法如下:
public delegate void DragEventHandler(object sender, QueryContinueDragEventArgs e)
该委托封装的方法必须接受两个参数,第一个是object类型对象,该对象用来指定为拖放目标对象;第二个为QueryContinueDragEventArgs 类型参数e,它主要包含拖动操作的相关数据。QueryContinueDragEventArgs 类型参数e有一个Action属性,该属性的属性值用来确定是否继续拖动、放置数据或取消操作。Action属性值及说明如表32.11所示。
表 QueryContinueDragEventArgs.Action属性值及说明
名称
说明
Continue
该操作将继续
Drop
该操作以放置而告终
Cancel
操作被取消,没有放置消息
就操作习惯而言,我们可以将Windows应用程序的用户分为两大类,第一类是偏好使用键盘的用户,第二类是偏好使用鼠标的用户。众多的实践经验让程序设计师充分了解到,务必提供热键(会显示出下划线的快速字符键)与快捷键(例如:Ctrl+某字符的组合按键)给大量运用键盘的用户,但是却反而常常忽略鼠标用户的需求。由于程序设计师本身就比较倾向于键盘用户,因此特别强调键盘导向的功能是可以理解的,但是每一位程序设计师也应该好好顾及鼠标的完整支持才是。
其实鼠标用户所最期盼的就是对拖放操作的充分支持。仔细端详大多数的Windows应用软件或Windows操作系统本身,我们会发现拖放能力是无处不在的。举例来说,用户早已非常习惯在Windows资源管理器中拖曳和置放文件,并且在Microsoft Word中拖曳和置放文字。
令人遗憾的是,只有极少数的Visual C#程序设计师会在他们所开发的应用程序中提供完善的拖放功能,当然,造成此现象的原因之一,就是要实现拖放功能确实有其困难度与复杂度。本节将让您知道要利用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键是否被按下。
const byte CtrlMask = 8;
// 替左侧的 TextBox 控件处理 MouseDown 事件。
// 当用户在此控件的范围内按下鼠标按键时便会引发此事件。
private void 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 事件。
private void 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 事件。
// 当用户放开鼠标按键时就会引发此事件,并终止拖放操作。
private void 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键没有被按下,表示要执行移动操作,此时会移除来源文字以便营造出移动文字的效果。
拖放文件
在Windows资源管理器中使用拖放操作来移动或复制文件是大家所惯用的方式。Windows资源管理器充分支持拖放操作,而且这也是非常多用户所偏爱的文件使用方式。此外,许多用户非常习惯直接从Windows资源管理器将文件拖放至对应的应用程序中来打开它们。例如,从Windows资源管理器将一个.doc 文档拖放至Microsoft Word即会将该文档在Microsoft Word中打开。
图8.15示范如何从Windows资源管理器中拖放文件
图8.15所示是程序范例CH8_DemoForm013.cs的运行画面。显而易见地,您可以从Windows资源管理器将一个或多个文件拖放至窗体上的ListBox控件中,而被拖放的文件的文件名会被添加到ListBox控件中。以下是CH8_DemoForm013.cs的程序代码内容:
private void ListBox1_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
e.Effect = DragDropEffects.All;
}
}
private void 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控件:
private void CH4_DemoForm067_Load(object sender, EventArgs e)
{
this.PopulateListView();
}
private void 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);
}
private void 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;
}
}
private void 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 键是否被按下。
const byte CtrlMask = 8;
// 处理两个 TreeView 控件的 ItemDrag 事件。
private void 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 事件。
private void 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 事件。
private void 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使用拖放操作来复制节点
展开阅读全文