WPF實現鼠標拖動控件並帶有中間動效

一. 前提

要實現鼠標對控件的拖拽移動,首先必須知道下面幾點:express

  1. WPF中的鼠標左鍵按下、鼠標移動事件,有時候經過XAML界面添加的時候並有沒有做用,咱們要經過觸發事件的元素和要監聽的路由事件綁定來進行手動觸發;canvas

  2. 若是在移動時候要持續修改控件的屬性,咱們經過改變RenderTransform來修改呈現,而不是直接修改控件自己的屬性(會卡);優化

  3. 經過VisualBrush來填充Rectangle,來實現鼠標拖動控件所造成的影子;this

  4. 經過建立一個帶有目標依賴屬性的Button的子類,來將有關數據放入Button的子類中;spa

  5. 並不須要經過 UIElement.CaptureMouse() 和 UIElement.ReleaseMouseCapture()來對鼠標進行捕獲和釋放;code

  6. 屏蔽一些鍵盤熱鍵致使鼠標擡起的消息失去的問題,如:Alt + Ctrl + A 截圖等等的熱鍵的影響;orm

二. 過程

這裏以按鈕的拖動,分析一下這個過程:xml

  1. 首先在點擊按鈕(鼠標左鍵按下),咱們以按鈕爲原型建立一個 「影子」 ;對象

  2. 在鼠標按住左鍵拖動的時候,實現對這個 「影子」 的拖動跟隨效果;blog

  3. 最後,在放開鼠標(鼠標左邊擡起)時,將原來的按鈕的位置直接移動到擡起時的位置並去除跟隨的 「影子」;

三. 代碼

這邊的代碼進行了封裝,如過要看沒有封裝的版本請見示例工程(下面能夠下載)

  • DragButton 類,繼承自 Button 類
/// <summary>
/// 拖拽按鈕
/// </summary>
public class DragButton : Button
{
    //依賴屬性
    private static readonly DependencyProperty IsDragProperty = DependencyProperty.Register("IsDrag", typeof(Boolean), typeof(DragButton));
    private static readonly DependencyProperty CurrentPosProperty = DependencyProperty.Register("CurrentPos", typeof(Point), typeof(DragButton));
    private static readonly DependencyProperty ClickPosProperty = DependencyProperty.Register("ClickPos", typeof(Point), typeof(DragButton));
    private static readonly DependencyProperty RectProperty = DependencyProperty.Register("Rect", typeof(Rectangle), typeof(DragButton));

    /// <summary>
    /// 是否拖拽
    /// </summary>
    public bool IsDrag
    {
        get
        {
            return (bool)this.GetValue(IsDragProperty);
        }
        set
        {
            this.SetValue(IsDragProperty, value);
        }
    }

    /// <summary>
    /// 按鈕的定位位置
    /// 按鈕左上角的位置
    /// </summary>
    public Point CurrentPos
    {
        get
        {
            //第一次獲取若是是沒有被初始化,那麼吧按鈕的座標初始化過來
            Point p = (Point)this.GetValue(CurrentPosProperty);
            if (p.X == 0 && p.Y == 0)
            {
                p.X = Canvas.GetLeft(this);
                p.Y = Canvas.GetTop(this);
            }
            return p;
        }
        set
        {
            this.SetValue(CurrentPosProperty, value);
        }
    }

    /// <summary>
    /// 當前鼠標點在按鈕上的位置
    /// </summary>
    public Point ClickPos
    {
        get
        {
            return (Point)this.GetValue(ClickPosProperty);
        }
        set
        {
            this.SetValue(ClickPosProperty, value);
        }
    }

    /// <summary>
    /// 虛擬出來的按鈕的顯示矩形
    /// </summary>
    public Rectangle Rect
    {
        get
        {
            if (this.GetValue(RectProperty) == null)
            {
                //建立VisualBrush
                VisualBrush visualBrush = new VisualBrush(this);
                Rectangle rect = new Rectangle() { Width = this.ActualWidth, Height = this.ActualHeight, Fill = visualBrush, Name = "rect" };

                //設置值
                Canvas.SetLeft(rect, Canvas.GetLeft(this));
                Canvas.SetTop(rect, Canvas.GetTop(this));

                rect.RenderTransform = new TranslateTransform(0d, 0d);
                rect.Opacity = 0.6;

                this.SetValue(RectProperty, rect);
            }

            return (Rectangle)this.GetValue(RectProperty);
        }
    }
}
  • MainWindow的XAML的部分代碼
<Window x:Class="Demo.MainWindow"
        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:local="clr-namespace:Demo"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525"
        x:Name="mainWindow">
    <Canvas x:Name="canvas" Background="Aqua" Margin="0,0,0,0">
        <local:DragButton x:Name="btn" Canvas.Left="173" Canvas.Top="64" Width="80" Height="30" Content="拖拽"/>
        <local:DragButton x:Name="btn1" Canvas.Left="94" Canvas.Top="166" Width="80" Height="30" Content="拖拽"/>
    </Canvas>
</Window>
  • MainWindow的C#後臺部分代碼
/// <summary>
/// MainWindow.xaml 的交互邏輯
/// </summary>
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        //添加事件
        this.btn.AddHandler(Canvas.MouseLeftButtonDownEvent, new MouseButtonEventHandler(this.MouseButtonLeftDown), true);
        this.btn1.AddHandler(Canvas.MouseLeftButtonDownEvent, new MouseButtonEventHandler(this.MouseButtonLeftDown), true);

        //防止一些熱鍵的影響
        this.AddHandler(Window.KeyDownEvent, new RoutedEventHandler(this.OtherKeyDownEvent), true);
    }

    /// <summary>
    /// 區域移動事件
    /// </summary>
    private void Canvas_MouseMove(object sender, MouseEventArgs e)
    {
        DragButton dragButton = sender as DragButton;
        if (dragButton != null && dragButton.IsDrag)
        {
            Point offsetPoint = e.GetPosition(this.canvas);
            double xOffset = offsetPoint.X - dragButton.CurrentPos.X - dragButton.ClickPos.X;
            double yOffset = offsetPoint.Y - dragButton.CurrentPos.Y - dragButton.ClickPos.Y;

            TranslateTransform transform = (TranslateTransform)dragButton.Rect.RenderTransform;
            transform.X += xOffset;
            transform.Y += yOffset;

            dragButton.CurrentPos = new Point(offsetPoint.X - dragButton.ClickPos.X, offsetPoint.Y - dragButton.ClickPos.Y);
        }
    }

    /// <summary>
    /// 鼠標左鍵按下
    /// </summary>
    private void MouseButtonLeftDown(object sender, MouseButtonEventArgs e)
    {
        DragButton dragButton = sender as DragButton;
        if (dragButton != null && !dragButton.IsDrag)
        {
            dragButton.ClickPos = e.GetPosition(dragButton);
            this.canvas.Children.Add(dragButton.Rect);
            dragButton.IsDrag = true;

            //註冊事件
            dragButton.AddHandler(Canvas.MouseMoveEvent, new MouseEventHandler(this.Canvas_MouseMove), true);
            dragButton.AddHandler(Canvas.MouseLeftButtonUpEvent, new MouseButtonEventHandler(this.CanvasButtonLeftUp), true);
        }
    }

    /// <summary>
    /// 區域鼠標左鍵擡起
    /// </summary>
    private void CanvasButtonLeftUp(object sender, MouseButtonEventArgs e)
    {
        ReducingButton(sender);
    }

    /// <summary>
    /// 防止一些熱鍵的影響
    /// </summary>
    private void OtherKeyDownEvent(object sender, RoutedEventArgs e)
    {
        ReducingButton(sender);
    }

    /// <summary>
    /// 避免重複代碼
    /// </summary>
    private void ReducingButton(object sender)
    {
        DragButton dragButton = sender as DragButton;
        if (dragButton != null && dragButton.IsDrag)
        {
            Canvas.SetLeft(dragButton, dragButton.CurrentPos.X);
            Canvas.SetTop(dragButton, dragButton.CurrentPos.Y);

            this.canvas.Children.Remove(dragButton.Rect);
            dragButton.IsDrag = false;

            //移除事件
            dragButton.RemoveHandler(Canvas.MouseMoveEvent, new MouseEventHandler(this.Canvas_MouseMove));
            dragButton.RemoveHandler(Canvas.MouseLeftButtonUpEvent, new MouseButtonEventHandler(this.CanvasButtonLeftUp));
        }
    }
}

四. 原理圖

 

  • 鼠標拖動的距離 = offsetPoint - ( CurrentPos + ClickPos) = offsetPoint - CurrentPos - ClickPos
  • 鼠標拖動以後按鈕左上角的座標位置(相對於Canvas):Current = offsetPoint - ClickPos

五. 運行效果

 六. 工程代碼

下載地址

七. 一些補充

這點的內容是後來本身看以前的代碼,以爲很差以後修改了一下,而後補充的。一共寫了4各版本,每一個版本都在以前的版本上進行了優化,最終的版本是名字後面有 "最終版" 的那一個。

這邊稍微記錄一下:

1. 關於路由事件的綁定,以前看書的時候,書上並無寫的特別明白。首先 "UIElement.AddHandler" 這邊的 UIElement 將會是事件 xxxHandler 的 sender 對象,而這個事件到底是誰觸發路由傳遞過來的,要經過 e.Source 或者 e.OriginalSource 來得到。總而言之,要讓哪一個元素來處理,則指明 UIElement ;處理什麼,經過一棵樹上的指定路由事件來傳遞;

2. 設置元素到 Canvas 子類的左邊距的時候,使用:

Canvas.SetLeft(UIElement,double);

而設置的時候使用:

Canvas.GetLeft(UIElement);

而不是經過下面的方式來設置/獲取:

UIElement.SetValue(DependencyProperty,object);
UIElement.GetValue(DependencyProperty);

注:上面的方法能夠是能夠,就是寫的比較煩瑣,咱們要充分利用附加屬性的特色。通常附加屬性的設置都在附加屬性所在的對象上而不是在被附加的對象上,例如給 Person 增長一個學校的 School 類的班級的附加屬性,那麼這個設置班級附加屬性的方法應該存在於學校 School 中。因此這邊和直接調用學校 School 的方法來給 Person 添加班級屬性是一個道理。

3. 路由事件能夠進行延遲綁定,不須要在開始的時候就進行聲明;

4. 關於 UIElement.CaptureMouse() 和 UIElement.ReleaseMouseCapture() 是否是要讓元素捕獲鼠標,防止一些特殊 Bug ,這個要依據狀況來定。這邊,咱們每當要用來鼠標點擊、拖拽的時候,就要考慮到這個問題。

5. 對於 Canvas 等等的元素的填充,可使用 Margin = "0,0,0,0" 來實現;

相關文章
相關標籤/搜索