WPFで図形をマウスに追従させる


作るもの

作り方

適当な図形を作る

<Rectangle Stroke="Green"
           StrokeThickness="5"
           Width="50"
           Height="50">
</Rectangle>

RenderTransformにTranslateTransformオブジェクトをセット

WPFではコントロールの座標を指定することはできないので、UIElementクラスにあるRenderTransformプロパティに、図形の変形情報を表すTranslateTransformオブジェクトを格納します。

<Rectangle Stroke="Green"
           StrokeThickness="5"
           Width="50"
           Height="50">
    <Rectangle.RenderTransform>
        <TranslateTransform X="{Binding X.Value}"
                            Y="{Binding Y.Value}"/>
    </Rectangle.RenderTransform>
</Rectangle>

ViewModelを作ってバインド

TranslateTransformクラスのXYに移動量を入れます。
バインドしたいのですごく簡単なViewModelを作ります。

class MainWindowViewModel
{
    public ReactivePropertySlim<double> X { get; } = new ReactivePropertySlim<double>();
    public ReactivePropertySlim<double> Y { get; } = new ReactivePropertySlim<double>();
}

バインドします。

<Rectangle Stroke="Green"
           StrokeThickness="5"
           Width="50"
           Height="50">
    <Rectangle.RenderTransform>
        <TransformGroup>
            <TranslateTransform X="{Binding X.Value}"
                                Y="{Binding Y.Value}"/>
        </TransformGroup>
    </Rectangle.RenderTransform>
</Rectangle>

ViewModelのX, Yをいじる

あとはViewModelのX,Yをいじれば図形が動きます。

コードビハインドを汚す場合

コードビハインドを使ってもいいなら簡単です。
WindowのMouseMoveイベントで座標を取得してVMに直接代入すればいいです。

public partial class MainWindow : Window
{
    public MainWindow() {
        InitializeComponent();
    }

    private MainWindowViewModel VM => (MainWindowViewModel)DataContext;

    private void Window_MouseMove(object sender, MouseEventArgs e) {
        var mousepos = e.GetPosition(this);
        VM.X.Value = mousepos.X;
        VM.Y.Value = mousepos.Y;
    }
}
<Window x:Class="MouseTrackShape.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:MouseTrackShape"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800"
        MouseMove="Window_MouseMove">
    <Window.DataContext>
        <local:MainWindowViewModel/>
    </Window.DataContext>
    <Grid>
        <Rectangle Stroke="Green"
                   StrokeThickness="5"
                   Width="50"
                   Height="50">
            <Rectangle.RenderTransform>
                <TransformGroup>
                    <TranslateTransform X="{Binding X.Value}"
                                        Y="{Binding Y.Value}"/>
                </TransformGroup>
            </Rectangle.RenderTransform>
        </Rectangle>
    </Grid>
</Window>

しかし、実はこのままだとおかじな動きになります。

これは、TranslateTransformが初期位置からの移動量を指定するオブジェクトだからですね。
これを回避するために図形の初期位置を左上にしておきます。

<!--略-->
        <Rectangle Stroke="Green"
                   StrokeThickness="5"
                   Width="50"
                   Height="50"
                   HorizontalAlignment="Left"
                   VerticalAlignment="Top">
<!--略-->

ちゃんとマウスに追従するようになりました。

コードビハインドを汚さない場合

コードビハインドを汚さずにやるなら少し大変です。
ビヘイビアを使うので、System.Windows.Interactivityを持っていない人はnugetからインストールしてください。

次のようなビヘイビアを作成します。


public class MouseBehaviour : System.Windows.Interactivity.Behavior<FrameworkElement>
{
    public static readonly DependencyProperty MouseYProperty = DependencyProperty.Register(
        "MouseY", typeof(double), typeof(MouseBehaviour), new PropertyMetadata(default(double)));

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

    public static readonly DependencyProperty MouseXProperty = DependencyProperty.Register(
        "MouseX", typeof(double), typeof(MouseBehaviour), new PropertyMetadata(default(double)));

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

    protected override void OnAttached() {
        AssociatedObject.MouseMove += AssociatedObjectOnMouseMove;
    }

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

    protected override void OnDetaching() {
        AssociatedObject.MouseMove -= AssociatedObjectOnMouseMove;
    }
}

このコードは以下サイトから拝借しました。
https://stackoverflow.com/questions/30047415/how-do-i-get-mouse-positions-in-my-view-model

このビヘイビアは割り当てられたコントロールのMouseMoveイベントを購読して、そのXY座標をMouseX, MouseY依存関係プロパティに格納するものです。

そしてWindowにこのビヘイビアを割り当てて、ViewModelのX, YとビヘイビアのMouseX, MouseYとバインドすればOKですね。


<Window x:Class="MouseTrackShape.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:MouseTrackShape"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <local:MouseEventArgsToPositionConverter x:Key="MouseEventArgsToPositionConverter"/>
    </Window.Resources>
    <Window.DataContext>
        <local:MainWindowViewModel/>
    </Window.DataContext>
    <Grid>
        <Rectangle Stroke="Green"
                   StrokeThickness="5"
                   Width="50"
                   Height="50"
                   HorizontalAlignment="Left"
                   VerticalAlignment="Top">
            <Rectangle.RenderTransform>
                <TranslateTransform X="{Binding X.Value}"
                                    Y="{Binding Y.Value}"/>
            </Rectangle.RenderTransform>
        </Rectangle>
    </Grid>
    <i:Interaction.Behaviors>
        <local:MouseBehaviour MouseX="{Binding X.Value, Mode=OneWayToSource}" MouseY="{Binding Y.Value, Mode=OneWayToSource}" />
    </i:Interaction.Behaviors>
</Window>

さいごに

以上です。

RenderTransformプロパティはUIElementクラスにあるので、図形だけじゃなくて例えばボタンなんかをマウスに追従させることもできますよ。
使いみちは謎ですが…