对象拖动是一个老生常谈的话题,在SL上要实现对象拖动,一般有三种思路:
一、基于Canvas绝对定位布局的拖动
这种处理方法最简单,修改对象的Canvas.Top与Canvas.Left即可,简单明了!
在线案例: silverlight图片局部放大效果
但是很多时候,我们采用的布局并不是Canvas,如果仅仅为了实现对象拖动,把整个布局重构,代价太大,有点得不偿失。
二、基于对象Margin值的拖动
Margin是对象的通用属性,通过改变Margin值理论上可在任何布局下,重新定位对象的位置。
缺点就是算法处理有些小复杂,初次看着有点晕。
三、基于TranslateTransform偏移量的拖动
每个对象都可以设置一系列RenderTransform,以实现变形、旋转、偏移等多种很Cool的效果。这也是一种通用的做法,不局限于某种特定的布局方法。
而且可以借助Behaviour将其封装起来,直接应用于多个对象,这也是我个人认为最优雅的解决方案。
封装代码如下:
using System.ComponentModel; using System.Windows; using System.Windows.Controls; using System.Windows.Interactivity; using System.Windows.Media; using System.Windows.Media.Imaging; namespace SLControls { public class Drag : Behavior<FrameworkElement> { public static readonly DependencyProperty IsMovableProperty = DependencyProperty.Register("IsMovable", typeof(bool), typeof(Drag), new PropertyMetadata(null)); [Category("Target Properties")] public bool IsMovable { get; set; } private bool _isDragging = false; private Point _offset; private readonly TranslateTransform _elementTranslate = new TranslateTransform(); private TranslateTransform _imgTranslate = new TranslateTransform(); private Image _img = new Image(); /// <summary> /// Drag行为附加到对象上时触发 /// </summary> protected override void OnAttached() { base.OnAttached(); AssociatedObject.Loaded += AssociatedObjectLoaded; //先将对象置于左上角 AssociatedObject.HorizontalAlignment = HorizontalAlignment.Left; AssociatedObject.VerticalAlignment = VerticalAlignment.Top; AssociatedObject.MouseLeftButtonDown += AssociatedObjectMouseLeftButtonDown; } void AssociatedObjectLoaded(object sender, RoutedEventArgs e) { //默认先给对象创建一个TranslateTransform AssociatedObject.RenderTransform = _elementTranslate; } /// <summary> /// Drag行为从对象剥离时触发 /// </summary> protected override void OnDetaching() { base.OnDetaching(); //移除鼠标左键事件处理 AssociatedObject.MouseLeftButtonDown -= AssociatedObjectMouseLeftButtonDown; } /// <summary> /// 动象拖动时的处理 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void AssociatedObjectMouseMove(object sender, System.Windows.Input.MouseEventArgs e) { if (!_isDragging) return; FrameworkElement parent = _img.Parent as FrameworkElement; Point newPosition = e.GetPosition(parent); //移动的其实只是对象的"影子副本" _imgTranslate.X = (newPosition.X - _offset.X); _imgTranslate.Y = (newPosition.Y - _offset.Y); } /// <summary> /// 托运结束时的处理 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void AssociatedObjectMouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e) { if (!_isDragging) return; Panel panel = AssociatedObject.Parent as Panel; //停止拖动 _isDragging = false; //释放鼠标 _img.ReleaseMouseCapture(); //解除事件绑定 _img.MouseMove -= AssociatedObjectMouseMove; _img.MouseLeftButtonUp -= AssociatedObjectMouseLeftButtonUp; //如果允许移动,则将"影子Transform"的偏移量赋值给"对象的Transform" if (IsMovable) { _elementTranslate.X = _imgTranslate.X; _elementTranslate.Y = _imgTranslate.Y; } //重新初始化偏移量,同时将对象本身恢复原透明度 _imgTranslate = new TranslateTransform(); _offset = new Point(0, 0); AssociatedObject.Opacity = 1; //清除Image if (panel != null) panel.Children.Remove(_img); //为下次移动准备一个新的Image _img = new Image(); } /// <summary> /// 开始拖动时触发 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void AssociatedObjectMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e) { _isDragging = true;//处理标志位 AssociatedObject.Opacity = .35;//将对象透明度降低 //生成对象的"位图影子副本" WriteableBitmap bitmap = new WriteableBitmap(AssociatedObject, new TranslateTransform()); if (_img == null) return; _img.Source = bitmap; _img.HorizontalAlignment = HorizontalAlignment.Left; _img.VerticalAlignment = VerticalAlignment.Top; _img.Stretch = Stretch.None; _img.Width = bitmap.PixelWidth; _img.Height = bitmap.PixelHeight; _imgTranslate.X = _elementTranslate.X; _imgTranslate.Y = _elementTranslate.Y; _img.RenderTransform = _imgTranslate; //注册鼠标事件,以响应拖动 _img.MouseMove += AssociatedObjectMouseMove; _img.MouseLeftButtonUp += AssociatedObjectMouseLeftButtonUp; Panel panel = AssociatedObject.Parent as Panel; if (panel != null) panel.Children.Add(_img); _offset = e.GetPosition(_img); //捕获鼠标,以防止鼠标移动过快时,甩掉"影子对象" _img.CaptureMouse(); } } }
而且很多时候,对象拖动后要求能保存新的位置信息,以方便用户下次进入时,能自动恢复到上次改变过的位置。
示例代码: Xaml部分
示例代码: Xaml部分
<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:ctl="clr-namespace:SLControls;assembly=SlControls" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" x:Class="slApp.MainPage" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400"> <Grid x:Name="LayoutRoot" Background="White"> <Grid.RowDefinitions> <RowDefinition Height="*"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <Border x:Name="bdr" Cursor="Hand" BorderBrush="#FFC2B529" BorderThickness="10" Background="#FF291313" HorizontalAlignment="Center" VerticalAlignment="Center" Width="50" Height="50"> <i:Interaction.Behaviors> <!--一行代码就搞定了拖动!--> <ctl:Drag IsMovable="True"/> </i:Interaction.Behaviors> </Border> <StackPanel Orientation="Horizontal" Grid.Row="1" Grid.ColumnSpan="2" VerticalAlignment="Center" HorizontalAlignment="Center"> <Button x:Name="btnSave" Click="btnSave_Click" Grid.Column="1" Padding="10,5">保存当前位置</Button> <Button x:Name="btnLoad" Click="btnLoad_Click" Grid.Row="1" Grid.Column="1" Margin="10,0,0,0" Padding="10,5">加载上次位置</Button> </StackPanel> </Grid> </UserControl>
示例代码:Xaml.cs部分
using System.Windows; using System.Windows.Controls; using System.Windows.Media; namespace slApp { public partial class MainPage : UserControl { Point p; public MainPage() { InitializeComponent(); } private void btnSave_Click(object sender, RoutedEventArgs e) { TranslateTransform transform = bdr.RenderTransform as TranslateTransform; if (transform != null) { p.X = transform.X; p.Y = transform.Y; } } private void btnLoad_Click(object sender, RoutedEventArgs e) { TranslateTransform transform = bdr.RenderTransform as TranslateTransform; if (transform != null) { transform.X = p.X; transform.Y = p.Y; } } } }
四、基于MatrixTransform的拖动
Blend自带的MouseDragElementBehavior,其内部原理就是利用MatrixTransform形成的偏移。
示例源码