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

  这是一篇介绍如何在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. MyEclipse主题设置

    1. 打开网页: http://eclipsecolorthemes.org/ 选择自己喜欢的主题,并下载(下载epf文件) 我下载的是 Vibrant Ink 2. 下载完成后,打开myeclips ...

  2. HDU 4747 Mex(线段树)(2013 ACM/ICPC Asia Regional Hangzhou Online)

    Problem Description Mex is a function on a set of integers, which is universally used for impartial ...

  3. mongoDB 常用操作CRUD

    1.显示所有的数据库 show dbs   2.切换数据库(如果没有数据库,即是创建数据库) use 数据库名称   3.显示所有的表 show tables   4.查看数据库里的表 show co ...

  4. 山科 STUST OJ Problem B: 编写函数:String to Double (II) (Append Code)

    这道题没啥别的毛病,我的错误在于看不懂题. 另外还有一点是注意浮点数存在-0 #include <stdio.h> #include <ctype.h> #include &l ...

  5. Python调用MySQL的一些用法小结

    目标:1个excel表内容导入到数据库中,例如:原始excel文件为 aaa.xls 首先:将aaa.xls 转换成aaa.txt ,注意当文件中含有中文字符时,可以通过notepad++打开,在“格 ...

  6. Java中WeakHashMap实现原理深究

    一.前言 我发现Java很多开源框架都使用了WeakHashMap,刚开始没怎么去注意,只知道它里面存储的值会随时间的推移慢慢减少(在 WeakHashMap 中,当某个“弱键”不再正常使用时,会被从 ...

  7. OSCache页面缓存的使用

    完成项目时,为了减少对数据库的频繁操作,引出了缓存,缓存分为以下几种: 1.一级缓存 一级缓存的存储域是session,作用于单个的dao 2.二级缓存 二级缓存的存储域是sessionFactory ...

  8. 黑群晖DSM 6.1网卡支持列表

    黑群晖DSM 6.1网卡支持列表 Network Drivers====================================AMDamd8111e : AMD 8111 (new PCI ...

  9. [BZOJ1921] [CTSC2010]珠宝商

    Description Input 第一行包含两个整数 N,M,表示城市个数及特征项链的长度. 接下来的N-1 行, 每行两个整数 x,y, 表示城市 x 与城市 y 有直接道路相连.城市由1~N进行 ...

  10. CTSC2018 & APIO2018 颓废 + 打铁记

    CTSC2018 & APIO2018 颓废 + 打铁记 CTSC 5 月 6 日 完美错过报道,到酒店领了房卡放完行李后直接奔向八十中拿胸牌.饭票和资料.试机时是九省联考的题,从来没做过,我 ...