[WPF] MVVM 軟體架構模式 - 透過 Behavior 獲取滑鼠座標 (附記 UIElement/FrameworkElement/Control 繼承關係與主要功能)

MVVM 軟體架構模式 - 透過 Behavior 獲取滑鼠座標

參考之前文章 [WPF] MVVM 軟體架構模式 - 透過 AttachedBehaviorCommand 偵測滑鼠狀態 改寫一下讓程式碼比較精簡的版本

這版本只要實作 Behavior 介面就好,不用再去自定義處理的介面,這次比較貪心直接套用泛型類型是 UIElement

順便記錄一下 UIElement/FrameworkElement/Control 的核心功能與繼承關係:(參考處)

  • UIElement  (Layout + Input + Focus + Events)
    • Layout behavior (parent/child relationship, measure/arrange passes)
    • Responding to user input (input events, command bindings)
    • Managing focus
    • Raising and responding to routed events
  • FrameworkElement adds
    • Alignment-related and Margin properties
    • Animation support
    • Data binding
    • Data templates
    • Styles
    • Defaults Focusable to false
  • Control adds
    • Control templates
    • Background, Foreground
    • Font-related properties
    • Border-related properties
    • Defaults Focusable to true

回到主題,直接上碼看這次主要的功能核心~XD

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

namespace MVVM
{
    class FrameworkElementMouseBehavior : Behavior<UIElement>
    {
        public static readonly DependencyProperty MouseXProperty
            = DependencyProperty.Register("MouseX",
                                          typeof(double),
                                          typeof(FrameworkElementMouseBehavior),
                                          new PropertyMetadata(default(double)));
        public static readonly DependencyProperty MouseYProperty
            = DependencyProperty.Register("MouseY",
                                          typeof(double),
                                          typeof(FrameworkElementMouseBehavior),
                                          new PropertyMetadata(default(double)));

        public double MouseX
        {
            get { return (double)GetValue(MouseXProperty); }
            set { SetValue(MouseXProperty, value); }
        }
        public double MouseY
        {
            get { return (double)GetValue(MouseYProperty); }
            set { SetValue(MouseYProperty, value); }
        }

        protected override void OnAttached()
        {
            base.OnAttached();

            this.AssociatedObject.PreviewMouseMove += AssociatedObject_OnMouseMove;
        }
        protected override void OnDetaching()
        {
            base.OnDetaching();

            this.AssociatedObject.PreviewMouseMove -= AssociatedObject_OnMouseMove;
        }

        private void AssociatedObject_OnMouseMove(object sender, MouseEventArgs e)
        {
            var pos = e.GetPosition(AssociatedObject);
            MouseX = pos.X;
            MouseY = pos.Y;
        }
    }
}

同樣以下開始使用時作範例,在開始前要在前臺 XAML 加入以下的命名空間

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

如之前文章一樣附加在 Canvas 上,但作用是取得滑鼠在其中 Image 上的座標

這次還要在 ViewModel 增加兩個屬性來承接 Behavior 獲得的座標 X,Y

private double _ImageMousePoxX = 0.0;
public double ImageMousePoxX
{
    get { return _ImageMousePoxX; }
    set
    {
        _ImageMousePoxX = value;
        OnPropertyChanged();
    }
}
private double _ImageMousePoxY = 0.0;
public double ImageMousePoxY
{
    get { return _ImageMousePoxY; }
    set
    {
        _ImageMousePoxY = value;
        OnPropertyChanged();
    }
}

對了!這裡要參考一下這一篇,才會知道為什麼中間有那個 root

<!-- Canvas must have a background, even if it's Transparent -->
<Canvas Background="White">
    <i:Interaction.Behaviors>
        <root:FrameworkElementMouseBehavior MouseX="{Binding ImageMousePoxX, Mode=OneWayToSource}"
                                            MouseY="{Binding ImageMousePoxY, Mode=OneWayToSource}"/>
    </i:Interaction.Behaviors>
    <Image Source="Lena.png" Stretch="Fill"/>
</Canvas>

另外要注意是這兩個屬性綁定模式是 OneWayToSource,單向 UI 數值給定 Property;因為我們是要得知滑鼠座標而不是去改變它!

這樣一陣操作下來,感覺比先前的方法寫的程式碼少了些,只是有些使用限制不一樣,就自行斟酌使用囉~

PS. 中間有一些技術點要往前回顧一下,這邊就省略了(ex: 設定 DataContext)