对象拖动是一个老生常谈的话题,在SL上要实现对象拖动,一般有三种思路:
一、基于Canvas绝对定位布局的拖动
这种处理方法最简单,修改对象的Canvas.Top与Canvas.Left即可,简单明了!
在线案例: silverlight图片局部放大效果
但是很多时候,我们采用的布局并不是Canvas,如果仅仅为了实现对象拖动,把整个布局重构,代价太大,有点得不偿失。
二、基于对象Margin值的拖动
Margin是对象的通用属性,通过改变Margin值理论上可在任何布局下,重新定位对象的位置。
在线案例: silverlight:类似iBaidu,iGoogle的拖放功能
缺点就是算法处理有些小复杂,初次看着有点晕。
三、基于TranslateTransform偏移量的拖动
每个对象都可以设置一系列RenderTransform,以实现变形、旋转、偏移等多种很Cool的效果。这也是一种通用的做法,不局限于某种特定的布局方法。
而且可以借助Behaviour将其封装起来,直接应用于多个对象,这也是我个人认为最优雅的解决方案。
封装代码如下:
代码语言:javascript复制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部分
代码语言:javascript复制<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部分
代码语言:javascript复制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形成的偏移。
示例源码
http://files.cnblogs.com/yjmyzz/DragElementBehaviorSample.zip