フローチャートを描きたいと思ったとき、多くのソフトウェアがライセンスキーを必要としたり、会員登録が必要だったりすることに気づきます。そこで、自分でフローチャートソフトウェアを作れないかと考えました。この記事では、簡単なサンプルを通して、WPFを使って自分だけのフローチャートソフトウェアを作る方法を説明します。あくまで学習と共有のためのものであり、不十分な点があればご指摘ください。
関連知識
このサンプルは主にWPF技術を使用して開発されています。関連する知識は以下の通りです。
- WPFの描画機能:矩形、直線などの図形に関する技術。
- Thumbコントロール:ユーザーが自由にドラッグできるコントロール。サンプルで使用する移動可能な図形コントロールは、すべてThumbコントロールを継承しています。
Thumbコントロールの概要
Thumbコントロールは、WPFが提供するユーザーがドラッグできるコントロールです。デフォルトではツールボックスに含まれていないため、手動で追加する必要があります。以下の手順で行います。
ツールボックス → 全般 → 右クリック → アイテムの選択 を開き、[ツールボックスアイテムの選択]ダイアログを表示します。以下のようになります。

[ツールボックスアイテムの選択]ページで、WPFコンポーネント → Thumbを選択 → OKボタンをクリックします。以下のようになります。

追加が成功すると、ツールボックスからページにドラッグできるようになります。以下のようになります。

Thumbコントロールの概要は以下の通りです。
namespace System.Windows.Controls.Primitives
{
//
// 要約:
// ユーザーがドラッグできるコントロールを表します。
[DefaultEvent("DragDelta")]
[Localizability(LocalizationCategory.NeverLocalize)]
public class Thumb : Control
{
//
// 要約:
// System.Windows.Controls.Primitives.Thumb.DragStarted ルーティングイベントを識別します。
//
// 戻り値:
// System.Windows.Controls.Primitives.Thumb.DragStarted ルーティングイベントの識別子。
public static readonly RoutedEvent DragStartedEvent;
//
// 要約:
// System.Windows.Controls.Primitives.Thumb.DragDelta ルーティングイベントを識別します。
//
// 戻り値:
// System.Windows.Controls.Primitives.Thumb.DragDelta ルーティングイベントの識別子。
public static readonly RoutedEvent DragDeltaEvent;
//
// 要約:
// System.Windows.Controls.Primitives.Thumb.DragCompleted ルーティングイベントを識別します。
//
// 戻り値:
// System.Windows.Controls.Primitives.Thumb.DragCompleted ルーティングイベントの識別子。
public static readonly RoutedEvent DragCompletedEvent;
//
// 要約:
// System.Windows.Controls.Primitives.Thumb.IsDragging 依存関係プロパティを識別します。
//
// 戻り値:
// System.Windows.Controls.Primitives.Thumb.IsDragging 依存関係プロパティの識別子。
public static readonly DependencyProperty IsDraggingProperty;
//
// 要約:
// System.Windows.Controls.Primitives.Thumb クラスの新しいインスタンスを初期化します。
public Thumb();
//
// 要約:
// System.Windows.Controls.Primitives.Thumb コントロールが論理フォーカスを持ち、マウスをキャプチャし、マウスの左ボタンが押されているかどうかを取得します。
//
// 戻り値:
// System.Windows.Controls.Primitives.Thumb コントロールがフォーカスとマウスキャプチャを持っている場合は true。それ以外の場合は false。既定値は
// false。
[Bindable(true)]
[Browsable(false)]
[Category("Appearance")]
public bool IsDragging { get; protected set; }
//
// 要約:
// System.Windows.Controls.Primitives.Thumb コントロールが論理フォーカスとマウスキャプチャを受け取ったときに発生します。
[Category("Behavior")]
public event DragStartedEventHandler DragStarted;
//
// 要約:
// マウスの位置が変更されたときに 1 回以上発生します。 System.Windows.Controls.Primitives.Thumb コントロールが論理フォーカスとマウスキャプチャを持っているときに発生します。
[Category("Behavior")]
public event DragDeltaEventHandler DragDelta;
//
// 要約:
// System.Windows.Controls.Primitives.Thumb コントロールがマウスキャプチャを失ったときに発生します。
[Category("Behavior")]
public event DragCompletedEventHandler DragCompleted;
//
// 要約:
// System.Windows.Controls.Primitives.Thumb のドラッグ操作をキャンセルします。
public void CancelDrag();
//
// 要約:
// System.Windows.Controls.Primitives.Thumb コントロールの System.Windows.Automation.Peers.AutomationPeer を作成します。
//
// 戻り値:
// System.Windows.Controls.Primitives.Thumb コントロールの System.Windows.Automation.Peers.ThumbAutomationPeer。
protected override AutomationPeer OnCreateAutomationPeer();
//
// 要約:
// System.Windows.Controls.Primitives.Thumb.IsDragging プロパティの値の変更に応答します。
//
// パラメーター:
// e:
// イベントデータ。
protected virtual void OnDraggingChanged(DependencyPropertyChangedEventArgs e);
//
// 要約:
// System.Windows.ContentElement.MouseLeftButtonDown イベントのクラス処理を提供します。
//
// パラメーター:
// e:
// イベントデータ。
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e);
//
// 要約:
// System.Windows.ContentElement.MouseLeftButtonUp イベントのクラス処理を提供します。
//
// パラメーター:
// e:
// イベントデータ。
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e);
//
// 要約:
// System.Windows.UIElement.MouseMove イベントのクラス処理を提供します。
//
// パラメーター:
// e:
// イベントデータ。
protected override void OnMouseMove(MouseEventArgs e);
}
}
上記の概要から、Thumbコントロールは3つのイベントを提供していることがわかります。
- ドラッグ開始イベント: public event DragStartedEventHandler DragStarted;
- ドラッグ中イベント: public event DragDeltaEventHandler DragDelta;
- ドラッグ完了イベント: public event DragCompletedEventHandler DragCompleted;
Thumbコントロールのサンプル
まず、ウィンドウページにThumbコントロールを追加し、3つのイベントをそれぞれ追加します。以下のようになります。
<Window
x:Class="DemoVisio.MainWindow1"
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:DemoVisio"
mc:Ignorable="d"
Title="Thumbサンプル"
Height="450"
Width="800"
>
<Canvas>
<Thumb
x:Name="thumb"
Canvas.Left="0"
Canvas.Top="0"
Height="100"
Width="100"
DragStarted="thumb_DragStarted"
DragDelta="thumb_DragDelta"
DragCompleted="thumb_DragCompleted"
/>
</Canvas>
</Window>
次に、3つのイベントにコードを追加します。特に重要なのはDragDeltaイベントです。以下のようになります。
namespace DemoVisio
{
/// <summary>
/// MainWindow1.xaml の相互作用ロジック
/// </summary>
public partial class MainWindow1 : Window
{
public MainWindow1()
{
InitializeComponent();
}
private void thumb_DragStarted(object sender, DragStartedEventArgs e)
{
// ドラッグ開始
}
/// <summary>
/// ドラッグ中
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void thumb_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
{
Thumb myThumb = (Thumb)sender;
double nTop = Canvas.GetTop(myThumb) + e.VerticalChange;
double nLeft = Canvas.GetLeft(myThumb) + e.HorizontalChange;
Canvas.SetTop(myThumb, nTop);
Canvas.SetLeft(myThumb, nLeft);
}
private void thumb_DragCompleted(object sender, DragCompletedEventArgs e)
{
// ドラッグ完了
}
}
}
注意点: XAMLでは、Thumbは必ずCanvasレイアウトコンテナ内に配置し、Canvas.Left="0" と Canvas.Top="0" の2つのプロパティを設定する必要があります。設定しないと値がNaNになり、ドラッグできません。
デフォルトでは、Thumbコントロールはシンプルな四角形として表示されます。以下のようになります。

ドラッグ可能なコントロールの基底クラス
このサンプルでは、フローチャートに複数のコントロール(円、矩形、線など)が必要であり、それぞれに DragStarted、DragDelta、DragCompleted の3つのイベントを実装するのは非効率的です。そこで、基底クラス ThumbControl を定義し、共通の実装を提供します。具体的には以下の通りです。
namespace DemoVisio
{
public class ThumbControl : Thumb
{
/// <summary>
/// 入力が可能かどうか
/// </summary>
public bool IsEnableInput { get { return (bool)GetValue(IsEnableInputProperty); } set { SetValue(IsEnableInputProperty, value); } }
/// <summary>
/// 依存関係プロパティ
/// </summary>
public static readonly DependencyProperty IsEnableInputProperty = DependencyProperty.Register("IsEnableInput", typeof(bool), typeof(ThumbControl));
public ThumbControl() : base()
{
this.DragStarted += ThumbControl_DragStarted;
this.DragDelta += ThumbControl_DragDelta;
this.DragCompleted += ThumbControl_DragCompleted;
this.MouseDoubleClick += ThumbControl_MouseDoubleClick;
this.IsKeyboardFocusedChanged += ThumbControl_IsKeyboardFocusedChanged;
}
public void SetIsEnableInput(bool flag)
{
this.IsEnableInput = flag;
}
/// <summary>
/// キーボードフォーカスの変化
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ThumbControl_IsKeyboardFocusedChanged(object sender, DependencyPropertyChangedEventArgs e)
{
this.IsEnableInput = this.IsKeyboardFocused;
}
/// <summary>
/// ダブルクリックイベント
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ThumbControl_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
this.IsEnableInput = true;
}
private void ThumbControl_DragStarted(object sender, DragStartedEventArgs e)
{
// 移動開始
}
private void ThumbControl_DragDelta(object sender, DragDeltaEventArgs e)
{
Thumb myThumb = (Thumb)sender;
double nTop = Canvas.GetTop(myThumb) + e.VerticalChange;
double nLeft = Canvas.GetLeft(myThumb) + e.HorizontalChange;
Canvas.SetTop(myThumb, nTop);
Canvas.SetLeft(myThumb, nLeft);
}
private void ThumbControl_DragCompleted(object sender, DragCompletedEventArgs e)
{
// 移動終了
}
}
}
具体的な図形コントロール
基底クラスをカプセル化した後、他のコントロールはそれを基に使用でき、ControlTemplate によって異なる形状を表現します。以下の通りです。
1. 矩形(四角形)
フローチャートでは、矩形は通常「処理」を表します。実装コードは以下の通りです。
<UserControl
x:Class="DemoVisio.SquareControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:DemoVisio"
mc:Ignorable="d"
d:DesignHeight="450"
d:DesignWidth="800"
>
<Canvas>
<local:ThumbControl
x:Name="s"
BorderThickness="1"
Canvas.Top="0"
Canvas.Left="0"
Width="100"
Height="60"
Background="AliceBlue"
>
<Thumb.Template>
<ControlTemplate>
<Border
Width="{Binding ElementName=s, Path=Width}"
Height="{Binding ElementName=s, Path=Height}"
BorderBrush="Black"
Background="{Binding ElementName=s, Path=Background}"
BorderThickness="{Binding ElementName=s, Path=BorderThickness}"
Padding="2"
>
<TextBox
Background="{Binding ElementName=s, Path=Background}"
BorderThickness="0"
VerticalAlignment="Center"
IsEnabled="{Binding ElementName=s, Path=IsEnableInput}"
MinLines="3"
MaxLines="3"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
></TextBox>
</Border>
</ControlTemplate>
</Thumb.Template>
</local:ThumbControl>
</Canvas>
</UserControl>
2. 円
フローチャートでは、円は通常「開始」または「終了」を表します。以下の通りです。
<UserControl
x:Class="DemoVisio.CircleControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:DemoVisio"
mc:Ignorable="d"
d:DesignHeight="450"
d:DesignWidth="800"
>
<Canvas>
<local:ThumbControl
x:Name="s"
Canvas.Top="0"
Canvas.Left="0"
Width="60"
Height="60"
Background="AliceBlue"
BorderThickness="1"
>
<local:ThumbControl.Template>
<ControlTemplate>
<Grid>
<Border
Width="{Binding Width}"
Height="{Binding Height}"
CornerRadius="30"
BorderThickness="{Binding ElementName=s, Path=BorderThickness}"
BorderBrush="Black"
Background="{Binding ElementName=s, Path=Background}"
>
<TextBox
VerticalAlignment="Center"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
MaxLines="2"
MinLines="1"
Width="{Binding Width}"
Height="{Binding Height}"
Background="{Binding ElementName=s, Path=Background}"
BorderThickness="0"
IsEnabled="{Binding ElementName=s, Path=IsEnableInput}"
></TextBox>
</Border>
</Grid>
</ControlTemplate>
</local:ThumbControl.Template>
</local:ThumbControl>
</Canvas>
</UserControl>
3. 菱形
フローチャートでは、菱形は通常「選択」を表し、プログラムの真理値を表現します。以下の通りです。
<UserControl
x:Class="DemoVisio.RhombusControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:DemoVisio"
mc:Ignorable="d"
d:DesignHeight="450"
d:DesignWidth="800"
>
<Canvas>
<local:ThumbControl
x:Name="s"
Canvas.Left="0"
Canvas.Top="0"
Width="80"
Height="80"
Background="AliceBlue"
BorderThickness="1"
>
<Thumb.Template>
<ControlTemplate>
<Border
Width="{Binding ElementName=s, Path=Width}"
Height="{Binding ElementName=s, Path=Height}"
BorderBrush="Black"
Background="{Binding ElementName=s, Path=Background}"
BorderThickness="{Binding ElementName=s, Path=BorderThickness}"
Padding="1"
>
<TextBox
Background="{Binding ElementName=s, Path=Background}"
BorderThickness="0"
VerticalAlignment="Center"
IsEnabled="{Binding ElementName=s, Path=IsEnableInput}"
Width="50"
Height="50"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
>
<TextBox.RenderTransform>
<RotateTransform
CenterX="25"
CenterY="25"
Angle="-45"
></RotateTransform>
</TextBox.RenderTransform>
</TextBox>
</Border>
</ControlTemplate>
</Thumb.Template>
<Thumb.RenderTransform>
<TransformGroup>
<RotateTransform
CenterX="40"
CenterY="40"
Angle="45"
></RotateTransform>
<ScaleTransform
CenterX="40"
CenterY="40"
ScaleX="0.8"
></ScaleTransform>
<TranslateTransform X="10" Y="15"></TranslateTransform>
</TransformGroup>
</Thumb.RenderTransform>
</local:ThumbControl>
</Canvas>
</UserControl>
4. 直線
フローチャートでは、直線は通常2つの処理間の接続を表します。以下の通りです。
<UserControl
x:Class="DemoVisio.LineArrowControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:DemoVisio"
mc:Ignorable="d"
d:DesignHeight="450"
d:DesignWidth="800"
>
<Canvas>
<local:ThumbControl
x:Name="s"
Canvas.Left="0"
Canvas.Top="0"
Width="100"
Height="100"
Background="AliceBlue"
IsEnableInput="False"
>
<local:ThumbControl.Template>
<ControlTemplate>
<Grid>
<Line
X1="0"
Y1="0"
X2="0"
Y2="100"
Stroke="Black"
StrokeThickness="2"
HorizontalAlignment="Center"
>
</Line>
<Line
X1="-5"
Y1="90"
X2="1"
Y2="100"
StrokeThickness="2"
Stroke="Black"
HorizontalAlignment="Center"
></Line>
<Line
X1="8"
Y1="90"
X2="3"
Y2="100"
StrokeThickness="2"
Stroke="Black"
HorizontalAlignment="Center"
></Line>
<TextBox
VerticalAlignment="Center"
Height="30"
HorizontalContentAlignment="Center"
BorderThickness="0"
VerticalContentAlignment="Center"
MinLines="1"
MaxLines="2"
IsEnabled="{Binding ElementName=s, Path=IsEnableInput}"
Opacity="0"
Visibility="{Binding ElementName=s, Path=IsEnableInput}"
></TextBox>
</Grid>
</ControlTemplate>
</local:ThumbControl.Template>
</local:ThumbControl>
</Canvas>
</UserControl>
メインウィンドウ
メインウィンドウはフローチャートを描画するためのもので、左側にコントロール一覧、右側にレイアウトコンテナを配置します。以下の通りです。
<Window
x:Class="DemoVisio.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:DemoVisio"
mc:Ignorable="d"
Title="フローチャート"
Height="800"
Width="800"
MouseDown="Window_MouseDown"
Loaded="Window_Loaded"
>
<Window.Resources>
<Style TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Center"></Setter>
</Style>
</Window.Resources>
<Grid ShowGridLines="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" MinWidth="150"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" x:Name="left" Orientation="Vertical">
<Rectangle
Width="80"
Height="50"
Fill="AliceBlue"
Stroke="Black"
Margin="5"
x:Name="rectangle"
MouseLeftButtonDown="rectangle_MouseLeftButtonDown"
></Rectangle>
<TextBlock Text="矩形"></TextBlock>
<Rectangle
Width="100"
Height="100"
Fill="AliceBlue"
Stroke="Black"
Margin="5"
x:Name="rhombus"
MouseLeftButtonDown="rhombus_MouseLeftButtonDown"
>
<Rectangle.RenderTransform>
<TransformGroup>
<RotateTransform
CenterX="50"
CenterY="50"
Angle="45"
></RotateTransform>
<ScaleTransform
CenterX="50"
CenterY="50"
ScaleX="0.5"
ScaleY="0.7"
></ScaleTransform>
</TransformGroup>
</Rectangle.RenderTransform>
</Rectangle>
<TextBlock Text="菱形" Margin="0,5"></TextBlock>
<Line
X1="0"
Y1="0"
X2="0"
Y2="80"
Stroke="Black"
StrokeThickness="2"
HorizontalAlignment="Center"
Margin="5"
x:Name="line"
MouseLeftButtonDown="line_MouseLeftButtonDown"
></Line>
<TextBlock Text="直線"></TextBlock>
<Ellipse
Width="60"
Height="60"
Fill="AliceBlue"
Stroke="Black"
Margin="5"
x:Name="circle"
MouseLeftButtonDown="circle_MouseLeftButtonDown"
></Ellipse>
<TextBlock Text="円"></TextBlock>
</StackPanel>
<Canvas Grid.Column="1" x:Name="right"> </Canvas>
</Grid>
</Window>
左側の基本コントロールをクリックすると、右側のコンテナに新しいコントロールが生成されます。以下の通りです。
namespace DemoVisio
{
/// <summary>
/// MainWindow.xaml の相互作用ロジック
/// </summary>
public partial class MainWindow : Window
{
/// <summary>
/// コントロールリスト
/// </summary>
private List<Control> rightControls = new List<Control>();
public MainWindow()
{
InitializeComponent();
}
private void Window_MouseDown(object sender, MouseButtonEventArgs e)
{
//this.Focus();
//this.one.SetIsEnableInput(false);
foreach (var control in this.right.Children)
{
var thumb = control as ThumbControl;
if (thumb != null)
{
thumb.SetIsEnableInput(false);
}
}
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
var width = this.right.ActualWidth;
var height = this.right.ActualHeight;
int x = 0;
int y = 0;
// 横線
while (y < height)
{
Line line = new Line();
line.X1 = x;
line.Y1 = y;
line.X2 = width;
line.Y2 = y;
line.Stroke = Brushes.LightGray;
line.StrokeThickness = 1;
this.right.Children.Add(line);
y = y + 10;
}
// 再初期化
x = 0;
y = 0;
// 縦線
while (x < width)
{
Line line = new Line();
line.X1 = x;
line.Y1 = y;
line.X2 = x;
line.Y2 = height;
line.Stroke = Brushes.LightGray;
line.StrokeThickness = 1;
this.right.Children.Add(line);
x = x + 10;
}
}
private void rectangle_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
SquareControl squareControl = new SquareControl();
this.right.Children.Add(squareControl);
}
private void rhombus_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
RhombusControl rhombusControl = new RhombusControl();
this.right.Children.Add(rhombusControl);
}
private void line_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
LineArrowControl lineArrowControl = new LineArrowControl();
this.right.Children.Add(lineArrowControl);
}
private void circle_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
CircleControl circleControl = new CircleControl();
this.right.Children.Add(circleControl);
}
}
}
サンプルスクリーンショット
サンプルの基本スクリーンショットは以下の通りです。

備考
上記のサンプルは基本的なものであり、機能は完全ではありません。あくまで抛砖引玉(参考として提供する)の意図であり、共に学び、共に進歩することを目的としています。皆様の参考になれば幸いです。
賀新郎·九日
【作者】劉克庄 【朝代】宋
湛湛長空黒。更那堪、斜風細雨、乱愁如織。
老眼平生空四海、頼有高樓百尺。
看浩蕩、千崖秋色。
白髪書生神州涙、尽淒涼、不向牛山滴。
追往事、去無跡。
少年自負凌雲筆。到而今、春華落尽、満懐蕭瑟。
常恨世人新意少、愛説南朝狂客。
把破帽、年年拈出。
若対黄花孤負酒、怕黄花、也笑人岑寂。
鴻北去、日西匿。