[WPF] MVVM 軟體架構模式 - 透過 AttachedBehaviorCommand 偵測滑鼠狀態

MVVM 軟體架構模式 - 透過 AttachedBehaviorCommand 偵測滑鼠狀態

這篇使用 Behavior 來實作滑鼠行為並附加到 FrameworkElement 類型元件上,這類別代表 WPF 架構層級實作,基本上包含所有 WPF 的控件

首先實作滑鼠偵測代理人的介面 interface 與滑鼠偵測使用的參數類別

using System;

namespace MVVM
{
    public interface IMouseCaptureProxy
    {
        event EventHandler Capture;
        event EventHandler Release;

        void OnMouseDown(object sender, MouseCaptureArgs e);
        void OnMouseMove(object sender, MouseCaptureArgs e);
        void OnMouseUp(object sender, MouseCaptureArgs e);
    }

    public class MouseCaptureArgs
    {
        public double X { get; set; }
        public double Y { get; set; }
        public bool LeftButton { get; set; }
        public bool RightButton { get; set; }
    }
}

以下就是繼承 Behavior 並實作滑鼠偵測的行為方法,主要重載實作 OnAttached 與 OnDetaching 這兩個方法,以便在之後使用時,用 OnAttached 處理附加在作用的 FrameworkElement 時的操作,並使用 OnDetaching 執行解除附加 FrameworkElement 時的操作

using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interactivity;

namespace MVVM
{
    public class MouseCaptureBehavior : Behavior<FrameworkElement>
    {
        public static readonly
            DependencyProperty ProxyProperty =
            DependencyProperty.RegisterAttached("Proxy",
                                                typeof(IMouseCaptureProxy),
                                                typeof(MouseCaptureBehavior),
                                                new PropertyMetadata(null, OnProxyChanged));

        public static void SetProxy(DependencyObject source, IMouseCaptureProxy value)
        {
            source.SetValue(ProxyProperty, value);
        }

        public static IMouseCaptureProxy GetProxy(DependencyObject source)
        {
            return (IMouseCaptureProxy)source.GetValue(ProxyProperty);
        }

        private static void OnProxyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (e.OldValue is IMouseCaptureProxy)
            {
                (e.OldValue as IMouseCaptureProxy).Capture -= OnCapture;
                (e.OldValue as IMouseCaptureProxy).Release -= OnRelease;
            }
            if (e.NewValue is IMouseCaptureProxy)
            {
                (e.NewValue as IMouseCaptureProxy).Capture += OnCapture;
                (e.NewValue as IMouseCaptureProxy).Release += OnRelease;
            }
        }

        static void OnCapture(object sender, EventArgs e)
        {
            var behavior = sender as MouseCaptureBehavior;
            if (behavior != null)
                behavior.AssociatedObject.CaptureMouse();
        }

        static void OnRelease(object sender, EventArgs e)
        {
            var behavior = sender as MouseCaptureBehavior;
            if (behavior != null)
                behavior.AssociatedObject.ReleaseMouseCapture();
        }

        protected override void OnAttached()
        {
            base.OnAttached();
            this.AssociatedObject.PreviewMouseDown += OnMouseDown;
            this.AssociatedObject.PreviewMouseMove += OnMouseMove;
            this.AssociatedObject.PreviewMouseUp += OnMouseUp;
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();
            this.AssociatedObject.PreviewMouseDown -= OnMouseDown;
            this.AssociatedObject.PreviewMouseMove -= OnMouseMove;
            this.AssociatedObject.PreviewMouseUp -= OnMouseUp;
        }

        private void OnMouseDown(object sender, MouseButtonEventArgs e)
        {
            var proxy = GetProxy(this);
            if (proxy != null)
            {
                var pos = e.GetPosition(this.AssociatedObject);
                var args = new MouseCaptureArgs
                {
                    X = pos.X,
                    Y = pos.Y,
                    LeftButton = (e.LeftButton == MouseButtonState.Pressed),
                    RightButton = (e.RightButton == MouseButtonState.Pressed)
                };
                proxy.OnMouseDown(this, args);
            }
        }

        private void OnMouseMove(object sender, MouseEventArgs e)
        {
            var proxy = GetProxy(this);
            if (proxy != null)
            {
                var pos = e.GetPosition(this.AssociatedObject);
                var args = new MouseCaptureArgs
                {
                    X = pos.X,
                    Y = pos.Y,
                    LeftButton = (e.LeftButton == MouseButtonState.Pressed),
                    RightButton = (e.RightButton == MouseButtonState.Pressed)
                };
                proxy.OnMouseMove(this, args);
            }
        }

        private void OnMouseUp(object sender, MouseButtonEventArgs e)
        {
            var proxy = GetProxy(this);
            if (proxy != null)
            {
                var pos = e.GetPosition(this.AssociatedObject);
                var args = new MouseCaptureArgs
                {
                    X = pos.X,
                    Y = pos.Y,
                    LeftButton = (e.LeftButton == MouseButtonState.Pressed),
                    RightButton = (e.RightButton == MouseButtonState.Pressed)
                };
                proxy.OnMouseUp(this, args);
            }
        }
    }
}

在開始實作這個滑鼠操作行為前,一樣別忘了在前臺 XAML 上加入以下的命名空間

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"

這裡將 Behavior 附加在 Canvas 上做範例

<!-- Canvas must have a background, even if it's Transparent -->
<Canvas Background="White">
    <i:Interaction.Behaviors>
        <behaviors:MouseCaptureBehavior Proxy="{Binding}" />
    </i:Interaction.Behaviors>
</Canvas>

在 ViewModel 部分實作 ImouseCaptureProxy 介面的三個方法(OnMouseDown/OnMouseMove/OnMouseUp),並觸發 Behavior 時響應的 Capture/Release 事件;要注意的一點是兩個事件的發送者與三個方法的 Handler 應該是同一個!當不使用傳遞給 Capture/Release 的事件參數時,事件參數設定為 null

public class XXViewModel : ViewModelBase, IMouseCaptureProxy
{
    public event EventHandler Capture;
    public event EventHandler Release;

    public void OnMouseDown(object sender, MouseCaptureArgs e) {...}
    public void OnMouseMove(object sender, MouseCaptureArgs e) {...}
    public void OnMouseUp(object sender, MouseCaptureArgs e) {...}
}