silverlight:对象拖动的优雅解决方案

对象拖动是一个老生常谈的话题,在SL上要实现对象拖动,一般有三种思路:

一、基于Canvas绝对定位布局的拖动
这种处理方法最简单,修改对象的Canvas.Top与Canvas.Left即可,简单明了!
但是很多时候,我们采用的布局并不是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部分
<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形成的偏移。
示例源码
posted @ 2011-06-22 21:04  菩提树下的杨过  阅读(3773)  评论(3编辑  收藏  举报