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

简介: 对象拖动是一个老生常谈的话题,在SL上要实现对象拖动,一般有三种思路: 一、基于Canvas绝对定位布局的拖动 这种处理方法最简单,修改对象的Canvas.Top与Canvas.Left即可,简单明了! 在线案例: silverlight图片局部放大效果 但是很多时候,我们采用的布局并不是Canvas,如果仅仅为了实现对象拖动,把整个布局重构,代价太大,有点得不偿失。
对象拖动是一个老生常谈的话题,在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形成的偏移。
示例源码
目录
相关文章
Silverlight自定义数据绑定控件应该如何处理IEditableObject和IEditableCollectionView对象
原文:Silverlight自定义数据绑定控件应该如何处理IEditableObject和IEditableCollectionView对象 原创文章,如需转载,请注明出处。   最近在一直研究Silverlight下的数据绑定控件,发现有这样两个接口IEditableObject 和IEditableCollectionView,记录一下结论,欢迎交流指正。
869 0
Silverlight & Blend动画设计系列六:动画技巧(Animation Techniques)之对象与路径转化、波感特效
原文:Silverlight & Blend动画设计系列六:动画技巧(Animation Techniques)之对象与路径转化、波感特效   当我们在进行Silverlight & Blend进行动画设计的过程中,可能需要设计出很多效果不一的图形图像出来作为动画的基本组成元素。
1046 0

热门文章

最新文章

下一篇
无影云桌面