本画板在WPF-Diagram-Designer的基础上进行的开发，界面框架使用Fluent.Ribbon的框架。

先上源码地址：https://gitee.com/akwkevin/aistudio.-wpf.-diagram

## 2023年5月1号更新内容（做一个可编程画板）：

![输入图片说明](Images/55.gif)

### 1.简单使用，自定义一个text模块的代码如下：
```
Code = @"using System;
namespace AIStudio.Wpf.CSharpScript
{
    public class Writer
    {   
        public string StringValue{ get; set;} = ""Welcome to AIStudio.Wpf.Diagram"";

        public string Execute()
        {
            return StringValue;
        }
    }
}";
```
是不是很简单。

### 2.本次扩展的主要内容

【1】.可编程模块，使用C#语言。

【2】.控制台打印控件，可以打印程序中的Console.WriteLine数据

【3】.为了便于大家使用，写了一个Box工厂分配Box的数据流向效果图。

### 3.可编程模块的实现原理
使用Microsoft.CodeAnalysis.CSharp.Scripting对代码进行编译，生成Assembly，然后对Assembly反射获得对象，对象内部固定有一个Execute方法，每次扫描的时候执行即可。
1.编译使用的Using,必须添加引用集，为了省事，把整个程序的Reference都放入进行编译，获得引用的核心代码如下：

```
var references = AppDomain.CurrentDomain.GetAssemblies().Where(p => !p.IsDynamic && !string.IsNullOrEmpty(p.Location)).Select(x => MetadataReference.CreateFromFile(x.Location)).ToList();
//Costura.Fody压缩后，无Location，读取资源文件中的reference
foreach (var assemblyEmbedded in AppDomain.CurrentDomain.GetAssemblies().Where(p => !p.IsDynamic && string.IsNullOrEmpty(p.Location)))
{
    using (var stream = Assembly.GetEntryAssembly().GetManifestResourceStream($"costura.{assemblyEmbedded.GetName().Name.ToLowerInvariant()}.dll.compressed"))
    {
        if (stream != null)
        {
            using (var compressStream = new DeflateStream(stream, CompressionMode.Decompress))
            {
                var memStream = new MemoryStream();
                CopyTo(compressStream, memStream);
                memStream.Position = 0;
                references.Add(MetadataReference.CreateFromStream(memStream));
            }

        }
    }
}
```
2.动态编译的代码的核心代码如下：

```
public static Assembly GenerateAssemblyFromCode(string code, out string message)
{
    Assembly assembly = null;
    message = "";
    // 丛代码中转换表达式树
    SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(code);
    // 随机程序集名称
    string assemblyName = Path.GetRandomFileName();
    // 引用

    // 创建编译对象
    CSharpCompilation compilation = CSharpCompilation.Create(assemblyName, new[] { syntaxTree }, References, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));

    using (var ms = new MemoryStream())
    {
        // 将编译好的IL代码放入内存流
        EmitResult result = compilation.Emit(ms);

        // 编译失败，提示
        if (!result.Success)
        {
            IEnumerable<Diagnostic> failures = result.Diagnostics.Where(diagnostic =>
                        diagnostic.IsWarningAsError ||
                        diagnostic.Severity == DiagnosticSeverity.Error).ToList();
            foreach (Diagnostic diagnostic in failures)
            {
                message += $"{diagnostic.Id}: {diagnostic.GetMessage()}";
                Console.WriteLine(message);
            }
        }
        else
        {
            // 编译成功，从内存中加载编译好的程序集
            ms.Seek(0, SeekOrigin.Begin);
            assembly = Assembly.Load(ms.ToArray());
        }
    }
    return assembly;
}
```
3.获得编译后的程序集，以及执行。  
```
// 反射获取程序集中 的类
Type type = assembly.GetTypes().FirstOrDefault(p => p.FullName.StartsWith("AIStudio.Wpf"));   //assembly.GetType("AIStudio.Wpf.CSharpScript.Write");

// 创建该类的实例
object obj = Activator.CreateInstance(type);

// 通过反射方式调用类中的方法。
var result = type.InvokeMember("Execute",
    BindingFlags.Default | BindingFlags.InvokeMethod,
    null,
    obj,
    new object[] { });
```
### 4.代码编辑模块的实现
选择AvalonEdit控件，另外为了使用VS2019_Dark的黑色皮肤，引用官方Demo中的HL和TextEditlib实现自定义换肤。

![输入图片说明](Images/editor.png)

官方Demo的换肤写的超级复杂，看不懂，但是我们只要理解换肤的核心部分就是动态资源字典，因此我简化下，改进后的核心换肤代码如下：

```
public class TextEditorThemeHelper
{
    static Dictionary<string, ResourceDictionary> ThemeDictionary = new Dictionary<string, ResourceDictionary>();

    public static List<string> Themes = new List<string>() { "Dark", "Light", "TrueBlue", "VS2019_Dark" };
    public static string CurrentTheme { get; set; }

    static TextEditorThemeHelper()
    {
        var resource = new ResourceDictionary { Source = new Uri("/TextEditLib;component/Themes/LightBrushs.xaml", UriKind.RelativeOrAbsolute) };
        ThemeDictionary.Add("Light", resource);

        resource = new ResourceDictionary { Source = new Uri("/TextEditLib;component/Themes/DarkBrushs.xaml", UriKind.RelativeOrAbsolute) };
        ThemeDictionary.Add("Dark", resource);

        Application.Current.Resources.MergedDictionaries.Add(resource);
    }

    /// <summary>
    /// 设置主题
    /// </summary>
    /// <param name="theme"></param>
    public static void SetCurrentTheme(string theme)
    {
        OnAppThemeChanged(theme);//切换到VS2019_Dark
        CurrentTheme = theme;
    }

    /// <summary>
    /// Invoke this method to apply a change of theme to the content of the document
    /// (eg: Adjust the highlighting colors when changing from "Dark" to "Light"
    ///      WITH current text document loaded.)
    /// </summary>
    internal static void OnAppThemeChanged(string theme)
    {
        ThemedHighlightingManager.Instance.SetCurrentTheme(theme);

        if (ThemeDictionary.ContainsKey(theme))
        {
            foreach (var key in ThemeDictionary[theme].Keys)
            {
                ApplyToDynamicResource(key, ThemeDictionary[theme][key]);
            }
        }
        // Does this highlighting definition have an associated highlighting theme?
        else if (ThemedHighlightingManager.Instance.CurrentTheme.HlTheme != null)
        {
            // A highlighting theme with GlobalStyles?
            // Apply these styles to the resource keys of the editor
            foreach (var item in ThemedHighlightingManager.Instance.CurrentTheme.HlTheme.GlobalStyles)
            {
                switch (item.TypeName)
                {
                    case "DefaultStyle":
                        ApplyToDynamicResource(TextEditLib.Themes.ResourceKeys.EditorBackground, item.backgroundcolor);
                        ApplyToDynamicResource(TextEditLib.Themes.ResourceKeys.EditorForeground, item.foregroundcolor);
                        break;

                    case "CurrentLineBackground":
                        ApplyToDynamicResource(TextEditLib.Themes.ResourceKeys.EditorCurrentLineBackgroundBrushKey, item.backgroundcolor);
                        ApplyToDynamicResource(TextEditLib.Themes.ResourceKeys.EditorCurrentLineBorderBrushKey, item.bordercolor);
                        break;

                    case "LineNumbersForeground":
                        ApplyToDynamicResource(TextEditLib.Themes.ResourceKeys.EditorLineNumbersForeground, item.foregroundcolor);
                        break;

                    case "Selection":
                        ApplyToDynamicResource(TextEditLib.Themes.ResourceKeys.EditorSelectionBrush, item.backgroundcolor);
                        ApplyToDynamicResource(TextEditLib.Themes.ResourceKeys.EditorSelectionBorder, item.bordercolor);
                        break;

                    case "Hyperlink":
                        ApplyToDynamicResource(TextEditLib.Themes.ResourceKeys.EditorLinkTextBackgroundBrush, item.backgroundcolor);
                        ApplyToDynamicResource(TextEditLib.Themes.ResourceKeys.EditorLinkTextForegroundBrush, item.foregroundcolor);
                        break;

                    case "NonPrintableCharacter":
                        ApplyToDynamicResource(TextEditLib.Themes.ResourceKeys.EditorNonPrintableCharacterBrush, item.foregroundcolor);
                        break;

                    default:
                        throw new System.ArgumentOutOfRangeException("GlobalStyle named '{0}' is not supported.", item.TypeName);
                }
            }
        }

    }

    /// <summary>
    /// Re-define an existing <seealso cref="SolidColorBrush"/> and backup the originial color
    /// as it was before the application of the custom coloring.
    /// </summary>
    /// <param name="key"></param>
    /// <param name="newColor"></param>
    private static void ApplyToDynamicResource(ComponentResourceKey key, Color? newColor)
    {
        if (Application.Current.Resources[key] == null || newColor == null)
            return;

        // Re-coloring works with SolidColorBrushs linked as DynamicResource
        if (Application.Current.Resources[key] is SolidColorBrush)
        {
            //backupDynResources.Add(resourceName);

            var newColorBrush = new SolidColorBrush((Color)newColor);
            newColorBrush.Freeze();

            Application.Current.Resources[key] = newColorBrush;
        }
    }

    private static void ApplyToDynamicResource(object key, object newValue)
    {
        if (Application.Current.Resources[key] == null || newValue == null)
            return;

        Application.Current.Resources[key] = newValue;
    }
}
```
使用方法：
TextEditorThemeHelper.SetCurrentTheme("VS2019_Dark");

或者
TextEditorThemeHelper.SetCurrentTheme("TrueBlue");

或者
TextEditorThemeHelper.SetCurrentTheme("Dark");

或者
TextEditorThemeHelper.SetCurrentTheme("Light");

是不是超级简单。

### 5.代码编辑模块的编译与测试。

![输入图片说明](Images/build.png)

![输入图片说明](Images/test.png)

### 6.WPF打印控制台数据

```
控制台打印方法支持切换运行输出方法Console.SetOut，核心代码如下：
public class ConsoleWriter : TextWriter
{
    private readonly Action<string> _Write;
    private readonly Action<string> _WriteLine;
    private readonly Action<string, string, string, int> _WriteCallerInfo;

    public ConsoleWriter()
    {

    }

    /// <summary>
    /// Console 输出重定向
    /// </summary>
    /// <param name="write">日志方法委托（针对于 Write）</param>
    /// <param name="writeLine">日志方法委托（针对于 WriteLine）</param>
    public ConsoleWriter(Action<string> write, Action<string> writeLine, Action<string, string, string, int> writeCallerInfo)
    {
        _Write = write;
        _WriteLine = writeLine?? write;
        _WriteCallerInfo = writeCallerInfo;
    }

    /// <summary>
    /// Console 输出重定向
    /// </summary>
    /// <param name="write">日志方法委托（针对于 Write）</param>
    /// <param name="writeLine">日志方法委托（针对于 WriteLine）</param>
    public ConsoleWriter(Action<string> write, Action<string> writeLine)
    {
        _Write = write;
        _WriteLine = writeLine;
    }

    /// <summary>
    /// Console 输出重定向
    /// </summary>
    /// <param name="write">日志方法委托</param>
    public ConsoleWriter(Action<string> write)
    {
        _Write = write;
        _WriteLine = write;
    }

    /// <summary>
    /// Console 输出重定向（带调用方信息）
    /// </summary>
    /// <param name="write">日志方法委托（后三个参数为 CallerFilePath、CallerMemberName、CallerLineNumber）</param>
    public ConsoleWriter(Action<string, string, string, int> write)
    {
        _WriteCallerInfo = write;
    }

    /// <summary>
    /// 使用 UTF-16 避免不必要的编码转换
    /// </summary>
    public override Encoding Encoding => Encoding.Unicode;

    /// <summary>
    /// 最低限度需要重写的方法
    /// </summary>
    /// <param name="value">消息</param>
    public override void Write(string value)
    {
        if (_WriteCallerInfo != null)
        {
            WriteWithCallerInfo(value);
            return;
        }

        _Write(value);
    }

    /// <summary>
    /// 为提高效率直接处理一行的输出
    /// </summary>
    /// <param name="value">消息</param>
    public override void WriteLine(string value)
    {
        if (_WriteCallerInfo != null)
        {
            WriteWithCallerInfo(value);
            return;
        }

        _WriteLine(value);
    }

    /// <summary>
    /// 带调用方信息进行写消息
    /// </summary>
    /// <param name="value">消息</param>
    private void WriteWithCallerInfo(string value)
    {
        //3、System.Console.WriteLine -> 2、System.IO.TextWriter + SyncTextWriter.WriteLine -> 1、DotNet.Utilities.ConsoleHelper.ConsoleWriter.WriteLine -> 0、DotNet.Utilities.ConsoleHelper.ConsoleWriter.WriteWithCallerInfo
        var callInfo = ClassHelper.GetMethodInfo(4);
        _WriteCallerInfo(value, callInfo?.FileName, callInfo?.MethodName, callInfo?.LineNumber ?? 0);
    }

    public override void Close()
    {
        var standardOutput = new StreamWriter(Console.OpenStandardOutput());
        standardOutput.AutoFlush = true;
        Console.SetOut(standardOutput);
        base.Close();
    }
}
```
使用： 

ConsoleWriter ConsoleWriter = new ConsoleWriter(_write, _writeLine);

Console.SetOut(ConsoleWriter);
      
### 7.动态编译模块的输入输出自动生成。
1.输入输出模块：public string Value{ get; set;}
2.输入模块：public string Value{private get; set;}
3.输出模块：public string Value{get;private set;}
4.与外部交互模块：private string Value{ get; set;} ,必须同名同属性。
核心代码如下：

```
public static Dictionary<string, List<PropertyInfo>> GetPropertyInfo(Type type)
{
    Dictionary<string, List<PropertyInfo>> puts = new Dictionary<string, List<PropertyInfo>>()
    {
        {"Input", new List<PropertyInfo>() },
        {"Output", new List<PropertyInfo>() },
        {"Input_Output", new List<PropertyInfo>() },
        {"Inner", new List<PropertyInfo>() }
    };

    try
    {
        foreach (System.Reflection.PropertyInfo info in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
        {
            if (info.CanRead && info.CanWrite)
            {
                if (info.SetMethod.IsPublic && info.GetMethod.IsPublic)
                {
                    puts["Input_Output"].Add(info);
                }
                else if (info.SetMethod.IsPublic)
                {
                    puts["Input"].Add(info);
                }
                else if (info.GetMethod.IsPublic)
                {
                    puts["Output"].Add(info);
                }
            }
            else if (info.CanRead)
            {
                if (info.GetMethod.IsPublic)
                {
                    puts["Output"].Add(info);
                }
            }
        }

        foreach (System.Reflection.PropertyInfo info in type.GetProperties(BindingFlags.NonPublic | BindingFlags.Instance))
        {
            if (info.CanRead)
            {
                puts["Inner"].Add(info);
            }
        }
    }
    catch (Exception ex)
    {

    }

    return puts;
}
```

### 8.最后介绍一下Demo的实现。
1#.Int整数模块，界面定义一个TextBox绑定Int模块的输入管脚。
2#.Box产生模块，如果内部数组为空，那么按照输入管脚的数量初始化一个容量为输入整数数量的数组（随机颜色与形状），然后把数据放到输出管脚，当数据被取走后，下一个数据再次放到输出管脚。
3#.Bool模块，为false的时候按照颜色进行分配，为true的时候按照形状进行分配。
4#.Box分配模块，当输入管脚为空的时候，2#模块的输出可以移动到4#的输入管脚，移动时间为1s，移动完成后，清除2#模块的输出。同时把数据按照颜色或者形状分配到输出，同时把输入管脚清除。
按照颜色分配时：
（1.如果颜色为红色，那么输出到1号
（2.如果颜色为橙色，那么输出到2号
（3.如果颜色为黄色，那么输出到3号
（4.如果颜色为绿色，那么输出到4号
（5.如果颜色为青色，那么输出到5号
（6.如果颜色为蓝色，那么输出到6号
（7.如果颜色为紫色，那么输出到7号
按照形状分配时：
（1.如果形状为圆形，那么输出到1号
（2.如果形状为三角形，那么输出到2号
（3.如果形状为方形，那么输出到3号
（4.如果形状为菱形，那么输出到4号
（5.如果形状为梯形，那么输出到5号
（6.如果形状为五角星，那么输出到6号
（7.如果形状为六边形，那么输出到7号
6#.有两个红色|圆形收集器（7#，8#），按两个容器中的数量比较反馈，均匀分配到这两个收集器中。
9#，10#，11#，12#，13#，14#按照管脚取走数据即可。


## 2023年4月5号更新内容（本次更新主要仿照百度脑图）：
### 1.思维导图、目录组织图、鱼骨头图、逻辑结构图、组织结构图，入口在文件新建下。

![输入图片说明](Images/c022f4a4-f474-4a94-8730-22f7eadda2c4.png)

### 2.思维导图工具栏（只有思维导图模式下可见）

![输入图片说明](Images/a16723ba-2e88-4391-b9b0-5361ecc7741b.png)

#### 2.1插入链接

![输入图片说明](Images/3db9354c-3a9d-4c66-940a-cedb2d776022.png)

#### 2.2插入图片

![输入图片说明](Images/761b291d-472c-4883-8c17-6e6466df0d0b.png)

#### 2.3插入备注

![输入图片说明](Images/cab534e3-9230-47ce-b1aa-2471a3d7f6b2.png)

#### 2.4插入优先级

![输入图片说明](Images/c99448f6-2342-4a19-a263-3723df7bdda7.png)

#### 2.5插入进度

![输入图片说明](Images/47ccfe90-9156-43e5-8d19-fe8520979fba.png)

#### 2.6切换类型

![输入图片说明](Images/34506c6e-0318-4408-80f1-49a2f1bddf0d.png)
![输入图片说明](Images/2d05d1a4-752a-4789-a98c-a16bc8da0733.png)
![输入图片说明](Images/4d22ce80-0521-4bd7-85d9-eab8a442cc12.png)
![输入图片说明](Images/cefd98ca-3809-48f1-9e23-a8f408046f2d.png)

#### 2.7切换主题

![输入图片说明](Images/1cf93bdc-c9bb-4b59-b385-4bd87b6c9d8a.png)
![输入图片说明](Images/5341f7e9-4e13-4c16-a137-82502ee57483.png)
![输入图片说明](Images/7e7dc7b9-2bcf-4bf9-8411-87d0aec9596a.png)
![输入图片说明](Images/07137f85-c85d-4712-9f4b-ba0ec2358565.png)
![输入图片说明](Images/d2b38816-aae2-438d-b89c-ba86225c57b4.png)
![输入图片说明](Images/33ce6c3e-3840-439e-b713-c0278671ba9a.png)

#### 2.8还有展开节点，全选，居中，适应窗体大小等功能，不在介绍。

### 3 添加搜索功能（不仅仅思维导图可以使用）

![输入图片说明](Images/08a182af-6eaa-4291-9007-3017ec6b2995.png)

### 4 MindEditor：最后为了方便大家使用，我封装了一个思维脑图的控件MindEditor，可以直接绑定json格式的数据，数据改变，可以直接加载应用。（见[AIStudio.Wpf.DiagramDesigner.Demo](https://gitee.com/akwkevin/aistudio.-wpf.-diagram/tree/master/Demos/AIStudio.Wpf.DiagramDesigner.Demo)）

![输入图片说明](Images/8a8d5ead-a780-4b06-92d4-09dd1677c2fb.png)

## 2023年2月5号更新内容：
本次更新主要参照了一个Blazor的Diagram的画线算法，链接地址:[https://github.com/Blazor-Diagrams/Blazor.Diagrams](https://github.com/Blazor-Diagrams/Blazor.Diagrams),感谢作者。

### 1.连线改进，新增连线算法，目前共4种连线：Smooth（曲线），Straight（直线），Boundary（网格边界连接模式），Corner（折线）
### 2.序列化改进，xml与json序列化，新增自定义元素后，无需更改根元素，只需要在新增的元素上添加序列化的对象即可，扩展性更灵活了。
### 3.箭头改进，箭头按照连线的实际角度显示（即0-360度），还支持自定义的箭头path。
### 4.新增快捷键自定义扩展，用户可根据自己的习惯定义快捷键。
### 5.FlowchartEditor：封装了一个标准的工作流控件FlowchartEditor，具体使用可以参照开源权限管理框架种的用法： [https://gitee.com/akwkevin/aistudio.-wpf.-aclient](https://gitee.com/akwkevin/aistudio.-wpf.-aclient)

nuget地址：![输入图片说明](Images/nuget.png)

### 6.连接上添加动画：路径动画效果和线条流动效果。
### 7.改变结构，使用户更容易自定义自己的样式，覆盖系统默认样式。
### 8.从Blazor.Diagrams种引入PortlessLinks（直接连接两个node，不需要port），自动连接节点Snapping，按距离最近连接ReconnectLinksToClosestPorts
### 9.新增Demo示例，帮助用户快速上手，见底部2023年2月5号更新附加说明。

## 2023年之前发布内容：
界面图：

本画板在WPF-Diagram-Designer进行的开发，界面框架使用Fluent.Ribbon的框架。

界面图：
![输入图片说明](https://images.gitee.com/uploads/images/2021/0723/095141_dbfbe652_4799126.png "4.png")

### 1.支持字体样式，字体颜色，字体阴影，对齐方向，行间距。

### 2.支持复制粘贴剪贴，格式化，撤销重做。

### 3.支持形状绘制。

### 4.连接线

### 5.位置，组合，对齐

### 6.元素翻转，旋转。

### 7.填充颜色，支持线性渐变色，径向渐变色等

### 8.支持箭头样式

### 9.锁定与解锁

### 10.快速样式

### 11.支持矢量文本，二维码

### 12.支持插入图片，视频，SVG

### 13.支持画板大小，方向，标尺，网格是否显示，画板背景色

### 14.支持流程图（在文件新建下-基本绘图-流程图）

![输入图片说明](https://images.gitee.com/uploads/images/2021/0723/095156_62819165_4799126.png "1.png")

![输入图片说明](https://images.gitee.com/uploads/images/2021/0723/095209_6cdce752_4799126.png "2.png")

### 15支持逻辑图（在文件新建下-基本绘图-逻辑图）

![输入图片说明](https://images.gitee.com/uploads/images/2021/0723/095226_36478a7b_4799126.png "3.png")

### 16支持SFC顺序控制图（在文件新建下-基本绘图-顺序控制图）

![输入图片说明](https://images.gitee.com/uploads/images/2021/0802/215025_4aa32824_4799126.png "2 (2).png")

### 示例项目说明（[AIStudio.Wpf.DiagramDesigner.Demo](https://gitee.com/akwkevin/aistudio.-wpf.-diagram/tree/master/Demos/AIStudio.Wpf.DiagramDesigner.Demo)）目录如下：

#### 1 Simple 简单示例

![输入图片说明](Images/image.png)
#### 2 Locked 锁定节点
#### 3 Events 事件（暂未完成，敬请期待）
#### 4 DynamicInsertions 动态插入（暂未完成，敬请期待）
#### 5 Performance 性能（100个节点生成）
#### 6 Zoom 放大缩小
#### 7 SnapToGrid 对齐到网格

![输入图片说明](Images/snaptogrid.png)
#### 8 DragAndDrop 拖拽

![输入图片说明](Images/draganddrop.png)
#### 9 Nodes 节点示例
##### 9.1 Svg svg样式

![输入图片说明](Images/svg.png)
##### 9.2 CustomDefinedNode 自定义节点

![输入图片说明](Images/customdefinednode.png)
##### 9.3 PortlessLinks 无Port的node连接

![输入图片说明](Images/portlesslink.png)
##### 9.4 GradientNode 渐变色node

![输入图片说明](Images/gradinetnode.png)
##### 9.5 Rotate 旋转node（连接线还需要优化，还算连接在旋转之前的位置上）

![输入图片说明](Images/rotate.png)
#### 10 Links 连线示例
##### 10.1 Snapping 连接线靠近节点自动连接
##### 10.2 Labels 连接线上的文字（支持多处）

![输入图片说明](Images/labels.png)
##### 10.3 Vertices 连接线上的中间节点

![输入图片说明](Images/vertices.png)
##### 10.4 Markers 箭头，支持自定义

![输入图片说明](Images/markers.png)
##### 10.5 Routers 连线模式

![输入图片说明](Images/routers.png)
##### 10.6 PathGenerators 连线算法

![输入图片说明](Images/pathgenerators.png)
#### 11 Ports 连接点示例
##### 11.1 ColoredPort 彩色连接点，相同颜色的连接点才能连接

![输入图片说明](Images/coloredport.png)
##### 11.2 InnerPort 内部连接点

![输入图片说明](Images/innerport.png)
#### 12 Groups 分组示例
##### 12.1 Group 分组
##### 12.2 CustomDefinedGroup 自定义分组
##### 12.3 CustomShortcutGroup 自定义分组快捷键
#### 13 Texts 文本节点示例
##### 13.1 Text 文本
##### 13.2 Alignment 对齐方式
##### 13.3 FontSize 字体大小
##### 13.4 ColorText 彩色字体

![输入图片说明](Images/colortext.png)
##### 13.5 OutlineText 轮廓文本
#### 14 Customization 自定义
##### 14.1 CustomNode 覆盖默认节点样式

![输入图片说明](Images/customnode.png)
##### 14.2 CustomLink 设置线条连接样式

![输入图片说明](Images/customlink.png)
##### 14.3 CustomPort 覆盖默认连接点样式

![输入图片说明](Images/customport.png)
##### 14.4 CustomGroup 覆盖默认分组样式

![输入图片说明](Images/customgroup.png)
#### 15 Algorithms 算法
##### 14.6 ReconnectLinksToClosestPorts 重新计算，按最近的连接点连接。
#### 15 Animations
##### 15.1 PathAnimation 动画路径
![输入图片说明](Images/11.gif)
##### 15.2 LineAnimation 线条流动动画
![输入图片说明](Images/22.gif)
#### 16 Editor
##### 16.1 FlowchartEditor 工作流封装控件
采用兼容主流的diagram的序列化格式

![输入图片说明](Images/flowcharteditor.png)

`{"Nodes":[{"Kind":1,"UserIds":null,"RoleIds":null,"ActType":null,"Id":"e0f2c29c-2c89-4c0c-857e-35eb0b121d7e","ParentId":null,"Name":null,"Color":"#1890ff","Label":"开始","Width":100.0,"Height":80.0,"X":12.5,"Y":147.5,"Type":"FlowchartNode","ZIndex":0,"PortAlignmentList":["Top","Bottom","Left","Right","Top","Bottom","Left","Right"]},{"Kind":3,"UserIds":[],"RoleIds":[],"ActType":null,"Id":"716f64ec-bcdb-438c-9748-9546abf990cc","ParentId":null,"Name":null,"Color":"#1890ff","Label":"节点1","Width":100.0,"Height":80.0,"X":137.5,"Y":147.5,"Type":"FlowchartNode","ZIndex":2,"PortAlignmentList":["Top","Bottom","Left","Right","Top","Bottom","Left","Right"]},{"Kind":4,"UserIds":null,"RoleIds":null,"ActType":null,"Id":"3cd6c332-6b5b-44ef-96c4-c7aef66fd5dd","ParentId":null,"Name":null,"Color":"#1890ff","Label":"条件节点","Width":100.0,"Height":80.0,"X":262.5,"Y":147.5,"Type":"FlowchartNode","ZIndex":3,"PortAlignmentList":["Top","Bottom","Left","Right","Top","Bottom","Left","Right"]},{"Kind":3,"UserIds":[],"RoleIds":[],"ActType":null,"Id":"7d953234-ddff-4701-a52a-bf6460ffa7b9","ParentId":null,"Name":null,"Color":"#1890ff","Label":"节点2","Width":100.0,"Height":80.0,"X":387.5,"Y":22.5,"Type":"FlowchartNode","ZIndex":6,"PortAlignmentList":["Top","Bottom","Left","Right","Top","Bottom","Left","Right"]},{"Kind":3,"UserIds":[],"RoleIds":[],"ActType":null,"Id":"7dfd4102-2751-42c7-a386-adcfcca27ede","ParentId":null,"Name":null,"Color":"#1890ff","Label":"节点3","Width":100.0,"Height":80.0,"X":387.5,"Y":272.5,"Type":"FlowchartNode","ZIndex":7,"PortAlignmentList":["Top","Bottom","Left","Right","Top","Bottom","Left","Right"]},{"Kind":2,"UserIds":null,"RoleIds":null,"ActType":null,"Id":"ad57e53f-8860-4212-9afb-f67e14eecbc8","ParentId":null,"Name":null,"Color":"#1890ff","Label":"结束","Width":100.0,"Height":80.0,"X":512.5,"Y":147.5,"Type":"FlowchartNode","ZIndex":10,"PortAlignmentList":["Top","Bottom","Left","Right","Top","Bottom","Left","Right"]}],"Links":[{"Id":"65f6432f-2084-462d-93d8-a6b3ff889182","Color":"#FF808080","SelectedColor":"#FF000000","Width":2.0,"Label":null,"SourceId":"e0f2c29c-2c89-4c0c-857e-35eb0b121d7e","TargetId":"716f64ec-bcdb-438c-9748-9546abf990cc","SourcePortAlignment":"Right","TargetPortAlignment":"Left","Type":"DiagramLink","Router":null,"PathGenerator":null,"SourceMarkerPath":null,"SourceMarkerWidth":null,"TargetMarkerPath":null,"TargetMarkerWidth":null},{"Id":"7d1dcf2d-ee69-4c24-84ff-3a99b6555692","Color":"#FF808080","SelectedColor":"#FF000000","Width":2.0,"Label":null,"SourceId":"716f64ec-bcdb-438c-9748-9546abf990cc","TargetId":"3cd6c332-6b5b-44ef-96c4-c7aef66fd5dd","SourcePortAlignment":"Right","TargetPortAlignment":"Left","Type":"DiagramLink","Router":null,"PathGenerator":null,"SourceMarkerPath":null,"SourceMarkerWidth":null,"TargetMarkerPath":null,"TargetMarkerWidth":null},{"Id":"cd18c02f-0cdb-4eb5-9793-b9db87eeea09","Color":"#FF808080","SelectedColor":"#FF000000","Width":2.0,"Label":"条件1","SourceId":"3cd6c332-6b5b-44ef-96c4-c7aef66fd5dd","TargetId":"7d953234-ddff-4701-a52a-bf6460ffa7b9","SourcePortAlignment":"Top","TargetPortAlignment":"Left","Type":"DiagramLink","Router":null,"PathGenerator":null,"SourceMarkerPath":null,"SourceMarkerWidth":null,"TargetMarkerPath":null,"TargetMarkerWidth":null},{"Id":"69bbb083-8eb4-403b-937a-b0f0d3c80eb0","Color":"#FF808080","SelectedColor":"#FF000000","Width":2.0,"Label":"条件2","SourceId":"3cd6c332-6b5b-44ef-96c4-c7aef66fd5dd","TargetId":"7dfd4102-2751-42c7-a386-adcfcca27ede","SourcePortAlignment":"Bottom","TargetPortAlignment":"Left","Type":"DiagramLink","Router":null,"PathGenerator":null,"SourceMarkerPath":null,"SourceMarkerWidth":null,"TargetMarkerPath":null,"TargetMarkerWidth":null},{"Id":"d640c547-5ba8-428c-8d65-74874b1d28bd","Color":"#FF808080","SelectedColor":"#FF000000","Width":2.0,"Label":null,"SourceId":"7d953234-ddff-4701-a52a-bf6460ffa7b9","TargetId":"ad57e53f-8860-4212-9afb-f67e14eecbc8","SourcePortAlignment":"Right","TargetPortAlignment":"Top","Type":"DiagramLink","Router":null,"PathGenerator":null,"SourceMarkerPath":null,"SourceMarkerWidth":null,"TargetMarkerPath":null,"TargetMarkerWidth":null},{"Id":"74ad5635-c96d-42e8-9c0a-42c613c66b7a","Color":"#FF808080","SelectedColor":"#FF000000","Width":2.0,"Label":null,"SourceId":"7dfd4102-2751-42c7-a386-adcfcca27ede","TargetId":"ad57e53f-8860-4212-9afb-f67e14eecbc8","SourcePortAlignment":"Right","TargetPortAlignment":"Bottom","Type":"DiagramLink","Router":null,"PathGenerator":null,"SourceMarkerPath":null,"SourceMarkerWidth":null,"TargetMarkerPath":null,"TargetMarkerWidth":null}]}`

近期会持续更新，欢迎大家光临。
##### 最后上一个动画流程图。
![输入图片说明](Images/33.gif)

## 特别说明

### 博客园文章地址 [https://www.cnblogs.com/akwkevin/p/15047453.html](https://www.cnblogs.com/akwkevin/p/15047453.html)

### 用Wpf做一个Diagram画板（续2）（包含封装一个控件FlowchartEditor）： [https://www.cnblogs.com/akwkevin/p/17093865.html](https://www.cnblogs.com/akwkevin/p/17093865.html)

### 用Wpf做一个思维导图（续3-Diagram画板）[https://www.cnblogs.com/akwkevin/p/17288814.html](https://www.cnblogs.com/akwkevin/p/17288814.html)

### 用Wpf做一个可编程画板（续4-Diagram画板）[https://www.cnblogs.com/akwkevin/p/17367212.html](https://www.cnblogs.com/akwkevin/p/17367212.html)

    相关链接地址：

    Fluent.Ribbon: https://github.com/fluentribbon/Fluent.Ribbon

    WPF-Diagram-Designer:https://github.com/LinRaise/WPF-Diagram-Designer

    个人QQ:80267720

    QQ技术交流群:51286643（如果您还喜欢，帮忙点个星，谢谢）

    特别感谢：https://dotnet9.com/ 的站长：dotnet9。

    有想加入开发的朋友可以联系我。