写在前面:本文为即兴而作,因此难免有疏漏和词不达意的地方。在这里,非常期望您提供评论,分享您的想法和建议。

  这是一篇介绍如何在WPF中实现拖放功能的短文。

  首先要读者清楚的一件事情是:拖放主要分为拖放源和拖放目标两个组成。拖放源和拖放目标各自拥有不同的事件。软件开发人员需要在适当的事件中完成相应功能。

  试想拖放是如何操作的:用户选中一个界面元素,并在鼠标左键按下的情况下移动鼠标,最后,在到达拖放目标时松开鼠标左键,从而完成数据拖放的全过程。从程序编写的角度来看,用户需要在左键选中项目并按下的情况下移动以启动拖放,并在鼠标移动的过程中给出当前拖放状态的外观回馈,并在松开鼠标时尝试将项目添加到目标中。

  对于任意的软件界面,鼠标左键按下并移动的行为并不一定会导致拖放的开始。因此软件开发人员需要自行编写代码启动拖放功能,而不是由WPF决定。这也就是软件开发人员在特定条件下需要自行调用DragDrop.DoDragDrop()以启动拖放操作的原因。DragDrop.DoDragDrop()函数接受三个参数:dragSource、data以及allowedEffects。特别需要注意的是dragSource参数。该参数标示了拖拽操作的消息源,也决定了所有的消息源事件由谁发出。参数data则用来包装Drag&Drop所操作的数据。一般情况下,其都是一个DataObject类型的实例。该实例内部应包装拖拽所实际操作的数据。最后,allowedEffects可以用来指定拖拽操作的效果。调用该函数的片断可以如下所示:

1 DragDrop.DoDragDrop(mListBox, dataObject, DragDropEffects.Copy);

  启动了拖拽操作以后,软件开发人员就需要处理拖拽过程中所发生的一系列事件了。在这种情况下,软件开发人员不能寄望于通过响应Mouse.MouseMove等事件完成拖拽行为的响应。这是因为DragDrop.DoDragDrop()函数实际上是一个阻塞函数。在拖拽行为终止之前,这些事件都不会被发送。取而代之的是,软件开发人员可以使用拖拽源和目标所提供的事件。拖拽源提供的事件为QueryContinueDrag、GiveFeedback以及对应的Preview-事件。QueryContinueDrag事件用来决定是否继续拖放操作。该事件发生的时机为键盘或鼠标按钮状态发生变化时。GiveFeedback则用来为用户提供拖放的反馈信息,如令被拖拽的界面元素的图像随鼠标变化,或提供一个Tooltip提示用户当前的拖放效果等。该消息会在拖拽过程中定时发出,因此软件开发人员也可以将其作为Timer事件使用。在这里强调一下,因为在拖拽过程中软件开发人员不能使用其它事件,因此了解各拖拽事件发生时机是一个很重要的事情。

  另外,在您看到Preview-事件的时候,相信您一定能够想到这是一个隧道/冒泡路由事件。因此,对这些事件的处理并不一定需要向拖放源添加事件处理函数,而可以在较高层次中重写相应函数即可,如重写Window的OnGiveFeedback()函数。这样做的好处在于,其能提供较为集中的拖拽处理逻辑,并在需要更改较高层次界面元素,如窗口的状态栏状态时拥有较好的语义特征。这样做的不足之处也有,如其并不太适合窗口中拥有多个拖拽源的情况。当然,在高层次侦听消息源发出的消息也是非常好的选择。

  如果我们需要实现在拖拽过程中将拖拽的界面元素的样子作为预览这一功能,那么该功能的实现如下所示:

 1 mListBox.PreviewMouseMove += OnPreviewListBoxMouseMove;
2 mListBox.QueryContinueDrag += OnQueryContinueDrag;
3
4 private void OnQueryContinueDrag(object sender, QueryContinueDragEventArgs e)
5 {
6 mAdornerLayer.Update();
7 …
8 }
9
10 private void OnPreviewListBoxMouseMove(object sender, MouseEventArgs e)
11 {
12 ListBoxItem listBoxItem = … // Find your actual visual you want to drag
13 …
14 DragDropAdorner adorner = new DragDropAdorner(listBoxItem);
15 mAdornerLayer = AdornerLayer.GetAdornerLayer(mTopLevelGrid);
16 mAdornerLayer.Add(adorner);
17
18 DataItem dataItem = listBoxItem.Content as DataItem;
19 DataObject dataObject = new DataObject(dataItem.Clone());
20 // Here, we should notice that dragsource param will specify on which
21 // control the drag&drop event will be fired
22 System.Windows.DragDrop.DoDragDrop(mListBox, dataObject, DragDropEffects.Copy);
23 …
24 }

  其中DragDropAdorner用来显示被拖拽的界面元素的预览。其使用了Adorner。如果读者对该控件的实现有兴趣,请自行下载示例程序查看。有关Adorner的使用,我会在有时间的时候专门撰写一篇文章。

  接下来就是拖拽目标事件了。拖拽目标会发送DragEnter、DragOver、DragLeave、Drop以及相应的Preview-事件。这些事件的意义十分清晰,相信您从名字中就能看出这些事件发生的时机。在这些事件中需要注意的则是传入的DragEventArgs。通过设置它的Effects成员,软件开发人员可以控制鼠标的状态,以提示用户当前拖拽动作的光标反馈。同时通过它的Data属性,软件开发人员可以获得DoDragDrop()函数调用时所传入的数据。

  下面就是一段响应拖拽目标事件的代码:

 1 private void OnDragOver(object sender, DragEventArgs e)
2 {
3 e.Effects = DragDropEffects.None;
4
5 // Find the corresponding treeview item in mTreeView and select it
6 Point pos = e.GetPosition(mTreeView);
7 HitTestResult result = VisualTreeHelper.HitTest(mTreeView, pos);
8 if (result == null)
9 return;
10
11 TreeViewItem selectedItem = Utils.FindVisualParent<TreeViewItem>(result.VisualHit);
12 if (selectedItem != null)
13 selectedItem.IsSelected = true;
14
15 e.Effects = DragDropEffects.Copy;
16 }
17
18 private void OnDrop(object sender, DragEventArgs e)
19 {
20 // Drop the data item into corresponding treeview item
21 Point pos = e.GetPosition(mTreeView);
22 HitTestResult result = VisualTreeHelper.HitTest(mTreeView, pos);
23 if (result == null)
24 return;
25
26 TreeViewItem selectedItem = Utils.FindVisualParent<TreeViewItem>(result.VisualHit);
27 if (selectedItem == null)
28 return;
29
30 DataItem parent = selectedItem.Header as DataItem;
31 DataItem dataItem = e.Data.GetData(typeof(DataItem)) as DataItem;
32 if (parent != null && dataItem != null)
33 parent.Items.Add(dataItem);
34 }

  需要读者注意的则是该段代码中对GetData()函数的调用。Drag&Drop过程中,如果希望从DataObject中获取数据,那么必须使用原有类型。如A是B的基类,而DataObject中封存的则是类型B的实例,那么软件开发人员需要在DataObject.GetData()中使用typeof(B),而不能是typeof(A)。

  在实现拖拽功能的时候,软件开发人员需要注意一系列问题。

  首先是DragDropEffect。该枚举中的每个值都对应着拖放过程的一种特定行为。这些外观在UI设计和用户使用中拥有特定的惯用法。因此在开发过程中要想好到底希望对拖拽目标执行何种操作,以防止用户在使用过程中产生疑惑。

  另外,外部拖拽源也是一种常见的拖拽功能,如将文件拖拽到应用程序内以进行加载。在某些情况下,拖拽目标事件并不能提供所需的信息。如在拖拽多个文件到应用程序内的时候,IDataObject接口只提供了GetData()函数。在这种情况下,软件开发人员可以尝试将其转化为DataObject类型实例,并通过GetFileDropList()函数返回所有被拖拽的文件。

  同时需要注意的是鼠标位置的获取方法。在拖拽过程中,鼠标的位置不能通过Mouse类等WPF标准方法获得。在某些情况下,该方法将会返回一个错误的位置。这是因为在拖拽过程中,鼠标的控制权是由拖拽源所管理的。该管理过程中会使用Win32函数,从而使WPF无法正确地返回鼠标的位置信息。一个变通的方法则是使用PInvoke调用Win32 API GetCursorPos()。

  另一个需要提及的小技巧则是如何禁用拖拽。标准控件包括一些默认情况下可作为拖拽目标的控件,如TextBox。为了禁止该功能,软件开发人员可以将OnPreviewDragEnter()和OnPreviewDragOver()重载中DragEventArgs的Handled属性设置为true,并设置Effects为None,以模拟禁止拖放的效果。

源码下载:http://download.csdn.net/detail/silverfox715/3884722

注明  原文地址:http://www.cnblogs.com/loveis715/archive/2011/12/05/2277384.html

WPF拖放功能实现的更多相关文章

  1. WPF拖放功能实现zz

    写在前面:本文为即兴而作,因此难免有疏漏和词不达意的地方.在这里,非常期望您提供评论,分享您的想法和建议. 这是一篇介绍如何在WPF中实现拖放功能的短文. 首先要读者清楚的一件事情是:拖放主要分为拖放 ...

  2. WPF开发快速入门【7】WPF的拖放功能(Drag and Drop)

    概述 本文描述WPF的拖放功能(Drag and Drop). 拖放功能涉及到两个功能,一个就是拖,一个是放.拖放可以发生在两个控件之间,也可以在一个控件自己内部拖放.假设界面上有两个控件,一个Tre ...

  3. 如何使用LightningChart拖放功能进行数据转移 ?

    本文主要介绍如何使用LightningChart扩展拖放功能为所有图表组件创建图表,如:系列,标题,轴线等等.支持用鼠标放置自定义对象到另一个图表中,如:可以添加或修改JSON/CSV或其他格式的数据 ...

  4. Draggabilly – 轻松实现拖放功能(Drag & Drop)

    Draggabilly 是一个很小的 JavaScript 库,专注于拖放功能.只需要简单的设置参数就可以在你的网站用添加拖放功能.兼容 IE8+ 浏览器,支持多点触摸.可以灵活绑定事件,支持 Req ...

  5. 脚本div实现拖放功能

    脚本div实现拖放功能 网页上有很多拖曳的操作,比如拖动树状列表,可拖曳的图片等. 1.原生拖放实现 <!doctype html> <html lang="en" ...

  6. 小强的HTML5移动开发之路(16)——神奇的拖放功能

    来自:http://blog.csdn.net/dawanganban/article/details/18181273 在智能手机发展飞速的现在拖放功能已经成为一种时尚,但是在我们的浏览器上是不是还 ...

  7. JavaScript如何实现拖放功能

    1.在学习ExtJs时,对其拖放功能感到很陌生,然后找了个拖放功能实现. 转载地址 2.拖拽的基本原理就是根据鼠标的移动来移动被拖拽的元素.鼠标的移动也就是x.y坐标的变化:元素的移动就是style. ...

  8. HOW TO: 在 Visual C# .NET 应用程序中提供文件拖放功能

    本文假定您熟悉下列主题: Windows 窗体列表框控件 Windows 窗体事件处理 生成示例的步骤 列表框控件提供了您需要处理的两个拖放事件: DragEnter 和 DragDrop. 当您在控 ...

  9. 一步一步学Silverlight 2系列(5):实现简单的拖放功能

    述 Silverlight 2 Beta 1版本发布了,无论从Runtime还是Tools都给我们带来了很多的惊喜,如支持框架语言Visual Basic, Visual C#, IronRuby, ...

随机推荐

  1. DFS(5)——hdu1728逃离迷宫

    一.题目回顾 题目链接:逃离迷宫 Problem Description 给定一个m × n (m行, n列)的迷宫,迷宫中有两个位置,gloria想从迷宫的一个位置走到另外一个位置,当然迷宫中有些地 ...

  2. [转]Linux UDP严重丢包问题的解决

    测试系统在Linux上的性能发现丢包率极为严重,发210000条数据,丢包达110000之巨,丢包率超过50%.同等情形下Windows上测试,仅丢几条数据.形势严峻,必须解决.考虑可能是因为协议栈B ...

  3. servlet入门(1)

    第一个servlet类 1.编写一个java类,继承HttpServlet类 2.重写doget和dopost方法 3.Servlet程序在tomcat服务器运行 第一步:找到server窗口,并新建 ...

  4. Winform常用知识总结

    Label中的文字自动换行 设置MaximumSize的width为正确的值,设置height为0,设置AutoSize为true. 绘制线条 放置一个Panel,设置size的高度为1,设置Bord ...

  5. Docker实战系列一:初识Docker for Windows

    windows下安装Docker官网教程Install Docker for Windows Docker配置官网教程Get started with Docker for Windows

  6. hdu DIY FLIGHT GAME (dfs)

    FLIGHT GAME Time Limit : 3000/1000ms (Java/Other)   Memory Limit : 65535/32768K (Java/Other) Total S ...

  7. ARC078 D.Fennec VS. Snuke(树上博弈)

    题目大意: 给定一棵n个结点的树 一开始黑方占据1号结点,白方占据n号结点 其他结点都没有颜色 每次黑方可以选择黑色结点临近的未染色结点,染成黑色 白方同理. 最后谁不能走谁输. 题解: 其实简单想想 ...

  8. [POJ1784]Huffman's Greed

    题面在这里 题意 给出一棵\(n\)个节点的二叉查找树的中序遍历中每个节点的访问次数\(p[i]\),和相邻两节点\(i\)和\(i+1\)的访问次数\(q[i]\),构造一棵二叉查找树使得\(\su ...

  9. ZOJ 3496 Assignment | 二分+有上下界网络流

    题目: http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3496 大概意思:给你一个网络,有源汇,在保证最大流的情况下求下面两 ...

  10. C++——派生类中的访问——可见性问题

    C++中派生类对基类成员的访问形式主要有以下两种: 1.内部访问:由派生类中新增成员对基类继承来的成员的访问. 2.对象访问:在派生类外部,通过派生类的对象对从基类继承来的成员的访问.今天给大家介绍在 ...