ポリラインやポリゴンを描画する

任意の頂点データ(座標コレクション)を使って、ポリラインやポリゴンを描画を描画します。

これができるということは、画面に、三角関数の波形を表示したり、音声データの波形を表示したりすることが可能になります。

Fig. 1 ポリラインを使って波形を描画する

私たちが目指すのは、画像の任意断面における濃度プロファイル波形を画像にオーバレイ描画することです。

まずはそのイントロとして、マウスでクリックした座標を頂点とし、それらをむすぶポリラインやポリゴンを描画する方法を紹介します。

Fig. 2 ポリラインの場合
Fig. 3 ポリゴンの場合

xaml でポリライン、または、ポリゴン、描画アイテムを準備します。MainWindow.xaml.cs においてマウスの動きから、その頂点配列作成して、ポリラインまたはポリゴンと関連付けてやります。それだけで多角形が描画でき、とても簡単です。

MainWindow.xaml の 47行目、または、50行目をコメントアウトして動作確認してください。
47行目を活かすとポリラインです。
50行目を活かすとポリゴンです。
( 47行目と50行目を同時に活かすと意味が分からなくなります )

<Window x:Class="aaa.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:aaa"
        mc:Ignorable="d"
        Title="MainWindow" Height="640" Width="640">

    <DockPanel>

        <Menu DockPanel.Dock="Top">

            <MenuItem x:Name="menuApplication" Header="Application">
                <MenuItem x:Name="menuApplicationQuit" Header="Quit" Click="menuApplicationQuit_Click"/>
            </MenuItem>

            <MenuItem x:Name="menuDebug" Header="Debug">
                <MenuItem x:Name="menuDebugExec000" Header="Exec000" Click="menuDebugExec000_Click" />
            </MenuItem>

        </Menu>

        <ToolBarTray DockPanel.Dock="Top">
            <ToolBar>
                <Button x:Name="tbnDebugExec000" Content="Clear" Width ="48" Height="48" Click="menuDebugExec000_Click"></Button>
            </ToolBar>
        </ToolBarTray>

        <StatusBar DockPanel.Dock="Bottom" Height="30">
            <Label Name="stbLabel000"/>
            <Separator/>
            <Label Name="stbLabel001"/>
            <Separator/>
            <Label Name="stbLabel002"/>
            <Separator/>
            <Label Name="stbLabel003"/>
        </StatusBar>

        <Border BorderBrush="DarkViolet" BorderThickness="1" >
            <ScrollViewer Name="scv000" Background="DarkViolet" ScrollViewer.HorizontalScrollBarVisibility="Visible" ScrollViewer.VerticalScrollBarVisibility="Visible">
                <Grid x:Name="grid000" MouseDown="OnMouseDown">

                    <Canvas x:Name="canvas000" Background="Transparent">

                        <!-- ポリライン -->
                        <Polyline x:Name="poly" Stroke="White" StrokeThickness="3"/>

                        <!-- ポリゴン -->
                        <!--<Polygon x:Name="poly" Stroke="White" StrokeThickness="3"/>-->

                        <Line x:Name="cross_hair_horz" Stroke="Lime" StrokeThickness="3"/>
                        <Line x:Name="cross_hair_vert" Stroke="Lime" StrokeThickness="3"/>

                    </Canvas>
                    
                </Grid>
            </ScrollViewer>
        </Border>

    </DockPanel>


</Window>

次に MainWindow.xaml.cs について解説します。

38~70行目が OnRender() が描画のためのコードです。
ここ以外で xaml で定義しておいた描画アイテム(ポリラインまたはポリゴン)の位置や色などの描画指令を与えてはいけません。

72~96行目がマウスをクリックしたときに実行されるコードです。
ここでは、クリックした位置の座標が配列コレクションの末尾に追加するだけということに注目してください。これは描画のコードではありません。

あくまでも描画自体は OnRender() の中の58行目

poly.Points = ThePoints;

で実施されます。

92行目の this.InvalidateVisual() が、座標データを追加した直後に再描画のための OnRender() を明示しています。

なぜ OnRender() を直接コールせずに、InvalidateVisual() をコールするというまどろっこしいことをするのでしょうか。

OnRender() は描画のためのコードです。ただし、Windows はユーザが望んでいる以外のコードも裏では実施しています。例えばウインドウに表示されているマウスポインタの矢印などは、ユーザが「マウスの位置が変わったので違う位置に矢印のグラフィックを描画をしてくれ」と明示しているわけではないですが当然のように描画されています。

このことを説明するのは難しいのですが、かなり簡単に説明しますと、ユーザが描画するプログラムコードというのは Windows にとって最優先ではありません。どちらかというと後回しにされるほうが多いのです。ただでさえ忙しいのに OnRender() を乱発しないでほしい、本当に必要なときに最低限の再描画命令を要求してほしい、というのが Windows の本音です。

このために用意されているのが InvalidateVisual() というメソッドです。これで「Windowsシステムの描画余力があるときに再描画を実施してくださいね」とお願いベースで再描画命令を出すのです。

ためしに InvalidateVisual() をコメントアウトするとこの行の効き目がわかると思いますので、ぜひ試してみてください。

InvalidateVisual() たった一行ですが、バグの素になりますので忘れないようにしてください。( 私は画面にコーディングの結果があらわれないとき最高にアセります )

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Media;

namespace aaa
{

	public partial class MainWindow : Window
	{

		int TheX;
		int TheY;

		// 頂点座標を記録するコレクション.
		PointCollection ThePoints = new PointCollection();

		public MainWindow()
		{

			// これはデフォルトのコード.
			InitializeComponent();

			// ウインドウのタイトル.
			this.Title = "GazoYaro";

		}

		protected override void OnRender( DrawingContext dc )
		{

			// キャンバスの幅高サイズ.
			double cvs_aw = canvas000.ActualWidth;
			double cvs_ah = canvas000.ActualHeight;

			// 垂直線を引く.
			cross_hair_vert.X1 = TheX;
			cross_hair_vert.Y1 = 0;
			cross_hair_vert.X2 = TheX;
			cross_hair_vert.Y2 = cvs_ah;

			// 水平線を引く.
			cross_hair_horz.X1 = 0;
			cross_hair_horz.Y1 = TheY;
			cross_hair_horz.X2 = cvs_aw;
			cross_hair_horz.Y2 = TheY;

			// 記録した座標配列にしたがってポリラインを描画する.
			poly.Points = ThePoints;

			// ステータスバーのラベルに座標を表示する.
			String str000 = String.Format( "number is {0}", ThePoints.Count );
			String str001 = String.Format( "Cavnvas ActualWH {0}*{1}", cvs_aw, cvs_ah );
			String str002 = "";
			String str003 = "";
			stbLabel000.Content = str000;
			stbLabel001.Content = str001;
			stbLabel002.Content = str002;
			stbLabel003.Content = str003;

		}

		private void OnMouseDown(object sender, MouseEventArgs e)
		{

			MouseButtonState mbs = MouseButtonState.Pressed;

			// 左ボタンがおされたかどうか.
			if (( e.LeftButton & mbs ) == mbs )
			{

				// とりあえず座標を取得する.
				Point pnt = e.GetPosition( grid000 );

				// 座標コレクションに追加する.
				ThePoints.Add( pnt );

				// 現在クリックした座標を取得する.
				TheX = (int)( Math.Round( pnt.X ));
				TheY = (int)( Math.Round( pnt.Y ));

				// 再描画をうながす.
				this.InvalidateVisual();

			}

		}

		private void menuDebugExec000_Click( object sender, RoutedEventArgs e )
		{

			if ( ThePoints != null )
			{

				// 座標のコレクションをクリアする.
				ThePoints.Clear();

				// 再描画をうながす.
				this.InvalidateVisual();

			}

		}

		private void menuApplicationQuit_Click( object sender, RoutedEventArgs e )
		{

			// ウインドウを閉じてアプリケーションを終了する.
			this.Close();

		}

	}
}

重ねての注意点です.
MainWindow.xaml の 47行目、または、50行目をコメントアウトして動作確認してください。
47行目を活かすとポリラインです。
50行目を活かすとポリゴンです。

上記のコードを実行すると、マウスでクリックした座標を頂点として図形が描画されていきます。どちらも同じく頂点の数は10です。

しかし、多角形が、閉じない or 閉じる、の違いがあります。前者がポリラインで、後者がポリゴンです。

Fig. 4 ポリラインの場合
Fig. 5 ポリゴンの場合

サンプルソースを用意しました。上記のコードのコピーペーストでうまく動かない場合にご利用ください。