mirror of
https://gitee.com/langsisi_admin/serein-flow
synced 2026-03-11 12:10:47 +08:00
使用异步重构了节点执行方法,将触发器节点与其他节点统一。使用Channel代替Tcs更改了信号触发,使其符合异步编程的习惯。增加了节点是否启用勾选框、参数遮罩勾选框,节点右键面板增加中断功能(试验)。增加了选择后被选择的节点的视觉效果。更改平移缩放逻辑,使其更加符合一般的使用习惯。
This commit is contained in:
@@ -11,6 +11,7 @@ namespace Serein.WorkBench
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public void AppendText(string text)
|
||||
{
|
||||
Dispatcher.BeginInvoke(() =>
|
||||
@@ -19,6 +20,13 @@ namespace Serein.WorkBench
|
||||
LogTextBox.ScrollToEnd();
|
||||
});
|
||||
}
|
||||
public void Clear()
|
||||
{
|
||||
Dispatcher.BeginInvoke(() =>
|
||||
{
|
||||
LogTextBox.Clear();
|
||||
});
|
||||
}
|
||||
private void ClearLog_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
LogTextBox.Clear();
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
AllowDrop="True" Drop="Window_Drop" DragOver="Window_DragOver"
|
||||
Loaded="Window_Loaded"
|
||||
ContentRendered="Window_ContentRendered"
|
||||
PreviewKeyDown="Window_PreviewKeyDown"
|
||||
PreviewTextInput="Window_PreviewTextInput"
|
||||
Closing="Window_Closing">
|
||||
|
||||
<Window.Resources>
|
||||
@@ -108,7 +110,7 @@
|
||||
Stroke="Blue"
|
||||
StrokeThickness="2"
|
||||
Fill="LightBlue"
|
||||
Opacity="0.5"
|
||||
Opacity="0.2"
|
||||
Panel.ZIndex="999999"
|
||||
Visibility="Collapsed"/>
|
||||
|
||||
|
||||
@@ -92,9 +92,17 @@ namespace Serein.WorkBench
|
||||
private readonly List<NodeControlBase> selectNodeControls = [];
|
||||
|
||||
/// <summary>
|
||||
/// 记录拖动开始时的鼠标位置
|
||||
/// 记录开始拖动节点控件时的鼠标位置
|
||||
/// </summary>
|
||||
private Point startPoint;
|
||||
private Point startControlDragPoint;
|
||||
/// <summary>
|
||||
/// 记录移动画布开始时的鼠标位置
|
||||
/// </summary>
|
||||
private Point startCanvasDragPoint;
|
||||
/// <summary>
|
||||
/// 记录开始选取节点控件时的鼠标位置
|
||||
/// </summary>
|
||||
private Point startSelectControolPoint;
|
||||
|
||||
/// <summary>
|
||||
/// 记录开始连接的文本块
|
||||
@@ -134,8 +142,10 @@ namespace Serein.WorkBench
|
||||
logWindow = new LogWindow();
|
||||
logWindow.Show();
|
||||
// 重定向 Console 输出
|
||||
var logTextWriter = new LogTextWriter(WriteLog);
|
||||
var logTextWriter = new LogTextWriter(WriteLog,() => logWindow.Clear());;
|
||||
Console.SetOut(logTextWriter);
|
||||
|
||||
|
||||
InitUI();
|
||||
|
||||
var project = App.FData;
|
||||
@@ -202,7 +212,7 @@ namespace Serein.WorkBench
|
||||
//{
|
||||
// connection.Refresh();
|
||||
//}
|
||||
Console.WriteLine((FlowChartStackPanel.ActualWidth, FlowChartStackPanel.ActualHeight));
|
||||
//Console.WriteLine((FlowChartStackPanel.ActualWidth, FlowChartStackPanel.ActualHeight));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -517,62 +527,6 @@ namespace Serein.WorkBench
|
||||
|
||||
#region 节点控件的创建
|
||||
|
||||
private static TControl CreateNodeControl<TControl, TViewModel>(NodeModelBase model)
|
||||
where TControl : NodeControlBase
|
||||
where TViewModel : NodeControlViewModelBase
|
||||
{
|
||||
|
||||
if (model == null)
|
||||
{
|
||||
throw new Exception("无法创建节点控件");
|
||||
}
|
||||
|
||||
var viewModel = Activator.CreateInstance(typeof(TViewModel), [model]);
|
||||
var controlObj = Activator.CreateInstance(typeof(TControl), [viewModel]);
|
||||
if (controlObj is TControl control)
|
||||
{
|
||||
return control;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("无法创建节点控件");
|
||||
}
|
||||
}
|
||||
|
||||
private static TControl CreateNodeControl<TNode, TControl,TViewModel>(MethodDetails? methodDetails = null)
|
||||
where TNode : NodeModelBase
|
||||
where TControl : NodeControlBase
|
||||
where TViewModel : NodeControlViewModelBase
|
||||
{
|
||||
|
||||
var nodeObj = Activator.CreateInstance(typeof(TNode));
|
||||
var nodeBase = nodeObj as NodeModelBase;
|
||||
if (nodeBase == null)
|
||||
{
|
||||
throw new Exception("无法创建节点控件");
|
||||
}
|
||||
|
||||
|
||||
nodeBase.Guid = Guid.NewGuid().ToString();
|
||||
|
||||
if (methodDetails != null)
|
||||
{
|
||||
var md = methodDetails.Clone();
|
||||
nodeBase.DisplayName = md.MethodTips;
|
||||
nodeBase.MethodDetails = md;
|
||||
}
|
||||
|
||||
var viewModel = Activator.CreateInstance(typeof(TViewModel), [nodeObj]);
|
||||
var controlObj = Activator.CreateInstance(typeof(TControl), [viewModel] );
|
||||
if(controlObj is TControl control)
|
||||
{
|
||||
return control;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("无法创建节点控件");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建了节点,添加到画布。配置默认事件
|
||||
@@ -593,50 +547,6 @@ namespace Serein.WorkBench
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 配置节点右键菜单
|
||||
/// </summary>
|
||||
/// <param name="nodeControl"></param>
|
||||
private void ConfigureContextMenu(NodeControlBase nodeControl)
|
||||
{
|
||||
var contextMenu = new ContextMenu();
|
||||
|
||||
// var nodeModel = nodeControl.ViewModel.Node;
|
||||
|
||||
if (nodeControl.ViewModel.Node?.MethodDetails?.ReturnType is Type returnType && returnType != typeof(void))
|
||||
{
|
||||
contextMenu.Items.Add(CreateMenuItem("查看返回类型", (s, e) =>
|
||||
{
|
||||
DisplayReturnTypeTreeViewer(returnType);
|
||||
}));
|
||||
}
|
||||
var nodeGuid = nodeControl?.ViewModel?.Node?.Guid;
|
||||
contextMenu.Items.Add(CreateMenuItem("设为起点", (s, e) => FlowEnvironment.SetStartNode(nodeGuid)));
|
||||
contextMenu.Items.Add(CreateMenuItem("删除", (s, e) => FlowEnvironment.RemoteNode(nodeGuid)));
|
||||
|
||||
contextMenu.Items.Add(CreateMenuItem("添加 真分支", (s, e) => StartConnection(nodeControl, ConnectionType.IsSucceed)));
|
||||
contextMenu.Items.Add(CreateMenuItem("添加 假分支", (s, e) => StartConnection(nodeControl, ConnectionType.IsFail)));
|
||||
contextMenu.Items.Add(CreateMenuItem("添加 异常分支", (s, e) => StartConnection(nodeControl, ConnectionType.IsError)));
|
||||
contextMenu.Items.Add(CreateMenuItem("添加 上游分支", (s, e) => StartConnection(nodeControl, ConnectionType.Upstream)));
|
||||
|
||||
|
||||
nodeControl.ContextMenu = contextMenu;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建菜单子项
|
||||
/// </summary>
|
||||
/// <param name="header"></param>
|
||||
/// <param name="handler"></param>
|
||||
/// <returns></returns>
|
||||
private static MenuItem CreateMenuItem(string header, RoutedEventHandler handler)
|
||||
{
|
||||
var menuItem = new MenuItem { Header = header };
|
||||
menuItem.Click += handler;
|
||||
return menuItem;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 配置节点事件
|
||||
/// </summary>
|
||||
@@ -682,6 +592,55 @@ namespace Serein.WorkBench
|
||||
|
||||
#region 右键菜单事件
|
||||
|
||||
/// <summary>
|
||||
/// 配置节点右键菜单
|
||||
/// </summary>
|
||||
/// <param name="nodeControl"></param>
|
||||
private void ConfigureContextMenu(NodeControlBase nodeControl)
|
||||
{
|
||||
var contextMenu = new ContextMenu();
|
||||
|
||||
// var nodeModel = nodeControl.ViewModel.Node;
|
||||
|
||||
if (nodeControl.ViewModel.Node?.MethodDetails?.ReturnType is Type returnType && returnType != typeof(void))
|
||||
{
|
||||
contextMenu.Items.Add(CreateMenuItem("查看返回类型", (s, e) =>
|
||||
{
|
||||
DisplayReturnTypeTreeViewer(returnType);
|
||||
}));
|
||||
}
|
||||
var nodeGuid = nodeControl?.ViewModel?.Node?.Guid;
|
||||
|
||||
MenuItem? debugMenu = null;
|
||||
debugMenu = CreateMenuItem("在此中断", (s, e) =>
|
||||
{
|
||||
if (nodeControl!.ViewModel.DebugSetting.IsInterrupt)
|
||||
{
|
||||
nodeControl.ViewModel.IsInterrupt = false;
|
||||
debugMenu!.Header = "在此中断";
|
||||
}
|
||||
else
|
||||
{
|
||||
nodeControl.ViewModel.IsInterrupt = true;
|
||||
debugMenu!.Header = "取消中断";
|
||||
}
|
||||
});
|
||||
contextMenu.Items.Add(debugMenu);
|
||||
|
||||
|
||||
contextMenu.Items.Add(CreateMenuItem("设为起点", (s, e) => FlowEnvironment.SetStartNode(nodeGuid)));
|
||||
contextMenu.Items.Add(CreateMenuItem("删除", (s, e) => FlowEnvironment.RemoteNode(nodeGuid)));
|
||||
|
||||
|
||||
contextMenu.Items.Add(CreateMenuItem("添加 真分支", (s, e) => StartConnection(nodeControl, ConnectionType.IsSucceed)));
|
||||
contextMenu.Items.Add(CreateMenuItem("添加 假分支", (s, e) => StartConnection(nodeControl, ConnectionType.IsFail)));
|
||||
contextMenu.Items.Add(CreateMenuItem("添加 异常分支", (s, e) => StartConnection(nodeControl, ConnectionType.IsError)));
|
||||
contextMenu.Items.Add(CreateMenuItem("添加 上游分支", (s, e) => StartConnection(nodeControl, ConnectionType.Upstream)));
|
||||
|
||||
|
||||
nodeControl.ContextMenu = contextMenu;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 配置连接曲线的右键菜单
|
||||
/// </summary>
|
||||
@@ -693,6 +652,7 @@ namespace Serein.WorkBench
|
||||
connection.ArrowPath.ContextMenu = contextMenu;
|
||||
connection.BezierPath.ContextMenu = contextMenu;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除该连线
|
||||
/// </summary>
|
||||
@@ -776,24 +736,7 @@ namespace Serein.WorkBench
|
||||
/// </summary>
|
||||
private void FlowChartCanvas_MouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (IsSelectControl && e.LeftButton == MouseButtonState.Pressed) // 正在选取节点
|
||||
{
|
||||
// 获取当前鼠标位置
|
||||
Point currentPoint = e.GetPosition(FlowChartCanvas);
|
||||
|
||||
// 更新选取矩形的位置和大小
|
||||
double x = Math.Min(currentPoint.X, startPoint.X);
|
||||
double y = Math.Min(currentPoint.Y, startPoint.Y);
|
||||
double width = Math.Abs(currentPoint.X - startPoint.X);
|
||||
double height = Math.Abs(currentPoint.Y - startPoint.Y);
|
||||
|
||||
Canvas.SetLeft(SelectionRectangle, x);
|
||||
Canvas.SetTop(SelectionRectangle, y);
|
||||
SelectionRectangle.Width = width;
|
||||
SelectionRectangle.Height = height;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (IsConnecting) // 正在连接节点
|
||||
{
|
||||
Point position = e.GetPosition(FlowChartCanvas);
|
||||
@@ -809,22 +752,41 @@ namespace Serein.WorkBench
|
||||
if (IsCanvasDragging) // 正在移动画布
|
||||
{
|
||||
Point currentMousePosition = e.GetPosition(this);
|
||||
double deltaX = currentMousePosition.X - startPoint.X;
|
||||
double deltaY = currentMousePosition.Y - startPoint.Y;
|
||||
double deltaX = currentMousePosition.X - startCanvasDragPoint.X;
|
||||
double deltaY = currentMousePosition.Y - startCanvasDragPoint.Y;
|
||||
|
||||
translateTransform.X += deltaX;
|
||||
translateTransform.Y += deltaY;
|
||||
|
||||
startPoint = currentMousePosition;
|
||||
startCanvasDragPoint = currentMousePosition;
|
||||
|
||||
foreach (var line in Connections)
|
||||
{
|
||||
line.Refresh();
|
||||
}
|
||||
|
||||
e.Handled = true; // 防止事件传播影响其他控件
|
||||
}
|
||||
|
||||
if (IsSelectControl && e.LeftButton == MouseButtonState.Pressed) // 正在选取节点
|
||||
{
|
||||
// 获取当前鼠标位置
|
||||
Point currentPoint = e.GetPosition(FlowChartCanvas);
|
||||
|
||||
// 更新选取矩形的位置和大小
|
||||
double x = Math.Min(currentPoint.X, startSelectControolPoint.X);
|
||||
double y = Math.Min(currentPoint.Y, startSelectControolPoint.Y);
|
||||
double width = Math.Abs(currentPoint.X - startSelectControolPoint.X);
|
||||
double height = Math.Abs(currentPoint.Y - startSelectControolPoint.Y);
|
||||
/*double x = Math.Min(currentPoint.X, startControlDragPoint.X);
|
||||
double y = Math.Min(currentPoint.Y, startControlDragPoint.Y);
|
||||
double width = Math.Abs(currentPoint.X - startControlDragPoint.X);
|
||||
double height = Math.Abs(currentPoint.Y - startControlDragPoint.Y);*/
|
||||
|
||||
Canvas.SetLeft(SelectionRectangle, x);
|
||||
Canvas.SetTop(SelectionRectangle, y);
|
||||
SelectionRectangle.Width = width;
|
||||
SelectionRectangle.Height = height;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -910,25 +872,6 @@ namespace Serein.WorkBench
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 穿透元素获取区域容器
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="element"></param>
|
||||
/// <returns></returns>
|
||||
private static T GetParentOfType<T>(DependencyObject element) where T : DependencyObject
|
||||
{
|
||||
while (element != null)
|
||||
{
|
||||
if (element is T)
|
||||
{
|
||||
return element as T;
|
||||
}
|
||||
element = VisualTreeHelper.GetParent(element);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将节点放在目标区域中
|
||||
/// </summary>
|
||||
@@ -972,12 +915,10 @@ namespace Serein.WorkBench
|
||||
/// </summary>
|
||||
private void Block_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (IsConnecting)
|
||||
return;
|
||||
|
||||
IsControlDragging = true;
|
||||
startPoint = e.GetPosition(FlowChartCanvas); // 记录鼠标按下时的位置
|
||||
startControlDragPoint = e.GetPosition(FlowChartCanvas); // 记录鼠标按下时的位置
|
||||
((UIElement)sender).CaptureMouse(); // 捕获鼠标
|
||||
e.Handled = true; // 防止事件传播影响其他控件
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -985,7 +926,15 @@ namespace Serein.WorkBench
|
||||
/// </summary>
|
||||
private void Block_MouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (IsControlDragging) // 如果正在拖动控件
|
||||
if (IsConnecting)
|
||||
return;
|
||||
if (IsCanvasDragging)
|
||||
return;
|
||||
if (IsSelectControl)
|
||||
return;
|
||||
|
||||
var IsSelect = Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift);
|
||||
if (!IsSelect && IsControlDragging) // 如果正在拖动控件
|
||||
{
|
||||
Point currentPosition = e.GetPosition(FlowChartCanvas); // 获取当前鼠标位置
|
||||
// 获取引发事件的控件
|
||||
@@ -994,8 +943,8 @@ namespace Serein.WorkBench
|
||||
return;
|
||||
}
|
||||
|
||||
double deltaX = currentPosition.X - startPoint.X; // 计算X轴方向的偏移量
|
||||
double deltaY = currentPosition.Y - startPoint.Y; // 计算Y轴方向的偏移量
|
||||
double deltaX = currentPosition.X - startControlDragPoint.X; // 计算X轴方向的偏移量
|
||||
double deltaY = currentPosition.Y - startControlDragPoint.Y; // 计算Y轴方向的偏移量
|
||||
|
||||
double newLeft = Canvas.GetLeft(block) + deltaX; // 新的左边距
|
||||
double newTop = Canvas.GetTop(block) + deltaY; // 新的上边距
|
||||
@@ -1012,7 +961,7 @@ namespace Serein.WorkBench
|
||||
|
||||
UpdateConnections(block);
|
||||
|
||||
startPoint = currentPosition; // 更新起始点位置
|
||||
startControlDragPoint = currentPosition; // 更新起始点位置
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1159,13 +1108,14 @@ namespace Serein.WorkBench
|
||||
#region 拖动画布实现缩放平移效果
|
||||
private void FlowChartCanvas_MouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.MiddleButton == MouseButtonState.Pressed)
|
||||
{
|
||||
IsCanvasDragging = true;
|
||||
startPoint = e.GetPosition(this);
|
||||
FlowChartCanvas.CaptureMouse();
|
||||
e.Handled = true; // 防止事件传播影响其他控件
|
||||
}
|
||||
IsCanvasDragging = true;
|
||||
startCanvasDragPoint = e.GetPosition(this);
|
||||
FlowChartCanvas.CaptureMouse();
|
||||
e.Handled = true; // 防止事件传播影响其他控件
|
||||
//if (e.MiddleButton == MouseButtonState.Pressed)
|
||||
//{
|
||||
|
||||
//}
|
||||
}
|
||||
|
||||
private void FlowChartCanvas_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
@@ -1180,7 +1130,7 @@ namespace Serein.WorkBench
|
||||
// 单纯缩放画布,不改变画布大小
|
||||
private void FlowChartCanvas_MouseWheel(object sender, MouseWheelEventArgs e)
|
||||
{
|
||||
if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
|
||||
// if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
|
||||
{
|
||||
if (e.Delta < 0 && scaleTransform.ScaleX < 0.2) return;
|
||||
if (e.Delta > 0 && scaleTransform.ScaleY > 1.5) return;
|
||||
@@ -1355,26 +1305,29 @@ namespace Serein.WorkBench
|
||||
/// <param name="e"></param>
|
||||
private void FlowChartCanvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
|
||||
{
|
||||
IsSelectControl = true;
|
||||
IsSelectControl = true;
|
||||
|
||||
// 开始选取时,记录鼠标起始点
|
||||
startPoint = e.GetPosition(FlowChartCanvas);
|
||||
// 开始选取时,记录鼠标起始点
|
||||
startSelectControolPoint = e.GetPosition(FlowChartCanvas);
|
||||
|
||||
// 初始化选取矩形的位置和大小
|
||||
Canvas.SetLeft(SelectionRectangle, startPoint.X);
|
||||
Canvas.SetTop(SelectionRectangle, startPoint.Y);
|
||||
SelectionRectangle.Width = 0;
|
||||
SelectionRectangle.Height = 0;
|
||||
// 初始化选取矩形的位置和大小
|
||||
Canvas.SetLeft(SelectionRectangle, startSelectControolPoint.X);
|
||||
Canvas.SetTop(SelectionRectangle, startSelectControolPoint.Y);
|
||||
SelectionRectangle.Width = 0;
|
||||
SelectionRectangle.Height = 0;
|
||||
|
||||
// 显示选取矩形
|
||||
SelectionRectangle.Visibility = Visibility.Visible;
|
||||
SelectionRectangle.ContextMenu ??= ConfiguerSelectionRectangle();
|
||||
// 显示选取矩形
|
||||
SelectionRectangle.Visibility = Visibility.Visible;
|
||||
SelectionRectangle.ContextMenu ??= ConfiguerSelectionRectangle();
|
||||
|
||||
// 捕获鼠标,以便在鼠标移动到Canvas外部时仍能处理事件
|
||||
FlowChartCanvas.CaptureMouse();
|
||||
}
|
||||
// 捕获鼠标,以便在鼠标移动到Canvas外部时仍能处理事件
|
||||
FlowChartCanvas.CaptureMouse();
|
||||
|
||||
//if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
|
||||
//{
|
||||
|
||||
//}
|
||||
e.Handled = true; // 防止事件传播影响其他控件
|
||||
}
|
||||
|
||||
private ContextMenu ConfiguerSelectionRectangle()
|
||||
@@ -1448,6 +1401,7 @@ namespace Serein.WorkBench
|
||||
if(selectNodeControls.Count == 0)
|
||||
{
|
||||
Console.WriteLine($"没有选择控件");
|
||||
SelectionRectangle.Visibility = Visibility.Collapsed;
|
||||
return;
|
||||
}
|
||||
Console.WriteLine($"一共选取了{selectNodeControls.Count}个控件");
|
||||
@@ -1455,6 +1409,8 @@ namespace Serein.WorkBench
|
||||
{
|
||||
node.ViewModel.Selected();
|
||||
node.ViewModel.CancelSelect();
|
||||
node.BorderBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FFC700"));
|
||||
node.BorderThickness = new Thickness(4);
|
||||
}
|
||||
}
|
||||
private void CancelSelectNode()
|
||||
@@ -1462,13 +1418,109 @@ namespace Serein.WorkBench
|
||||
foreach (var node in selectNodeControls)
|
||||
{
|
||||
node.ViewModel.CancelSelect();
|
||||
node.BorderBrush = Brushes.Black;
|
||||
node.BorderThickness = new Thickness(0);
|
||||
}
|
||||
selectNodeControls.Clear();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 窗体静态方法
|
||||
|
||||
|
||||
private static TControl CreateNodeControl<TControl, TViewModel>(NodeModelBase model)
|
||||
where TControl : NodeControlBase
|
||||
where TViewModel : NodeControlViewModelBase
|
||||
{
|
||||
|
||||
if (model == null)
|
||||
{
|
||||
throw new Exception("无法创建节点控件");
|
||||
}
|
||||
|
||||
var viewModel = Activator.CreateInstance(typeof(TViewModel), [model]);
|
||||
var controlObj = Activator.CreateInstance(typeof(TControl), [viewModel]);
|
||||
if (controlObj is TControl control)
|
||||
{
|
||||
return control;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("无法创建节点控件");
|
||||
}
|
||||
}
|
||||
|
||||
private static TControl CreateNodeControl<TNode, TControl, TViewModel>(MethodDetails? methodDetails = null)
|
||||
where TNode : NodeModelBase
|
||||
where TControl : NodeControlBase
|
||||
where TViewModel : NodeControlViewModelBase
|
||||
{
|
||||
|
||||
var nodeObj = Activator.CreateInstance(typeof(TNode));
|
||||
var nodeBase = nodeObj as NodeModelBase;
|
||||
if (nodeBase == null)
|
||||
{
|
||||
throw new Exception("无法创建节点控件");
|
||||
}
|
||||
|
||||
|
||||
nodeBase.Guid = Guid.NewGuid().ToString();
|
||||
|
||||
if (methodDetails != null)
|
||||
{
|
||||
var md = methodDetails.Clone();
|
||||
nodeBase.DisplayName = md.MethodTips;
|
||||
nodeBase.MethodDetails = md;
|
||||
}
|
||||
|
||||
var viewModel = Activator.CreateInstance(typeof(TViewModel), [nodeObj]);
|
||||
var controlObj = Activator.CreateInstance(typeof(TControl), [viewModel]);
|
||||
if (controlObj is TControl control)
|
||||
{
|
||||
return control;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("无法创建节点控件");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 创建菜单子项
|
||||
/// </summary>
|
||||
/// <param name="header"></param>
|
||||
/// <param name="handler"></param>
|
||||
/// <returns></returns>
|
||||
public static MenuItem CreateMenuItem(string header, RoutedEventHandler handler)
|
||||
{
|
||||
var menuItem = new MenuItem { Header = header };
|
||||
menuItem.Click += handler;
|
||||
return menuItem;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 穿透元素获取区域容器
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="element"></param>
|
||||
/// <returns></returns>
|
||||
private static T GetParentOfType<T>(DependencyObject element) where T : DependencyObject
|
||||
{
|
||||
while (element != null)
|
||||
{
|
||||
if (element is T)
|
||||
{
|
||||
return element as T;
|
||||
}
|
||||
element = VisualTreeHelper.GetParent(element);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
@@ -1505,7 +1557,11 @@ namespace Serein.WorkBench
|
||||
private async void ButtonDebugRun_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
logWindow?.Show();
|
||||
await FlowEnvironment.StartAsync();
|
||||
|
||||
await FlowEnvironment.StartAsync(); // 快
|
||||
|
||||
//await Task.Run( FlowEnvironment.StartAsync); // 上下文多次切换的场景中吗慢了1/5
|
||||
//await Task.Factory.StartNew(FlowEnvironment.StartAsync); // 慢了1/5
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1614,6 +1670,20 @@ namespace Serein.WorkBench
|
||||
Uri relativeUri = baseUri.MakeRelativeUri(fullUri);
|
||||
return Uri.UnescapeDataString(relativeUri.ToString().Replace('/', System.IO.Path.DirectorySeparatorChar));
|
||||
}
|
||||
|
||||
private void Window_PreviewTextInput(object sender, TextCompositionEventArgs e)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void Window_PreviewKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
//if (e.KeyStates == Keyboard.GetKeyStates(Key.D8) && Keyboard.Modifiers == ModifierKeys.Shift)
|
||||
//if (Keyboard.Modifiers == ModifierKeys.Shift)
|
||||
//{
|
||||
// startSelectControolPoint = e.GetPosition(FlowChartCanvas);
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
#region 创建两个控件之间的连接关系,在UI层面上显示为 带箭头指向的贝塞尔曲线
|
||||
|
||||
@@ -23,6 +23,9 @@ namespace Serein.WorkBench.Node.ViewModel
|
||||
/// </summary>
|
||||
internal NodeModelBase Node { get; }
|
||||
|
||||
|
||||
|
||||
|
||||
private bool isSelect;
|
||||
/// <summary>
|
||||
/// 表示节点控件是否被选中
|
||||
@@ -37,29 +40,68 @@ namespace Serein.WorkBench.Node.ViewModel
|
||||
}
|
||||
}
|
||||
|
||||
private MethodDetails methodDetails;
|
||||
|
||||
|
||||
public MethodDetails MethodDetails
|
||||
public NodeDebugSetting DebugSetting
|
||||
{
|
||||
get => methodDetails;
|
||||
get => Node.DebugSetting;
|
||||
set
|
||||
{
|
||||
methodDetails = value;
|
||||
OnPropertyChanged();
|
||||
if (value != null)
|
||||
{
|
||||
Node.DebugSetting = value;
|
||||
OnPropertyChanged(nameof(DebugSetting));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public MethodDetails MethodDetails
|
||||
{
|
||||
get => Node.MethodDetails;
|
||||
set
|
||||
{
|
||||
if(value != null)
|
||||
{
|
||||
Node.MethodDetails = value;
|
||||
OnPropertyChanged(nameof(MethodDetails));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsInterrupt
|
||||
{
|
||||
get => Node.DebugSetting.IsInterrupt;
|
||||
set
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
Node.Interrupt();
|
||||
}
|
||||
else
|
||||
{
|
||||
Node.CancelInterrupt();
|
||||
}
|
||||
OnPropertyChanged(nameof(IsInterrupt));
|
||||
}
|
||||
}
|
||||
|
||||
//public bool IsProtectionParameter
|
||||
//{
|
||||
// get => MethodDetails.IsProtectionParameter;
|
||||
// set
|
||||
// {
|
||||
// MethodDetails.IsProtectionParameter = value;
|
||||
// OnPropertyChanged(nameof(IsInterrupt));
|
||||
// }
|
||||
//}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
|
||||
|
||||
public void Selected()
|
||||
{
|
||||
IsSelect = true;
|
||||
@@ -69,5 +111,7 @@ namespace Serein.WorkBench.Node.ViewModel
|
||||
{
|
||||
IsSelect = false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,43 +5,73 @@
|
||||
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:themes="clr-namespace:Serein.WorkBench.Themes"
|
||||
MaxWidth="300">
|
||||
<UserControl.Resources>
|
||||
<!--<BooleanToVisibilityConverter x:Key="BoolToVisConverter" />-->
|
||||
<Converters:InvertableBooleanToVisibilityConverter x:Key="InvertedBoolConverter"/>
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<Grid.ToolTip>
|
||||
<ToolTip Background="LightYellow" Foreground="Black" Content="{Binding MethodDetails.MethodName, UpdateSourceTrigger=PropertyChanged}" />
|
||||
</Grid.ToolTip>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<StackPanel Grid.Row="0" Orientation="Horizontal" Background="#FFCFDF">
|
||||
<CheckBox VerticalContentAlignment="Center">
|
||||
<Border>
|
||||
<Border.Style>
|
||||
<Style TargetType="Border">
|
||||
<!-- 默认无边框 -->
|
||||
<Setter Property="BorderBrush" Value="Transparent" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Style.Triggers>
|
||||
<!-- 当 DebugSetting.IsInterrupt 为 True 时,显示红色边框 -->
|
||||
<DataTrigger Binding="{Binding IsInterrupt}" Value="True">
|
||||
<Setter Property="BorderBrush" Value="Red" />
|
||||
<Setter Property="BorderThickness" Value="2" />
|
||||
<Setter Property="Background" Value="#80000000" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Border.Style>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
|
||||
</CheckBox>
|
||||
<TextBlock Text="{Binding MethodDetails.MethodTips}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
|
||||
<themes:MethodDetailsControl Grid.Row="1" MethodDetails="{Binding MethodDetails}" />
|
||||
|
||||
<Grid Grid.Row="2" >
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="50"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border Grid.Column="0" Background="#EAFFD0" BorderBrush="#EAFFD0" BorderThickness="1">
|
||||
<TextBlock Text="result" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<Border Grid.Column="1" Background="#EAFFD0" BorderBrush="#EAFFD0" BorderThickness="1">
|
||||
<TextBlock Text="{Binding MethodDetails.ReturnType}" HorizontalAlignment="Left" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
|
||||
<StackPanel Grid.Row="0" Orientation="Horizontal" Background="#FFCFDF">
|
||||
<CheckBox IsChecked="{Binding DebugSetting.IsEnable, Mode=TwoWay}" VerticalContentAlignment="Center"/>
|
||||
<CheckBox IsChecked="{Binding MethodDetails.IsProtectionParameter, Mode=TwoWay}" VerticalContentAlignment="Center"/>
|
||||
<TextBlock Text="{Binding MethodDetails.MethodTips}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
<themes:MethodDetailsControl Grid.Row="1" MethodDetails="{Binding MethodDetails}"/>
|
||||
<!-- ParameterProtectionMask 参数保护 -->
|
||||
<!--取反 Visibility="{Binding DebugSetting.IsEnable, Converter={StaticResource InvertedBoolConverter}, ConverterParameter=Inverted}"-->
|
||||
<Border Grid.Row="1" x:Name="ParameterProtectionMask" Background="LightBlue" Opacity="0.5" BorderBrush="Black" BorderThickness="0"
|
||||
Visibility="{Binding MethodDetails.IsProtectionParameter, Converter={StaticResource InvertedBoolConverter},ConverterParameter=Normal}" />
|
||||
<Grid Grid.Row="2" >
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="50"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border Grid.Column="0" Background="#EAFFD0" BorderBrush="#EAFFD0" BorderThickness="1">
|
||||
<TextBlock Text="result" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<Border Grid.Column="1" Background="#EAFFD0" BorderBrush="#EAFFD0" BorderThickness="1">
|
||||
<TextBlock Text="{Binding MethodDetails.ReturnType}" HorizontalAlignment="Left" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
|
||||
</Border>
|
||||
|
||||
<!--Visibility="{Binding IsEnable, Converter={StaticResource BoolToVisConverter}, ConverterParameter=False}"-->
|
||||
|
||||
|
||||
</Grid>
|
||||
</local:NodeControlBase>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
xmlns:local="clr-namespace:Serein.WorkBench.Node.View"
|
||||
MaxWidth="300">
|
||||
<Grid>
|
||||
<Border BorderBrush="Black" BorderThickness="1" Padding="10">
|
||||
<!--<Border BorderBrush="Black" BorderThickness="1" Padding="10">
|
||||
<StackPanel>
|
||||
<Grid Margin="2,2,2,5">
|
||||
<TextBlock Text="动作区域" FontWeight="Bold" HorizontalAlignment="Left" FontSize="14" Margin="0,1,0,0"/>
|
||||
@@ -14,7 +14,7 @@
|
||||
</Grid>
|
||||
<ListBox x:Name="ActionsListBox" AllowDrop="True" Drop="ActionsListBox_Drop" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Border>-->
|
||||
|
||||
</Grid>
|
||||
|
||||
|
||||
@@ -8,9 +8,7 @@
|
||||
xmlns:themes="clr-namespace:Serein.WorkBench.Themes"
|
||||
MaxWidth="300">
|
||||
|
||||
<!--d:DataContext="{d:DesignInstance Type=vm:ConditionNodeControlViewModel}"-->
|
||||
<UserControl.Resources>
|
||||
<!--<vm:TypeToStringConverter x:Key="TypeToStringConverter"/>-->
|
||||
<BooleanToVisibilityConverter x:Key="BoolToVis" />
|
||||
</UserControl.Resources>
|
||||
|
||||
|
||||
@@ -20,9 +20,7 @@ namespace Serein.WorkBench.Themes
|
||||
/// </summary>
|
||||
public partial class TypeViewerWindow : Window
|
||||
{
|
||||
|
||||
public TypeViewerWindow()
|
||||
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
@@ -34,55 +32,244 @@ namespace Serein.WorkBench.Themes
|
||||
if (Type == null)
|
||||
return;
|
||||
|
||||
var rootNode = new TreeViewItem { Header = Type.Name };
|
||||
AddMembersToTreeNode(rootNode, Type);
|
||||
TypeNodeDetails typeNodeDetails = new TypeNodeDetails
|
||||
{
|
||||
Name = Type.Name,
|
||||
DataType = Type,
|
||||
};
|
||||
var rootNode = new TreeViewItem { Header = Type.Name, Tag = typeNodeDetails };
|
||||
AddPlaceholderNode(rootNode); // 添加占位符节点
|
||||
TypeTreeView.Items.Clear();
|
||||
TypeTreeView.Items.Add(rootNode);
|
||||
|
||||
rootNode.Expanded += TreeViewItem_Expanded; // 监听节点展开事件
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加占位符节点
|
||||
/// </summary>
|
||||
private void AddPlaceholderNode(TreeViewItem node)
|
||||
{
|
||||
node.Items.Add(new TreeViewItem { Header = "Loading..." });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 节点展开事件,延迟加载子节点
|
||||
/// </summary>
|
||||
private void TreeViewItem_Expanded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var item = (TreeViewItem)sender;
|
||||
|
||||
// 如果已经加载过子节点,则不再重复加载
|
||||
if (item.Items.Count == 1 && item.Items[0] is TreeViewItem placeholder && placeholder.Header.ToString() == "Loading...")
|
||||
{
|
||||
item.Items.Clear();
|
||||
if (item.Tag is TypeNodeDetails typeNodeDetails)
|
||||
{
|
||||
AddMembersToTreeNode(item, typeNodeDetails.DataType);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加属性节点
|
||||
/// </summary>
|
||||
/// <param name="node"></param>
|
||||
/// <param name="type"></param>
|
||||
private void AddMembersToTreeNode(TreeViewItem node, Type type)
|
||||
{
|
||||
var members = type.GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly);
|
||||
foreach (var member in members)
|
||||
{
|
||||
TreeViewItem memberNode;
|
||||
try
|
||||
TreeViewItem memberNode = ConfigureTreeViewItem(member); // 生成类型节点的子项
|
||||
if (ConfigureTreeItemMenu(memberNode,member, out ContextMenu? contextMenu))
|
||||
{
|
||||
memberNode = new TreeViewItem { Header = member.Name };
|
||||
}
|
||||
catch
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (member is PropertyInfo property)
|
||||
{
|
||||
var propertyType = property.PropertyType;
|
||||
memberNode.Header = $"{member.Name} : {propertyType.Name}";
|
||||
if (!propertyType.IsPrimitive && propertyType != typeof(string))
|
||||
{
|
||||
// 递归显示类型属性的节点
|
||||
AddMembersToTreeNode(memberNode, propertyType);
|
||||
}
|
||||
}
|
||||
else if (member is MethodInfo method)
|
||||
{
|
||||
var parameters = method.GetParameters();
|
||||
var paramStr = string.Join(", ", parameters.Select(p => $"{p.ParameterType.Name} {p.Name}"));
|
||||
memberNode.Header = $"{member.Name}({paramStr})";
|
||||
}
|
||||
else if (member is FieldInfo field)
|
||||
{
|
||||
memberNode.Header = $"{member.Name} : {field.FieldType.Name}";
|
||||
memberNode.ContextMenu = contextMenu; // 设置子项节点的事件
|
||||
}
|
||||
|
||||
node.Items.Add(memberNode);
|
||||
node.Items.Add(memberNode); // 添加到父节点中
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 生成类型节点的子项
|
||||
/// </summary>
|
||||
/// <param name="member"></param>
|
||||
/// <returns></returns>
|
||||
private TreeViewItem ConfigureTreeViewItem(MemberInfo member)
|
||||
{
|
||||
TreeViewItem memberNode = new TreeViewItem { Header = member.Name };
|
||||
if (member is PropertyInfo property)
|
||||
{
|
||||
TypeNodeDetails typeNodeDetails = new TypeNodeDetails
|
||||
{
|
||||
ItemType = TreeItemType.Property,
|
||||
DataType = property.PropertyType,
|
||||
Name = property.Name,
|
||||
DataValue = property,
|
||||
};
|
||||
memberNode.Tag = typeNodeDetails;
|
||||
|
||||
var propertyType = typeNodeDetails.DataType;
|
||||
memberNode.Header = $"{member.Name} : {propertyType.Name}";
|
||||
|
||||
if (!propertyType.IsPrimitive && propertyType != typeof(string))
|
||||
{
|
||||
// 延迟加载类型的子属性,添加占位符节点
|
||||
AddPlaceholderNode(memberNode);
|
||||
memberNode.Expanded += TreeViewItem_Expanded; // 监听展开事件
|
||||
}
|
||||
}
|
||||
else if (member is MethodInfo method)
|
||||
{
|
||||
TypeNodeDetails typeNodeDetails = new TypeNodeDetails
|
||||
{
|
||||
ItemType = TreeItemType.Method,
|
||||
DataType = typeof(MethodInfo),
|
||||
Name = method.Name,
|
||||
DataValue = null,
|
||||
};
|
||||
memberNode.Tag = typeNodeDetails;
|
||||
|
||||
var parameters = method.GetParameters();
|
||||
var paramStr = string.Join(", ", parameters.Select(p => $"{p.ParameterType.Name} {p.Name}"));
|
||||
memberNode.Header = $"{member.Name}({paramStr})";
|
||||
}
|
||||
else if (member is FieldInfo field)
|
||||
{
|
||||
TypeNodeDetails typeNodeDetails = new TypeNodeDetails
|
||||
{
|
||||
ItemType = TreeItemType.Field,
|
||||
DataType = field.FieldType,
|
||||
Name = field.Name,
|
||||
DataValue = field,
|
||||
};
|
||||
memberNode.Tag = typeNodeDetails;
|
||||
memberNode.Header = $"{member.Name} : {field.FieldType.Name}";
|
||||
}
|
||||
return memberNode;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 设置子项节点的事件
|
||||
/// </summary>
|
||||
/// <param name="member"></param>
|
||||
/// <returns></returns>
|
||||
private bool ConfigureTreeItemMenu(TreeViewItem memberNode, MemberInfo member,out ContextMenu? contextMenu)
|
||||
{
|
||||
bool isChange = false;
|
||||
if (member is PropertyInfo property)
|
||||
{
|
||||
//isChange = true;
|
||||
contextMenu = new ContextMenu();
|
||||
}
|
||||
else if (member is MethodInfo method)
|
||||
{
|
||||
//isChange = true;
|
||||
contextMenu = new ContextMenu();
|
||||
}
|
||||
else if (member is FieldInfo field)
|
||||
{
|
||||
isChange = true;
|
||||
contextMenu = new ContextMenu();
|
||||
contextMenu.Items.Add(MainWindow.CreateMenuItem($"取值表达式", (s, e) =>
|
||||
{
|
||||
string fullPath = GetNodeFullPath(memberNode);
|
||||
string copyValue = "@Get " + fullPath;
|
||||
Clipboard.SetDataObject(copyValue);
|
||||
}));
|
||||
}
|
||||
else
|
||||
{
|
||||
contextMenu = new ContextMenu();
|
||||
}
|
||||
return isChange;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前节点的完整路径,例如 "node1.node2.node3.node4"
|
||||
/// </summary>
|
||||
/// <param name="node">目标节点</param>
|
||||
/// <returns>节点路径</returns>
|
||||
private string GetNodeFullPath(TreeViewItem node)
|
||||
{
|
||||
if (node == null)
|
||||
return string.Empty;
|
||||
|
||||
TypeNodeDetails typeNodeDetails = (TypeNodeDetails)node.Tag;
|
||||
var parent = GetParentTreeViewItem(node);
|
||||
if (parent != null)
|
||||
{
|
||||
// 递归获取父节点的路径,并拼接当前节点的 Header
|
||||
return $"{GetNodeFullPath(parent)}.{typeNodeDetails.Name}";
|
||||
}
|
||||
else
|
||||
{
|
||||
return "";
|
||||
|
||||
|
||||
// 没有父节点,则说明这是根节点,直接返回 Header
|
||||
// return typeNodeDetails.Name.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定节点的父级节点
|
||||
/// </summary>
|
||||
/// <param name="node">目标节点</param>
|
||||
/// <returns>父节点</returns>
|
||||
private TreeViewItem GetParentTreeViewItem(TreeViewItem node)
|
||||
{
|
||||
DependencyObject parent = VisualTreeHelper.GetParent(node);
|
||||
while (parent != null && !(parent is TreeViewItem))
|
||||
{
|
||||
parent = VisualTreeHelper.GetParent(parent);
|
||||
}
|
||||
return parent as TreeViewItem;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public class TypeNodeDetails
|
||||
{
|
||||
/// <summary>
|
||||
/// 属性名称
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
/// <summary>
|
||||
/// 属性类型
|
||||
/// </summary>
|
||||
public TreeItemType ItemType { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 数据类型
|
||||
/// </summary>
|
||||
public Type DataType { get; set; }
|
||||
/// <summary>
|
||||
/// 数据(调试用?)
|
||||
/// </summary>
|
||||
public object DataValue { get; set; }
|
||||
/// <summary>
|
||||
/// 数据路径
|
||||
/// </summary>
|
||||
public string DataPath { get; set; }
|
||||
}
|
||||
|
||||
public enum TreeItemType
|
||||
{
|
||||
Property,
|
||||
Method,
|
||||
Field
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
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.Tool.Converters
|
||||
{
|
||||
[ValueConversion(typeof(bool), typeof(Visibility))]
|
||||
public class InvertableBooleanToVisibilityConverter : IValueConverter
|
||||
{
|
||||
enum Parameters
|
||||
{
|
||||
Normal, 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.IO;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace Serein.WorkBench.tool
|
||||
@@ -6,10 +7,24 @@ namespace Serein.WorkBench.tool
|
||||
/// <summary>
|
||||
/// 可以捕获类库输出的打印输出
|
||||
/// </summary>
|
||||
public class LogTextWriter(Action<string> logAction) : TextWriter
|
||||
public class LogTextWriter : TextWriter
|
||||
{
|
||||
private readonly Action<string> logAction = logAction;
|
||||
private readonly Action<string> logAction;
|
||||
private readonly StringWriter stringWriter = new();
|
||||
private readonly BlockingCollection<string> logQueue = new();
|
||||
private readonly Task logTask;
|
||||
|
||||
// 用于计数的字段
|
||||
private int writeCount = 0;
|
||||
private const int maxWrites = 500;
|
||||
private readonly Action clearTextBoxAction;
|
||||
|
||||
public LogTextWriter(Action<string> logAction, Action clearTextBoxAction)
|
||||
{
|
||||
this.logAction = logAction;
|
||||
this.clearTextBoxAction = clearTextBoxAction;
|
||||
logTask = Task.Run(ProcessLogQueue); // 异步处理日志
|
||||
}
|
||||
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
|
||||
@@ -18,28 +33,60 @@ namespace Serein.WorkBench.tool
|
||||
stringWriter.Write(value);
|
||||
if (value == '\n')
|
||||
{
|
||||
logAction(stringWriter.ToString());
|
||||
stringWriter.GetStringBuilder().Clear();
|
||||
EnqueueLog();
|
||||
}
|
||||
}
|
||||
|
||||
public override void Write(string? value)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(value)) { return; }
|
||||
if (string.IsNullOrWhiteSpace(value)) return;
|
||||
stringWriter.Write(value);
|
||||
if (value.Contains('\n'))
|
||||
{
|
||||
logAction(stringWriter.ToString());
|
||||
stringWriter.GetStringBuilder().Clear();
|
||||
EnqueueLog();
|
||||
}
|
||||
}
|
||||
|
||||
public override void WriteLine(string? value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value)) { return; }
|
||||
if (string.IsNullOrWhiteSpace(value)) return;
|
||||
stringWriter.WriteLine(value);
|
||||
logAction(stringWriter.ToString());
|
||||
EnqueueLog();
|
||||
}
|
||||
|
||||
private void EnqueueLog()
|
||||
{
|
||||
logQueue.Add(stringWriter.ToString());
|
||||
stringWriter.GetStringBuilder().Clear();
|
||||
}
|
||||
|
||||
private async Task ProcessLogQueue()
|
||||
{
|
||||
foreach (var log in logQueue.GetConsumingEnumerable())
|
||||
{
|
||||
// 异步执行日志输出操作
|
||||
await Task.Run(() =>
|
||||
{
|
||||
logAction(log);
|
||||
|
||||
// 计数器增加
|
||||
writeCount++;
|
||||
if (writeCount >= maxWrites)
|
||||
{
|
||||
// 计数器达到50,清空文本框
|
||||
clearTextBoxAction?.Invoke();
|
||||
writeCount = 0; // 重置计数器
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public new void Dispose()
|
||||
{
|
||||
logQueue.CompleteAdding();
|
||||
logTask.Wait();
|
||||
base.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user