Silverlight实现强壮、可复用的拖放行为
本文版权归 51CTO 及其译者 黄永兵 共同所有,转载请标明原译者及出处,以示尊重!!
http://developer.51cto.com/art/200906/126291.htm
简单的Silverlight拖放行为可以通过某些Silverlight控制实现,但Silverlight的总 体表现却没有WPF中对行为控制得那么好,不过可以使用附加的行为操作元素的RenderTransform,这样可以 大大增加Silverlight中拖放实现的强壮性和可复用性。
新型实现
默认情况下Silverlight通过System.Windows.Controls.Primitives中的Thumb控制让拖放支持变得更容易 ,实现拖放最简单的方法是使用Thumb控制和为应用程序外观应用合适的ControlTemplate(控制模板)。但 在某些情况下,使用Thumb不太实际,在XAML中要“模板”化一个Thumb控制是很笨重的,你必须在你使用的 每个单独的区域中为应用程序逻辑重复一次这个行为,随着应用程序的增长,这种需求将会使应用程序的维 护变得异常困难。
不过有两个新方法让你可以在任何UI元素上开启拖放操作,下面是第一个方法:
<ELLIPSE MouseMove="DragDelta" MouseLeftButtonDown="DragStart" MouseLeftButtonUp="DragComplete" VerticalAlignment="Top" Fill="#FFC20707" Stroke="#FF000000"/> |
这个实现最重要的就是具体开启拖放操作的事件:MouseMove、MouseLeftButtonDown和 MouseLeftButtonUp,通过在这些事件上增加处理程序,你就可以执行一些简单的拖放操作了。
第二个方法是:
private bool isDragging = false; private void DragStart(object sender, MouseButtonEventArgs args) { Shape draggable = sender as Shape; if (draggable != null) { isDragging = true; draggable.CaptureMouse(); } } private void DragDelta(object sender, MouseEventArgs args) { Shape draggable = sender as Shape; if (draggable != null && isDragging) { Point currentPosition = args.GetPosition(null); TranslateTransform transform = draggable.RenderTransform as TranslateTransform; if (transform == null) { transform = new TranslateTransform(); draggable.RenderTransform = transform; } transform.X = (currentPosition.X - draggable.Width / 2); transform.Y = (currentPosition.Y - draggable.Height / 2); } } private void DragComplete(object sender, MouseButtonEventArgs args) { Shape draggable = sender as Shape; if (draggable != null) { isDragging = false; draggable.ReleaseMouseCapture(); } }
正如你所看到的,通过影响控制RenderTransform而不是特定面板布局参数,如Canvas.Top和Canvas.Left 等附加属性,或是Grid边缘空白,你可以将一个元素移动到任何一个容器中,而且这种方法对任何一个UI元 素都是可复用的,它不允许你横跨不同的控制重新使用应用程序逻辑,为此,你需要一个具有附加行为的实 现。
使用附加行为
WPF和.NET 3.0引入了依赖属性概念,也就是说当属性发生变化后,它会通知所有者(它必须是一个 DependencyObject[依赖对象]),允许所有者执行一块应用程序逻辑。
有两种类型的依赖属性,最常用的是依赖属性自身,建立和使用DependencyProperty.Register进行设置 都是相同的依赖对象。第二种类型是附加属性,使用DependencyProperty.RegisterAttached进行设置,附加 属性被设置为它所有者不同的类型。
看一下下么的附加属性例子:
public static readonly DependencyProperty HoverProperty = DependencyProperty.RegisterAttached( "Hover", typeof(Brush), typeof(Hover), new PropertyMetadata(null, OnHoverChanged)); |
通过事件处理程序OnHoverChanged,当你在XAML中设置那个属性时,你可以提供一些应用程序逻辑:
private static void OnHoverChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) { Border border = obj as Border; if (border != null) { if(args.OldValue == null && args.NewValue != null) { border.MouseEnter += SetHoverBackground; border.MouseLeave += SetNotHoverBackground; } else if(args.OldValue != null && args.NewValue == null) { border.MouseEnter -= SetHoverBackground; border.MouseLeave -= SetNotHoverBackground; } } }
OnHoverChanged代码仅仅简单地增加了事件处理程序,在触发MouseEnter事件时修改对象的背景颜色,触 发MouseLeave事件时还原到对象的原始颜色,这段简短的代码功能叫做附加行为,附加行为是后面章节讲述 的拖放实现的核心概念,下么来看一个附加行为IsEnabled:
public static readonly DependencyProperty IsEnabledProperty = DependencyProperty.RegisterAttached( "IsEnabled", typeof(bool), typeof(SimpleDragDropBehavior), new PropertyMetadata(false, OnIsEnabledChanged)); |
通过注册OnIsEnabledChanged作为改变事件处理程序的属性,你可以订阅你前面使用的事件处理程序:
private static void OnIsEnabledChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) { UIElement dragSource = obj as UIElement; bool wasEnabled = (args.OldValue != null) ? (bool)args.OldValue : false; bool isEnabled = (args.NewValue != null) ? (bool)args.NewValue : false; //如果行为被禁用,移除这个事件处理程序 if (wasEnabled && !isEnabled) { dragSource.MouseLeftButtonDown -= DragStart; } //如果行为被附加,添加这个事件处理程序 if (!wasEnabled && isEnabled) { dragSource.MouseLeftButtonDown += DragStart; } }
改变事件处理程序的属性IsEnabled使用前面谈到的新型实现向事件添加处理程序,因此,只需要在你的 应用程序中任何或是有UI元素上重复使用这个行为,但这种新型实现有其自身的设置限制,还不能立即显现 出来。
调用args.GetPosition(null)跟踪绝对位置不能适应一个允许嵌套拖放的行为,更好的解决办法是反复计 算拖动的变量,为了解决这个限制,你可以转向私有附加属性,在思考私有附加行为的范围时,私有附加属 性和私有成员变量或属性类似,公共附加行为和公共附加属性类似。因为你只需要跟踪你所选元素的x和y坐 标值,因此需要两个私有附加属性:
public static readonly DependencyProperty XProperty = DependencyProperty.RegisterAttached( "X", typeof(double), typeof(DragDropBehavior), new PropertyMetadata(double.NaN)); public static readonly DependencyProperty YProperty = DependencyProperty.RegisterAttached( "Y", typeof(double), typeof(DragDropBehavior), new PropertyMetadata(double.NaN));
这些属性的默认值都是double.NAN,表明对象当前没有拖放位置,这对于后面拖放元素时重置元素是很重 要的,此外这些私有附加属性,你还需要知道哪些容器是用于参照的,也就是说你需要指定原始的坐标值, (X,Y) = (0,0),你只需要简单地位你的宿主指定另一个附加属性:
public static readonly DependencyProperty IsHostProperty = DependencyProperty.RegisterAttached( "IsHost", typeof(bool), typeof(DragDropBehavior),new PropertyMetadata (false)); |
现在你已经为你反复跟踪拖放行为建立起了必要的数据,你需要做的全部内容是实现前面定义的相同的三 个处理程序:DragStart,DragDelta,DragComplete。在DragStart中,你可以增加更多的功能,在你的行为 类中改成使用静态函数:
private static void DragStart(object sender, MouseButtonEventArgs args) { UIElement dragSource = sender as UIElement; //创建执行拖动操作的TranslateTransform TranslateTransform dragTransform = new TranslateTransform(); dragTransform.X = 0; dragTransform.Y = 0; //如果是首次使用拖放,先要设置TranslateTransform dragSource.RenderTransform = ( dragSource.RenderTransform is TranslateTransform) ? dragSource.RenderTransform : dragTransform; //分别为MouseMove和MouseLeftButtonUp添加事件处理程序 dragSource.MouseMove += OnDragDelta; dragSource.MouseLeftButtonUp += OnDragComplete; //捕获鼠标 dragSource.CaptureMouse(); }
这和你最初的实现相差不多,你获得了一个拖动项目,设置它的RenderTransform,附加适当的事件处理 程序,并捕获鼠标。但DragDelta实现完全不同,因为你现在跟踪的是鼠标动作了,你需要:
1、找到你的宿主容器;
2、相对这个容器计算当前位置;
3、比较前一次记录的位置和现在的位置,计算出拖动变量;
4、更新RenderTransform和你的私有附加属性,x和y。
private static void DragDelta(object sender, MouseEventArgs args) { FrameworkElement dragSource = sender as FrameworkElement; //计算dragSource的偏移量,更新TranslateTransform FrameworkElement dragDropHost = FindDragDropHost(dragSource); Point relativeLocationInHost = args.GetPosition(dragDropHost); Point relativeLocationInSource = args.GetPosition(dragSource); //获得当前位置 double xPosition = GetX(dragSource); double yPosition = GetY(dragSource); //从前一次位置计算变数 double xChange = relativeLocationInHost.X - xPosition; double yChange = relativeLocationInHost.Y - yPosition; //如果这不是首次鼠标移动,更新位置 if (!double.IsNaN(xPosition)) { ((TranslateTransform)dragSource.RenderTransform).X += xChange; } if (!double.IsNaN(yPosition)) { ((TranslateTransform)dragSource.RenderTransform).Y += yChange; } //更新你的私有附加属性跟踪拖动位置 SetX(dragSource, relativeLocationInHost.X); SetY(dragSource, relativeLocationInHost.Y); }
需要特别指出的是找出参照宿主非常重要,不过说来也很简单,只需要在可视化树结构中搜索IsHost = true即可。
DragComplete新的实现
现在,DragStart和DragDelta中的部件算是全部弄好了,现在只剩下DragComplete了,和DragStart一样 ,DragComplete实现和你的新型实现不是完全一样,值得注意的是它移除了事件处理程序,并释放了鼠标捕 获,你也必须重置你的私有附加属性x和y,指出拖动项目不再进行拖动了。
private static void DragComplete(object sender, MouseButtonEventArgs args) { UIElement dragSource = sender as UIElement; dragSource.MouseMove -= DragDelta; dragSource.MouseLeftButtonUp -= DragComplete; //设置x和y的值,以便下一次MouseDown时好重置 SetX(dragSource, double.NaN); SetY(dragSource, double.NaN); //释放鼠标捕获 dragSource.ReleaseMouseCapture(); } 这个新的迭代实现克服了前面描述的限制,它也允许流畅跟踪鼠标,在新的实现中,你只需要把鼠标放在 拖动项目的中心,在前面的实现中,鼠标需要保持放在容器里面。 public static FrameworkElement FindDragDropHost( UIElement element) { DependencyObject parent = VisualTreeHelper.GetParent(element); while (parent != null && !GetIsHost(parent)) { parent = VisualTreeHelper.GetParent(parent); } return parent as FrameworkElement; }
限制和扩展
值得注意的是本文讲述的拖放行为也有一些限制,这将成为未来的改进方向。首先,即使这种方法能够在 一个容器中实现简单的拖放,但在拖放的不同阶段不能接受更强壮的应用程序逻辑,但在WPF中却可以实现, 如DragEnter、DragLeave和Drop在Silverlight中就还不能实现,但你可以通过自定义附加事件实现来增加这 些功能。
在设置IsEnabled后,行为也不能执行自定义拖放逻辑,如果在拖放实现中使用了Canvas.Top和 Canvas.Left,意味着你需要使用独立的附加行为,而不是使用自定义事件。这是因为Silverlight缺乏附加 事件引起的,在Silverlight 3.0这将可能会有这些特性,但你可以通过复杂的行为实现来克服这些限制,这 个内容已经超出了本文的范畴,这里概述的方法倾向为一个起点,更好的实现希望通过开源Silverlight库 Quasar来实现。
Silverlight实现强壮、可复用的拖放行为相关推荐
- Silverlight Blend动画设计系列八:拖放(Drag-Drop)操作与拖放行为(DragBehavior)
Silverlight & Blend动画设计系列八:拖放(Drag-Drop)操作与拖放行为(DragBehavior) 原文:Silverlight & Blend动画设计系列八: ...
- 《低代码PaaS驱动集团企业数字化创新白皮书》-平台化加低代码提供破解之道(2)
平台化加低代码提供破解之道 低代码向业务的赋能:以效率和创新为核心,提升组织效率,促进创新,优化体验 通过IDC对大型企业的调研发现,当前拥有100个及以上应用数量的企业已经高达70%:IDC预测 , ...
- Visual Studio 2010 所有版本 中文 英文 下载,激活,序列号,注册码,下载地址
Visual Studio 2010 下载,安装,激活,序列号,注册码,下载地址(全集) 附Visual Studio 2008下载地址 激活 sp1下载 Visual Studio 2010 激活码 ...
- 兼容Silverlight4的实用的Silverlight可拖放工具类源代码
开发日常的Silverlight应用程序时,常常要对一个域多个控件实现可拖放的MOUSE操作,在Silverlight中实现拖放的功能其实非常简单,但是为了提高程序功能代码的可复用性,程序员常常喜欢把 ...
- 一步一步学Silverlight 2系列(5):实现简单的拖放功能
概述 Silverlight 2 Beta 1版本发布了,无论从Runtime还是Tools都给我们带来了很多的惊喜,如支持框架语言Visual Basic, Visual C#, IronRuby, ...
- (5):Silverlight 2 实现简单的拖放功能
概述 Silverlight 2 Beta 1版本发布了,无论从Runtime还是Tools都给我们带来了很多的惊喜,如支持框架语言Visual Basic, Visual C#, IronRuby, ...
- 一步一步学Silverlight 2系列(5):实现简单的拖放功能_转载
概述 Silverlight 2 Beta 1版本发布了,无论从Runtime还是Tools都给我们带来了很多的惊喜,如支持框架语言Visual Basic, Visual C#, IronRuby, ...
- 分享Silverlight/WPF/Windows Phone一周学习导读(10月30日-11月6日)
分享Silverlight/WPF/Windows Phone一周学习导读(10月30日-11月6日) 本周Silverlight学习资源更新 Silverlight 定位 niejunhua [学习 ...
- vs2010 学习Silverlight学习笔记(7):控件样式与模板
概要: 终于知道Silverlight--App.xaml是干什么用的了,不仅可以用来封装样式(类似css),还可以制定控件模版...好强大的功能啊. 封装: 继续学习<一步一步学Silverl ...
最新文章
- mysql常见面试题(持续更新)
- 无法去掉自增标识_为什么 MySQL 的自增主键不单调也不连续
- Python中numpy中tile和repeat用法和区别
- zip伪加密做法及原理
- python没有库_Python开发者必备6个基本库,这个都没有怎么做开发呢
- 林洋能源:布局能源互联网 分布式光伏龙头再扬帆
- quartus仿真19:JK触发器序列1001检测器
- 计算机辅助制造讲义翻译,计算机辅助制造讲义-2007-2演示文稿.PPT
- python软件是什么作用,python-dotenv的用途是什么?
- nifi执行linux命令log,shell - 无法使用Nifi curl命令启动处理器 - SO中文参考 - www.soinside.com...
- 在SCDN中如何转载别人文章
- 大一高数下册笔记整理_高等数学下册知识点总结.doc
- 查找与清除线程插入式木马(转)
- mac flutter开发环境 flutter环境变量的配置
- mysql不停库全量备份,mysql全量备份数据
- java docx4j 工具和xdocreport工具创建PDF或者word报表
- php的变量命名规则
- python 输出结果图文混排_Django图文混排
- linux卡利系统设置密码,Windows 10 Windows Linux子系统安装指南-官网
- Fine-Gray检验、竞争风险模型、列线图绘制
热门文章
- 洛谷——P1024 [NOIP2001 提高组] 一元三次方程求解
- linux监测系统动态的进程状态命令,Linux命令:动态查看进程的变化(top)!
- channelinactive触发后不关闭channel_Go语言 | goroutine不只有基础的用法,还有这些你不知道的操作...
- 基于阿里云物联网平台实现的简易出入监控
- Zookeeper的一些概念
- 计算机的视觉应用研究,计算机视觉技术应用研究.doc
- 大白话讲解如何给github上项目贡献代码
- Atitit.http httpclient实践java c# .net php attilax总结
- JavaScript数组快速入门
- Cloud Foundry 在 Azure 中国正式发布