[C#/xaml] System.Windows.Pointのリストを使って線を引く


もくじ
https://qiita.com/tera1707/items/4fda73d86eded283ec4f

やりたいこと

System.Windows.Pointクラスと、System.Windows.Media.PointCollectionクラスを使って、xamlで作った画面に線を引きたい。
System.Drawing.PointクラスではなくSystem.Windows.Pointクラス。

流れ

  • 準備
    • System.Windows.Pointを持たせるクラスに(以下、Pointクラスと言う)
      InotifyPropertyChangedを実装する
    • PointPointCollectionに変換してくれるコンバータをつくる
  • 線を書く
    • System.Windows.PointのObservableCollectionをつくる
    • 好きなタイミングで、線を引きたい座標を入れたPointをObservableCollectionにAddする
    • OnPropertyChanged(PropertyChanged)を実行する
    • →PointのObservableCollectionにあった線が描画される

前提

.NET core 3.1で実験。

サンプル

画面

MainWindow.xaml
<Window x:Class="WpfApp57.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:WpfApp57"
        xmlns:conv="clr-namespace:Converter"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800"
        Name="root">
    <Window.Resources>
        <conv:ListPointToPointCollectionConverter x:Key="ListPointToPointCollectionConverter" />
    </Window.Resources>
    <Grid>
        <Canvas>
            <!-- 線 -->
            <Polyline Name="MyLine" Stroke="Red" StrokeThickness="1" 
                      Points="{Binding Points, ElementName=root, Converter={StaticResource ListPointToPointCollectionConverter}}"/>
            <!-- ボタン -->
            <Button Content="Button" Height="100" Canvas.Left="10" Canvas.Top="324" Width="100" Click="Button_Click"/>
        </Canvas>
    </Grid>
</Window>
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Windows;
using System.Collections.ObjectModel;

namespace WpfApp57
{
    /// <summary>
    /// ボタンをおしたら、サインカーブを書く
    /// </summary>
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(string name = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }

        // ------------------------------------------------

        public ObservableCollection<Point> Points
        {
            get { return _points; }
            set { _points = value; OnPropertyChanged(nameof(Points)); }
        }
        private ObservableCollection<Point> _points = new ObservableCollection<Point>();

        public MainWindow()
        {
            InitializeComponent();

            // Point追加時に自動でOnPropertyChangedしてくれるようにする
            Points.CollectionChanged += ((sender, e) =>
            {
                var oc = sender as ObservableCollection<Point>;
                OnPropertyChanged(nameof(Points));
                Debug.WriteLine("count = " + oc.Count);
            });
        }

        private double y = 150.0;

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            var _ = Task.Run(() =>
            {
                this.Dispatcher.Invoke(new Action(async () =>
                {
                    for (int i = 0; i < 100; i++)
                    {
                        Points.Add(new Point((double)i, y + 150.0 * Math.Sin((double)i / (Math.PI))));
                        OnPropertyChanged(nameof(Points));
                        await Task.Delay(30);
                    }
                }));
            });
        }
    }
}

コンバータ

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media;
using System.Collections.ObjectModel;

namespace Converter
{
    public class ListPointToPointCollectionConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var points = (ObservableCollection<Point>)value;
            return points != null ? new PointCollection(points) : null;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

実行イメージ

備考

試してないが、コンバータを使わなくても、コード内でPointをPointCollectionにしてもよいと思う。

コード