【WPF学習中】Pathで動的に直線を引く

コンピュータ
調べたところWPFで直線を引こうと思ったらXAMLでPathで静的に直線を引くことが出来るようです。
【WPF】Pathで線形を書く(直線のみ) - Qiita
#目的XAMLで適当な線形を書きたい。なのでSystem.Windows.Shapes名前空間のPathを使って書いてみる。#環境・IDE :Visual Studio 2017#方法事…

こちらを参考に、マウスのクリックイベントで直線の始点と終点を拾って、動的に直線を引く方法を考えてみました。
MVVMぽくプログラミングしようとするとPathオブジェクトを動的に生成しようとした段階で、どうすれば良いか詰んでしまいました。
自分のスキルが足りないせいだとは思いますが、とりあえずXAMLで始点や終点の情報が空のPathを用意し、C#のコード側で始点や終点の情報を追加するようにしてみました。
また、クリックした座標を拾う方法は以下の記事のコードを使わせてもらいました。
MVVM でイベント引数の値を ViewModel のコマンドに渡す方法 - かずきのBlog@hatena
こちらを見て、そういえばさらっと書いてるだけだったなぁと思ったので…。 elf-mission.net イベント引数を ViewModel で使いたい マウス系イベントや選択系イベントは、イベント引数にしか入ってない値とかもあったりして使い...

プロジェクトの作成

PowerShellで実行。要dotnet.exe

mkdir プロジェクト名
cd プロジェクト名
dotnet new wpf
dotnet add package Microsoft.Xaml.Behaviors.Wpf
dotnet add package ReactiveProperty.WPF
code .

ソースコード

ファイル名:MainWindow.xaml

<Window x:Class="DrawLine.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:DrawLine"
        mc:Ignorable="d"
        xmlns:i="clr-namespace:Microsoft.Xaml.Behaviors;assembly=Microsoft.Xaml.Behaviors"
        xmlns:interactivity="clr-namespace:Reactive.Bindings.Interactivity;assembly=ReactiveProperty.WPF"
        Title="MainWindow" Height="450" Width="800">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="Closed">
            <interactivity:EventToReactiveCommand Command="{Binding WindowClosedCommand}" />
        </i:EventTrigger>
        <i:EventTrigger EventName="MouseDown">
            <interactivity:EventToReactiveCommand Command="{Binding MouseDownCommand}">
                <local:MouseDownToMousePositionConverter />
            </interactivity:EventToReactiveCommand>
        </i:EventTrigger>
    </i:Interaction.Triggers>
    <Window.DataContext>
        <local:MainWindowViewModel />
    </Window.DataContext>
    <Grid>
        <Path Stroke="Red" StrokeThickness="1" Data="{Binding Points.Value}"/>
    </Grid>
</Window>

ファイル名:MainWindowViewModel.cs

using Reactive.Bindings;
using Reactive.Bindings.Extensions;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;


using System;
using System.Reactive.Disposables;

namespace DrawLine
{
    public class MainWindowViewModel : INotifyPropertyChanged, IDisposable
    {
        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged(string name) =>
            PropertyChanged(this, new PropertyChangedEventArgs(name));
        private CompositeDisposable Disposable { get; } = new ();
        public ReactiveCommand<MousePosition> MouseDownCommand { get; }

        public ReadOnlyReactivePropertySlim<string> Message { get; }
        public void Dispose() => Disposable.Dispose();

        public ReactiveCommand<EventArgs> WindowClosedCommand { get; }
        public ReactiveProperty<string> Points { get; private set; }

        public MainWindowViewModel()
        {
            PropertyChanged += (o, e) => {};
            WindowClosedCommand = new ReactiveCommand<EventArgs>()
                .WithSubscribe(e =>this.Dispose()).AddTo(Disposable);
            
            LinePair lp = new();
            MouseDownCommand = new ReactiveCommand<MousePosition>()
                .WithSubscribe(e => 
                {
                    if (lp.Start.X == -1)
                    {
                        lp.SetStart(e);
                    }
                    else
                    {
                        lp.SetEnd(e);
                        Points.Value = lp.ToStr();
                    }
                }).AddTo(Disposable);
            
            Points = new ReactiveProperty<string>("").AddTo(Disposable);

        }
    }//class
}//ns

ファイル名:MousePosition.cs

namespace DrawLine
{
    public class MousePosition
    {
        public double X { get; set; } = -1;
        public double Y { get; set; } = -1;
    }
}

ファイル名:MouseDownToMousePositionConverter.cs

using Reactive.Bindings.Interactivity;
using System;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Input;

namespace DrawLine
{
    public class MouseDownToMousePositionConverter : ReactiveConverter<MouseButtonEventArgs, MousePosition>
    {
        protected override IObservable<MousePosition> OnConvert(IObservable<MouseButtonEventArgs> source) => source
            .Where(x => x.LeftButton == MouseButtonState.Pressed)
            .Select(x => x.GetPosition((IInputElement)AssociateObject))
            .Select(x => new MousePosition
            {
                X = x.X,
                Y = x.Y,
            });
    }
}//ns

ファイル名:LinePair.cs

namespace DrawLine
{
    class LinePair
    {
        public MousePosition Start {get; set;} = new MousePosition();
        public MousePosition End {get; set;} = new MousePosition();

        public void SetStart(MousePosition x)
        {
            Start.X = x.X;
            Start.Y = x.Y;
        }
        public void SetEnd(MousePosition x)
        {
            End.X = x.X;
            End.Y = x.Y;
        }
        public string ToStr()
        {
            return string.Format("M{0},{1} L{2},{3} ", Start.X, Start.Y, End.X, End.Y);
        }
    }
}


マウスクリックの1回目で直線の始点、2回目以降が終点として直線が引けました。

コメント