When you want to draw a flowchart, you will find that many software either requires secret keys or members. At this time, I wonder if I can make my own flowchart software? This article uses a simple small example to briefly describe how to use WPF to make your own flowchart software. It is only for learning and sharing. Please correct if there are any shortcomings.
Knowledge points involved
This example is mainly developed using WPF technology, and the knowledge points involved are as follows:
- WPF drawing, related graphics technology for functions such as rectangles, straight lines, etc.
- Thumb control, which can be freely dragged by the user. The movable graphical controls used in the example all inherit the Thumb control.
Introduction to Thumb Control
The Thumb control is a control provided by WPF for user dragging. By default, the Thumb control is not in the toolbox and needs to be added manually, as shown below:
Toolbox--> General--> Right-click--> Select items, and then open the [Select Toolbox Dialog Box], as shown below:

On the Select Toolbox Item page, open WPF Component--> Select Thumb--> Click the OK button, as follows:

After the addition is successful, you can drag it from the toolbox to the page, as follows:

A brief introduction to the Thumb control is as follows:
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 控件具有逻辑焦点和捕获鼠标并按下鼠标左键。
//
// 返回结果:
// true 如果 System.Windows.Controls.Primitives.Thumb 控件具有焦点且鼠标捕获; 否则为 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;
//
// 摘要:
// 根据鼠标位置更改时出现了一个或多个次 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.Automation.Peers.AutomationPeer 为 System.Windows.Controls.Primitives.Thumb
// 控件。
//
// 返回结果:
// 一个 System.Windows.Automation.Peers.ThumbAutomationPeer 为 System.Windows.Controls.Primitives.Thumb
// 控件。
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);
}
}
Through the above summary introduction, it is found that the Thumb control provides three events, namely:
- Drag start event: public event DragStartedEventHandler DragStarted;
- Drag to perform an event: public event DragDeltaEventHandler DragDelta;
- Drag completion event: public event DragCompleted EventHandler DragCompleted;
Thumb control example
First add a Thumb control on the window page, and then add three events respectively, as follows:
<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>
Then add code to three events, ending with the DragDelta event, as follows:
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)
{
//拖动完成
}
}
}
Note that in XAML, Thumb must be in the Canvas layout container, and the two properties [Canvas.Left="0" Canvas.Top="0"] must be set. If you do not set it, you cannot drag it when the value is NaN.
By default, the Thumb control is an ugly square, as follows:

Drag control base class
In this example, the flowchart requires multiple controls (such as circles, rectangles, lines, etc.), and each control cannot implement the three events [DragStarted, DragDelta, DragCompleted], so a base class ThumbControl needs to be defined to unify the implementation plan. The details are as follows:
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)
{
//移动结束
}
}
}
Specific graphical controls
After encapsulating the base class, other controls can be used on this basis, showing different forms through ControlTemplate, as shown below:
- rectangular dice
In the flowchart, rectangular squares generally represent the process, and the implementation code is as follows:
<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>
- circular chart
In a flowchart, circles generally represent the beginning and end, as follows:
<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>
- diamond diagram
In flow charts, diamonds generally represent choices and express boolean values in the program. As follows:
<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>
- straight line
In a flowchart, a straight line generally represents the connection between two processes, as follows:
<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>
main form
The main form is mainly used to draw flow charts. It is divided into two parts: the control list on the left and the layout container on the right, as shown below:
<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>
When you click on the base control on the left, a new control is generated in the container on the right. As follows:
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);
}
}
}
example screenshot
The basic screenshot of the example is as follows:

remarks
The above examples are only the basics and the functions are not perfect. They are designed to attract people, learn together, and make progress together. I hope they can inspire everyone.
贺新郎·九日
【作者】刘克庄 【朝代】宋
湛湛长空黑。更那堪、斜风细雨,乱愁如织。
老眼平生空四海,赖有高楼百尺。
看浩荡、千崖秋色。
白发书生神州泪,尽凄凉、不向牛山滴。
追往事,去无迹。
少年自负凌云笔。到而今、春华落尽,满怀萧瑟。
常恨世人新意少,爱说南朝狂客。
把破帽、年年拈出。
若对黄花孤负酒,怕黄花、也笑人岑寂。
鸿北去,日西匿。