# AIStudio.Wpf.Diagram **Repository Path**: l-xmim/aistudio.-wpf.-diagram ## Basic Information - **Project Name**: AIStudio.Wpf.Diagram - **Description**: 用Wpf做一个Diagram画板(包含流程图FlowChart,思维导图MindEditor,可编程模块等等) - **Primary Language**: C# - **License**: LGPL-3.0 - **Default Branch**: master - **Homepage**: https://www.cnblogs.com/akwkevin/p/15047453.html - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 291 - **Created**: 2023-09-01 - **Last Updated**: 2023-09-01 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README 本画板在WPF-Diagram-Designer的基础上进行的开发,界面框架使用Fluent.Ribbon的框架。 先上源码地址:https://gitee.com/akwkevin/aistudio.-wpf.-diagram ## 写在前面 友情提示 很多朋友老问为什么编译不过去,您需要安装对应的net版本,或者修改工程的net版本,如下图。 ![输入图片说明](Images/help.png) ## 2023年7月2日更新内容(做一个Block编程画板) 先上一张效果动图,本次更新主要仿照Scratch,目前仅完成拖拽部分,逻辑部分后续完善。 ![输入图片说明](Images/block.gif) ### 本次扩展主要内容: ### 1.Block模块,入口在文件新建下。 ![输入图片说明](Images/block2.png) ### 2.简易Block的使用: ![输入图片说明](Images/block1.png) ### 3.仿Scratch的Block的使用(完成图鉴,准备编写逻辑): ![输入图片说明](Images/block3.png) ## 2023年5月17日更新内容(做一个画笔画板) ![输入图片说明](Images/66.gif) ### 更新内容如下 - [1] 画笔实现 - [2] 封闭画笔实现 - [3] 钢笔实现 - [4] 文字画笔 - [5] 直线,矩形,椭圆 - [6] Path形状 - [7] 取色器 - [8] 三种画笔可选 ### 画笔示例入口 ![输入图片说明](Images/drawingdemo1.png) ![输入图片说明](Images/drawing.png) ### 画笔示例截图 ![输入图片说明](Images/drawingdemo2.png) ## 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 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 ThemeDictionary = new Dictionary(); public static List Themes = new List() { "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); } /// /// 设置主题 /// /// public static void SetCurrentTheme(string theme) { OnAppThemeChanged(theme);//切换到VS2019_Dark CurrentTheme = theme; } /// /// 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.) /// 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); } } } } /// /// Re-define an existing and backup the originial color /// as it was before the application of the custom coloring. /// /// /// 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 _Write; private readonly Action _WriteLine; private readonly Action _WriteCallerInfo; public ConsoleWriter() { } /// /// Console 输出重定向 /// /// 日志方法委托(针对于 Write) /// 日志方法委托(针对于 WriteLine) public ConsoleWriter(Action write, Action writeLine, Action writeCallerInfo) { _Write = write; _WriteLine = writeLine?? write; _WriteCallerInfo = writeCallerInfo; } /// /// Console 输出重定向 /// /// 日志方法委托(针对于 Write) /// 日志方法委托(针对于 WriteLine) public ConsoleWriter(Action write, Action writeLine) { _Write = write; _WriteLine = writeLine; } /// /// Console 输出重定向 /// /// 日志方法委托 public ConsoleWriter(Action write) { _Write = write; _WriteLine = write; } /// /// Console 输出重定向(带调用方信息) /// /// 日志方法委托(后三个参数为 CallerFilePath、CallerMemberName、CallerLineNumber) public ConsoleWriter(Action write) { _WriteCallerInfo = write; } /// /// 使用 UTF-16 避免不必要的编码转换 /// public override Encoding Encoding => Encoding.Unicode; /// /// 最低限度需要重写的方法 /// /// 消息 public override void Write(string value) { if (_WriteCallerInfo != null) { WriteWithCallerInfo(value); return; } _Write(value); } /// /// 为提高效率直接处理一行的输出 /// /// 消息 public override void WriteLine(string value) { if (_WriteCallerInfo != null) { WriteWithCallerInfo(value); return; } _WriteLine(value); } /// /// 带调用方信息进行写消息 /// /// 消息 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> GetPropertyInfo(Type type) { Dictionary> puts = new Dictionary>() { {"Input", new List() }, {"Output", new List() }, {"Input_Output", new List() }, {"Inner", new List() } }; 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) ### 用Wpf做一个画笔画板(续5-Diagram画板)[https://www.cnblogs.com/akwkevin/p/17417546.html](https://www.cnblogs.com/akwkevin/p/17417546.html) ## 捐助 如果您觉得我们的开源软件对你有所帮助,请扫下方二维码打赏我们一杯咖啡,开源不易,感谢您的支持。(可以联系我,在下面添加特别鸣谢,谢谢。) ## 捐赠列表: ![输入图片说明](Images/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20230825164357.png) 相关链接地址: 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。 有想加入开发的朋友可以联系我。