When you want to draw a flowchart, you'll find that many software tools either require a license key or a membership. At that point, I thought, can I create my own flowchart software? This article uses a simple example to briefly describe how to use WPF to create your own flowchart software. It is only for learning and sharing purposes. If there are any shortcomings, please point them out.
Involved Knowledge
This example is mainly developed using WPF technology, involving the following knowledge points:
- WPF drawing, such as rectangles, lines, and other related graphics techniques.
- Thumb control. This control allows users to drag freely. The movable graphics controls used in the example all inherit from the Thumb control.
Introduction to Thumb Control
The Thumb control is a WPF control used for user dragging. By default, the Thumb control is not in the toolbox and needs to be added manually, as follows:
Toolbox --> General --> Right-click --> Choose Items, then open the [Choose Toolbox Items] dialog, as shown below:

On the [Choose Toolbox Items] page, open WPF Components --> Select Thumb --> Click the OK button, as shown below:

After successful addition, you can drag it from the toolbox to the page, as shown below:

A brief introduction to the Thumb control is as follows:
namespace System.Windows.Controls.Primitives
{
//
// Summary:
// Represents a control that can be dragged by the user.
[DefaultEvent("DragDelta")]
[Localizability(LocalizationCategory.NeverLocalize)]
public class Thumb : Control
{
//
// Summary:
// Identifies the System.Windows.Controls.Primitives.Thumb.DragStarted routed event.
//
// Returns:
// The identifier for the System.Windows.Controls.Primitives.Thumb.DragStarted routed event.
public static readonly RoutedEvent DragStartedEvent;
//
// Summary:
// Identifies the System.Windows.Controls.Primitives.Thumb.DragDelta routed event.
//
// Returns:
// The identifier for the System.Windows.Controls.Primitives.Thumb.DragDelta routed event.
public static readonly RoutedEvent DragDeltaEvent;
//
// Summary:
// Identifies the System.Windows.Controls.Primitives.Thumb.DragCompleted routed event.
//
// Returns:
// The identifier for the System.Windows.Controls.Primitives.Thumb.DragCompleted routed event.
public static readonly RoutedEvent DragCompletedEvent;
//
// Summary:
// Identifies the System.Windows.Controls.Primitives.Thumb.IsDragging dependency property.
//
// Returns:
// The identifier for the System.Windows.Controls.Primitives.Thumb.IsDragging dependency property.
public static readonly DependencyProperty IsDraggingProperty;
//
// Summary:
// Initializes a new instance of the System.Windows.Controls.Primitives.Thumb class.
public Thumb();
//
// Summary:
// Gets whether the System.Windows.Controls.Primitives.Thumb has logical focus and mouse capture and the left mouse button is pressed.
//
// Returns:
// true if the System.Windows.Controls.Primitives.Thumb has focus and mouse capture; otherwise false. The default is false.
[Bindable(true)]
[Browsable(false)]
[Category("Appearance")]
public bool IsDragging { get; protected set; }
//
// Summary:
// Occurs when the System.Windows.Controls.Primitives.Thumb control receives logical focus and mouse capture.
[Category("Behavior")]
public event DragStartedEventHandler DragStarted;
//
// Summary:
// Occurs one or more times as the mouse position changes when the System.Windows.Controls.Primitives.Thumb control has logical focus and mouse capture.
[Category("Behavior")]
public event DragDeltaEventHandler DragDelta;
//
// Summary:
// Occurs when the System.Windows.Controls.Primitives.Thumb control loses mouse capture.
[Category("Behavior")]
public event DragCompletedEventHandler DragCompleted;
//
// Summary:
// Cancels the drag operation for the System.Windows.Controls.Primitives.Thumb.
public void CancelDrag();
//
// Summary:
// Creates a System.Windows.Automation.Peers.AutomationPeer for the System.Windows.Controls.Primitives.Thumb control.
//
// Returns:
// A System.Windows.Automation.Peers.ThumbAutomationPeer for the System.Windows.Controls.Primitives.Thumb control.
protected override AutomationPeer OnCreateAutomationPeer();
//
// Summary:
// Responds to a change in the value of the System.Windows.Controls.Primitives.Thumb.IsDragging property.
//
// Parameters:
// e:
// The event data.
protected virtual void OnDraggingChanged(DependencyPropertyChangedEventArgs e);
//
// Summary:
// Provides class handling for the System.Windows.ContentElement.MouseLeftButtonDown event.
//
// Parameters:
// e:
// The event data.
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e);
//
// Summary:
// Provides class handling for the System.Windows.ContentElement.MouseLeftButtonUp event.
//
// Parameters:
// e:
// The event data.
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e);
//
// Summary:
// Provides class handling for the System.Windows.UIElement.MouseMove event.
//
// Parameters:
// e:
// The event data.
protected override void OnMouseMove(MouseEventArgs e);
}
}
From the summary above, we can see that the Thumb control provides three events:
- Drag start event: public event DragStartedEventHandler DragStarted;
- Drag progress event: public event DragDeltaEventHandler DragDelta;
- Drag completion event: public event DragCompletedEventHandler DragCompleted;
Thumb Control Example
First, add a Thumb control to the window page, then add three events 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 Example"
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 the three events, with the focus on the DragDelta event, as shown below:
namespace DemoVisio
{
/// <summary>
/// Interaction logic for MainWindow1.xaml
/// </summary>
public partial class MainWindow1 : Window
{
public MainWindow1()
{
InitializeComponent();
}
private void thumb_DragStarted(object sender, DragStartedEventArgs e)
{
// Drag started
}
/// <summary>
/// Drag method
/// </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)
{
// Drag completed
}
}
}
Note that in XAML, the Thumb must be inside a Canvas layout container, and both Canvas.Left="0" and Canvas.Top="0" must be set. If not set, the values will be NaN and dragging won't work.
By default, the Thumb control is just an ugly square, as shown below:

Drag Control Base Class
In this example, the flowchart requires multiple controls (such as circles, rectangles, lines, etc.). It would be inefficient to implement the three events [DragStarted, DragDelta, DragCompleted] for each control. Therefore, a base class ThumbControl is defined to unify the implementation. The details are as follows:
namespace DemoVisio
{
public class ThumbControl : Thumb
{
/// <summary>
/// Whether text input is enabled
/// </summary>
public bool IsEnableInput { get { return (bool)GetValue(IsEnableInputProperty); } set {SetValue( IsEnableInputProperty, value); } }
/// <summary>
/// Dependency property
/// </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>
/// Whether the control has keyboard focus
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ThumbControl_IsKeyboardFocusedChanged(object sender, DependencyPropertyChangedEventArgs e)
{
this.IsEnableInput = this.IsKeyboardFocused;
}
/// <summary>
/// Double-click event
/// </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)
{
// Start moving
}
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)
{
// Move completed
}
}
}
Specific Graphic Controls
After encapsulating the base class, other controls can be used on top of it, displaying different appearances through ControlTemplate, as shown below:
- Rectangle
In a flowchart, rectangles usually represent processes. 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>
- Circle
In a flowchart, circles usually represent start and end points, as shown below:
<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>
- Rhombus
In a flowchart, rhombuses usually represent decisions, expressing boolean values in programs. As shown below:
<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>
- Line
In a flowchart, lines usually represent connections between two processes, as shown below:
<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 Window
The main window is used to draw the flowchart. It is divided into two parts: the left side is the control list, and the right side is the layout container, 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="Flowchart"
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="Rectangle"></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="Rhombus" 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="Line"></TextBlock>
<Ellipse
Width="60"
Height="60"
Fill="AliceBlue"
Stroke="Black"
Margin="5"
x:Name="circle"
MouseLeftButtonDown="circle_MouseLeftButtonDown"
></Ellipse>
<TextBlock Text="Circle"></TextBlock>
</StackPanel>
<canvas Grid.Column="1" x:Name="right"> </canvas>
</Grid>
</Window>
When clicking the basic controls on the left, new controls are generated in the container on the right. As shown below:
namespace DemoVisio
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
/// <summary>
/// List of controls
/// </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;
// Horizontal lines
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;
}
// Reinitialize values
x = 0;
y = 0;
// Vertical lines
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);
}
}
}
Screenshot
The basic screenshot of the example is as follows:

Remarks
The above example is just a basic demonstration, not fully functional. It is intended to provide a starting point for discussion and learning, promoting mutual progress. I hope it can inspire everyone.
贺新郎·九日
【Author】Liu Kezhuang 【Dynasty】Song
Deep dark endless sky. How to bear, slanting wind and fine rain, tangled sorrow like weaving.
Old eyes have seen the world all my life, relying on a hundred-foot tower.
Looking at the vast, autumn colors of a thousand cliffs.
White-haired scholar weeping for the divine land, utterly desolate, not shedding tears at Cow Mountain.
Chasing past events, gone without a trace.
Young, I was proud of my soaring pen. Now, spring flowers have fallen, my heart full of desolation.
Often I resent that people today lack new ideas, fond of talking about the wild guests of the Southern Dynasty.
Bringing out the broken hat year after year.
If I face the yellow flowers without drinking, I fear the yellow flowers will also laugh at my loneliness.
Wild geese fly north, the sun hides in the west.