在好例子网,分享、交流、成长!
您当前所在位置:首页C# 开发实例C#图形和图像处理 → C#流程框架(WorkflowDesigner)

C#流程框架(WorkflowDesigner)

C#图形和图像处理

下载此实例
  • 开发语言:C#
  • 实例大小:0.12M
  • 下载次数:7
  • 浏览次数:15
  • 发布时间:2026-08-11
  • 实例类别:C#图形和图像处理
  • 发 布 人:陈六弟
  • 文件格式:.zip
  • 所需积分:2
 相关标签: Designer 流程 框架 C# Workflow

实例介绍

【实例简介】

【实例截图】

from clipboard

【核心代码】

using System; // 提供事件和基础类型。
using System.Drawing; // 提供界面颜色、字体及图标绘制类型。
using System.IO; // 提供流程文件读写功能。
using System.Linq; // 提供文档数据校验所需的集合查询。
using System.Text; // 提供 UTF-8 文本编码类型。
using System.Web.Script.Serialization; // 提供 .NET Framework 内置 JSON 序列化器。
using System.Windows.Forms; // 提供 WinForms 主界面控件类型。
namespace WorkflowDesigner // 隔离应用程序主界面类型。
{ // 开始定义主界面命名空间。
    public sealed class MainForm : Form // 定义流程设计器主窗体。
    { // 开始定义主窗体成员。
        private readonly WorkflowCanvas canvas; // 保存中心流程设计画布。
        private readonly PropertyGrid propertyGrid; // 保存右侧对象属性编辑器。
        private readonly ListView toolbox; // 保存左侧可拖放节点工具箱。
        private readonly ToolStripButton selectButton; // 保存选择模式切换按钮。
        private readonly ToolStripButton connectButton; // 保存连线模式切换按钮。
        private readonly ToolStripStatusLabel statusLabel; // 保存底部操作提示文本。
        private readonly JavaScriptSerializer serializer; // 保存 JSON 文档序列化器。
        private readonly ScriptRuntime scriptRuntime; // 保存 Roslyn C# 脚本运行服务。
        private string currentFile; // 保存当前文档对应的磁盘路径。
        private bool dirty; // 标记当前文档是否存在未保存修改。
        public MainForm() // 构建并连接流程设计器界面。
        { // 开始初始化主窗体。
            Text = "工作流程图设计器"; // 设置窗口标题。
            StartPosition = FormStartPosition.CenterScreen; // 在屏幕中央首次显示窗口。
            MinimumSize = new Size(1000, 650); // 防止布局缩小到不可操作尺寸。
            Size = new Size(1380, 820); // 提供舒适的默认设计空间。
            KeyPreview = true; // 允许窗体优先处理删除和取消快捷键。
            serializer = new JavaScriptSerializer { MaxJsonLength = int.MaxValue }; // 支持保存较大的流程文档。
            scriptRuntime = new ScriptRuntime(); // 创建可复用的 Roslyn 脚本运行服务。
            canvas = new WorkflowCanvas { Dock = DockStyle.Fill }; // 创建填满中心区域的画布。
            propertyGrid = new PropertyGrid { Dock = DockStyle.Fill, HelpVisible = true, ToolbarVisible = true }; // 创建带说明区的属性面板。
            toolbox = CreateToolbox(); // 创建可发起拖放的节点工具箱。
            ToolStrip toolStrip = CreateToolStrip(out selectButton, out connectButton); // 创建顶部命令工具栏。
            statusLabel = new ToolStripStatusLabel(); // 创建可动态更新的状态标签。
            StatusStrip statusStrip = new StatusStrip(); // 创建底部状态栏容器。
            statusStrip.Items.Add(statusLabel); // 将提示标签加入状态栏。
            SplitContainer outerSplit = CreateWorkspace(); // 创建工具箱、画布和属性区布局。
            Controls.Add(outerSplit); // 将主要工作区加入窗体。
            Controls.Add(statusStrip); // 将状态栏加入窗体底部。
            Controls.Add(toolStrip); // 将工具栏加入窗体顶部。
            MainMenuStrip = CreateMenu(); // 创建菜单并启用标准快捷键。
            Controls.Add(MainMenuStrip); // 将主菜单加入窗体控件集合。
            WireEvents(); // 连接界面交互事件。
            NewDocument(false); // 创建初始空白流程文档。
        } // 结束初始化主窗体。
        private ListView CreateToolbox() // 创建并配置节点工具箱列表。
        { // 开始创建节点工具箱。
            ListView list = new ListView(); // 创建工具箱列表控件。
            list.Dock = DockStyle.Fill; // 让列表填满左侧面板。
            list.View = View.Tile; // 使用便于拖动的大项目样式。
            list.TileSize = new Size(170, 50); // 设置舒适的工具项尺寸。
            list.MultiSelect = false; // 限制每次只能拖动一种节点。
            list.HideSelection = false; // 保持当前工具项的视觉反馈。
            list.BackColor = Color.FromArgb(248, 250, 252); // 使用浅色工具区背景。
            list.Items.Add(CreateToolItem("开始", NodeKind.Start)); // 添加开始节点工具。
            list.Items.Add(CreateToolItem("处理", NodeKind.Process)); // 添加处理节点工具。
            list.Items.Add(CreateToolItem("判断", NodeKind.Decision)); // 添加判断节点工具。
            list.Items.Add(CreateToolItem("C# 脚本", NodeKind.Script)); // 添加 Roslyn 动态脚本节点工具。
            list.Items.Add(CreateToolItem("结束", NodeKind.End)); // 添加结束节点工具。
            return list; // 返回配置完成的工具箱。
        } // 结束创建节点工具箱。
        private static ListViewItem CreateToolItem(string text, NodeKind kind) // 创建携带节点类型的工具项。
        { // 开始创建工具项。
            ListViewItem item = new ListViewItem(text); // 使用中文节点名称创建项目。
            item.Tag = kind; // 保存拖放时需要的节点类型。
            item.ToolTipText = "拖到中间画布创建"   text   "节点"; // 提供具体操作提示。
            return item; // 返回配置完成的工具项。
        } // 结束创建工具项。
        private ToolStrip CreateToolStrip(out ToolStripButton select, out ToolStripButton connect) // 创建常用命令工具栏。
        { // 开始创建工具栏。
            ToolStrip strip = new ToolStrip(); // 创建顶部工具栏容器。
            strip.GripStyle = ToolStripGripStyle.Hidden; // 隐藏不必要的拖动手柄。
            strip.Padding = new Padding(6, 4, 6, 4); // 增加工具按钮周围留白。
            strip.Items.Add(CreateCommandButton("新建", (sender, args) => NewDocument(true))); // 添加新建文档命令。
            strip.Items.Add(CreateCommandButton("打开", (sender, args) => OpenDocument())); // 添加打开文档命令。
            strip.Items.Add(CreateCommandButton("保存", (sender, args) => SaveDocument(false))); // 添加保存文档命令。
            strip.Items.Add(new ToolStripSeparator()); // 分隔文件和编辑命令。
            select = CreateCommandButton("选择/移动", (sender, args) => SetCanvasMode(CanvasMode.Select)); // 添加选择模式命令。
            connect = CreateCommandButton("连接节点", (sender, args) => SetCanvasMode(CanvasMode.Connect)); // 添加连线模式命令。
            select.CheckOnClick = true; // 允许按钮显示持续选中状态。
            connect.CheckOnClick = true; // 允许按钮显示持续选中状态。
            strip.Items.Add(select); // 将选择模式按钮加入工具栏。
            strip.Items.Add(connect); // 将连线模式按钮加入工具栏。
            strip.Items.Add(new ToolStripSeparator()); // 分隔模式和删除命令。
            strip.Items.Add(CreateCommandButton("删除", (sender, args) => canvas.DeleteSelection())); // 添加删除选中对象命令。
            strip.Items.Add(new ToolStripSeparator()); // 分隔普通编辑与脚本命令。
            strip.Items.Add(CreateCommandButton("编辑脚本", (sender, args) => EditSelectedScript())); // 添加脚本代码编辑命令。
            strip.Items.Add(CreateCommandButton("运行脚本", async (sender, args) => await RunSelectedScriptAsync(null))); // 添加 Roslyn 脚本执行命令。
            return strip; // 返回配置完成的工具栏。
        } // 结束创建工具栏。
        private static ToolStripButton CreateCommandButton(string text, EventHandler handler) // 创建带单击命令的文字按钮。
        { // 开始创建工具栏按钮。
            ToolStripButton button = new ToolStripButton(text); // 使用命令名称创建按钮。
            button.DisplayStyle = ToolStripItemDisplayStyle.Text; // 使用不依赖资源文件的文字样式。
            button.AutoSize = true; // 根据中文名称自动调整宽度。
            button.Click  = handler; // 连接调用方提供的命令逻辑。
            return button; // 返回配置完成的按钮。
        } // 结束创建工具栏按钮。
        private SplitContainer CreateWorkspace() // 创建三栏设计工作区。
        { // 开始创建工作区布局。
            SplitContainer outer = new SplitContainer(); // 创建左侧工具箱与其余区域的分隔器。
            outer.Dock = DockStyle.Fill; // 填满菜单工具栏以下的区域。
            outer.FixedPanel = FixedPanel.Panel1; // 固定工具箱宽度避免缩放变形。
            outer.SplitterDistance = 190; // 设置工具箱初始宽度。
            outer.Panel1.Padding = new Padding(8); // 为工具列表提供边缘留白。
            outer.Panel1.Controls.Add(CreateGroup("节点工具箱", toolbox)); // 将工具箱放入带标题容器。
            SplitContainer inner = new SplitContainer(); // 创建画布与属性面板分隔器。
            inner.Dock = DockStyle.Fill; // 填满右侧剩余区域。
            inner.FixedPanel = FixedPanel.Panel2; // 固定属性面板宽度。
            inner.SplitterDistance = 820; // 为画布预留主要工作空间。
            inner.Panel1.Padding = new Padding(8); // 为画布提供边缘留白。
            inner.Panel2.Padding = new Padding(8); // 为属性面板提供边缘留白。
            inner.Panel1.Controls.Add(CreateGroup("流程画布", canvas)); // 将画布放入带标题容器。
            inner.Panel2.Controls.Add(CreateGroup("属性编辑", propertyGrid)); // 将属性表放入带标题容器。
            outer.Panel2.Controls.Add(inner); // 将内部分隔器加入外部右侧区域。
            return outer; // 返回完整工作区布局。
        } // 结束创建工作区布局。
        private static GroupBox CreateGroup(string title, Control content) // 创建统一样式的标题容器。
        { // 开始创建标题容器。
            GroupBox group = new GroupBox(); // 创建分组框控件。
            group.Text = title; // 设置区域中文标题。
            group.Dock = DockStyle.Fill; // 让分组框填满父级面板。
            group.Padding = new Padding(8); // 避免内容紧贴边框。
            group.Controls.Add(content); // 将主要内容加入分组框。
            return group; // 返回配置完成的分组框。
        } // 结束创建标题容器。
        private MenuStrip CreateMenu() // 创建文件和编辑菜单。
        { // 开始创建主菜单。
            MenuStrip menu = new MenuStrip(); // 创建菜单栏容器。
            ToolStripMenuItem fileMenu = new ToolStripMenuItem("文件(&F)"); // 创建文件操作菜单。
            fileMenu.DropDownItems.Add(CreateMenuItem("新建(&N)", Keys.Control | Keys.N, (sender, args) => NewDocument(true))); // 添加新建快捷命令。
            fileMenu.DropDownItems.Add(CreateMenuItem("打开(&O)...", Keys.Control | Keys.O, (sender, args) => OpenDocument())); // 添加打开快捷命令。
            fileMenu.DropDownItems.Add(CreateMenuItem("保存(&S)", Keys.Control | Keys.S, (sender, args) => SaveDocument(false))); // 添加保存快捷命令。
            fileMenu.DropDownItems.Add(CreateMenuItem("另存为(&A)...", Keys.Control | Keys.Shift | Keys.S, (sender, args) => SaveDocument(true))); // 添加另存为快捷命令。
            fileMenu.DropDownItems.Add(new ToolStripSeparator()); // 分隔文档和退出命令。
            fileMenu.DropDownItems.Add(CreateMenuItem("退出(&X)", Keys.Alt | Keys.F4, (sender, args) => Close())); // 添加退出应用命令。
            ToolStripMenuItem editMenu = new ToolStripMenuItem("编辑(&E)"); // 创建编辑操作菜单。
            editMenu.DropDownItems.Add(CreateMenuItem("选择/移动", Keys.F2, (sender, args) => SetCanvasMode(CanvasMode.Select))); // 添加选择模式快捷命令。
            editMenu.DropDownItems.Add(CreateMenuItem("连接节点", Keys.F3, (sender, args) => SetCanvasMode(CanvasMode.Connect))); // 添加连线模式快捷命令。
            editMenu.DropDownItems.Add(CreateMenuItem("删除选中项", Keys.Delete, (sender, args) => canvas.DeleteSelection())); // 添加删除选中项快捷命令。
            ToolStripMenuItem scriptMenu = new ToolStripMenuItem("脚本(&S)"); // 创建动态脚本操作菜单。
            scriptMenu.DropDownItems.Add(CreateMenuItem("编辑选中脚本", Keys.Control | Keys.E, (sender, args) => EditSelectedScript())); // 添加脚本编辑快捷命令。
            scriptMenu.DropDownItems.Add(CreateMenuItem("运行选中脚本", Keys.F5, async (sender, args) => await RunSelectedScriptAsync(null))); // 添加脚本运行快捷命令。
            menu.Items.Add(fileMenu); // 将文件菜单加入菜单栏。
            menu.Items.Add(editMenu); // 将编辑菜单加入菜单栏。
            menu.Items.Add(scriptMenu); // 将脚本菜单加入菜单栏。
            return menu; // 返回配置完成的菜单栏。
        } // 结束创建主菜单。
        private static ToolStripMenuItem CreateMenuItem(string text, Keys shortcut, EventHandler handler) // 创建标准菜单命令项。
        { // 开始创建菜单命令项。
            ToolStripMenuItem item = new ToolStripMenuItem(text); // 使用显示名称创建菜单项。
            item.ShortcutKeys = shortcut; // 设置菜单项的键盘快捷键。
            item.Click  = handler; // 连接菜单项对应命令。
            return item; // 返回配置完成的菜单项。
        } // 结束创建菜单命令项。
        private void WireEvents() // 连接设计器控件之间的交互事件。
        { // 开始连接界面事件。
            toolbox.ItemDrag  = ToolboxItemDrag; // 支持从工具箱拖出节点类型。
            canvas.SelectionChanged  = CanvasSelectionChanged; // 同步画布选择到属性面板。
            canvas.DocumentChanged  = CanvasDocumentChanged; // 跟踪画布产生的文档修改。
            propertyGrid.PropertyValueChanged  = PropertyGridValueChanged; // 重绘属性编辑后的节点或连线。
            FormClosing  = MainFormClosing; // 关闭窗口前检查未保存内容。
            KeyDown  = MainFormKeyDown; // 支持取消当前连线操作。
        } // 结束连接界面事件。
        private void ToolboxItemDrag(object sender, ItemDragEventArgs e) // 从工具箱发起节点类型拖放。
        { // 开始处理工具项拖动。
            ListViewItem item = e.Item as ListViewItem; // 获取正在拖动的工具项。
            if (item == null || !(item.Tag is NodeKind)) // 忽略缺少节点类型的异常工具项。
            { // 开始处理无效工具项。
                return; // 不发起拖放操作。
            } // 结束处理无效工具项。
            DataObject data = new DataObject(); // 创建拖放数据容器。
            data.SetData(WorkflowCanvas.NodeDragFormat, item.Tag); // 使用画布约定格式保存节点类型。
            toolbox.DoDragDrop(data, DragDropEffects.Copy); // 以复制语义启动拖放操作。
        } // 结束处理工具项拖动。
        private void CanvasSelectionChanged(object sender, EventArgs e) // 将画布选择同步到属性面板。
        { // 开始同步选择对象。
            propertyGrid.SelectedObject = canvas.SelectedObject; // 显示节点或连线可编辑属性。
            propertyGrid.Refresh(); // 立即刷新移动后的坐标等动态值。
        } // 结束同步选择对象。
        private void CanvasDocumentChanged(object sender, EventArgs e) // 响应画布产生的数据修改。
        { // 开始处理文档修改。
            SetDirty(true); // 将当前文档标记为未保存。
        } // 结束处理文档修改。
        private void PropertyGridValueChanged(object sender, PropertyValueChangedEventArgs e) // 提交属性面板中的对象修改。
        { // 开始处理属性修改。
            WorkflowNode node = canvas.SelectedObject as WorkflowNode; // 获取可能被修改的节点。
            if (node != null) // 保证节点尺寸和位置保持可操作范围。
            { // 开始规范化节点布局值。
                node.Width = Math.Max(60, node.Width); // 防止节点宽度过小。
                node.Height = Math.Max(40, node.Height); // 防止节点高度过小。
                node.X = Math.Max(0, node.X); // 防止节点移出画布左侧。
                node.Y = Math.Max(0, node.Y); // 防止节点移出画布顶部。
                propertyGrid.Refresh(); // 显示规范化后的属性值。
            } // 结束规范化节点布局值。
            canvas.Invalidate(); // 根据新属性重绘设计内容。
            SetDirty(true); // 标记属性修改尚未保存。
        } // 结束处理属性修改。
        private void SetCanvasMode(CanvasMode mode) // 切换画布选择或连接模式。
        { // 开始切换画布模式。
            canvas.Mode = mode; // 将模式传递给画布交互逻辑。
            selectButton.Checked = mode == CanvasMode.Select; // 同步选择按钮的高亮状态。
            connectButton.Checked = mode == CanvasMode.Connect; // 同步连线按钮的高亮状态。
            statusLabel.Text = mode == CanvasMode.Select ? "选择模式:单击选择,拖动节点移动位置。" : "连接模式:依次单击起点节点和终点节点。"; // 显示当前模式操作说明。
        } // 结束切换画布模式。
        private void EditSelectedScript() // 打开当前 C# 脚本节点的代码编辑器。
        { // 开始编辑选中脚本。
            WorkflowNode node = canvas.SelectedObject as WorkflowNode; // 获取画布当前选中的节点。
            if (node == null || node.Kind != NodeKind.Script) // 仅允许编辑 C# 脚本类型节点。
            { // 开始处理无效选择。
                MessageBox.Show(this, "请先选择一个 C# 脚本节点。", "脚本编辑", MessageBoxButtons.OK, MessageBoxIcon.Information); // 提示用户选择正确节点。
                return; // 不打开空脚本编辑器。
            } // 结束处理无效选择。
            using (ScriptEditorForm editor = new ScriptEditorForm(node.Name, node.ScriptCode)) // 创建自动释放的脚本编辑窗口。
            { // 开始使用脚本编辑窗口。
                editor.RunRequested  = async (sender, args) => await RunSelectedScriptAsync(editor); // 支持在编辑器中直接运行未保存代码。
                if (editor.ShowDialog(this) == DialogResult.OK) // 等待用户保存或取消修改。
                { // 开始保存脚本修改。
                    node.ScriptCode = editor.ScriptCode; // 将编辑后的代码写回节点模型。
                    propertyGrid.Refresh(); // 刷新属性面板中的脚本文本。
                    SetDirty(true); // 标记脚本修改尚未保存到流程文件。
                } // 结束保存脚本修改。
            } // 释放脚本编辑窗口资源。
        } // 结束编辑选中脚本。
        private async System.Threading.Tasks.Task RunSelectedScriptAsync(ScriptEditorForm editor) // 使用 Roslyn 执行当前脚本节点代码。
        { // 开始运行选中脚本。
            WorkflowNode node = canvas.SelectedObject as WorkflowNode; // 获取画布当前选中的节点。
            if (node == null || node.Kind != NodeKind.Script) // 仅允许运行 C# 脚本类型节点。
            { // 开始处理无效选择。
                MessageBox.Show(this, "请先选择一个 C# 脚本节点。", "运行脚本", MessageBoxButtons.OK, MessageBoxIcon.Information); // 提示用户选择正确节点。
                return; // 不调用 Roslyn 运行服务。
            } // 结束处理无效选择。
            string code = editor == null ? node.ScriptCode : editor.ScriptCode; // 优先运行编辑器中尚未保存的代码。
            statusLabel.Text = "正在编译并执行脚本:"   node.Name; // 显示当前动态执行状态。
            ScriptExecutionResult result = await scriptRuntime.ExecuteAsync(node, canvas.Document, code); // 异步编译并运行脚本代码。
            node.LastResult = result.Output; // 将执行结果保存到节点只读属性。
            propertyGrid.Refresh(); // 在属性面板中显示最近执行结果。
            statusLabel.Text = result.Success ? "脚本执行成功:"   node.Name : "脚本执行失败:"   node.Name; // 显示最终执行状态。
            if (editor != null) // 编辑器内运行时直接写入下方输出区域。
            { // 开始更新编辑器输出。
                editor.ShowOutput(result.Output, result.Success); // 显示编译诊断、日志和返回值。
            } // 结束更新编辑器输出。
            else // 工具栏或菜单运行时使用结果对话框。
            { // 开始显示独立结果窗口。
                MessageBox.Show(this, string.IsNullOrWhiteSpace(result.Output) ? "执行成功,没有输出。" : result.Output, result.Success ? "脚本执行成功" : "脚本执行失败", MessageBoxButtons.OK, result.Success ? MessageBoxIcon.Information : MessageBoxIcon.Error); // 显示完整脚本结果。
            } // 结束显示独立结果窗口。
            canvas.Invalidate(); // 重绘可能被脚本修改的流程节点。
            SetDirty(true); // 标记脚本可能已经修改流程文档。
        } // 结束运行选中脚本。
        private void NewDocument(bool confirmChanges) // 创建新的空白流程文档。
        { // 开始创建新文档。
            if (confirmChanges && !ConfirmDiscardChanges()) // 在需要时先确认未保存修改。
            { // 开始处理用户取消新建。
                return; // 保留当前文档不变。
            } // 结束处理用户取消新建。
            currentFile = null; // 新文档尚未关联磁盘路径。
            canvas.Document = new WorkflowDocument(); // 向画布提供新的空白文档。
            SetCanvasMode(CanvasMode.Select); // 返回默认选择和移动模式。
            SetDirty(false); // 将新文档标记为已保存状态。
        } // 结束创建新文档。
        private void OpenDocument() // 从 JSON 文件加载流程文档。
        { // 开始执行打开文档操作。
            if (!ConfirmDiscardChanges()) // 先处理当前文档未保存修改。
            { // 开始处理用户取消打开。
                return; // 不显示文件选择窗口。
            } // 结束处理用户取消打开。
            using (OpenFileDialog dialog = new OpenFileDialog()) // 创建自动释放的打开文件窗口。
            { // 开始使用打开文件窗口。
                dialog.Title = "打开工作流程"; // 设置窗口操作说明。
                dialog.Filter = "工作流程文件 (*.workflow.json)|*.workflow.json|JSON 文件 (*.json)|*.json|所有文件 (*.*)|*.*"; // 限制并说明支持的文件类型。
                if (dialog.ShowDialog(this) != DialogResult.OK) // 等待用户选定流程文件。
                { // 开始处理用户取消选择。
                    return; // 保留当前文档不变。
                } // 结束处理用户取消选择。
                try // 捕获文件格式和磁盘读取错误。
                { // 开始尝试读取流程文件。
                    string json = File.ReadAllText(dialog.FileName, Encoding.UTF8); // 使用 UTF-8 读取完整 JSON 内容。
                    WorkflowDocument loaded = serializer.Deserialize<WorkflowDocument>(json); // 将 JSON 转换为流程文档。
                    ValidateDocument(loaded); // 验证并规范化加载的数据结构。
                    canvas.Document = loaded; // 在画布上显示加载完成的流程。
                    currentFile = dialog.FileName; // 记住后续保存使用的文件路径。
                    SetCanvasMode(CanvasMode.Select); // 加载后回到安全的选择模式。
                    SetDirty(false); // 将成功加载的文档标记为未修改。
                    statusLabel.Text = "已打开:"   currentFile; // 显示成功打开的文件位置。
                } // 结束尝试读取流程文件。
                catch (Exception exception) // 处理所有可预期的加载失败。
                { // 开始显示加载错误。
                    MessageBox.Show(this, "无法打开流程文件:\r\n"   exception.Message, "打开失败", MessageBoxButtons.OK, MessageBoxIcon.Error); // 向用户说明失败原因。
                } // 结束显示加载错误。
            } // 释放打开文件窗口资源。
        } // 结束执行打开文档操作。
        private bool SaveDocument(bool saveAs) // 将当前流程文档保存为 JSON 文件。
        { // 开始执行保存文档操作。
            string fileName = currentFile; // 默认使用当前文档关联路径。
            if (saveAs || string.IsNullOrWhiteSpace(fileName)) // 首次保存或另存为时选择路径。
            { // 开始选择保存路径。
                using (SaveFileDialog dialog = new SaveFileDialog()) // 创建自动释放的保存文件窗口。
                { // 开始使用保存文件窗口。
                    dialog.Title = "保存工作流程"; // 设置窗口操作说明。
                    dialog.Filter = "工作流程文件 (*.workflow.json)|*.workflow.json|JSON 文件 (*.json)|*.json"; // 提供标准保存文件类型。
                    dialog.DefaultExt = "workflow.json"; // 设置缺少扩展名时的默认后缀。
                    dialog.AddExtension = true; // 自动补充用户未输入的扩展名。
                    dialog.FileName = string.IsNullOrWhiteSpace(currentFile) ? "未命名流程.workflow.json" : Path.GetFileName(currentFile); // 提供易于识别的默认文件名。
                    if (dialog.ShowDialog(this) != DialogResult.OK) // 等待用户确认保存位置。
                    { // 开始处理用户取消保存。
                        return false; // 告知调用方文档没有保存。
                    } // 结束处理用户取消保存。
                    fileName = dialog.FileName; // 使用用户选定的完整路径。
                } // 释放保存文件窗口资源。
            } // 结束选择保存路径。
            try // 捕获磁盘写入和序列化错误。
            { // 开始尝试保存流程文件。
                string json = serializer.Serialize(canvas.Document); // 将当前文档转换为 JSON 文本。
                File.WriteAllText(fileName, json, new UTF8Encoding(false)); // 使用无 BOM UTF-8 写入流程文件。
                currentFile = fileName; // 记住成功保存的磁盘路径。
                SetDirty(false); // 清除未保存修改标记。
                statusLabel.Text = "已保存:"   currentFile; // 显示成功保存的文件位置。
                return true; // 告知调用方保存成功。
            } // 结束尝试保存流程文件。
            catch (Exception exception) // 处理所有可预期的保存失败。
            { // 开始显示保存错误。
                MessageBox.Show(this, "无法保存流程文件:\r\n"   exception.Message, "保存失败", MessageBoxButtons.OK, MessageBoxIcon.Error); // 向用户说明失败原因。
                return false; // 告知调用方保存没有完成。
            } // 结束显示保存错误。
        } // 结束执行保存文档操作。
        private static void ValidateDocument(WorkflowDocument document) // 验证并修复反序列化后的文档结构。
        { // 开始验证流程文档。
            if (document == null) // 空 JSON 或类型不匹配时无法继续加载。
            { // 开始处理空文档。
                throw new InvalidDataException("文件中没有有效的工作流程数据。"); // 抛出便于用户理解的格式错误。
            } // 结束处理空文档。
            document.Title = document.Title ?? "未命名流程"; // 修复可能为空的文档标题。
            document.Nodes = document.Nodes ?? new System.Collections.Generic.List<WorkflowNode>(); // 修复缺失的节点集合。
            document.Connections = document.Connections ?? new System.Collections.Generic.List<WorkflowConnection>(); // 修复缺失的连线集合。
            foreach (WorkflowNode node in document.Nodes) // 逐个规范化加载的节点。
            { // 开始规范化一个节点。
                node.Id = string.IsNullOrWhiteSpace(node.Id) ? Guid.NewGuid().ToString("N") : node.Id; // 为缺少标识的节点补充唯一值。
                node.Name = node.Name ?? string.Empty; // 修复可能为空的节点名称。
                node.Description = node.Description ?? string.Empty; // 修复可能为空的节点说明。
                node.ScriptCode = node.ScriptCode ?? string.Empty; // 修复旧版流程中缺失的脚本代码。
                node.LastResult = node.LastResult ?? string.Empty; // 修复旧版流程中缺失的脚本结果。
                node.ScriptCode = node.ScriptCode ?? string.Empty; // 修复旧版流程中缺失的脚本代码。
                node.LastResult = node.LastResult ?? string.Empty; // 修复旧版流程中缺失的脚本结果。
                node.Width = Math.Max(60, node.Width); // 保证加载节点具有可操作宽度。
                node.Height = Math.Max(40, node.Height); // 保证加载节点具有可操作高度。
                node.X = Math.Max(0, node.X); // 防止节点位于画布左侧之外。
                node.Y = Math.Max(0, node.Y); // 防止节点位于画布顶部之外。
            } // 结束规范化一个节点。
            System.Collections.Generic.HashSet<string> nodeIds = new System.Collections.Generic.HashSet<string>(document.Nodes.Select(item => item.Id)); // 收集所有有效节点标识。
            document.Connections.RemoveAll(item => item == null || !nodeIds.Contains(item.FromNodeId) || !nodeIds.Contains(item.ToNodeId) || item.FromNodeId == item.ToNodeId); // 删除无效或自连接关系。
            foreach (WorkflowConnection connection in document.Connections) // 逐个规范化有效连线。
            { // 开始规范化一个连线。
                connection.Id = string.IsNullOrWhiteSpace(connection.Id) ? Guid.NewGuid().ToString("N") : connection.Id; // 为缺少标识的连线补充唯一值。
                connection.Name = connection.Name ?? string.Empty; // 修复可能为空的连线名称。
                connection.Condition = connection.Condition ?? string.Empty; // 修复可能为空的流转条件。
            } // 结束规范化一个连线。
        } // 结束验证流程文档。
        private bool ConfirmDiscardChanges() // 询问用户如何处理未保存修改。
        { // 开始检查未保存状态。
            if (!dirty) // 没有修改时无需显示确认窗口。
            { // 开始处理干净文档。
                return true; // 允许调用方继续操作。
            } // 结束处理干净文档。
            DialogResult result = MessageBox.Show(this, "当前流程尚未保存,是否先保存?", "未保存的更改", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Warning); // 询问保存、放弃或取消。
            if (result == DialogResult.Cancel) // 用户选择取消当前操作。
            { // 开始处理取消选择。
                return false; // 阻止新建、打开或关闭。
            } // 结束处理取消选择。
            if (result == DialogResult.Yes) // 用户希望先保存当前文档。
            { // 开始处理保存选择。
                return SaveDocument(false); // 仅在保存成功时允许继续。
            } // 结束处理保存选择。
            return true; // 用户明确选择放弃修改。
        } // 结束检查未保存状态。
        private void SetDirty(bool value) // 更新未保存标记和窗口标题。
        { // 开始更新文档状态。
            dirty = value; // 保存新的修改状态。
            string name = string.IsNullOrWhiteSpace(currentFile) ? "未命名流程" : Path.GetFileName(currentFile); // 获取窗口中显示的文档名称。
            Text = name   (dirty ? " *" : string.Empty)   " - 工作流程图设计器"; // 使用星号提示未保存修改。
        } // 结束更新文档状态。
        private void MainFormKeyDown(object sender, KeyEventArgs e) // 处理窗体级键盘操作。
        { // 开始处理键盘按键。
            if (e.KeyCode == Keys.Escape) // Escape 用于取消未完成的连线。
            { // 开始处理取消按键。
                canvas.ClearSelection(); // 清除连接起点和选择状态。
                SetCanvasMode(CanvasMode.Select); // 返回默认选择模式。
                e.Handled = true; // 防止按键继续传递给子控件。
            } // 结束处理取消按键。
        } // 结束处理键盘按键。
        private void MainFormClosing(object sender, FormClosingEventArgs e) // 关闭应用前保护未保存数据。
        { // 开始处理窗体关闭。
            if (!ConfirmDiscardChanges()) // 检查用户是否允许关闭应用。
            { // 开始处理取消关闭。
                e.Cancel = true; // 保持主窗体和当前文档打开。
            } // 结束处理取消关闭。
        } // 结束处理窗体关闭。
    } // 结束主窗体类型定义。
} // 结束主界面命名空间定义。

实例下载地址

C#流程框架(WorkflowDesigner)

不能下载?内容有错? 点击这里报错 + 投诉 + 提问

好例子网口号:伸出你的我的手 — 分享

网友评论

发表评论

(您的评论需要经过审核才能显示)

查看所有0条评论>>

小贴士

感谢您为本站写下的评论,您的评论对其它用户来说具有重要的参考价值,所以请认真填写。

  • 类似“顶”、“沙发”之类没有营养的文字,对勤劳贡献的楼主来说是令人沮丧的反馈信息。
  • 相信您也不想看到一排文字/表情墙,所以请不要反馈意义不大的重复字符,也请尽量不要纯表情的回复。
  • 提问之前请再仔细看一遍楼主的说明,或许是您遗漏了。
  • 请勿到处挖坑绊人、招贴广告。既占空间让人厌烦,又没人会搭理,于人于己都无利。

关于好例子网

本站旨在为广大IT学习爱好者提供一个非营利性互相学习交流分享平台。本站所有资源都可以被免费获取学习研究。本站资源来自网友分享,对搜索内容的合法性不具有预见性、识别性、控制性,仅供学习研究,请务必在下载后24小时内给予删除,不得用于其他任何用途,否则后果自负。基于互联网的特殊性,平台无法对用户传输的作品、信息、内容的权属或合法性、安全性、合规性、真实性、科学性、完整权、有效性等进行实质审查;无论平台是否已进行审查,用户均应自行承担因其传输的作品、信息、内容而可能或已经产生的侵权或权属纠纷等法律责任。本站所有资源不代表本站的观点或立场,基于网友分享,根据中国法律《信息网络传播权保护条例》第二十二与二十三条之规定,若资源存在侵权或相关问题请联系本站客服人员,点此联系我们。关于更多版权及免责申明参见 版权及免责申明

;
报警