【WPF】Behaviorの実装方法【MVVM】


Behaviorを実装してみる

WPFのBehaviorってなんかやること多いしXaml側には<i:Interactivity>~... みたいなよくわからん名前空間がネストしてあるしで何となく敬遠していたのでちゃんと調べました。

あとMVVM的に書きたいけどマウスイベントトリガーでアクションさせたい時どうすればいいのかわからなかったので
チートシート的にまとめました。
世はコードビハインドの記事で溢れかえっている🤕

やりたいこと - 左クリックした時に座標を取得

  1. Viewでのイベントを検知してViewModelで何かしたい(今回はCanvas上での左クリック)
  2. Viewのコントロールがもつ値をViewModelに渡したい(今回は左クリックした座標)

自分のチートシート用に一枚にまとめました。

こうなる↓

コード

CanvasDrawing.xaml
<UserControl x:Class="DrawRectangle.Views.CanvasDrawing"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:DrawRectangle.ViewModels" 
             xmlns:i="http://schemas.microsoft.com/xaml/behaviors" 
             xmlns:behaviors="clr-namespace:DrawRectangle.Behaviors"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">

    <UserControl.DataContext>
       <local:CanvasDrawingViewModel/>
    </UserControl.DataContext>

    <Grid>
        <TextBlock Name="Text1" 
                   HorizontalAlignment="Left" 
                   Margin="10,10,0,0" 
                   TextWrapping="Wrap"
                   Text="{Binding Text.Value}"
                   VerticalAlignment="Top"
                   Width="200"/>

        <Canvas Name="Canvas1"
                Background="#01010101" 
                Margin="0,37,0,0" >
                <i:Interaction.Behaviors>
                    <behaviors:MouseButtonBehavior MouseLeftButtonClicked="{Binding ClickLeftCommand}"/>
                </i:Interaction.Behaviors>
        </Canvas>
    </Grid>
</UserControl>

CanvasDrawingViewModel.cs
using Reactive.Bindings;
using System.Windows;

namespace DrawRectangle.ViewModels
{
    public class CanvasDrawingViewModel
    {
        public ReactivePropertySlim<string> Text { get; } = new ReactivePropertySlim<string>("クリックされていないな...");

        public ReactiveCommand<Point> ClickLeftCommand { get; } = new ReactiveCommand<Point>();

        public CanvasDrawingViewModel()
        {
            ClickLeftCommand.WithSubscribe(p => OnClickLeft(p));
        }

        private void OnClickLeft(Point p)
        {

            Text.Value = $"左クリックされたよ! X: {p.X} Y: {p.Y}";
        }
    }
}
MouseButtonBehavior.cs
using System.Windows;
using System.Windows.Input;
using Microsoft.Xaml.Behaviors;

namespace DrawRectangle.Behaviors
{
    public class MouseButtonBehavior : Behavior<FrameworkElement>
    {
        public static readonly DependencyProperty MouseLeftButtonClickedProperty =
            DependencyProperty.Register(
                nameof(MouseLeftButtonClicked),
                typeof(ICommand),
                typeof(MouseButtonBehavior),
                new FrameworkPropertyMetadata());

        public ICommand MouseLeftButtonClicked
        {
            get => (ICommand) GetValue(MouseLeftButtonClickedProperty);
            set => SetValue(MouseLeftButtonClickedProperty, value);
        }

        protected override void OnAttached()
        {
            AssociatedObject.MouseLeftButtonDown += AssociatedObjectOnMouseLeftButtonClicked;
        }

        private void AssociatedObjectOnMouseLeftButtonClicked(object sender, MouseEventArgs e)
        {
            AssociatedObject.CaptureMouse();
            var prevPoint = e.GetPosition(AssociatedObject);
            var point = new Point(prevPoint.X, prevPoint.Y);

            if (MouseLeftButtonClicked == null || !MouseLeftButtonClicked.CanExecute(point)) return;
            MouseLeftButtonClicked.Execute(point);

        }
    }
}

おまけ

ネットでBehaviorについて検索しているとSystem.Window.InteractivityMicrosoft.Xaml.Behaviorという名前空間が出てきてどう違うんだろうかと思っていましたが、どうもInteractivityをOSS化したのがXaml.Behaviorみたいです。