top-silverlight | Just another WordPress weblog

六/09

1

Silverlight中实现强壮的、可复用的拖放行为

  51CTO独家特稿简单的Silverlight拖放行为可以通过某些Silverlight控制实现,但Silverlight的总体表现却没有WPF中对行为控制得那么好,不过可以使用附加的行为操作元素的RenderTransform,这样可以大大增加Silverlight中拖放实现的强壮性和可复用性。 

  新型实现

  默认情况下Silverlight通过System.Windows.Controls.Primitives中的Thumb控制让拖放支持变得更容易,实现拖放最简单的方法是使用Thumb控制和为应用程序外观应用合适的ControlTemplate(控制模板)。但在某些情况下,使用Thumb不太实际,在XAML中要“模板”化一个Thumb控制是很笨重的,你必须在你使用的每个单独的区域中为应用程序逻辑重复一次这个行为,随着应用程序的增长,这种需求将会使应用程序的维护变得异常困难。

  不过有两个新方法让你可以在任何UI元素上开启拖放操作,下面是第一个方法:

  MouseMove=”DragDelta”MousecenterButtonDown=”DragStart”MousecenterButtonUp=”DragComplete”

  VerticalAlignment=”Top” Fill=”#FFC20707″

  Stroke=”#FF000000″/>

  这个实现最重要的就是具体开启拖放操作的事件:MouseMove、MousecenterButtonDown和MousecenterButtonUp,通过在这些事件上增加处理程序,你就可以执行一些简单的拖放操作了。

  第二个方法是:

  private bool isDragging = false;

  private void DragStart(t sender, MouseButtonEventArgs args)

  Shape draggable = sender as Shape;

  if (draggable != null)

  isDragging = true;

  draggable.CaptureMouse();

  private void DragDelta(t 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.border=”1″ Height /2);

  private void DragComplete(t sender, MouseButtonEventArgsargs)

  Shape draggable = sender as Shape;

  if (draggable != null)

  isDragging = false;

  draggable.ReleaseMouseCapture();

  正如你所看到的,通过影响控制RenderTransform而不是特定面板布局参数,如Canvas.Top和Canvas.center等附加属性,或是Grid边缘空白,你可以将一个元素移动到任何一个容器中,而且这种方法对任何一个UI元素都是可复用的,它不允许你横跨不同的控制重新使用应用程序逻辑,为此,你需要一个具有附加行为的实现。

  使用附加行为

  WPF和.NET3.0引入了依赖属性概念,也就是说当属性发生变化后,它会通知所有者(它必须是一个Dependencyt[依赖对象]),允许所有者执行一块应用程序逻辑。

  有两种类型的依赖属性,最常用的是依赖属性自身,建立和使用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(Dependencyt obj,

  DependencyPropertyChangedEventArgs args)

  Border border = obj as Border;

  if (border != null)

  border.MouseEnter +=

  SetHoverBackground;

  border.MouseLeave +=

  SetNotHoverBackground;

  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(Dependencyt 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.MousecenterButtonDown -= DragStart;

  //如果行为被附加,添加这个事件处理程序

  if (!wasEnabled && isEnabled)

  dragSource.MousecenterButtonDown += 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(t sender,MouseButtonEventArgsargs)

  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和MousecenterButtonUp添加事件处理程序

  dragSource.MouseMove += OnDragDelta;

  dragSource.MousecenterButtonUp += OnDragComplete;

  //捕获鼠标

  dragSource.CaptureMouse();

  这和你最初的实现相差不多,你获得了一个拖动项目,设置它的RenderTransform,附加适当的事件处理程序,并捕获鼠标。但DragDelta实现完全不同,因为你现在跟踪的是鼠标动作了,你需要:

  1、找到你的宿主容器;

  2、相对这个容器计算当前位置;

  3、比较前一次记录的位置和现在的位置,计算出拖动变量;

  4、更新RenderTransform和你的私有附加属性,x和y。

  private static void DragDelta(t 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(t sender,

  MouseButtonEventArgs args)

  UIElement dragSource = sender as UIElement;

  dragSource.MouseMove -= DragDelta;

  dragSource.MousecenterButtonUp -= DragComplete;

  //设置x和y的值,以便下一次MouseDown时好重置

  SetX(dragSource, double.NaN);

  SetY(dragSource, double.NaN);

  //释放鼠标捕获

  dragSource.ReleaseMouseCapture();

  这个新的迭代实现克服了前面描述的限制,它也允许流畅跟踪鼠标,在新的实现中,你只需要把鼠标放在

  拖动项目的中心,在前面的实现中,鼠标需要保持放在容器里面。

  public static FrameworkElement FindDragDropHost(

  UIElement element)

  Dependencyt 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.center,意味着你需要使用独立的附加行为,而不是使用自定义事件。这是因为Silverlight缺乏附加事件引起的,在Silverlight3.0这将可能会有这些特性,但你可以通过复杂的行为实现来克服这些限制,这个内容已经超出了本文的范畴,这里概述的方法倾向为一个起点,更好的实现希望通过开源Silverlight库Quasar来实现。

  您正在阅读的是51CTO独家特稿《Silverlight中实现强壮的、可复用的拖放行为》

  编辑推荐

  VS 2010 Beta1和Silverlight不得不说的事

  Flash与Silverlight多领域实测对比

  详解 Silverlight和WPF互相扩展

No tags

No comments yet.

Leave a Reply

<<

>>

Theme Design by devolux.nh2.me