図形コントロール

Last Updated 2011/09/21


WPF 標準の図形コントロールは種類が少なく不満です。そこで、自作してみました。このページの中では使用したクラスおよび個々のプロパティやメソッドの説明は省略します。詳しくは、別途公開している WPFClass を参考にしてください。


私はグラフィックス編集ソフトの作成を計画していますが、WPF 標準の図形コントロールの中にないコントロールを作ってみました。何を作ったかは下図を見てのとおりです。また、図形コントロールではありませんが、テキストを回転して表示するコントロールと縦書き表示するコントロールもあわせて収録しました。

DrawTool


円弧コントロール(Arc クラス)

円弧は ArcSegment クラスを使うことで形状を定義できます。しかし、このコントロールでは以下にリストアップする制限を設けました。

というのは、Size プロパティや RotationAngle プロパティを使うと、思惑通りの円弧の形状を定義することが難しく、グラフィックスソフトとしては実用性がないと考えたからです。

上図を作成したときの関連する XAML コードは以下のとおりです。名前空間 ctrl の追加を忘れないように。

<Window x:Class="ArcControlTest.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:ctrl="clr-namespace:emanual.Wpf.Controls"
    Title="Arc コントロール" Height="300" Width="300">
  <Canvas Name="canvas" Background="GhostWhite">
    <ctrl:Arc Radius="100" StartPoint="20,40" EndPoint="160,40" Stroke="Blue" StrokeThickness="2" />
    <ctrl:Arc Radius="50" StartPoint="90,100" EndPoint="140,150" Stroke="Green" StrokeThickness="5" />
    <ctrl:Arc Radius="50" StartPoint="140,150" EndPoint="90,200" Stroke="Fuchsia" StrokeThickness="5" />
    <ctrl:Arc Radius="50" StartPoint="90,200" EndPoint="40,150" Stroke="Blue" StrokeThickness="5" />
    <ctrl:Arc Radius="50" StartPoint="40,150" EndPoint="90,100" Stroke="Red" StrokeThickness="5" />

  ....
  </Canvas>
</Window>

Arc クラスを定義する C# コード

コード全体をファイル(たとえば、Arc.cs)に保存し、使用する場合はプロジェクトに追加してください。

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;

namespace emanual.Wpf.Controls
{
  public class Arc : Shape
  {
    // 依存関係プロパティの宣言
    public static readonly DependencyProperty RadiusProperty;         // 円の半径
    public static readonly DependencyProperty StartPointProperty;     // 図形の始点
    public static readonly DependencyProperty EndPointProperty;       // 同、終点
    public static readonly DependencyProperty IsLargeArcProperty;     // 円弧が 180 度を超えるとき true
    public static readonly DependencyProperty SweepDirectionProperty; // 円弧を描画する方向

    // 境界線の色は Stroke プロパティ
    // 同、幅は StrokeThickness プロパティで設定する

    // プロパティ
    public double Radius
    {
      get { return (double)base.GetValue(RadiusProperty); }
      set { base.SetValue(RadiusProperty, value); }
    }

    public Point StartPoint
    {
      get { return (Point)base.GetValue(StartPointProperty); }
      set { base.SetValue(StartPointProperty, value); }
    }

    public Point EndPoint
    {
      get { return (Point)base.GetValue(EndPointProperty); }
      set { base.SetValue(EndPointProperty, value); }
    }

    public bool IsLargeArc
    {
      get { return (bool)base.GetValue(IsLargeArcProperty); }
      set { base.SetValue(IsLargeArcProperty, value); }
    }

    public SweepDirection SweepDirection
    {
      get { return (SweepDirection)base.GetValue(SweepDirectionProperty); }
      set { base.SetValue(SweepDirectionProperty, value); }
    }

    //-------------------------------------------------------------------------------------
    // 依存関係プロパティの登録
    static Arc()
    {
      RadiusProperty = DependencyProperty.Register("Radius", typeof(double), typeof(Arc),
          new FrameworkPropertyMetadata(20.0,
            FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure));
      StartPointProperty = DependencyProperty.Register("StartPoint", typeof(Point), typeof(Arc),
          new FrameworkPropertyMetadata(new Point(0, 0),
            FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure));
      EndPointProperty = DependencyProperty.Register("EndPoint", typeof(Point), typeof(Arc),
          new FrameworkPropertyMetadata(new Point(20, 0),
            FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure));
      IsLargeArcProperty = DependencyProperty.Register("IsLargeArc", typeof(bool), typeof(Arc),
          new FrameworkPropertyMetadata(false,
            FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure));
      SweepDirectionProperty = DependencyProperty.Register("SweepDirection", typeof(SweepDirection), typeof(Arc),
          new FrameworkPropertyMetadata(SweepDirection.Clockwise,
            FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure));
    }

    public Arc()
    {
    }

    protected override Geometry DefiningGeometry
    {
      get
      {
        PathFigure pathFigure = new PathFigure();

        pathFigure.StartPoint = this.StartPoint;

        ArcSegment segment = new ArcSegment();
        segment.Point = this.EndPoint;
        segment.Size = new Size(this.Radius, this.Radius);
        segment.RotationAngle = 0.0;
        segment.IsLargeArc = this.IsLargeArc;
        segment.SweepDirection = this.SweepDirection;
        segment.IsStroked = true;

        pathFigure.Segments.Add(segment);

        PathGeometry geometry = new PathGeometry();
        geometry.Figures.Add(pathFigure);

        return geometry;
      }
    }

  } // end of Arc class
} // end of namespace

バルーンコントロール(Balloon クラス)

吹き出しコントロールと呼ぶ場合もありますが、「吹き出し」に相当する英語は "Callout" です。なじみが薄い言葉ですので、バルーンコントロールとしました。

さて、このコントロールは ArrangeOverride と MeasureOverride のページで取り上げたカスタムコントロールです。このページを呼んでもらえば、コントロールを作成する過程が分かりやすいと思います。rrangeOverride と MeasureOverride の各メソッドのほかに、以下にリストアップするクラスを使っています。

上図を作成したときの関連する XAML コードは以下のとおりです。

  <ctrl:Balloon x:Name="balloon" Canvas.Left="200" Canvas.Top="30" Background="Beige" Width="160">
    <TextBlock>ベジエ曲線による自由曲線</TextBlock>
  </ctrl:Balloon>

Balloon クラスを定義する C# コード

コード全体をファイル(たとえば、Balloon.cs)に保存し、使用する場合はプロジェクトに追加してください。

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Media;
using System.Windows.Controls;
using System.Diagnostics;

namespace emanual.Wpf.Controls
{
  public class Balloon : System.Windows.Controls.ContentControl
  {
    private const double ARROWLENGTH = 20; // 吹き出し部の幅、高さ、左辺からの距離
    private const double MAXRADIUS = 10; // コーナー部の最大半径
    private double FCornerRadius; // 四隅の半径は一定値

    // note : BorderThickness プロパティは Thickness 型だが四辺で一定値に制限

    //-------------------------------------------------------------------------------------
    // プロパティ
    public double CornerRadius { get { return FCornerRadius; } set { FCornerRadius = value; }}

    //-------------------------------------------------------------------------------------
    static Balloon()
    {
      // フォーカスを与えないように
      UIElement.FocusableProperty.OverrideMetadata(typeof(Balloon), new FrameworkPropertyMetadata(false));
      Control.IsTabStopProperty.OverrideMetadata(typeof(Balloon), new FrameworkPropertyMetadata(false));
    }

    //-------------------------------------------------------------------------------------
    // コンストラクタ
    public Balloon()
    {
      this.Width = 100; // デフォルトのサイズ
      this.Height = 60;

      this.Background = Brushes.GhostWhite;
      this.BorderBrush = Brushes.Black;
      this.BorderThickness = new Thickness(1);
      FCornerRadius = 6;
    }

    //-------------------------------------------------------------------------------------
    // このコントロールはサイズの指定がない状態で使う可能性はないので、このメソッドのオーバーライドは不要
    //protected override Size MeasureOverride(Size constraint)
    //{
    //  return base.MeasureOverride(constraint);
    //}

    //-------------------------------------------------------------------------------------
    // 子要素を矩形部に配置する
    protected override Size ArrangeOverride(Size arrangeBounds)
    {
      Size size = new Size(this.Width - MAXRADIUS, this.Height - ARROWLENGTH - MAXRADIUS);
      UIElement element = null;

      if (this.VisualChildrenCount > 0)
      {
        element = this.GetVisualChild(0) as UIElement;
      }

      if (element != null)
      {
        element.Arrange(new Rect(MAXRADIUS / 2, MAXRADIUS / 2, size.Width, size.Height));
      }

      return arrangeBounds;
    }

    //-------------------------------------------------------------------------------------
    protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
    {
      // BorderThickness プロパティは Thickness 型だが四辺で一定に制限しているので、
      // Left に 0 を指定したときは 1 にする
      if (e.Property == BorderThicknessProperty)
      {
        Thickness t = (Thickness)e.OldValue;
        if (t != null)
        {
          if (((Thickness)e.NewValue).Left != 0)
            this.BorderThickness = new Thickness(((Thickness)e.NewValue).Left);
          else
            this.BorderThickness = new Thickness(1);
        }
      }

      this.InvalidateVisual(); // これは必要ないはずだが、こうしないと一部のプロパティの変化に対して再描画しないため
      base.OnPropertyChanged(e);
    }

    //-------------------------------------------------------------------------------------
    // コントロールの形状を定義し、描画する
    protected override void OnRender(System.Windows.Media.DrawingContext context)
    {
      // 上辺の直線
      LineSegment line1 = new LineSegment(new Point(this.Width - FCornerRadius, 0), true);

      // 右上のコーナー
      ArcSegment arc1 = new ArcSegment();
      arc1.Size = new Size(FCornerRadius * 2, FCornerRadius * 2);
      arc1.RotationAngle = 90;
      arc1.IsLargeArc = false;
      arc1.SweepDirection = SweepDirection.Clockwise;
      arc1.Point = new Point(this.Width, FCornerRadius);

      // 右辺の直線
      LineSegment line2 = new LineSegment(new Point(this.Width, this.Height - ARROWLENGTH - FCornerRadius), true);

      // 右下のコーナー
      ArcSegment arc2 = new ArcSegment();
      arc2.Size = new Size(FCornerRadius * 2, FCornerRadius * 2);
      arc2.RotationAngle = 90;
      arc2.IsLargeArc = false;
      arc2.SweepDirection = SweepDirection.Clockwise;
      arc2.Point = new Point(this.Width - FCornerRadius, this.Height - ARROWLENGTH);

      // 下辺の直線
      LineSegment line3 = new LineSegment(new Point(ARROWLENGTH * 2, this.Height - ARROWLENGTH), true);

      // 吹き出し部の右辺
      LineSegment line4 = new LineSegment(new Point(ARROWLENGTH, this.Height), true);

      // 吹き出し部の左辺
      LineSegment line5 = new LineSegment(new Point(ARROWLENGTH, this.Height - ARROWLENGTH), true);

      // 下辺の左下コーナー手前の直線
      LineSegment line6 = new LineSegment(new Point(FCornerRadius, this.Height - ARROWLENGTH), true);

      // 左下のコーナー
      ArcSegment arc3 = new ArcSegment();
      arc3.Size = new Size(FCornerRadius * 2, FCornerRadius * 2);
      arc3.RotationAngle = 90;
      arc3.IsLargeArc = false;
      arc3.SweepDirection = SweepDirection.Clockwise;
      arc3.Point = new Point(0, this.Height - ARROWLENGTH - FCornerRadius);

      // 左辺の直線
      LineSegment line7 = new LineSegment(new Point(0, FCornerRadius), true);

      // 左上のコーナー
      ArcSegment arc4 = new ArcSegment();
      arc4.Size = new Size(FCornerRadius * 2, FCornerRadius * 2);
      arc4.RotationAngle = 90;
      arc4.IsLargeArc = false;
      arc4.SweepDirection = SweepDirection.Clockwise;
      arc4.Point = new Point(FCornerRadius, 0);

      PathSegmentCollection segments = new PathSegmentCollection(11);
      segments.Add(line1);
      segments.Add(arc1);
      segments.Add(line2);
      segments.Add(arc2);
      segments.Add(line3);
      segments.Add(line4);
      segments.Add(line5);
      segments.Add(line6);
      segments.Add(arc3);
      segments.Add(line7);
      segments.Add(arc4);

      PathFigure pathFigure = new PathFigure();
      pathFigure.StartPoint = new Point(FCornerRadius, 0);
      pathFigure.Segments = segments;

      PathFigureCollection col = new PathFigureCollection();
      col.Add(pathFigure);

      PathGeometry geometry = new PathGeometry(col);

      Pen pen = new Pen(this.BorderBrush, this.BorderThickness.Left);

      context.DrawGeometry(this.Background, pen, geometry);
    }
  } // end of Balloon class
} // end of namespace

三角形コントロール(Triangle クラス)

上図を作成したときの関連する XAML コードは以下のとおりです。

  <ctrl:Triangle x:Name="triangle" Canvas.Left="200" Canvas.Top="120" Points="50,0 120,80 0,100"
                 Fill="RoyalBlue" Stroke="Red" StrokeThickness="2" Height="100" />

Triangle クラスを定義する C# コード

コード全体をファイル(たとえば、Triangle.cs)に保存し、使用する場合はプロジェクトに追加してください。

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;

namespace emanual.Wpf.Control
{
  public class Triangle : Shape
  {
    // 依存関係プロパティの宣言
    public static readonly DependencyProperty PointsProperty;

    // プロパティ
    // 三角形の各頂点の座標値
    public PointCollection Points
    {
      get { return (PointCollection)base.GetValue(PointsProperty); }
      set { base.SetValue(PointsProperty, value); }
    }

    //-------------------------------------------------------------------------------------
    // 依存関係プロパティの登録
    static Triangle()
    {
     PointsProperty = DependencyProperty.Register("Points", typeof(PointCollection), typeof(Triangle),
       new FrameworkPropertyMetadata(new PointCollection(),
       FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure));
    }

    public Triangle()
    {
    }

    // 図形の形状を定義する
    protected override Geometry DefiningGeometry
    {
      get
      {
        PointCollection points = this.Points;
        PathFigure pathFigure = new PathFigure();

        if ((points == null) || (points.Count < 2))
          return Geometry.Empty;

        // StartPoint の設定は必須、でないと (0, 0) の位置が StartPoint になる
        pathFigure.StartPoint = points[0];

        // 残りの 2 点を Point 型の配列に代入
        Point[] pointArray = new Point[points.Count - 1];
        pointArray[0] = points[1];
        pointArray[1] = points[2];

        pathFigure.Segments.Add(new PolyLineSegment(pointArray, true));

        pathFigure.IsClosed = true;

        PathGeometry geometry = new PathGeometry();
        geometry.Figures.Add(pathFigure);

        return geometry;
      }
    }

  } // end of Triangle class
} // end of namespace

矢印コントロール(Arrow クラス)

上図を作成したときの関連する XAML コードは以下のとおりです。 

  <ctrl:Arrow x:Name="arrow1" Canvas.Left="340" Canvas.Top="20" Stroke="Red" Fill="Red"
                 X1="50" Y1="20" X2="100" Y2="20" />
  <ctrl:Arrow x:Name="arrow2" Canvas.Left="340" Canvas.Top="20" Stroke="Green" Fill="Green"
                 X1="50" Y1="50" X2="100" Y2="20" ArrowWidth="10" ArrowLength="10" />
  <ctrl:Arrow x:Name="arrow3" Canvas.Left="340" Canvas.Top="20" Stroke="Blue" Fill="Blue"
                 X1="100" Y1="50" X2="100" Y2="20" ArrowWidth="6" ArrowLength="3" />
  <ctrl:Arrow x:Name="arrow4" Canvas.Left="340" Canvas.Top="120" Stroke="Red" Fill="Red"
                 X1="50" Y1="20" X2="100" Y2="20" ArrowWidth="5" />
  <ctrl:Arrow x:Name="arrow5" Canvas.Left="340" Canvas.Top="120" Stroke="Green" Fill="Green"
                 X1="50" Y1="50" X2="100" Y2="20" ArrowWidth="5" ArrowLength="12" />
  <ctrl:Arrow x:Name="arrow6" Canvas.Left="340" Canvas.Top="120" Stroke="Blue" Fill="Blue"
                 X1="100" Y1="50" X2="100" Y2="20" ArrowWidth="5" ArrowLength="8" />

Arrow クラスを定義する C# コード

コード全体をファイル(たとえば、Arrow.cs)に保存し、使用する場合はプロジェクトに追加してください。注意事項はコードの中に書き込んでおきました。

using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Media;
using System.Windows.Input;
using System.Windows.Controls;
using System.Windows.Shapes;
using System.Diagnostics;

namespace emanual.Wpf.Controls
{
  public class Arrow : Shape
  {
    public static readonly DependencyProperty X1Property; // 矢じり側のX1 座標
    public static readonly DependencyProperty Y1Property; // 同、Y1 座標
    public static readonly DependencyProperty X2Property;
    public static readonly DependencyProperty Y2Property;
    public static readonly DependencyProperty ArrowLengthProperty; // 矢じり部の長さ
    public static readonly DependencyProperty ArrowWidthProperty;  // 同、幅(幅を長さの2 倍にすると直交三角形になる)

    [TypeConverter(typeof(LengthConverter))]
    public double X1
    {
      get { return (double)base.GetValue(X1Property); }
      set { base.SetValue(X1Property, value); }
    }

    [TypeConverter(typeof(LengthConverter))]
    public double Y1
    {
      get { return (double)base.GetValue(Y1Property); }
      set { base.SetValue(Y1Property, value); }
    }

    [TypeConverter(typeof(LengthConverter))]
    public double X2
    {
      get { return (double)base.GetValue(X2Property); }
      set { base.SetValue(X2Property, value); }
    }

    [TypeConverter(typeof(LengthConverter))]
    public double Y2
    {
      get { return (double)base.GetValue(Y2Property); }
      set { base.SetValue(Y2Property, value); }
    }

    [TypeConverter(typeof(LengthConverter))]
    public double ArrowLength
    {
      get { return (double)base.GetValue(ArrowLengthProperty); }
      set { base.SetValue(ArrowLengthProperty, value); }
    }

    [TypeConverter(typeof(LengthConverter))]
    public double ArrowWidth
    {
      get { return (double)base.GetValue(ArrowWidthProperty); }
      set { base.SetValue(ArrowWidthProperty, value); }
    }

    // コントロールの形状を定義する
    protected override Geometry DefiningGeometry
    {
      get
      {
        // 直線部の長さ
        double length = Math.Sqrt((X2 - X1) * (X2 - X1) + (Y2 - Y1) * (Y2 - Y1));

        PathFigure pathFigure = new PathFigure();
        pathFigure.StartPoint = new Point(this.X1, this.Y1 - length); // 矢じりでない側の位置

        Point[] pointArray = new Point[4];
        pointArray[0] = new Point(this.X1, this.Y1); // 矢じりの先端
        pointArray[1] = new Point(this.X1 - ArrowWidth / 2, this.Y1 - ArrowLength);
        pointArray[2] = new Point(this.X1 + ArrowWidth / 2, this.Y1 - ArrowLength);
        pointArray[3] = new Point(this.X1, this.Y1);

        pathFigure.Segments.Add(new PolyLineSegment(pointArray, true));
        pathFigure.IsFilled = true;

        PathGeometry geometry = new PathGeometry();
        geometry.FillRule = FillRule.Nonzero;
        geometry.Figures.Add(pathFigure);

        // 以上の操作は、垂直に立てた状態のときの形状である。次に、角度をつけるために座標変換する
        double angle = 180 - Math.Atan2(this.X2 - this.X1, this.Y2 - this.Y1) * 180 / Math.PI;
        RotateTransform transform = new RotateTransform(angle, this.X1, this.Y1);
        geometry.Transform = transform;

        return geometry;
      }
    }

    //-----------------------------------------------------------------------------------
    // コンストラクタ
    static Arrow()
    {
      X1Property = DependencyProperty.Register("X1", typeof(double), typeof(Arrow),
        new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure));
      Y1Property = DependencyProperty.Register("Y1", typeof(double), typeof(Arrow),
        new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure));
      X2Property = DependencyProperty.Register("X2", typeof(double), typeof(Arrow),
        new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure));
      Y2Property = DependencyProperty.Register("Y2", typeof(double), typeof(Arrow),
        new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure));
      ArrowLengthProperty = DependencyProperty.Register("ArrowLength", typeof(double), typeof(Arrow),
        new FrameworkPropertyMetadata(5.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure));
      ArrowWidthProperty = DependencyProperty.Register("AllowWidth", typeof(double), typeof(Arrow),
        new FrameworkPropertyMetadata(10.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure));
    }

    public Arrow()
    {
      this.Stroke = Brushes.Black;
      this.Fill = this.Stroke;
      this.SnapsToDevicePixels = true;
    }
  } // end of Arrow class
} // end of namespace

ベジエ曲線コントロール(Bezier クラス)

上図を作成したときの関連する XAML コードは以下のとおりです。

  <ctrl:Bezier x:Name="bezier" Canvas.Left="0" Canvas.Top="0" Stroke="Orange" StrokeThickness="4"
               Points="20,80 120,40 200,80 300,140 460 100" />

Bezier クラスを定義する C# コード

コード全体をファイル(たとえば、Bezier.cs)に保存し、使用する場合はプロジェクトに追加してください。注意事項はコードの中に書き込んでおきました。

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;

namespace emanual.Wpf.Controls
{
  public class Bezier : Shape
  {
    // 依存関係プロパティの宣言
    public static readonly DependencyProperty PointsProperty; // 5 点の座標値

    // 境界線の色は Stroke プロパティ
    // 同、幅は StrokeThickness プロパティで設定する

    // プロパティ
    // 5 点の座標値(最初の点は始点となる)
    public PointCollection Points
    {
      get { return (PointCollection)base.GetValue(PointsProperty); }
      set { base.SetValue(PointsProperty, value); }
    }

    //-------------------------------------------------------------------------------------
    // 依存関係プロパティの登録
    static Bezier()
    {
      PointsProperty = DependencyProperty.Register("Points", typeof(PointCollection), typeof(Bezier),
        new FrameworkPropertyMetadata(new PointCollection(),
        FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure));
    }

    public Bezier()
    {
    }

    protected override Geometry DefiningGeometry
    {
      get
      {
        if ((this.Points == null) || (this.Points.Count < 4))
          return Geometry.Empty;

        PathFigure pathFigure = new PathFigure();

        pathFigure.StartPoint = this.Points[0]; // 最初の点を始点とする

        PointCollection points = new PointCollection();

        // 始点以後の点座標をコピー
        for (int i = 1; i < 5; i++)
        {
          points.Add(this.Points[i]);
        }

        PolyQuadraticBezierSegment segment = new PolyQuadraticBezierSegment();
        segment.Points = points;
        segment.IsStroked = true;

        pathFigure.Segments.Add(segment);

        PathGeometry geometry = new PathGeometry();
        geometry.Figures.Add(pathFigure);

        return geometry;
      }
    }

  } // end of Bezier class
} // end of namespace

テキスト回転表示コントロール(RotateLabel クラス)

このコントロールは図形コントロールではありませんが、グラフィックスソフトに関連が深いと考え、このページに含めることにしました。コントロールの機能は下図を見てのとおりです。RotateAngle プロパティに回転角を指定するだけです。

RotateLabel

<Window x:Class="VerticalTextBlockTest.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:ctrl="clr-namespace:emanual.Wpf.Controls" ← 必須
    Title="VerticalTextLabel と RotateLabel" Height="300" Width="300">
  <Canvas>
    <ctrl:RotateLabel x:Name="label1" Canvas.Left="140" Canvas.Top="120">ラベルの回転</ctrl:RotateLabel>
    <ctrl:RotateLabel x:Name="label2" RotateAngle="90" Canvas.Left="140" Canvas.Top="120">ラベルの回転</ctrl:RotateLabel>
    <ctrl:RotateLabel x:Name="label3" RotateAngle="270" Canvas.Left="140" Canvas.Top="120">ラベルの回転</ctrl:RotateLabel>
  </Canvas>
</Window>

RotateLabel コントロールを定義する C# コード

以下のコードをファイル(たとえば、RotateLabel.cs)に保存し、プロジェクトに追加して使用してください。

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media; // TypeFace
using System.Windows.Controls;
using System.Diagnostics;

namespace emanual.Wpf.Controls
{
  public class RotateLabel : System.Windows.Controls.Label
  {
    // 依存関係プロパティの宣言
    public static readonly DependencyProperty RotateAngleProperty; // X 軸を 0 度とする時計回りの角度

    // プロパティ
    public double RotateAngle
    {
      get { return (double)base.GetValue(RotateAngleProperty); }
      set { base.SetValue(RotateAngleProperty, value); }
    }

    //-------------------------------------------------------------------------------------
    // 依存関係プロパティの登録
    static RotateLabel()
    {
      RotateAngleProperty = DependencyProperty.Register("RotateAngle", typeof(double), typeof(RotateLabel),
         new FrameworkPropertyMetadata(0.0,
           FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure));
    }

    public RotateLabel()
    {
    }

    //---------------------------------------------------------------------------------------
    // RotateAngle プロパティが変化したとき、コントロール全体を回転する
    protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
    {
      if (e.Property == RotateAngleProperty)
      {
        RotateTransform transform = new RotateTransform(this.RotateAngle);
        this.RenderTransform = transform;
      }

      base.OnPropertyChanged(e);
    }
  } // end of RotateLabel class
} // end of namespace

テキスト縦書き表示コントロール(VerticalTextLabel クラス)

RotateLabel クラスと同様、図形コントロールではありませんが、このページに収録しました。上下の文字の間隔を設定できるようにしておきました。なお、「大学生の就職内定率」はたまたま見ていた新聞記事の中にあったので引用しただけです。他意はありません。

VerticalTextLabel

<Window x:Class="VerticalTextBlockTest.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:ctrl="clr-namespace:emanual.Wpf.Controls"  ← 必要
    Title="VerticalTextLabel と RotateLabel" Height="300" Width="300">
  <Canvas>
    <ctrl:VerticalTextLabel Text="大学生の就職内定率" Canvas.Left="40" Canvas.Top="50" />
    <ctrl:VerticalTextLabel Text="大学生の就職内定率" CharSpacing="13" Canvas.Left="60" Canvas.Top="50" />
    <ctrl:VerticalTextLabel Text="大学生の就職内定率" CharSpacing="15" Canvas.Left="80" Canvas.Top="50" />
  </Canvas>
</Window>

VerticalTextLabel クラスを定義する C# コード

以下のコードをファイル(たとえば、VerticalTextLabel.cs)に保存し、プロジェクトに追加して使用してください。

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media; // TypeFace
using System.Windows.Controls;
using System.Diagnostics;

namespace emanual.Wpf.Controls
{
  public class VerticalTextLabel : System.Windows.Controls.Control
  {
    private Size FTextSize;

    // 依存関係プロパティの宣言
    public static readonly DependencyProperty CharSpacingProperty; // 上下の文字の間隔(デフォルトは文字の高さ)
    public static readonly DependencyProperty TextProperty;

    // プロパティ
    public double CharSpacing
    {
      get { return (double)base.GetValue(CharSpacingProperty); }
      set { base.SetValue(CharSpacingProperty, value); }
    }

    public string Text
    {
      get { return (string)base.GetValue(TextProperty); }
      set { base.SetValue(TextProperty, value); }
    }

    //-------------------------------------------------------------------------------------
    // 依存関係プロパティの登録
    static VerticalTextLabel()
    {
      CharSpacingProperty = DependencyProperty.Register("CharSpacing", typeof(double), typeof(VerticalTextLabel),
         new FrameworkPropertyMetadata(0.0,
           FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure));
      TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(VerticalTextLabel),
         new FrameworkPropertyMetadata(String.Empty,
           FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure));
    }

    public VerticalTextLabel()
    {
      this.CharSpacing = this.FontSize;
    }

    //-------------------------------------------------------------------------------------
    // テキストを縦書きした状態のときのテキストのサイズを取得する
    protected override Size MeasureOverride(Size constraint)
    {
      Typeface typeFace = new Typeface(this.FontFamily, this.FontStyle, this.FontWeight, this.FontStretch);
      string text = this.GetVerticalText(this.Text);
      FTextSize = this.MeasureString(text, this.FontSize, typeFace);

      return new Size(FTextSize.Width, FTextSize.Height);
    }

    //-------------------------------------------------------------------------------------
    // MeasureOverride メソッドで取得したテキスト表示範囲内でテキストを配置する
    protected override Size ArrangeOverride(Size arrangeBounds)
    {
      UIElement element = null;

      if (this.VisualChildrenCount > 0)
      {
        element = this.GetVisualChild(0) as UIElement;
      }

      if (element != null)
      {
        element.Arrange(new Rect(0, 0, FTextSize.Width, FTextSize.Height));
      }

      return arrangeBounds;
    }

    //-------------------------------------------------------------------------------------
    // テキストを描画する
    protected override void OnRender(DrawingContext context)
    {
      int x = 0, y = 0;
      Typeface typeFace = new Typeface(this.FontFamily, this.FontStyle, this.FontWeight, this.FontStretch);

      // Background プロパティを生かす
      context.DrawRectangle(this.Background, null, new Rect(new Point(0, 0), this.RenderSize));

      for (int i = 0; i < this.Text.Length; i++)
      {
        FormattedText formattedText = new FormattedText(new String(this.Text[i], 1), System.Globalization.CultureInfo.CurrentCulture,
           FlowDirection.LeftToRight, typeFace, this.FontSize, Brushes.Black);

        context.DrawText(formattedText, new Point(x, y + this.CharSpacing * i));
      }
    }

    //-------------------------------------------------------------------------------------
    protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
    {
      this.InvalidateVisual(); // これは必要ないはずだが、こうしないと一部のプロパティの変化に対して再描画しないため
      base.OnPropertyChanged(e);
    }

    //---------------------------------------------------------------------------------------
    // テキストの幅と高さを取得する
    private Size MeasureString(string text, double fontSize, Typeface typeFace)
    {
      FormattedText formattedText = new FormattedText(text, System.Globalization.CultureInfo.CurrentCulture,
           FlowDirection.LeftToRight, typeFace, fontSize, Brushes.Black);

      formattedText.LineHeight = this.CharSpacing;

      return new Size(formattedText.Width, formattedText.Height);
    }

    //---------------------------------------------------------------------------------------
    // 縦書きテキストを取得する(テキストの各文字ごとに改行する)
    private string GetVerticalText(string text)
    {
      string result = String.Empty;

      for (int i = 0; i < text.Length; i++)
      {
        result += new string(text[i], 1) + "\n";
      }

      return result;
    }
  } // end of VerticalTextLabel class
} // end of namsepace

−以上−