diff --git a/Library/Api/IDynamicFlowNode.cs b/Library/Api/IDynamicFlowNode.cs index 683b215..ff7ce76 100644 --- a/Library/Api/IDynamicFlowNode.cs +++ b/Library/Api/IDynamicFlowNode.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using System.Threading; using System.Threading.Tasks; namespace Serein.Library.Api @@ -11,6 +12,7 @@ namespace Serein.Library.Api /// public interface IDynamicFlowNode { + Task ExecutingAsync(IDynamicContext context, CancellationToken token); } } diff --git a/Library/Extension/FlowModelExtension.cs b/Library/Extension/FlowModelExtension.cs index c930380..9648f51 100644 --- a/Library/Extension/FlowModelExtension.cs +++ b/Library/Extension/FlowModelExtension.cs @@ -115,7 +115,7 @@ namespace Serein.Library ParameterData = parameterData.ToArray(), ErrorNodes = errorNodes.ToArray(), Position = nodeModel.Position, - IsProtectionParameter = nodeModel.MethodDetails.IsProtectionParameter, + IsProtectionParameter = nodeModel.DebugSetting.IsProtectionParameter, IsInterrupt = nodeModel.DebugSetting.IsInterrupt, IsEnable = nodeModel.DebugSetting.IsEnable, ParentNodeGuid = nodeModel.ContainerNode?.Guid, @@ -139,7 +139,7 @@ namespace Serein.Library nodeModel.Guid = nodeInfo.Guid; nodeModel.Position = nodeInfo.Position ?? new PositionOfUI(0, 0);// 加载位置信息 var md = nodeModel.MethodDetails; // 当前节点的方法说明 - nodeModel.MethodDetails.IsProtectionParameter = nodeInfo.IsProtectionParameter; // 保护参数 + nodeModel.DebugSetting.IsProtectionParameter = nodeInfo.IsProtectionParameter; // 保护参数 nodeModel.DebugSetting.IsInterrupt = nodeInfo.IsInterrupt; // 是否中断 nodeModel.DebugSetting.IsEnable = nodeInfo.IsEnable; // 是否使能 nodeModel.IsPublic = nodeInfo.IsPublic; // 是否全局公开 @@ -169,7 +169,7 @@ namespace Serein.Library for (int i = 0; i < nodeInfo.ParameterData.Length; i++) { - if (i >= pds.Length) + if (i >= pds.Length && nodeModel.ControlType != NodeControlType.FlowCall) { nodeModel.Env.WriteLine(InfoType.ERROR, $"保存的参数数量大于方法此时的入参参数数量:[{nodeInfo.Guid}][{nodeInfo.MethodName}]"); break; diff --git a/Library/FlowNode/MethodDetails.cs b/Library/FlowNode/MethodDetails.cs index 2c37b47..8293ecb 100644 --- a/Library/FlowNode/MethodDetails.cs +++ b/Library/FlowNode/MethodDetails.cs @@ -30,12 +30,6 @@ namespace Serein.Library private string _assemblyName; - /// - /// 是否保护参数 - /// - [PropertyInfo(IsNotification = true)] - private bool _isProtectionParameter; - /// /// 调用节点方法时需要的实例(多个相同的节点将拥有相同的类型) /// @@ -245,7 +239,6 @@ namespace Serein.Library ReturnType = this.ReturnType, // 拷贝 MethodName = this.MethodName, // 拷贝 MethodLockName = this.MethodLockName, // 拷贝 - IsProtectionParameter = this.IsProtectionParameter, // 拷贝 ParamsArgIndex = this.ParamsArgIndex, // 拷贝 ParameterDetailss = this.ParameterDetailss?.Select(p => p?.CloneOfModel(nodeModel)).ToArray(), // 拷贝属于节点方法的新入参描述 }; @@ -255,6 +248,10 @@ namespace Serein.Library public override string ToString() { + if (string.IsNullOrEmpty(this.MethodName)) + { + return ""; + } var tmp = this.MethodName.Split('.') ; var methodName = tmp[tmp.Length - 1]; StringBuilder sb = new StringBuilder(); diff --git a/Library/FlowNode/NodeDebugSetting.cs b/Library/FlowNode/NodeDebugSetting.cs index 51aa2b9..9743719 100644 --- a/Library/FlowNode/NodeDebugSetting.cs +++ b/Library/FlowNode/NodeDebugSetting.cs @@ -22,6 +22,13 @@ namespace Serein.Library NodeModel = nodeModel; } + + /// + /// 是否保护参数 + /// + [PropertyInfo(IsNotification = true)] + private bool _isProtectionParameter; + /// /// 对应的节点 /// @@ -37,7 +44,7 @@ namespace Serein.Library /// /// 是否中断节点。 /// - [PropertyInfo(IsNotification = true, CustomCodeAtEnd = "ChangeInterruptState(value);")] // CustomCode = "NodeModel?.Env?.SetNodeInterruptAsync(NodeModel?.Guid, value);" + [PropertyInfo(IsNotification = true)] private bool _isInterrupt = false; } @@ -66,18 +73,16 @@ namespace Serein.Library public Func GetInterruptTask => _getInterruptTask; - /// - /// 改变中断状态 - /// - public void ChangeInterruptState(bool state) + + partial void OnIsInterruptChanged(bool oldValue, bool newValue) { - if (state && _getInterruptTask is null) + if (newValue && _getInterruptTask is null) { // 设置获取中断的委托 _getInterruptTask = () => NodeModel.Env.IOC.Get().WaitTriggerAsync(NodeModel.Guid); - + } - else if (!state) + else if (!newValue) { if (_getInterruptTask is null) { @@ -90,11 +95,13 @@ namespace Serein.Library _cancelInterrupt.Invoke(); _getInterruptTask = null; } - + } } + + } } diff --git a/Library/FlowNode/NodeModelBaseData.cs b/Library/FlowNode/NodeModelBaseData.cs index 6f5d7be..c76a087 100644 --- a/Library/FlowNode/NodeModelBaseData.cs +++ b/Library/FlowNode/NodeModelBaseData.cs @@ -11,11 +11,13 @@ namespace Serein.Library + + /// /// 节点基类(数据) /// [NodeProperty(ValuePath = NodeValuePath.Node)] - public abstract partial class NodeModelBase : IDynamicFlowNode + public abstract partial class NodeModelBase : INotifyPropertyChanged, IDynamicFlowNode { /// /// 节点运行环境 @@ -56,9 +58,15 @@ namespace Serein.Library /// /// 是否公开 /// - [PropertyInfo(IsNotification = true, CustomCodeAtEnd = "NodePublicStateChanged();")] + [PropertyInfo(IsNotification = true)] private bool _isPublic; + /* /// + /// 是否保护参数 + /// + [PropertyInfo(IsNotification = true)] + private bool _isProtectionParameter;*/ + /// /// 附加的调试功能 /// @@ -68,7 +76,7 @@ namespace Serein.Library /// /// 方法描述。包含参数信息。不包含Method与委托,如若需要调用对应的方法,需要通过MethodName从环境中获取委托进行调用。 /// - [PropertyInfo(IsProtection = true)] + [PropertyInfo] private MethodDetails _methodDetails ; } @@ -108,7 +116,7 @@ namespace Serein.Library /// /// 不同分支的子节点(流程调用) /// - public Dictionary> SuccessorNodes { get; } + public Dictionary> SuccessorNodes { get; set; } /// /// 该节点的容器节点 @@ -123,10 +131,9 @@ namespace Serein.Library /// /// 节点公开状态发生改变 /// - private void NodePublicStateChanged() + partial void OnIsPublicChanged(bool oldValue, bool newValue) { - - if (IsPublic) + if (newValue) { // 公开节点 if (!CanvasDetails.PublicNodes.Contains(this)) @@ -143,6 +150,8 @@ namespace Serein.Library } } } + + } } diff --git a/Library/FlowNode/NodeModelBaseFunc.cs b/Library/FlowNode/NodeModelBaseFunc.cs index 0c734dc..d807cf9 100644 --- a/Library/FlowNode/NodeModelBaseFunc.cs +++ b/Library/FlowNode/NodeModelBaseFunc.cs @@ -62,33 +62,37 @@ namespace Serein.Library { this.DebugSetting.CancelInterrupt?.Invoke(); } - this.DebugSetting.NodeModel = null; - this.DebugSetting = null; - if (this.MethodDetails.ParameterDetailss != null) + + if (this.IsPublic) { - foreach (var pd in this.MethodDetails.ParameterDetailss) - { - pd.DataValue = null; - pd.Items = null; - pd.NodeModel = null; - pd.ExplicitType = null; - pd.DataType = null; - pd.Name = null; - pd.ArgDataSourceNodeGuid = null; - pd.InputType = ParameterValueInputType.Input; - } + this.CanvasDetails.PublicNodes.Remove(this); } - this.MethodDetails.ParameterDetailss = null; - //this.MethodDetails.ActingInstance = null; - this.MethodDetails.NodeModel = null; - this.MethodDetails.ReturnType = null; - this.MethodDetails.AssemblyName = null; - this.MethodDetails.MethodAnotherName = null; - this.MethodDetails.MethodLockName = null; - this.MethodDetails.MethodName = null; - this.MethodDetails.ActingInstanceType = null; - this.MethodDetails = null; + this.DebugSetting.NodeModel = null; + this.DebugSetting = null; + if(this.MethodDetails is not null) + { + if (this.MethodDetails.ParameterDetailss != null) + { + foreach (var pd in this.MethodDetails.ParameterDetailss) + { + pd.DataValue = null; + pd.Items = null; + pd.NodeModel = null; + pd.ExplicitType = null; + pd.DataType = null; + pd.Name = null; + pd.ArgDataSourceNodeGuid = null; + pd.InputType = ParameterValueInputType.Input; + } + } + this.MethodDetails.ParameterDetailss = null; + this.MethodDetails.NodeModel = null; + this.MethodDetails.ReturnType = null; + this.MethodDetails.ActingInstanceType = null; + this.MethodDetails = null; + } + this.Position = null; this.DisplayName = null; @@ -99,6 +103,8 @@ namespace Serein.Library /// 执行节点对应的方法 /// /// 流程上下文 + /// + /// 自定义参数 /// 节点传回数据对象 public virtual async Task ExecutingAsync(IDynamicContext context, CancellationToken token) { diff --git a/Library/FlowNode/ParameterDetails.cs b/Library/FlowNode/ParameterDetails.cs index 6f9b7d9..9434293 100644 --- a/Library/FlowNode/ParameterDetails.cs +++ b/Library/FlowNode/ParameterDetails.cs @@ -183,7 +183,7 @@ namespace Serein.Library //Convertor = this.Convertor, DataType = this.DataType, Name = this.Name, - DataValue = string.IsNullOrEmpty(DataValue) ? string.Empty : DataValue, + DataValue = this.DataValue, Items = this.Items?.Select(it => it).ToArray(), IsParams = this.IsParams, Description = this.Description, diff --git a/NodeFlow/Env/FlowEnvironment.cs b/NodeFlow/Env/FlowEnvironment.cs index 67c7b7c..cd7fd46 100644 --- a/NodeFlow/Env/FlowEnvironment.cs +++ b/NodeFlow/Env/FlowEnvironment.cs @@ -6,7 +6,9 @@ using Serein.Library.Utils.SereinExpression; using Serein.NodeFlow.Model; using Serein.NodeFlow.Tool; using System; +using System.Collections.Specialized; using System.Diagnostics; +using System.Net.Mime; using System.Reactive; using System.Reflection; using System.Text; @@ -57,6 +59,7 @@ namespace Serein.NodeFlow.Env NodeMVVMManagement.RegisterModel(NodeControlType.GlobalData, typeof(SingleGlobalDataNode)); // 全局数据节点 NodeMVVMManagement.RegisterModel(NodeControlType.Script, typeof(SingleScriptNode)); // 脚本节点 NodeMVVMManagement.RegisterModel(NodeControlType.NetScript, typeof(SingleNetScriptNode)); // 脚本节点 + NodeMVVMManagement.RegisterModel(NodeControlType.FlowCall, typeof(SingleFlowCallNode)); // 流程调用节点 #endregion #region 注册基本服务类 @@ -887,11 +890,12 @@ namespace Serein.NodeFlow.Env #region 从NodeInfo创建NodeModel foreach (NodeInfo? nodeInfo in nodeInfos) { + if (!EnumHelper.TryConvertEnum(nodeInfo.Type, out var controlType)) { continue; } - + #region 获取方法描述 MethodDetails? methodDetails; if (controlType.IsBaseNode()) diff --git a/NodeFlow/FlowNodeExtension.cs b/NodeFlow/FlowNodeExtension.cs index dbf9c44..538036b 100644 --- a/NodeFlow/FlowNodeExtension.cs +++ b/NodeFlow/FlowNodeExtension.cs @@ -20,6 +20,10 @@ namespace Serein.NodeFlow /// public static bool IsBaseNode(this NodeControlType nodeControlType) { + if(nodeControlType == NodeControlType.FlowCall) + { + return false; + } var nodeDesc = EnumHelper.GetAttribute(nodeControlType); if("base".Equals(nodeDesc?.Description, StringComparison.OrdinalIgnoreCase)) { diff --git a/NodeFlow/Model/SingleFlowCallNode.cs b/NodeFlow/Model/SingleFlowCallNode.cs index 36f0cf6..a3435da 100644 --- a/NodeFlow/Model/SingleFlowCallNode.cs +++ b/NodeFlow/Model/SingleFlowCallNode.cs @@ -1,23 +1,161 @@ -using Serein.Library; +using Newtonsoft.Json.Linq; +using Serein.Library; using Serein.Library.Api; using System; using System.Collections.Generic; +using System.Dynamic; using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Xml.Linq; +using static System.Runtime.InteropServices.JavaScript.JSType; namespace Serein.NodeFlow.Model { + + [NodeProperty(ValuePath = NodeValuePath.Node)] + public partial class SingleFlowCallNode + { + + + + /// + /// 使用目标节点的参数(如果为true,则使用目标节点的入参,如果为false,则使用节点自定义入参) + /// + [PropertyInfo(IsNotification = true)] + private bool _isShareParam = true ; + } + + + /// /// 流程调用节点 /// - public class SingleFlowCallNode : NodeModelBase + public partial class SingleFlowCallNode : NodeModelBase { + /// + /// 接口节点 + /// + private NodeModelBase targetNode; + /// + /// 接口节点Guid + /// + public string? TargetNodeGuid => targetNode?.Guid; + + public SingleFlowCallNode(IFlowEnvironment environment) : base(environment) { } + /// + /// 重置接口节点 + /// + public void ResetTargetNode() + { + if(targetNode is not null) + { + // 取消接口 + targetNode.PropertyChanged -= TargetNode_PropertyChanged; + this.MethodDetails = null; + foreach (ConnectionInvokeType ctType in NodeStaticConfig.ConnectionTypes) + { + this.SuccessorNodes[ctType] = new List(); + } + } + + + } + + /// + /// 设置接口节点(如果传入null,则视为取消设置) + /// + /// + public void SetTargetNode(NodeModelBase? value) + { + if( value is null) + { + return; + } + this.targetNode = value; + tmpMethodDetails = targetNode.MethodDetails.CloneOfNode(this); // 从目标节点复制一份 + targetNode.PropertyChanged += TargetNode_PropertyChanged; + this.MethodDetails = tmpMethodDetails; + this.SuccessorNodes = targetNode.SuccessorNodes; + } + + private MethodDetails tmpMethodDetails; + partial void OnIsShareParamChanged(bool value) + { + if (targetNode is null) + { + return; + } + if (value) + { + tmpMethodDetails = this.MethodDetails; + this.MethodDetails = targetNode.MethodDetails; + } + else + { + this.MethodDetails = tmpMethodDetails; + OnPropertyChanged(nameof(MethodDetails)); + } + } + + /* partial void OnTargetNodeGuidChanged(string value) + { + var guid = value; + if (string.IsNullOrEmpty(guid)) + { + targetNode = null; + return; + } + if (!Env.TryGetNodeModel(guid, out targetNode)) + { + SereinEnv.WriteLine(InfoType.ERROR, $"流程接口找不到节点{guid}"); + return; + } + SetTargetNode(targetNode); + //OnIsShareParamChanged(IsShareParam); // 更新参数状态 + }*/ + + + private void TargetNode_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) + { + // 如果不再公开 + if (sender is NodeModelBase node && !node.IsPublic) + { + this.SuccessorNodes = []; + targetNode.PropertyChanged -= TargetNode_PropertyChanged; + } + } + + + /// + /// 从节点Guid刷新实体 + /// + /// + private bool UploadTargetNode() + { + if (targetNode is null) + { + if (string.IsNullOrWhiteSpace(TargetNodeGuid)) + { + return false; + } + + if (!Env.TryGetNodeModel(TargetNodeGuid, out var targetNode) || targetNode is null) + { + return false; + } + + this.targetNode = targetNode; + } + return true; + + } + /// /// 需要调用其它流程图中的某个节点 /// @@ -25,9 +163,48 @@ namespace Serein.NodeFlow.Model /// /// public override async Task ExecutingAsync(IDynamicContext context, CancellationToken token) - { - await base.ExecutingAsync(context, token); - return new FlowResult(this, context, null); + { + if (!UploadTargetNode()) + { + throw new ArgumentNullException(); + } + return await base.ExecutingAsync(context, token); } + + + /// + /// 保存全局变量的数据 + /// + /// + /// + public override NodeInfo SaveCustomData(NodeInfo nodeInfo) + { + dynamic data = new ExpandoObject(); + data.TargetNodeGuid = targetNode?.Guid; // 变量名称 + data.IsShareParam = IsShareParam; + nodeInfo.CustomData = data; + return nodeInfo; + } + + /// + /// 加载全局变量的数据 + /// + /// + public override void LoadCustomData(NodeInfo nodeInfo) + { + string targetNodeGuid = nodeInfo.CustomData?.TargetNodeGuid ?? ""; + this.IsShareParam = nodeInfo.CustomData?.IsShareParam; + if (Env.TryGetNodeModel(targetNodeGuid, out var targetNode)) + { + this.targetNode = targetNode; + } + else + { + SereinEnv.WriteLine(InfoType.ERROR, $"流程接口节点[{this.Guid}]无法找到对应的节点:{targetNodeGuid}"); + } + } + + + } } \ No newline at end of file diff --git a/NodeFlow/Model/SingleGlobalDataNode.cs b/NodeFlow/Model/SingleGlobalDataNode.cs index 4ba93de..e59e106 100644 --- a/NodeFlow/Model/SingleGlobalDataNode.cs +++ b/NodeFlow/Model/SingleGlobalDataNode.cs @@ -23,7 +23,7 @@ namespace Serein.NodeFlow.Model /// /// 表达式 /// - [PropertyInfo(IsNotification = true, CustomCodeAtStart = "// ChangeName(value);")] + [PropertyInfo(IsNotification = true)] private string _keyName; } @@ -101,14 +101,7 @@ namespace Serein.NodeFlow.Model DataNode = null; } - private void ChangeName(string newName) - { - if(SereinEnv.GetFlowGlobalData(_keyName) == null) - { - return; - } - SereinEnv.ChangeNameFlowGlobalData(_keyName, newName); - } + /// /// 设置全局数据 diff --git a/NodeFlow/Serein.NodeFlow.csproj b/NodeFlow/Serein.NodeFlow.csproj index 5f3bb8d..17b8984 100644 --- a/NodeFlow/Serein.NodeFlow.csproj +++ b/NodeFlow/Serein.NodeFlow.csproj @@ -41,6 +41,7 @@ + diff --git a/Serein.Library.MyGenerator/ParameterDetailsPropertyGenerator.cs b/Serein.Library.MyGenerator/ParameterDetailsPropertyGenerator.cs index 51d7909..e4c5229 100644 --- a/Serein.Library.MyGenerator/ParameterDetailsPropertyGenerator.cs +++ b/Serein.Library.MyGenerator/ParameterDetailsPropertyGenerator.cs @@ -166,6 +166,8 @@ namespace Serein.Library.NodeGenerator var isProtection = attributeInfo.Search(nameof(PropertyInfo), nameof(PropertyInfo.IsProtection), value => bool.Parse(value)); // 是否为保护字段 + sb.AppendLine($" partial void On{propertyName}Changed({fieldType} oldValue,{fieldType} newValue);"); + sb.AppendLine($" partial void On{propertyName}Changed({fieldType} value);"); // 生成 getter / setter sb.AppendLine(leadingTrivia); sb.AppendLine($" public {fieldType} {propertyName}"); @@ -175,13 +177,10 @@ namespace Serein.Library.NodeGenerator sb.AppendLine(" {"); sb.AppendLine($" if ({fieldName} {(isProtection ? "== default" : "!= value")})"); // 非保护的Setter sb.AppendLine(" {"); - if (attributeInfo.Search(nameof(PropertyInfo), nameof(PropertyInfo.CustomCodeAtStart), value => !string.IsNullOrEmpty(value))) // 自定义代码 - { - var customCode = attributeInfo[nameof(PropertyInfo)][nameof(PropertyInfo.CustomCodeAtStart)] as string; - customCode = customCode.Trim().Substring(1, customCode.Length - 2); - sb.AddCode(5, $"{customCode} // 添加的自定义代码"); - } + sb.AppendLine($" var __oldValue = {fieldName};"); sb.AppendLine($" SetProperty<{fieldType}>(ref {fieldName}, value); // 通知UI属性发生改变了"); + sb.AppendLine($" On{propertyName}Changed(value);"); + sb.AppendLine($" On{propertyName}Changed(__oldValue, value);"); if (attributeInfo.Search(nameof(PropertyInfo), nameof(PropertyInfo.IsPrint), value => bool.Parse(value))) // 是否打印 { sb.AddCode(5, $"Console.WriteLine({fieldName});"); @@ -217,14 +216,7 @@ namespace Serein.Library.NodeGenerator } } - if (attributeInfo.Search(nameof(PropertyInfo), nameof(PropertyInfo.CustomCodeAtEnd), value => !string.IsNullOrEmpty(value))) // 自定义代码 - { - var customCode = attributeInfo[nameof(PropertyInfo)][nameof(PropertyInfo.CustomCodeAtEnd)] as string; - customCode = customCode.Trim().Substring(1, customCode.Length - 2); - sb.AddCode(5, $"{customCode} // 添加的自定义代码"); - } - //sb.AppendLine($" {fieldName} = value;"); - //sb.AppendLine($" OnPropertyChanged(); // 通知UI属性发生改变了"); + sb.AppendLine(" }"); sb.AppendLine(" }"); sb.AppendLine(" }"); // 属性的结尾大括号 @@ -253,6 +245,11 @@ namespace Serein.Library.NodeGenerator sb.AppendLine(" PropertyChanged?.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); "); sb.AppendLine(" } "); + sb.AppendLine(" protected void OnPropertyChanged(string propertyName) => "); + sb.AppendLine(" PropertyChanged?.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); "); + sb.AppendLine(" "); + sb.AppendLine(" "); + sb.AppendLine(" "); //sb.AppendLine(" /// "); //sb.AppendLine(" /// 略 "); //sb.AppendLine(" /// 此方法为自动生成 "); diff --git a/Workbench/Converters/BoolToVisibilityConverter.cs b/Workbench/Converters/BoolToVisibilityConverter.cs new file mode 100644 index 0000000..676924d --- /dev/null +++ b/Workbench/Converters/BoolToVisibilityConverter.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Data; +using System.Windows; +using System.Diagnostics; + +namespace Serein.Workbench.Converters +{ + public class BoolToVisibilityConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + Debug.WriteLine($"targetType:{targetType} value:{targetType} parameter:{parameter}"); + if (value is bool b) + { + return b ? Visibility.Visible : Visibility.Collapsed; + } + return Visibility.Collapsed; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + return value is Visibility v && v == Visibility.Visible; + } + } +} diff --git a/Workbench/Converters/EnumToBooleanConverter.cs b/Workbench/Converters/EnumToBooleanConverter.cs new file mode 100644 index 0000000..a55dae0 --- /dev/null +++ b/Workbench/Converters/EnumToBooleanConverter.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Data; + +namespace Serein.Workbench.Converters +{ + public class EnumToBooleanConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value == null || parameter == null) + return false; + + return value.ToString().Equals(parameter.ToString(), StringComparison.InvariantCultureIgnoreCase); + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } + +} diff --git a/Workbench/Converters/InvertableBooleanToVisibilityConverter.cs b/Workbench/Converters/InvertableBooleanToVisibilityConverter.cs new file mode 100644 index 0000000..b1f5aa2 --- /dev/null +++ b/Workbench/Converters/InvertableBooleanToVisibilityConverter.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Data; +using System.Windows; + +namespace Serein.Workbench.Converters +{ + /// + /// 根据bool类型控制可见性 + /// + [ValueConversion(typeof(bool), typeof(Visibility))] + public class InvertableBooleanToVisibilityConverter : IValueConverter + { + enum Parameters + { + /// + /// True为可见,False为不可见 + /// + Normal, + /// + /// False为可见,True为不可见 + /// + Inverted + } + + public object Convert(object value, Type targetType, + object parameter, CultureInfo culture) + { + var boolValue = (bool)value; + var direction = (Parameters)Enum.Parse(typeof(Parameters), (string)parameter); + + if (direction == Parameters.Inverted) + return !boolValue ? Visibility.Visible : Visibility.Collapsed; + + return boolValue ? Visibility.Visible : Visibility.Collapsed; + } + + public object? ConvertBack(object value, Type targetType, + object parameter, CultureInfo culture) + { + return null; + } + } +} diff --git a/Workbench/Converters/MethodDetailsSelectorConverter.cs b/Workbench/Converters/MethodDetailsSelectorConverter.cs new file mode 100644 index 0000000..dbd7331 --- /dev/null +++ b/Workbench/Converters/MethodDetailsSelectorConverter.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Data; + +namespace Serein.Workbench.Converters +{ + public class MethodDetailsSelectorConverter : IMultiValueConverter + { + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + bool isShareParam = (bool)values[0]; + var nodeDetails = values[1]; + var selectNodeDetails = values[2]; + + return isShareParam ? nodeDetails : selectNodeDetails; + } + + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } + } +} diff --git a/Workbench/Converters/ThumbPositionConverter.cs b/Workbench/Converters/ThumbPositionConverter.cs new file mode 100644 index 0000000..964c9ae --- /dev/null +++ b/Workbench/Converters/ThumbPositionConverter.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Data; + +namespace Serein.Workbench.Converters +{ + /// + /// 画布拉动范围距离计算器 + /// + public class RightThumbPositionConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + if (value is double width) + return width - 10; // Adjust for Thumb width + return 0; + } + + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + throw new NotImplementedException(); + } + } + /// + /// 画布拉动范围距离计算器 + /// + public class BottomThumbPositionConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + if (value is double height) + return height - 10; // Adjust for Thumb height + return 0; + } + + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + throw new NotImplementedException(); + } + } + /// + /// 画布拉动范围距离计算器 + /// + public class VerticalCenterThumbPositionConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + if (value is double height) + return height / 2 - 5; // Centering Thumb vertically + return 0; + } + + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + throw new NotImplementedException(); + } + } + /// + /// 画布拉动范围距离计算器 + /// + public class HorizontalCenterThumbPositionConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + if (value is double width) + return width / 2 - 5; // Centering Thumb horizontally + return 0; + } + + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + throw new NotImplementedException(); + } + } + +} diff --git a/Workbench/Converters/TypeToColorConverter.cs b/Workbench/Converters/TypeToColorConverter.cs new file mode 100644 index 0000000..198d3bd --- /dev/null +++ b/Workbench/Converters/TypeToColorConverter.cs @@ -0,0 +1,26 @@ +using Serein.Library; +using System.Globalization; +using System.Windows.Data; +using System.Windows.Media; + +namespace Serein.Workbench.Converters +{ + /// + /// 根据控件类型切换颜色 + /// + public class TypeToColorConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + // 根据 ControlType 返回颜色 + return value switch + { + NodeControlType.Action => Brushes.Blue, + NodeControlType.Flipflop => Brushes.Green, + _ => Brushes.Black, + }; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotImplementedException(); + } +} diff --git a/Workbench/Node/Junction/JunctionControlBase.cs b/Workbench/Node/Junction/JunctionControlBase.cs index d975989..1d810d5 100644 --- a/Workbench/Node/Junction/JunctionControlBase.cs +++ b/Workbench/Node/Junction/JunctionControlBase.cs @@ -14,6 +14,7 @@ using System.Windows.Documents; using System.Threading; using Serein.Workbench.Services; using Serein.Workbench.Tool; +using System.ComponentModel; namespace Serein.Workbench.Node.View { @@ -169,15 +170,24 @@ namespace Serein.Workbench.Node.View private readonly FlowNodeService flowNodeService; protected JunctionControlBase() { - flowNodeService = App.GetService(); + + this.Width = 25; this.Height = 20; this.MouseDown += JunctionControlBase_MouseDown; this.MouseMove += JunctionControlBase_MouseMove; - this.MouseLeave += JunctionControlBase_MouseLeave; ; + this.MouseLeave += JunctionControlBase_MouseLeave; +#if DEBUG + + if (DesignerProperties.GetIsInDesignMode(new DependencyObject())) + return; + +#endif + flowNodeService = App.GetService(); + } - + #region 控件属性,所在的节点 public static readonly DependencyProperty NodeProperty = DependencyProperty.Register(nameof(MyNode), typeof(NodeModelBase), typeof(JunctionControlBase), new PropertyMetadata(default(NodeModelBase))); @@ -238,7 +248,11 @@ namespace Serein.Workbench.Node.View { if(_isMouseOver != value) { - flowNodeService.ConnectingData.CurrentJunction = this; + if(flowNodeService is not null) + { + + flowNodeService.ConnectingData.CurrentJunction = this; + } _isMouseOver = value; InvalidateVisual(); } @@ -261,6 +275,10 @@ namespace Serein.Workbench.Node.View /// protected Brush GetBackgrounp() { + if(flowNodeService is null) + { + return Brushes.Transparent; + } var cd = flowNodeService.ConnectingData; if(!cd.IsCreateing) { diff --git a/Workbench/Node/Junction/View/ExecuteJunctionControl.cs b/Workbench/Node/Junction/View/ExecuteJunctionControl.cs index 9247df1..e32f5f3 100644 --- a/Workbench/Node/Junction/View/ExecuteJunctionControl.cs +++ b/Workbench/Node/Junction/View/ExecuteJunctionControl.cs @@ -8,9 +8,6 @@ namespace Serein.Workbench.Node.View { public class ExecuteJunctionControl : JunctionControlBase { - - - public ExecuteJunctionControl() { base.JunctionType = JunctionType.Execute; diff --git a/Workbench/Node/NodeControlBase.cs b/Workbench/Node/NodeControlBase.cs index 93950d6..a4b77fd 100644 --- a/Workbench/Node/NodeControlBase.cs +++ b/Workbench/Node/NodeControlBase.cs @@ -15,7 +15,7 @@ namespace Serein.Workbench.Node.View /// /// 节点控件基类(控件) /// - public abstract class NodeControlBase : UserControl, IDynamicFlowNode + public abstract class NodeControlBase : UserControl //, IDynamicFlowNode { /// /// 节点所在的画布(以后需要将画布封装出来,实现多画布的功能) diff --git a/Workbench/Node/NodeControlViewModelBase.cs b/Workbench/Node/NodeControlViewModelBase.cs index f2b891d..c3770a3 100644 --- a/Workbench/Node/NodeControlViewModelBase.cs +++ b/Workbench/Node/NodeControlViewModelBase.cs @@ -4,10 +4,11 @@ using System.Runtime.CompilerServices; using System.Windows.Controls; using System.Windows.Data; using System; +using CommunityToolkit.Mvvm.ComponentModel; namespace Serein.Workbench.Node.ViewModel { - public abstract class NodeControlViewModelBase + public abstract partial class NodeControlViewModelBase : ObservableObject { ///// @@ -21,30 +22,14 @@ namespace Serein.Workbench.Node.ViewModel } - - private bool isInterrupt; - private bool isReadonlyOnView = true; - ///// - ///// 控制中断状态的视觉效果 - ///// - public bool IsInterrupt - { - get => NodeModel.DebugSetting.IsInterrupt; - set - { - NodeModel.DebugSetting.IsInterrupt = value; - OnPropertyChanged(); - } - } + /// /// 工作台预览基本节点时,避免其中的文本框响应拖拽事件导致卡死 /// - public bool IsEnabledOnView { get => isReadonlyOnView; set - { - OnPropertyChanged(); isReadonlyOnView = value; - } - } + [ObservableProperty] + private bool isEnabledOnView = true; + public event PropertyChangedEventHandler? PropertyChanged; diff --git a/Workbench/Node/View/ActionNodeControl.xaml b/Workbench/Node/View/ActionNodeControl.xaml index 12b4954..8515fd3 100644 --- a/Workbench/Node/View/ActionNodeControl.xaml +++ b/Workbench/Node/View/ActionNodeControl.xaml @@ -5,7 +5,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:Serein.Workbench.Node.View" xmlns:vm="clr-namespace:Serein.Workbench.Node.ViewModel" - xmlns:converters="clr-namespace:Serein.Workbench.Tool.Converters" + xmlns:converter="clr-namespace:Serein.Workbench.Converters" xmlns:themes="clr-namespace:Serein.Workbench.Themes" d:DataContext="{d:DesignInstance vm:ActionNodeControlViewModel}" mc:Ignorable="d" @@ -13,7 +13,7 @@ - + @@ -69,10 +69,13 @@ + - + + + @@ -105,7 +108,7 @@ - + diff --git a/Workbench/Node/View/ActionNodeControl.xaml.cs b/Workbench/Node/View/ActionNodeControl.xaml.cs index f26e7ba..ec69362 100644 --- a/Workbench/Node/View/ActionNodeControl.xaml.cs +++ b/Workbench/Node/View/ActionNodeControl.xaml.cs @@ -18,8 +18,7 @@ namespace Serein.Workbench.Node.View InitializeComponent(); if(ExecuteJunctionControl.MyNode != null) { - - ExecuteJunctionControl.MyNode.Guid = viewModel.NodeModel.Guid; + ExecuteJunctionControl.MyNode.Guid = viewModel.NodeModel.Guid; } } @@ -38,46 +37,40 @@ namespace Serein.Workbench.Node.View /// JunctionControlBase INodeJunction.ReturnDataJunction => this.ResultJunctionControl; + + /// /// 方法入参控制点(可能有,可能没) /// - JunctionControlBase[] INodeJunction.ArgDataJunction - { - get - { - // 获取 MethodDetailsControl 实例 - var methodDetailsControl = this.MethodDetailsControl; - var itemsControl = FindVisualChild(methodDetailsControl); // 查找 ItemsControl - if (itemsControl != null) - { - var argDataJunction = new JunctionControlBase[base.ViewModel.NodeModel.MethodDetails.ParameterDetailss.Length]; - var controls = new List(); + JunctionControlBase[] INodeJunction.ArgDataJunction => GetArgJunction(); - for (int i = 0; i < itemsControl.Items.Count; i++) + private JunctionControlBase[] GetArgJunction() + { + // 获取 MethodDetailsControl 实例 + var methodDetailsControl = this.MethodDetailsControl; + var itemsControl = FindVisualChild(methodDetailsControl); // 查找 ItemsControl + if (itemsControl != null) + { + var argDataJunction = new JunctionControlBase[base.ViewModel.NodeModel.MethodDetails.ParameterDetailss.Length]; + var controls = new List(); + + for (int i = 0; i < itemsControl.Items.Count; i++) + { + var container = itemsControl.ItemContainerGenerator.ContainerFromIndex(i) as FrameworkElement; + if (container != null) { - var container = itemsControl.ItemContainerGenerator.ContainerFromIndex(i) as FrameworkElement; - if (container != null) + var argControl = FindVisualChild(container); + if (argControl != null) { - var argControl = FindVisualChild(container); - if (argControl != null) - { - controls.Add(argControl); // 收集 ArgJunctionControl 实例 - } + controls.Add(argControl); // 收集 ArgJunctionControl 实例 } } - return argDataJunction = controls.ToArray(); - } - else - { - return []; } + return argDataJunction = controls.ToArray(); } - - + return []; } - - } } diff --git a/Workbench/Node/View/ConditionNodeControl.xaml.cs b/Workbench/Node/View/ConditionNodeControl.xaml.cs index eeb295a..950bab1 100644 --- a/Workbench/Node/View/ConditionNodeControl.xaml.cs +++ b/Workbench/Node/View/ConditionNodeControl.xaml.cs @@ -1,4 +1,5 @@ -using Serein.NodeFlow.Model; +using Serein.Library.Api; +using Serein.NodeFlow.Model; using Serein.Workbench.Node.ViewModel; namespace Serein.Workbench.Node.View @@ -10,8 +11,10 @@ namespace Serein.Workbench.Node.View { public ConditionNodeControl() : base() { + // 窗体初始化需要 - base.ViewModel = new ConditionNodeControlViewModel (new SingleConditionNode(null)); + var env = App.GetService(); + base.ViewModel = new ConditionNodeControlViewModel (new SingleConditionNode(env)); base.ViewModel.IsEnabledOnView = false; DataContext = ViewModel; diff --git a/Workbench/Node/View/ExpOpNodeControl.xaml b/Workbench/Node/View/ExpOpNodeControl.xaml index 81c8d8d..c6e25ed 100644 --- a/Workbench/Node/View/ExpOpNodeControl.xaml +++ b/Workbench/Node/View/ExpOpNodeControl.xaml @@ -5,7 +5,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:Serein.Workbench.Node.View" xmlns:vm="clr-namespace:Serein.Workbench.Node.ViewModel" - d:DataContext="{d:DesignInstance vm:ExpOpNodeViewModel}" + d:DataContext="{d:DesignInstance vm:ExpOpNodeControlViewModel}" mc:Ignorable="d" MaxWidth="300"> diff --git a/Workbench/Node/View/ExpOpNodeControl.xaml.cs b/Workbench/Node/View/ExpOpNodeControl.xaml.cs index f46b4a5..4a96d69 100644 --- a/Workbench/Node/View/ExpOpNodeControl.xaml.cs +++ b/Workbench/Node/View/ExpOpNodeControl.xaml.cs @@ -1,4 +1,5 @@ -using Serein.NodeFlow.Model; +using Serein.Library.Api; +using Serein.NodeFlow.Model; using Serein.Workbench.Node.ViewModel; namespace Serein.Workbench.Node.View @@ -11,7 +12,8 @@ namespace Serein.Workbench.Node.View public ExpOpNodeControl() : base() { // 窗体初始化需要 - ViewModel = new ExpOpNodeControlViewModel(new SingleExpOpNode(null)); + var env = App.GetService(); + ViewModel = new ExpOpNodeControlViewModel(new SingleExpOpNode(env)); base.ViewModel.IsEnabledOnView = false; DataContext = ViewModel; InitializeComponent(); diff --git a/Workbench/Node/View/FlipflopNodeControl.xaml b/Workbench/Node/View/FlipflopNodeControl.xaml index 1a445b9..e6bf277 100644 --- a/Workbench/Node/View/FlipflopNodeControl.xaml +++ b/Workbench/Node/View/FlipflopNodeControl.xaml @@ -3,7 +3,7 @@ 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:Converters="clr-namespace:Serein.Workbench.Tool.Converters" + xmlns:converter="clr-namespace:Serein.Workbench.Converters" xmlns:local="clr-namespace:Serein.Workbench.Node.View" xmlns:vm="clr-namespace:Serein.Workbench.Node.ViewModel" xmlns:themes="clr-namespace:Serein.Workbench.Themes" @@ -14,12 +14,10 @@ - + - - @@ -53,17 +51,11 @@ - - + - + @@ -94,7 +86,7 @@ - + diff --git a/Workbench/Node/View/FlipflopNodeControl.xaml.cs b/Workbench/Node/View/FlipflopNodeControl.xaml.cs index 353ec67..b2d68e8 100644 --- a/Workbench/Node/View/FlipflopNodeControl.xaml.cs +++ b/Workbench/Node/View/FlipflopNodeControl.xaml.cs @@ -34,37 +34,33 @@ namespace Serein.Workbench.Node.View /// /// 方法入参控制点(可能有,可能没) /// - JunctionControlBase[] INodeJunction.ArgDataJunction - { - get - { - // 获取 MethodDetailsControl 实例 - var methodDetailsControl = this.MethodDetailsControl; - var itemsControl = FindVisualChild(methodDetailsControl); // 查找 ItemsControl - if (itemsControl != null) - { - var argDataJunction = new JunctionControlBase[base.ViewModel.NodeModel.MethodDetails.ParameterDetailss.Length]; - var controls = new List(); + JunctionControlBase[] INodeJunction.ArgDataJunction => GetArgJunction(); - for (int i = 0; i < itemsControl.Items.Count; i++) + private JunctionControlBase[] GetArgJunction() + { + // 获取 MethodDetailsControl 实例 + var methodDetailsControl = this.MethodDetailsControl; + var itemsControl = FindVisualChild(methodDetailsControl); // 查找 ItemsControl + if (itemsControl != null) + { + var argDataJunction = new JunctionControlBase[base.ViewModel.NodeModel.MethodDetails.ParameterDetailss.Length]; + var controls = new List(); + + for (int i = 0; i < itemsControl.Items.Count; i++) + { + var container = itemsControl.ItemContainerGenerator.ContainerFromIndex(i) as FrameworkElement; + if (container != null) { - var container = itemsControl.ItemContainerGenerator.ContainerFromIndex(i) as FrameworkElement; - if (container != null) + var argControl = FindVisualChild(container); + if (argControl != null) { - var argControl = FindVisualChild(container); - if (argControl != null) - { - controls.Add(argControl); // 收集 ArgJunctionControl 实例 - } + controls.Add(argControl); // 收集 ArgJunctionControl 实例 } } - return argDataJunction = controls.ToArray(); - } - else - { - return []; } + return argDataJunction = controls.ToArray(); } + return []; } } diff --git a/Workbench/Node/View/FlowCallNodeControl.xaml b/Workbench/Node/View/FlowCallNodeControl.xaml index b5eccaf..6348a38 100644 --- a/Workbench/Node/View/FlowCallNodeControl.xaml +++ b/Workbench/Node/View/FlowCallNodeControl.xaml @@ -1,16 +1,152 @@  - - - - + 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:Serein.Workbench.Node.View" + xmlns:vm="clr-namespace:Serein.Workbench.Node.ViewModel" + xmlns:converter="clr-namespace:Serein.Workbench.Converters" + xmlns:themes="clr-namespace:Serein.Workbench.Themes" + mc:Ignorable="d" + MaxWidth="300" + + d:DataContext="{d:DesignInstance vm:FlowCallNodeControlViewModel}"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Workbench/Node/View/FlowCallNodeControl.xaml.cs b/Workbench/Node/View/FlowCallNodeControl.xaml.cs index 3eb41de..33c1923 100644 --- a/Workbench/Node/View/FlowCallNodeControl.xaml.cs +++ b/Workbench/Node/View/FlowCallNodeControl.xaml.cs @@ -1,17 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using Serein.Library; +using Serein.Library.Api; +using Serein.NodeFlow.Model; +using Serein.Workbench.Node.ViewModel; using System.Windows; using System.Windows.Controls; -using System.Windows.Data; -using System.Windows.Documents; -using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using System.Windows.Navigation; -using System.Windows.Shapes; namespace Serein.Workbench.Node.View { @@ -20,17 +12,79 @@ namespace Serein.Workbench.Node.View /// public partial class FlowCallNodeControl : NodeControlBase, INodeJunction { + private new FlowCallNodeControlViewModel ViewModel { get; set; } public FlowCallNodeControl() { + var env = App.GetService(); + base.ViewModel = new FlowCallNodeControlViewModel(new SingleFlowCallNode(env)); + base.ViewModel.IsEnabledOnView = false; + DataContext = base.ViewModel; InitializeComponent(); } + public FlowCallNodeControl(FlowCallNodeControlViewModel viewModel) : base(viewModel) + { + DataContext = viewModel; + ViewModel = viewModel; + InitializeComponent(); + ViewModel.UploadMethodDetailsControl = UploadMethodDetailsControl; - public JunctionControlBase ExecuteJunction => throw new NotImplementedException(); + } - public JunctionControlBase NextStepJunction => throw new NotImplementedException(); + private void UploadMethodDetailsControl(MethodDetails methodDetails) + { + //MethodDetailsControl.MethodDetails = methodDetails; + + } + + /// + /// 入参控制点(可能有,可能没) + /// + JunctionControlBase INodeJunction.ExecuteJunction => this.ExecuteJunctionControl; + + /// + /// 下一个调用方法控制点(可能有,可能没) + /// + JunctionControlBase INodeJunction.NextStepJunction => throw new NotImplementedException("不存在下一个调用控制点"); + + /// + /// 返回值控制点(可能有,可能没) + /// + JunctionControlBase INodeJunction.ReturnDataJunction => throw new NotImplementedException("不存在返回值控制点"); + + + /// + /// 方法入参控制点(可能有,可能没) + /// + JunctionControlBase[] INodeJunction.ArgDataJunction => GetArgJunction(); + + private JunctionControlBase[] GetArgJunction() + { + // 获取 MethodDetailsControl 实例 + //var methodDetailsControl = ViewModel.NodeModel.IsShareParam ? this.SelectMethodDetailsControl : this.MyMethodDetailsControl; + var methodDetailsControl = this.MethodDetailsControl; + var itemsControl = FindVisualChild(methodDetailsControl); // 查找 ItemsControl + if (itemsControl != null) + { + var argDataJunction = new JunctionControlBase[base.ViewModel.NodeModel.MethodDetails.ParameterDetailss.Length]; + var controls = new List(); + + for (int i = 0; i < itemsControl.Items.Count; i++) + { + var container = itemsControl.ItemContainerGenerator.ContainerFromIndex(i) as FrameworkElement; + if (container != null) + { + var argControl = FindVisualChild(container); + if (argControl != null) + { + controls.Add(argControl); // 收集 ArgJunctionControl 实例 + } + } + } + return argDataJunction = controls.ToArray(); + } + return []; + } - public JunctionControlBase[] ArgDataJunction => throw new NotImplementedException(); - public JunctionControlBase ReturnDataJunction => throw new NotImplementedException(); } } diff --git a/Workbench/Node/View/GlobalDataControl.xaml.cs b/Workbench/Node/View/GlobalDataControl.xaml.cs index 070066b..bdf7018 100644 --- a/Workbench/Node/View/GlobalDataControl.xaml.cs +++ b/Workbench/Node/View/GlobalDataControl.xaml.cs @@ -1,4 +1,5 @@ -using Serein.NodeFlow.Model; +using Serein.Library.Api; +using Serein.NodeFlow.Model; using Serein.Workbench.Api; using Serein.Workbench.Node.ViewModel; @@ -12,7 +13,8 @@ namespace Serein.Workbench.Node.View public GlobalDataControl() : base() { // 窗体初始化需要 - base.ViewModel = new GlobalDataNodeControlViewModel(new SingleGlobalDataNode(null)); + var env = App.GetService(); + base.ViewModel = new GlobalDataNodeControlViewModel(new SingleGlobalDataNode(env)); base.ViewModel.IsEnabledOnView = false; DataContext = ViewModel; InitializeComponent(); diff --git a/Workbench/Node/View/NetScriptNodeControl.xaml.cs b/Workbench/Node/View/NetScriptNodeControl.xaml.cs index 8c314f6..c2b286b 100644 --- a/Workbench/Node/View/NetScriptNodeControl.xaml.cs +++ b/Workbench/Node/View/NetScriptNodeControl.xaml.cs @@ -1,4 +1,5 @@ -using Serein.NodeFlow.Model; +using Serein.Library.Api; +using Serein.NodeFlow.Model; using Serein.Workbench.Node.ViewModel; using System; using System.Collections.Generic; @@ -24,7 +25,9 @@ namespace Serein.Workbench.Node.View { public NetScriptNodeControl() { - base.ViewModel = new NetScriptNodeControlViewModel(new SingleNetScriptNode(null)); + + var env = App.GetService(); + base.ViewModel = new NetScriptNodeControlViewModel(new SingleNetScriptNode(env)); base.ViewModel.IsEnabledOnView = false; base.DataContext = ViewModel; InitializeComponent(); diff --git a/Workbench/Node/View/ScriptNodeControl.xaml.cs b/Workbench/Node/View/ScriptNodeControl.xaml.cs index d066bd2..a618463 100644 --- a/Workbench/Node/View/ScriptNodeControl.xaml.cs +++ b/Workbench/Node/View/ScriptNodeControl.xaml.cs @@ -1,4 +1,5 @@ -using Serein.NodeFlow.Model; +using Serein.Library.Api; +using Serein.NodeFlow.Model; using Serein.Workbench.Node.ViewModel; using System; using System.Collections.Generic; @@ -29,7 +30,9 @@ namespace Serein.Workbench.Node.View public ScriptNodeControl() { - base.ViewModel = new ScriptNodeControlViewModel(null); + + var env = App.GetService(); + base.ViewModel = new ScriptNodeControlViewModel(new SingleScriptNode(env)); base.ViewModel.IsEnabledOnView = false; base.DataContext = viewModel; InitializeComponent(); @@ -68,39 +71,33 @@ namespace Serein.Workbench.Node.View /// /// 方法入参控制点(可能有,可能没) /// - JunctionControlBase[] INodeJunction.ArgDataJunction - { - get - { - // 获取 MethodDetailsControl 实例 - var methodDetailsControl = this.MethodDetailsControl; - var itemsControl = FindVisualChild(methodDetailsControl); // 查找 ItemsControl - if (itemsControl != null && base.ViewModel.NodeModel.MethodDetails.ParameterDetailss != null) - { - var argDataJunction = new JunctionControlBase[base.ViewModel.NodeModel.MethodDetails.ParameterDetailss.Length]; - var controls = new List(); + JunctionControlBase[] INodeJunction.ArgDataJunction => GetArgJunction(); - for (int i = 0; i < itemsControl.Items.Count; i++) + private JunctionControlBase[] GetArgJunction() + { + // 获取 MethodDetailsControl 实例 + var methodDetailsControl = this.MethodDetailsControl; + var itemsControl = FindVisualChild(methodDetailsControl); // 查找 ItemsControl + if (itemsControl != null) + { + var argDataJunction = new JunctionControlBase[base.ViewModel.NodeModel.MethodDetails.ParameterDetailss.Length]; + var controls = new List(); + + for (int i = 0; i < itemsControl.Items.Count; i++) + { + var container = itemsControl.ItemContainerGenerator.ContainerFromIndex(i) as FrameworkElement; + if (container != null) { - var container = itemsControl.ItemContainerGenerator.ContainerFromIndex(i) as FrameworkElement; - if (container != null) + var argControl = FindVisualChild(container); + if (argControl != null) { - var argControl = FindVisualChild(container); - if (argControl != null) - { - controls.Add(argControl); // 收集 ArgJunctionControl 实例 - } + controls.Add(argControl); // 收集 ArgJunctionControl 实例 } } - return argDataJunction = controls.ToArray(); - } - else - { - return []; } + return argDataJunction = controls.ToArray(); } - - + return []; } diff --git a/Workbench/Node/ViewModel/FlowCallNodeControlViewModel.cs b/Workbench/Node/ViewModel/FlowCallNodeControlViewModel.cs index 4171b87..c3c783e 100644 --- a/Workbench/Node/ViewModel/FlowCallNodeControlViewModel.cs +++ b/Workbench/Node/ViewModel/FlowCallNodeControlViewModel.cs @@ -1,7 +1,16 @@ -using Serein.NodeFlow.Model; +using CommunityToolkit.Mvvm.ComponentModel; +using Serein.Library; +using Serein.NodeFlow.Model; +using Serein.Workbench.Api; +using Serein.Workbench.Services; +using Serein.Workbench.ViewModels; +using Serein.Workbench.Views; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; using System.Linq; +using System.Security.Permissions; using System.Text; using System.Threading.Tasks; @@ -9,10 +18,101 @@ namespace Serein.Workbench.Node.ViewModel { public partial class FlowCallNodeControlViewModel : NodeControlViewModelBase { - public new SingleFlowCallNode NodelModel { get; } + /// + /// 刷新方法控件 + /// + public Action UploadMethodDetailsControl; + + + [ObservableProperty] + private SingleFlowCallNode flowCallNode; + + /// + /// 当前所选画布 + /// + [ObservableProperty] + private FlowCanvasViewModel _selectCanvas; + + /// + /// 当前所选节点 + /// + [ObservableProperty] + private NodeModelBase _selectNode; + + + [ObservableProperty] + private FlowCanvasViewModel[] canvass; + + + + private readonly FlowNodeService flowNodeService; + private readonly IFlowEEForwardingService flowEEForwardingService; + public FlowCallNodeControlViewModel(SingleFlowCallNode node) : base(node) { - this.NodelModel = node; + this.FlowCallNode = node; + flowNodeService = App.GetService(); + flowEEForwardingService = App.GetService(); + RershCanvass(); // 首次加载 + InitNodeData(); + InitEvent(); } + private void InitNodeData() + { + if (string.IsNullOrEmpty(FlowCallNode.TargetNodeGuid)) + { + return; + } + var targetNodeControl = flowNodeService.FlowNodeControls.FirstOrDefault(n => n.ViewModel.NodeModel.Guid.Equals(FlowCallNode.TargetNodeGuid)); + if (targetNodeControl is null) + { + return; + } + if (targetNodeControl.FlowCanvas is FlowCanvasView view + && view.DataContext is FlowCanvasViewModel viewModel) + { + SelectCanvas = viewModel; + SelectNode = targetNodeControl.ViewModel.NodeModel; + } + } + + private void InitEvent() + { + flowEEForwardingService.OnCanvasCreate += (e) => RershCanvass(); // 画布创建了 + flowEEForwardingService.OnCanvasRemove += (e) => RershCanvass(); // 画布移除了 + } + + partial void OnSelectCanvasChanged(FlowCanvasViewModel value) + { + FlowCallNode.ResetTargetNode(); + } + + partial void OnSelectNodeChanged(NodeModelBase value) + { + FlowCallNode.SetTargetNode(value); + } + + private void RershCanvass() + { + var canvass = flowNodeService.FlowCanvass.Select(f => (FlowCanvasViewModel)f.DataContext).ToArray(); // .Where(f => f.Model.PublicNodes.Count > 0) + Canvass = canvass; + } + + + + /*private void RershMds() + { + if (NodeModel.IsShareParam && SelectNode is not null) + { + UploadMethodDetailsControl?.Invoke(SelectNode.MethodDetails); + + } + else + { + UploadMethodDetailsControl?.Invoke(base.NodeModel.MethodDetails); + } + } +*/ + } } diff --git a/Workbench/Serein.WorkBench.csproj b/Workbench/Serein.WorkBench.csproj index 0561aaf..cdb1c5c 100644 --- a/Workbench/Serein.WorkBench.csproj +++ b/Workbench/Serein.WorkBench.csproj @@ -15,12 +15,16 @@ + + + + @@ -30,6 +34,7 @@ + diff --git a/Workbench/Services/FlowNodeService.cs b/Workbench/Services/FlowNodeService.cs index 0476c55..c3c25dd 100644 --- a/Workbench/Services/FlowNodeService.cs +++ b/Workbench/Services/FlowNodeService.cs @@ -102,6 +102,7 @@ namespace Serein.Workbench.Services /// 当前所有画布 /// public FlowCanvasView[] FlowCanvass => Canvass.Select(c => c.Value).ToArray(); + public NodeControlBase[] FlowNodeControls => NodeControls.Select(c => c.Value).ToArray(); /// /// 记录流程画布 @@ -145,10 +146,10 @@ namespace Serein.Workbench.Services flowEnvironment.NodeMVVMManagement.RegisterUI(NodeControlType.Flipflop, typeof(FlipflopNodeControl), typeof(FlipflopNodeControlViewModel)); flowEnvironment.NodeMVVMManagement.RegisterUI(NodeControlType.ExpOp, typeof(ExpOpNodeControl), typeof(ExpOpNodeControlViewModel)); flowEnvironment.NodeMVVMManagement.RegisterUI(NodeControlType.ExpCondition, typeof(ConditionNodeControl), typeof(ConditionNodeControlViewModel)); - //flowEnvironment.NodeMVVMManagement.RegisterUI(NodeControlType.ConditionRegion, typeof(ConditionRegionControl), typeof(ConditionRegionNodeControlViewModel)); flowEnvironment.NodeMVVMManagement.RegisterUI(NodeControlType.GlobalData, typeof(GlobalDataControl), typeof(GlobalDataNodeControlViewModel)); flowEnvironment.NodeMVVMManagement.RegisterUI(NodeControlType.Script, typeof(ScriptNodeControl), typeof(ScriptNodeControlViewModel)); flowEnvironment.NodeMVVMManagement.RegisterUI(NodeControlType.NetScript, typeof(NetScriptNodeControl), typeof(NetScriptNodeControlViewModel)); + flowEnvironment.NodeMVVMManagement.RegisterUI(NodeControlType.FlowCall, typeof(FlowCallNodeControl), typeof(FlowCallNodeControlViewModel)); } /// @@ -301,6 +302,11 @@ namespace Serein.Workbench.Services } #endregion + //if (nodeModel.ControlType == NodeControlType.FlowCall) + //{ + // Console.WriteLine("test"); + //} + #region 创建控件 NodeControlBase nodeControl; @@ -335,6 +341,7 @@ namespace Serein.Workbench.Services SereinEnv.WriteLine(InfoType.INFO, $"无法移除画布,画布不存在。"); return; } + Canvass.Remove(eventArgs.CanvasGuid); OnRemoveFlowCanvasView.Invoke(nodeCanvas); } diff --git a/Workbench/Services/WorkbenchEventService.cs b/Workbench/Services/WorkbenchEventService.cs index 73422db..7e91d60 100644 --- a/Workbench/Services/WorkbenchEventService.cs +++ b/Workbench/Services/WorkbenchEventService.cs @@ -11,6 +11,7 @@ using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading.Tasks; +using System.Windows.Input; namespace Serein.Workbench.Services { @@ -56,16 +57,25 @@ namespace Serein.Workbench.Services private readonly IFlowEnvironment flowEnvironment; private readonly IFlowEEForwardingService flowEEForwardingService; + private readonly IKeyEventService keyEventService; + private readonly FlowNodeService flowNodeService; /// /// 管理工作台的事件 /// /// /// - public WorkbenchEventService(IFlowEnvironment flowEnvironment, IFlowEEForwardingService flowEEForwardingService) + /// + /// + public WorkbenchEventService(IFlowEnvironment flowEnvironment, + IFlowEEForwardingService flowEEForwardingService, + IKeyEventService keyEventService, + FlowNodeService flowNodeService) { this.flowEnvironment = flowEnvironment; this.flowEEForwardingService = flowEEForwardingService; + this.keyEventService = keyEventService; + this.flowNodeService = flowNodeService; InitEvents(); } @@ -73,6 +83,12 @@ namespace Serein.Workbench.Services { flowEEForwardingService.OnProjectSaving += SaveProjectToLocalFile; flowEEForwardingService.OnEnvOut += FlowEEForwardingService_OnEnvOut; + keyEventService.OnKeyDown += KeyEventService_OnKeyDown; ; + } + + private void KeyEventService_OnKeyDown(System.Windows.Input.Key key) + { + } private void FlowEEForwardingService_OnEnvOut(InfoType type, string value) diff --git a/Workbench/Themes/MethodDetailsControl.xaml b/Workbench/Themes/MethodDetailsControl.xaml index 6e3cdad..8258f8e 100644 --- a/Workbench/Themes/MethodDetailsControl.xaml +++ b/Workbench/Themes/MethodDetailsControl.xaml @@ -5,17 +5,17 @@ xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:serein="clr-namespace:Serein.Library;assembly=Serein.Library" - xmlns:converters="clr-namespace:Serein.Workbench.Tool.Converters"> + xmlns:converter="clr-namespace:Serein.Workbench.Converters"> - - - - + + + + diff --git a/Workbench/Views/BaseNodesView.xaml b/Workbench/Views/BaseNodesView.xaml index 8c1e870..c3429e3 100644 --- a/Workbench/Views/BaseNodesView.xaml +++ b/Workbench/Views/BaseNodesView.xaml @@ -12,10 +12,11 @@ - - - - + + + + + diff --git a/Workbench/Views/FlowCanvasView.xaml b/Workbench/Views/FlowCanvasView.xaml index 5c24964..c763a8f 100644 --- a/Workbench/Views/FlowCanvasView.xaml +++ b/Workbench/Views/FlowCanvasView.xaml @@ -4,7 +4,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:Serein.Workbench.Views" - xmlns:tool="clr-namespace:Serein.Workbench.Tool.Converters" + xmlns:converter="clr-namespace:Serein.Workbench.Converters" xmlns:vm="clr-namespace:Serein.Workbench.ViewModels" mc:Ignorable="d" d:DesignHeight="450" @@ -12,10 +12,10 @@ d:DataContext="{d:DesignInstance vm:FlowCanvasViewModel}"> - - - - + + + + { FlowChartCanvas.Children.Add(nodeControl); + if(nodeControl.ViewModel.NodeModel.ControlType == NodeControlType.UI) + { + // 需要切换到对应画布,尽可能让UI线程获取到适配器 + var edit = App.GetService().FlowEditViewModel; + var tab = edit.CanvasTabs.First(tab => tab.Content == this); + App.GetService().FlowEditViewModel.SelectedTab = tab; + + } }); ConfigureNodeEvents(nodeControl); // 配置相关事件 @@ -294,7 +302,7 @@ namespace Serein.Workbench.Views flowNodeService = App.GetService(); keyEventService = App.GetService(); flowNodeService.OnCreateNode += OnCreateNode; - keyEventService.OnKeyDown += KeyEventService_OnKeyDown; ; + keyEventService.OnKeyDown += KeyEventService_OnKeyDown; // 缩放平移容器 canvasTransformGroup = new TransformGroup(); @@ -413,14 +421,25 @@ namespace Serein.Workbench.Views { return; } - + + if (key == Key.F5) { - // F5 调试当前流程 - _ = flowEnvironment.StartFlowAsync([Guid]); - } + if (keyEventService.GetKeyState(Key.LeftCtrl) || keyEventService.GetKeyState(Key.RightCtrl)) + { + // Ctrl + F5 调试当前流程 + _ = flowEnvironment.StartFlowAsync([flowNodeService.CurrentSelectCanvas.Guid]); + } + else if (selectNodeControls.Count == 1 ) + { + // F5 调试当前选定节点 + var nodeModel = selectNodeControls[0].ViewModel.NodeModel; + SereinEnv.WriteLine(InfoType.INFO, $"调试运行当前节点:{nodeModel.Guid}"); + _ = nodeModel.StartFlowAsync(new DynamicContext(flowEnvironment), new CancellationToken()); + } + } if (key == Key.Escape) @@ -630,6 +649,7 @@ namespace Serein.Workbench.Views Type when typeof(GlobalDataControl).IsAssignableFrom(droppedType) => NodeControlType.GlobalData, Type when typeof(ScriptNodeControl).IsAssignableFrom(droppedType) => NodeControlType.Script, Type when typeof(NetScriptNodeControl).IsAssignableFrom(droppedType) => NodeControlType.NetScript, + Type when typeof(FlowCallNodeControl).IsAssignableFrom(droppedType) => NodeControlType.FlowCall, _ => NodeControlType.None, }; if (nodeControlType != NodeControlType.None) @@ -1146,7 +1166,7 @@ namespace Serein.Workbench.Views if (sender is NodeControlBase nodeControl) { //ChangeViewerObjOfNode(nodeControl); // 对象树 - if (nodeControl?.ViewModel?.NodeModel?.MethodDetails?.IsProtectionParameter == true) return; + //if (nodeControl?.ViewModel?.NodeModel?.DebugSetting.IsProtectionParameter == true) return; IsControlDragging = true; startControlDragPoint = e.GetPosition(FlowChartCanvas); // 记录鼠标按下时的位置 ((UIElement)sender).CaptureMouse(); // 捕获鼠标 diff --git a/Workbench/Views/FlowEditView.xaml b/Workbench/Views/FlowEditView.xaml index f208415..148879a 100644 --- a/Workbench/Views/FlowEditView.xaml +++ b/Workbench/Views/FlowEditView.xaml @@ -4,14 +4,14 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:Serein.Workbench.Views" - xmlns:converters="clr-namespace:Serein.Workbench.Tool.Converters" + xmlns:converter="clr-namespace:Serein.Workbench.Converters" xmlns:vm="clr-namespace:Serein.Workbench.ViewModels" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800" d:DataContext="{d:DesignInstance vm:FlowEditViewModel}" Background="#E7F0F6"> - +