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

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

こちらを参考に、マウスのクリックイベントで直線の始点と終点を拾って、動的に直線を引く方法を考えてみました。
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回目以降が終点として直線が引けました。

コメント