更改了远程环境下websocket会来回发送重复消息的问题

This commit is contained in:
fengjiayi
2024-10-28 10:25:57 +08:00
parent e2f1ec5810
commit f20cfb755c
20 changed files with 297 additions and 167 deletions

View File

@@ -13,7 +13,7 @@ namespace Serein.FlowStartTool
{
public static void Main(string[] args)
{
#if true
#if debug
args = [@"F:\临时\project\linux\project.dnf"];
#endif

View File

@@ -263,9 +263,9 @@ namespace Serein.Library
/// <summary>
/// 执行单个节点对应的方法,并不做状态检查
/// </summary>
/// <param name="env"></param>
/// <param name="context">运行时上下文</param>
/// <returns></returns>
public virtual async Task<object> InvokeAsync(IFlowEnvironment env)
public virtual async Task<object> InvokeAsync(IDynamicContext context)
{
try
{
@@ -274,16 +274,16 @@ namespace Serein.Library
{
throw new Exception($"不存在方法信息{md.MethodName}");
}
if (!env.TryGetDelegateDetails(md.MethodName, out var dd))
if (!Env.TryGetDelegateDetails(md.MethodName, out var dd))
{
throw new Exception($"不存在对应委托{md.MethodName}");
}
if (md.ActingInstance is null)
{
md.ActingInstance = env.IOC.Get(md.ActingInstanceType);
md.ActingInstance = Env.IOC.Get(md.ActingInstanceType);
if (md.ActingInstance is null)
{
md.ActingInstance = env.IOC.Instantiate(md.ActingInstanceType);
md.ActingInstance = Env.IOC.Instantiate(md.ActingInstanceType);
if (md.ActingInstance is null)
{
throw new Exception($"无法创建相应的实例{md.ActingInstanceType.FullName}");
@@ -291,7 +291,7 @@ namespace Serein.Library
}
}
object[] args = await GetParametersAsync(null, this, md);
object[] args = await GetParametersAsync(context, this, md);
var result = await dd.InvokeAsync(md.ActingInstance, args);
return result;
}
@@ -368,7 +368,6 @@ namespace Serein.Library
else if (ed.ArgDataSourceType == ConnectionArgSourceType.GetOtherNodeDataOfInvoke)
{
// 立刻调用对应节点获取数据。
var result = await context.Env.InvokeNodeAsync(ed.ArgDataSourceNodeGuid);
inputParameter = result;
}

View File

@@ -25,8 +25,17 @@ namespace Serein.Library.Network.WebSocketCommunication
[AttributeUsage(AttributeTargets.Class)]
public sealed class AutoSocketModuleAttribute : Attribute
{
/// <summary>
/// 业务标识
/// </summary>
public string ThemeKey;
/// <summary>
/// 数据标识
/// </summary>
public string DataKey;
/// <summary>
/// ID标识
/// </summary>
public string MsgIdKey;
}
@@ -55,10 +64,10 @@ namespace Serein.Library.Network.WebSocketCommunication
public string ThemeValue = string.Empty;
/// <summary>
/// <para>标记方法执行完成后是否需要将结果发送。</para>
/// <para>但以下情况将不会发送</para>
/// <para>1.返回类型为void</para>
/// <para>2.返回类型为Task</para>
/// <para>3.返回了null</para>
/// <para>注意以下返回值,返回的 json 中将不会新建 DataKey 字段</para>
/// <para>1.返回类型为 void </para>
/// <para>2.返回类型为 Task </para>
/// <para>2.返回类型为 Unit </para>
/// <para>补充如果返回类型是Task&lt;TResult&gt;</para>
/// <para>会进行异步等待当Task结束后自动获取TResult进行发送请避免Task&lt;Task&lt;TResult&gt;&gt;诸如此类的Task泛型嵌套</para>
/// </summary>

View File

@@ -89,9 +89,8 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
/// <summary>
/// 处理JSON数据
/// </summary>
public async void HandleSocketMsg(WebSocketMsgContext context) // Func<string, Task> sendAsync, JObject jsonObject
public async Task HandleAsync(WebSocketMsgContext context)
{
var jsonObject = context.JsonObject; // 获取到消息
string theme = jsonObject.GetValue(moduleConfig.ThemeJsonKey)?.ToString();
if (!MyHandleConfigs.TryGetValue(theme, out var handldConfig))
@@ -112,7 +111,7 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
{
var dataObj = jsonObject.GetValue(moduleConfig.DataJsonKey)?.ToObject<JObject>();
context.MsgData = dataObj; // 添加消息
if (TryGetParameters(handldConfig, context, out var args))
if (WebSocketHandleModule.TryGetParameters(handldConfig, context, out var args))
{
var result = await WebSocketHandleModule.HandleAsync(handldConfig, args);
if (handldConfig.IsReturnValue)
@@ -149,7 +148,7 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
/// <param name="context">处理上下文</param>
/// <param name="args">返回的入参参数</param>
/// <returns></returns>
internal static bool TryGetParameters(HandleConfiguration config,WebSocketMsgContext context, out object[] args)
internal static bool TryGetParameters(HandleConfiguration config, WebSocketMsgContext context, out object[] args)
{
args = new object[config.ParameterType.Length];
bool isCanInvoke = true; ; // 表示是否可以调用方法

View File

@@ -11,23 +11,28 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
/// <summary>
/// 消息处理上下文
/// </summary>
public class WebSocketMsgContext
public class WebSocketMsgContext : IDisposable
{
public WebSocketMsgContext(Func<string, Task> sendAsync)
{
this._sendAsync = sendAsync;
}
public void Dispose()
{
JsonObject = null;
MsgTheme = null;
MsgId = null;
MsgData = null;
MsgData = null;
_sendAsync = null;
}
/// <summary>
/// 标记是否已经处理,如果是,则提前退出
/// </summary>
public bool Handle { get; set; }
/// <summary>
/// 消息本体(文本)
/// </summary>
public string Msg { get; set; }
/// <summary>
/// 消息本体JObject
/// </summary>
@@ -107,6 +112,7 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
await SendAsync(msg);
}
}
}

View File

@@ -17,6 +17,7 @@ using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Diagnostics.CodeAnalysis;
using System.Reactive;
namespace Serein.Library.Network.WebSocketCommunication.Handle
{
@@ -118,14 +119,30 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
}
#region
var config = new WebSocketHandleConfiguration
{
IsReturnValue = methodsAttribute.IsReturnValue,
ThemeValue = methodsAttribute.ThemeValue,
ArgNotNull = methodsAttribute.ArgNotNull,
};
var parameterInfos = methodInfo.GetParameters();
var config = new WebSocketHandleConfiguration();
config.ThemeValue = methodsAttribute.ThemeValue;
config.ArgNotNull = methodsAttribute.ArgNotNull;
config.IsReturnValue = methodsAttribute.IsReturnValue;
//if (config.IsReturnValue)
//{
// // 重新检查是否能够返回
// if (methodInfo.ReturnType == typeof(void))
// {
// config.IsReturnValue = false; // void 不返回
// }
// else if (methodInfo.ReturnType == typeof(Unit))
// {
// config.IsReturnValue = false; // Unit 不返回
// }
// else if (methodInfo.ReturnType == typeof(Task))
// {
// config.IsReturnValue = false; // void 不返回
// }
//}
var parameterInfos = methodInfo.GetParameters();
config.DelegateDetails = new DelegateDetails(methodInfo); // 对应theme的emit构造委托调用工具类
config.Instance = socketControlBase; // 调用emit委托时的实例
config.OnExceptionTracking = onExceptionTracking; // 异常追踪
@@ -184,12 +201,11 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
/// </summary>
/// <param name="context">此次请求的上下文</param>
/// <returns></returns>
public void HandleMsg(WebSocketMsgContext context)
public async Task HandleAsync(WebSocketMsgContext context)
{
// OnExceptionTracking
foreach (var module in MyHandleModuleDict.Values)
{
module.HandleSocketMsg(context);
await module.HandleAsync(context);
}
}

View File

@@ -129,21 +129,28 @@ namespace Serein.Library.Network.WebSocketCommunication
public async Task HandleMsgAsync(WebSocket webSocket,
MsgQueueUtil msgQueueUtil)
{
async Task sendasync(string text)
{
await SocketExtension.SendAsync(webSocket, text); // 回复客户端,处理方法中入参如果需要发送消息委托,则将该回调方法作为委托参数传入
}
while (true)
{
var message = await msgQueueUtil.WaitMsgAsync(); // 有消息时通知
using (var context = new WebSocketMsgContext(sendasync))
{
context.JsonObject = JObject.Parse(message);
await MsgHandleHelper.HandleAsync(context); // 处理消息
}
_ = Task.Run(() => {
JObject json = JObject.Parse(message);
WebSocketMsgContext context = new WebSocketMsgContext(async (text) =>
{
await SocketExtension.SendAsync(webSocket, text); // 回复客户端,处理方法中入参如果需要发送消息委托,则将该回调方法作为委托参数传入
});
context.JsonObject = json;
MsgHandleHelper.HandleMsg(context); // 处理消息
});
//_ = Task.Run(() => {
// JObject json = JObject.Parse(message);
// WebSocketMsgContext context = new WebSocketMsgContext(async (text) =>
// {
// await SocketExtension.SendAsync(webSocket, text); // 回复客户端,处理方法中入参如果需要发送消息委托,则将该回调方法作为委托参数传入
// });
// context.JsonObject = json;
// await MsgHandleHelper.HandleAsync(context); // 处理消息
//});
}

View File

@@ -234,7 +234,10 @@ namespace Serein.Library.Network.WebSocketCommunication
MsgQueueUtil msgQueueUtil,
WebSocketAuthorizedHelper authorizedHelper)
{
async Task sendasync(string text)
{
await SocketExtension.SendAsync(webSocket, text); // 回复客户端,处理方法中入参如果需要发送消息委托,则将该回调方法作为委托参数传入
}
while (true)
{
var message = await msgQueueUtil.WaitMsgAsync(); // 有消息时通知
@@ -251,16 +254,16 @@ namespace Serein.Library.Network.WebSocketCommunication
return;
}
}
_ = Task.Run(() => {
JObject json = JObject.Parse(message);
WebSocketMsgContext context = new WebSocketMsgContext(async (text) =>
{
await SocketExtension.SendAsync(webSocket, text); // 回复客户端,处理方法中入参如果需要发送消息委托,则将该回调方法作为委托参数传入
});
context.JsonObject = json;
MsgHandleHelper.HandleMsg(context); // 处理消息
});
using (var context = new WebSocketMsgContext(sendasync))
{
context.JsonObject = JObject.Parse(message);
await MsgHandleHelper.HandleAsync(context); // 处理消息
}
//_ = Task.Run(() => {
//});
}

View File

@@ -2,6 +2,7 @@
using Newtonsoft.Json;
using Serein.Library;
using Serein.Library.Api;
using Serein.Library.Core.NodeFlow;
using Serein.Library.FlowNode;
using Serein.Library.Utils;
using Serein.Library.Utils.SereinExpression;
@@ -407,21 +408,18 @@ namespace Serein.NodeFlow.Env
/// <summary>
/// 单独运行一个节点
/// </summary>
/// <param name="context"></param>
/// <param name="nodeGuid"></param>
/// <returns></returns>
public async Task<object> InvokeNodeAsync(string nodeGuid)
{
if(this.NodeModels.TryGetValue(nodeGuid, out var model))
IDynamicContext context = new DynamicContext(this);
object result = true;
if (this.NodeModels.TryGetValue(nodeGuid, out var model))
{
return await model.InvokeAsync(this);
}
else
{
return null;
result = await model.InvokeAsync(context);
}
context.Exit();
return result;
}
/// <summary>

View File

@@ -364,9 +364,9 @@ namespace Serein.NodeFlow.Env
await currentFlowEnvironment.StartAsyncInSelectNode(startNodeGuid);
}
public async Task<object> InvokeNodeAsync(string nodeGuid)
public async Task<object> InvokeNodeAsync( string nodeGuid)
{
return await currentFlowEnvironment.InvokeNodeAsync(nodeGuid);
return await currentFlowEnvironment.InvokeNodeAsync( nodeGuid);
}
public async Task StartRemoteServerAsync(int port = 7525)

View File

@@ -78,7 +78,7 @@ namespace Serein.NodeFlow.Env
/// </summary>
/// <param name="msgId"></param>
/// <param name="flowEnvInfo"></param>
[AutoSocketHandle(ThemeValue = EnvMsgTheme.GetEnvInfo)]
[AutoSocketHandle(ThemeValue = EnvMsgTheme.GetEnvInfo, IsReturnValue = false)]
public void GetEnvInfo([UseMsgId] string msgId, [UseData] FlowEnvInfo flowEnvInfo)
{
remoteFlowEnvironment.TriggerSignal(msgId, flowEnvInfo);
@@ -90,19 +90,19 @@ namespace Serein.NodeFlow.Env
/// </summary>
/// <param name="msgId"></param>
/// <param name="sereinProjectData"></param>
[AutoSocketHandle(ThemeValue = EnvMsgTheme.GetProjectInfo)]
[AutoSocketHandle(ThemeValue = EnvMsgTheme.GetProjectInfo, IsReturnValue = false)]
public void GetProjectInfo([UseMsgId] string msgId, [UseData] SereinProjectData sereinProjectData)
{
remoteFlowEnvironment.TriggerSignal(msgId, sereinProjectData);
}
[AutoSocketHandle(ThemeValue = EnvMsgTheme.SetNodeInterrupt)]
[AutoSocketHandle(ThemeValue = EnvMsgTheme.SetNodeInterrupt, IsReturnValue = false)]
public void SetNodeInterrupt([UseMsgId] string msgId)
{
remoteFlowEnvironment.TriggerSignal(msgId, null);
}
[AutoSocketHandle(ThemeValue = EnvMsgTheme.AddInterruptExpression)]
[AutoSocketHandle(ThemeValue = EnvMsgTheme.AddInterruptExpression, IsReturnValue = false)]
public void AddInterruptExpression([UseMsgId] string msgId)
{
remoteFlowEnvironment.TriggerSignal(msgId, null);
@@ -110,37 +110,37 @@ namespace Serein.NodeFlow.Env
[AutoSocketHandle(ThemeValue = EnvMsgTheme.CreateNode)]
[AutoSocketHandle(ThemeValue = EnvMsgTheme.CreateNode, IsReturnValue = false)]
public void CreateNode([UseMsgId] string msgId, [UseData] NodeInfo nodeInfo)
{
remoteFlowEnvironment.TriggerSignal(msgId, nodeInfo);
}
[AutoSocketHandle(ThemeValue = EnvMsgTheme.RemoveNode)]
[AutoSocketHandle(ThemeValue = EnvMsgTheme.RemoveNode, IsReturnValue = false)]
public void RemoveNode([UseMsgId] string msgId, bool state)
{
remoteFlowEnvironment.TriggerSignal(msgId, state);
}
[AutoSocketHandle(ThemeValue = EnvMsgTheme.ConnectInvokeNode)]
[AutoSocketHandle(ThemeValue = EnvMsgTheme.ConnectInvokeNode, IsReturnValue = false)]
public void ConnectInvokeNode([UseMsgId] string msgId, bool state)
{
remoteFlowEnvironment.TriggerSignal(msgId, state);
}
[AutoSocketHandle(ThemeValue = EnvMsgTheme.RemoveInvokeConnect)]
[AutoSocketHandle(ThemeValue = EnvMsgTheme.RemoveInvokeConnect, IsReturnValue = false)]
public void RemoveInvokeConnect([UseMsgId] string msgId, bool state)
{
remoteFlowEnvironment.TriggerSignal(msgId, state);
}
[AutoSocketHandle(ThemeValue = EnvMsgTheme.ConnectArgSourceNode)]
[AutoSocketHandle(ThemeValue = EnvMsgTheme.ConnectArgSourceNode, IsReturnValue = false)]
public void ConnectArgSourceNode([UseMsgId] string msgId, bool state)
{
remoteFlowEnvironment.TriggerSignal(msgId, state);
}
[AutoSocketHandle(ThemeValue = EnvMsgTheme.RemoveArgSourceConnect)]
[AutoSocketHandle(ThemeValue = EnvMsgTheme.RemoveArgSourceConnect, IsReturnValue = false)]
public void RemoveArgSourceConnect([UseMsgId] string msgId, bool state)
{
remoteFlowEnvironment.TriggerSignal(msgId, state);

View File

@@ -358,33 +358,42 @@ namespace Serein.NodeFlow
/// <returns></returns>
private async Task FlipflopExecuteAsync(IFlowEnvironment env, SingleFlipflopNode singleFlipFlopNode, CancellationTokenSource cts)
{
var context = new DynamicContext(env); // 启动全局触发器时新建上下文
if(_flipFlopCts is null)
{
Console.WriteLine("flowStarter -> FlipflopExecuteAsync -> _flipFlopCts is null");
return;
}
while (!_flipFlopCts.IsCancellationRequested && !cts.IsCancellationRequested)
{
var context = new DynamicContext(env); // 启动全局触发器时新建上下文
try
{
var newFlowData = await singleFlipFlopNode.ExecutingAsync(context); // 获取触发器等待Task
await NodeModelBase.RefreshFlowDataAndExpInterrupt(context, singleFlipFlopNode, newFlowData); // 全局触发器触发后刷新该触发器的节点数据
if (context.NextOrientation != ConnectionInvokeType.None)
if (context.NextOrientation == ConnectionInvokeType.None)
{
var nextNodes = singleFlipFlopNode.SuccessorNodes[context.NextOrientation];
for (int i = nextNodes.Count - 1; i >= 0 && !_flipFlopCts.IsCancellationRequested; i--)
{
// 筛选出启用的节点
if (nextNodes[i].DebugSetting.IsEnable)
{
nextNodes[i].PreviousNode = singleFlipFlopNode;
if (nextNodes[i].DebugSetting.IsInterrupt) // 执行触发前
{
var cancelType = await nextNodes[i].DebugSetting.GetInterruptTask();
await Console.Out.WriteLineAsync($"[{nextNodes[i].MethodDetails.MethodName}]中断已{cancelType},开始执行后继分支");
}
await nextNodes[i].StartFlowAsync(context); // 启动执行触发器后继分支的节点
}
}
continue;
}
var nextNodes = singleFlipFlopNode.SuccessorNodes[context.NextOrientation];
for (int i = nextNodes.Count - 1; i >= 0 && !_flipFlopCts.IsCancellationRequested; i--)
{
// 筛选出启用的节点
if (!nextNodes[i].DebugSetting.IsEnable)
{
continue;
}
nextNodes[i].PreviousNode = singleFlipFlopNode;
if (nextNodes[i].DebugSetting.IsInterrupt) // 执行触发前
{
var cancelType = await nextNodes[i].DebugSetting.GetInterruptTask();
await Console.Out.WriteLineAsync($"[{nextNodes[i].MethodDetails.MethodName}]中断已{cancelType},开始执行后继分支");
}
await nextNodes[i].StartFlowAsync(context); // 启动执行触发器后继分支的节点
}
}
catch(FlipflopException ex)
catch (FlipflopException ex)
{
await Console.Out.WriteLineAsync($"触发器[{singleFlipFlopNode.MethodDetails.MethodName}]因非预期异常终止。"+ex.Message);
if (ex.Type == FlipflopException.CancelClass.Flow)
@@ -396,6 +405,10 @@ namespace Serein.NodeFlow
{
await Console.Out.WriteLineAsync(ex.Message);
}
finally
{
context.Exit();
}
}
}

View File

@@ -9,35 +9,7 @@ namespace Serein.NodeFlow.Tool;
public static class NodeMethodDetailsHelper
{
/// <summary>
/// 生成方法信息
/// </summary>
/// <param name="serviceContainer"></param>
/// <param name="type"></param>
/// <returns></returns>
//public static List<MethodDetails> GetList(Type type)
//{
// var methodDetailsDictionary = new List<MethodDetails>();
// var delegateDictionary = new List<Delegate>();
// var assemblyName = type.Assembly.GetName().Name;
// var methods = GetMethodsToProcess(type);
// foreach (var method in methods)
// {
// (var methodDetails,var methodDelegate) = CreateMethodDetails(type, method, assemblyName);
// methodDetailsDictionary.Add(methodDetails);
// delegateDictionary.Add(methodDelegate);
// }
// var mds = methodDetailsDictionary.OrderBy(it => it.MethodName).ToList();
// var dels = delegateDictionary;
// return mds;
//}
/// <summary>
/// 获取处理方法
/// </summary>

View File

@@ -13,7 +13,7 @@ namespace Serein.Workbench
void LoadLocalProject()
{
#if DEBUG
if (1 == 11)
if (1 == 1)
{
string filePath;
filePath = @"F:\临时\project\linux\project.dnf";

View File

@@ -213,17 +213,17 @@ Canvas.Top="{Binding ActualHeight, ElementName=FlowChartCanvas, Mode=OneWay, Con
Visibility="{Binding IsConnectionInvokeNode,
Converter={StaticResource InvertedBoolConverter},ConverterParameter=Normal}" >
<TextBlock Margin="8,2,8,0" Foreground="#FF2727" FontSize="14" Text="正在设置调用关系"/>
<TextBlock Margin="8,0,8,0" Foreground="#4A82E4" FontSize="14" Text=" 按 1 切换:上游分支"/>
<TextBlock Margin="8,0,8,0" Foreground="#04FC10" FontSize="14" Text=" 按 2 切换Succeed 分支"/>
<TextBlock Margin="8,0,8,0" Foreground="#F18905" FontSize="14" Text=" 按 3 切换Fail 分支"/>
<TextBlock Margin="8,0,8,2" Foreground="#FE1343" FontSize="14" Text=" 按 4 切换:异常分支"/>
<TextBlock Margin="8,0,8,0" Foreground="#4A82E4" FontSize="14" Text=" 按 1 切换:上游分支(运行本节点前,优先执行目标节点)"/>
<TextBlock Margin="8,0,8,0" Foreground="#04FC10" FontSize="14" Text=" 按 2 切换Succeed 分支(本节点运行完成,将会运行目标节点)"/>
<TextBlock Margin="8,0,8,0" Foreground="#F18905" FontSize="14" Text=" 按 3 切换Fail 分支条件节点的false分支"/>
<TextBlock Margin="8,0,8,2" Foreground="#FE1343" FontSize="14" Text=" 按 4 切换:异常分支(本节点运行发生异常时执行目标节点)"/>
</StackPanel>
<StackPanel Margin="14" Width="auto" HorizontalAlignment="Left" Background="White" Opacity="0.9"
Visibility="{Binding IsConnectionArgSourceNode,
Converter={StaticResource InvertedBoolConverter},ConverterParameter=Normal}" >
<TextBlock Margin="8,2,8,0" Foreground="#FF2727" FontSize="14" Text="正在设置参数传递关系"/>
<TextBlock Margin="8,0,8,0" Foreground="#56CEF6" FontSize="14" Text=" 按 1 切换:调用时获取指定节点返回值"/>
<TextBlock Margin="8,0,8,2" Foreground="#B06BBB" FontSize="14" Text=" 按 2 切换:调用时立刻调用指定节点,使用其返回值作为入参参数"/>
<TextBlock Margin="8,0,8,0" Foreground="#56CEF6" FontSize="14" Text=" 按 1 切换:入参使用目标节点返回值"/>
<TextBlock Margin="8,0,8,2" Foreground="#B06BBB" FontSize="14" Text=" 按 2 切换:立刻调用目标节点,其返回值作为入参参数"/>
</StackPanel>
</StackPanel>
</Grid>

View File

@@ -18,14 +18,11 @@
</UserControl.Resources>
<Border BorderBrush="#8DE9FD" BorderThickness="4">
<Grid>
<Grid.ToolTip>
<ToolTip Background="LightYellow" Foreground="#071042" Content="{Binding NodeModel.MethodDetails.MethodTips}" />
</Grid.ToolTip>
<!--<TextBlock Text="{Binding NodelModel.DebugSetting.IsInterrupt}}"></TextBlock>-->
<Border x:Name="InterruptBorder" DataContext="{Binding}">
<Border.Style>

View File

@@ -23,10 +23,10 @@ namespace Serein.Workbench.Node.View
/// 入参控制点(可能有,可能没)
/// </summary>
JunctionControlBase INodeJunction.ExecuteJunction => this.ExecuteJunctionControl;
/// <summary>
/// 下一个调用方法控制点(可能有,可能没)
/// </summary>
JunctionControlBase INodeJunction.NextStepJunction => this.NextStepJunctionControl;
/// <summary>
@@ -34,6 +34,10 @@ namespace Serein.Workbench.Node.View
/// </summary>
JunctionControlBase INodeJunction.ReturnDataJunction => this.ResultJunctionControl;
/// <summary>
/// 方法入参控制点(可能有,可能没)
/// </summary>
private JunctionControlBase[] argDataJunction;
/// <summary>
/// 方法入参控制点(可能有,可能没)
/// </summary>
@@ -67,28 +71,8 @@ namespace Serein.Workbench.Node.View
return argDataJunction;
} }
/// <summary>
/// 方法入参控制点(可能有,可能没)
/// </summary>
private JunctionControlBase[] argDataJunction;
private T FindVisualChild<T>(DependencyObject parent) where T : DependencyObject
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
if (child is T typedChild)
{
return typedChild;
}
var childOfChild = FindVisualChild<T>(child);
if (childOfChild != null)
{
return childOfChild;
}
}
return null;
}
}
}

View File

@@ -29,34 +29,79 @@
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal" Background="#FCB334">
<Grid Grid.Row="0" Background="#FCB334" >
<!--<Grid Grid.Row="0" >-->
<Grid.RowDefinitions>
<RowDefinition Height="3*"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<local:ExecuteJunctionControl Grid.Column="0" MyNode="{Binding NodeModel}" x:Name="ExecuteJunctionControl" HorizontalAlignment="Left" Grid.RowSpan="2"/>
<StackPanel Grid.Column="1" Grid.RowSpan="2" >
<TextBlock Text="{Binding NodeModel.MethodDetails.MethodTips, Mode=TwoWay}" HorizontalAlignment="Center"/>
</StackPanel>
<local:NextStepJunctionControl Grid.Column="2" MyNode="{Binding NodeModel}" x:Name="NextStepJunctionControl" HorizontalAlignment="Right" Grid.RowSpan="2"/>
</Grid>
<!--<StackPanel Grid.Row="0" Orientation="Horizontal" Background="#FCB334">
<CheckBox IsChecked="{Binding NodeModel.DebugSetting.IsEnable, Mode=TwoWay}" VerticalContentAlignment="Center"/>
<CheckBox IsChecked="{Binding NodeModel.MethodDetails.IsProtectionParameter, Mode=TwoWay}" VerticalContentAlignment="Center"/>
<TextBlock Text="{Binding NodeModel.MethodDetails.MethodTips, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</StackPanel>
<themes:MethodDetailsControl Grid.Row="1" MethodDetails="{Binding NodeModel.MethodDetails}" />
</StackPanel>-->
<Border Grid.Row="1" x:Name="ParameterProtectionMask" Background="LightBlue" Opacity="0.5" BorderBrush="#0A4651" BorderThickness="0"
<themes:MethodDetailsControl x:Name="MethodDetailsControl" Grid.Row="1" MethodDetails="{Binding NodeModel.MethodDetails}" />
<Border Grid.Row="2" x:Name="ParameterProtectionMask" Background="LightBlue" Opacity="0.5" BorderBrush="#0A4651" BorderThickness="0"
Visibility="{Binding NodeModel.MethodDetails.IsProtectionParameter, Converter={StaticResource InvertedBoolConverter},ConverterParameter=Normal}" />
<!--<Border Grid.Row="0" Background="#FCB334" >
</Border>-->
<!--<themes:ExplicitDataControl Grid.Row="1" ExplicitDatas="{Binding ExplicitDatas}" />-->
<Grid Grid.Row="2" Background="#D5F0FC" >
<Grid Grid.Row="3" Background="#D5F0FC" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" BorderThickness="1">
<TextBlock Text="result" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<Border Grid.Column="0" BorderThickness="1">
<TextBlock Text="result ->" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
<Border Grid.Column="1" BorderThickness="1">
<TextBlock Text="{Binding NodeModel.MethodDetails.ReturnType}" HorizontalAlignment="Left" VerticalAlignment="Center"/>
<Border Grid.Column="1" BorderThickness="1">
<TextBlock Text="{Binding NodeModel.MethodDetails.ReturnType.FullName, Mode=OneTime}" TextTrimming="CharacterEllipsis" HorizontalAlignment="Left" VerticalAlignment="Center"/>
</Border>
<Border Grid.Column="2" BorderThickness="1">
<local:ResultJunctionControl Grid.Column="2" MyNode="{Binding NodeModel}" x:Name="ResultJunctionControl" HorizontalAlignment="Right"/>
</Border>
</Grid>
<Grid Grid.Row="4" Background="Azure" >
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<CheckBox Grid.Row="0" Grid.Column="0" IsChecked="{Binding NodeModel.DebugSetting.IsEnable, Mode=TwoWay}"/>
<TextBlock Grid.Row="0" Grid.Column="1" Text="是否使能" HorizontalAlignment="Left" VerticalAlignment="Center"/>
<CheckBox Grid.Row="1" Grid.Column="0" IsChecked="{Binding NodeModel.MethodDetails.IsProtectionParameter, Mode=TwoWay}"/>
<TextBlock Grid.Row="1" Grid.Column="1" Text="参数保护" HorizontalAlignment="Left" VerticalAlignment="Center"/>
<CheckBox Grid.Row="2" Grid.Column="0" IsChecked="{Binding NodeModel.DebugSetting.IsInterrupt, Mode=TwoWay}"/>
<TextBlock Grid.Row="2" Grid.Column="1" Text="中断节点" HorizontalAlignment="Left" VerticalAlignment="Center"/>
</Grid>
<!--<themes:ConditionControl Grid.Row="2" ></themes:ConditionControl>-->
</Grid>

View File

@@ -1,17 +1,76 @@
using Serein.NodeFlow.Model;
using Serein.Workbench.Node.ViewModel;
using System.Windows.Controls;
using System.Windows;
namespace Serein.Workbench.Node.View
{
/// <summary>
/// StateNode.xaml 的交互逻辑
/// </summary>
public partial class FlipflopNodeControl : NodeControlBase
public partial class FlipflopNodeControl : NodeControlBase, INodeJunction
{
public FlipflopNodeControl(FlipflopNodeControlViewModel viewModel) : base(viewModel)
{
DataContext = viewModel;
InitializeComponent();
}
/// <summary>
/// 入参控制点(可能有,可能没)
/// </summary>
JunctionControlBase INodeJunction.ExecuteJunction => this.ExecuteJunctionControl;
/// <summary>
/// 下一个调用方法控制点(可能有,可能没)
/// </summary>
JunctionControlBase INodeJunction.NextStepJunction => this.NextStepJunctionControl;
/// <summary>
/// 返回值控制点(可能有,可能没)
/// </summary>
JunctionControlBase INodeJunction.ReturnDataJunction => this.ResultJunctionControl;
/// <summary>
/// 方法入参控制点(可能有,可能没)
/// </summary>
private JunctionControlBase[] argDataJunction;
/// <summary>
/// 方法入参控制点(可能有,可能没)
/// </summary>
JunctionControlBase[] INodeJunction.ArgDataJunction
{
get
{
if (argDataJunction == null)
{
// 获取 MethodDetailsControl 实例
var methodDetailsControl = this.MethodDetailsControl;
argDataJunction = new JunctionControlBase[base.ViewModel.NodeModel.MethodDetails.ParameterDetailss.Length];
var itemsControl = FindVisualChild<ItemsControl>(methodDetailsControl); // 查找 ItemsControl
if (itemsControl != null)
{
var controls = new List<JunctionControlBase>();
for (int i = 0; i < itemsControl.Items.Count; i++)
{
var container = itemsControl.ItemContainerGenerator.ContainerFromIndex(i) as FrameworkElement;
if (container != null)
{
var argControl = FindVisualChild<ArgJunctionControl>(container);
if (argControl != null)
{
controls.Add(argControl); // 收集 ArgJunctionControl 实例
}
}
}
argDataJunction = controls.ToArray();
}
}
return argDataJunction;
}
}
}
}

View File

@@ -99,7 +99,30 @@ namespace Serein.Workbench.Node.View
BindingOperations.SetBinding(this, Canvas.TopProperty, topBinding);
}
/// <summary>
/// 穿透视觉树获取指定类型的第一个元素
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="parent"></param>
/// <returns></returns>
protected T FindVisualChild<T>(DependencyObject parent) where T : DependencyObject
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
if (child is T typedChild)
{
return typedChild;
}
var childOfChild = FindVisualChild<T>(child);
if (childOfChild != null)
{
return childOfChild;
}
}
return null;
}