添加了节点树视图。

This commit is contained in:
fengjiayi
2024-09-26 21:00:17 +08:00
parent e81c527086
commit 42bf85b970
24 changed files with 877 additions and 357 deletions

View File

@@ -78,6 +78,7 @@
<Button x:Name="ButtonDebugRun" Content="运行" Width="100" Margin="10" Click="ButtonDebugRun_Click"></Button>
<Button x:Name="ButtonDebugFlipflopNode" Content="结束" Width="100" Margin="10" Click="ButtonDebugFlipflopNode_Click"></Button>
<Button x:Name="ButtonStartFlowInSelectNode" Content="从选定节点开始" Width="100" Margin="10" Click="ButtonStartFlowInSelectNode_Click"></Button>
<!--<Button x:Name="ButtonLoadStartNodeTree" Content="加载起始节点树" Width="100" Margin="10" Click="ButtonLoadStartNodeTree_Click"></Button>-->
<!--<Button x:Name="ButtonReflushCanvasConfig" Content="重置画布设置" Width="100" Margin="10" Click="ButtonReflushCanvasConfig_Click"></Button>-->
<!--<Button x:Name="ButtonLoadCanvasConfig" Content="加载画布设置" Width="100" Margin="10" Click="ButtonLoadCanvasConfig_Click"></Button>-->
</StackPanel>
@@ -190,12 +191,13 @@ Canvas.Top="{Binding ActualHeight, ElementName=FlowChartCanvas, Mode=OneWay, Con
<!--IOC容器属性-->
<Grid Grid.Column="4" >
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0" >
<!--<themes:LazyTreeView x:Name="lazyTreeView" />-->
<themes:NodeTreeViewControl x:Name="NodeTreeViewer"></themes:NodeTreeViewControl>
</Grid>
<Grid Grid.Row="1" >
<themes:IOCObjectViewControl x:Name="IOCObjectViewer">

View File

@@ -75,6 +75,11 @@ namespace Serein.WorkBench
/// </summary>
private List<Connection> Connections { get; } = [];
/// <summary>
/// 起始节点
/// </summary>
//private NodeControlBase StartNodeControl{ get; set; }
#region
/// <summary>
@@ -149,6 +154,7 @@ namespace Serein.WorkBench
ViewModel = new MainWindowViewModel(this);
FlowEnvironment = ViewModel.FlowEnvironment;
ViewObjectViewer.FlowEnvironment = FlowEnvironment;
IOCObjectViewer.FlowEnvironment = FlowEnvironment;
InitFlowEnvironmentEvent(); // 配置环境事件
logWindow = InitConsoleOut(); // 重定向 Console 输出
@@ -160,6 +166,9 @@ namespace Serein.WorkBench
{
FlowEnvironment.LoadProject(App.FlowProjectData, App.FileDataPath); // 加载项目
}
IOCObjectViewer.SelectObj += ViewObjectViewer.LoadObjectInformation;
}
private void InitFlowEnvironmentEvent()
@@ -178,6 +187,8 @@ namespace Serein.WorkBench
FlowEnvironment.OnNodeInterruptStateChange += FlowEnvironment_OnNodeInterruptStateChange;
FlowEnvironment.OnInterruptTrigger += FlowEnvironment_OnInterruptTrigger;
FlowEnvironment.OnIOCMembersChanged += FlowEnvironment_OnIOCMembersChanged;
}
private void InitCanvasUI()
{
@@ -242,8 +253,6 @@ namespace Serein.WorkBench
}
#endregion
#region
/// <summary>
/// 加载完成
@@ -265,7 +274,8 @@ namespace Serein.WorkBench
/// <exception cref="NotImplementedException"></exception>
private void FlowEnvironment_OnFlowRunComplete(FlowEventArgs eventArgs)
{
Console.WriteLine("-------运行完成---------\r\n");
Console.WriteLine("-------运行完成---------\r\n");
IOCObjectViewer.ClearObjItem();
}
/// <summary>
@@ -307,48 +317,69 @@ namespace Serein.WorkBench
/// <param name="connectionType"></param>
private void FlowEnvironment_NodeConnectChangeEvemt(NodeConnectChangeEventArgs eventArgs)
{
this.Dispatcher.Invoke(() =>
string fromNodeGuid = eventArgs.FromNodeGuid;
string toNodeGuid = eventArgs.ToNodeGuid;
NodeControlBase fromNode = GuidToControl(fromNodeGuid);
NodeControlBase toNode = GuidToControl(toNodeGuid);
ConnectionType connectionType = eventArgs.ConnectionType;
Action? action = null;
if (eventArgs.ChangeType == NodeConnectChangeEventArgs.ConnectChangeType.Create) // 添加连接
{
string fromNodeGuid = eventArgs.FromNodeGuid;
string toNodeGuid = eventArgs.ToNodeGuid;
NodeControlBase fromNode = GuidToControl(fromNodeGuid);
NodeControlBase toNode = GuidToControl(toNodeGuid);
ConnectionType connectionType = eventArgs.ConnectionType;
if (eventArgs.ChangeType == NodeConnectChangeEventArgs.ConnectChangeType.Create)
// 添加连接
var connection = new Connection
{
lock (Connections)
{
// 添加连接
var connection = new Connection
{
Start = fromNode,
End = toNode,
Type = connectionType
};
BsControl.Draw(FlowChartCanvas, connection); // 添加贝塞尔曲线显示
ConfigureLineContextMenu(connection); // 设置连接右键事件
Connections.Add(connection);
EndConnection();
}
Start = fromNode,
End = toNode,
Type = connectionType
};
if (toNode is FlipflopNodeControl flipflopControl) // 某个节点连接到了触发器,尝试从全局触发器视图中移除该触发器
{
var nodeModel = flipflopControl?.ViewModel?.Node;
NodeTreeViewer.RemoteGlobalFlipFlop(nodeModel); // 从全局触发器树树视图中移除
}
else if (eventArgs.ChangeType == NodeConnectChangeEventArgs.ConnectChangeType.Remote)
action = () => {
BsControl.Draw(FlowChartCanvas, connection); // 添加贝塞尔曲线显示
ConfigureLineContextMenu(connection); // 设置连接右键事件
Connections.Add(connection);
EndConnection();
};
}
else if (eventArgs.ChangeType == NodeConnectChangeEventArgs.ConnectChangeType.Remote) // 移除连接
{
// 需要移除连接
var removeConnections = Connections.Where(c => c.Start.ViewModel.Node.Guid.Equals(fromNodeGuid)
&& c.End.ViewModel.Node.Guid.Equals(toNodeGuid))
.ToList();
action = () =>
{
// 需要移除连接
var removeConnections = Connections.Where(c => c.Start.ViewModel.Node.Guid.Equals(fromNodeGuid)
&& c.End.ViewModel.Node.Guid.Equals(toNodeGuid))
.ToList();
foreach (var connection in removeConnections)
{
connection.RemoveFromCanvas();
Connections.Remove(connection);
JudgmentFlipFlopNode(connection.End); // 连接关系变更时判断
}
}
};
}
this.Dispatcher.Invoke(() =>
{
action?.Invoke();
});
}
/// <summary>
/// 节点移除事件
/// </summary>
@@ -364,9 +395,19 @@ namespace Serein.WorkBench
selectNodeControls.Remove(nodeControl);
}
}
#region
if (nodeControl is FlipflopNodeControl flipflopControl) // 判断是否为触发器
{
var node = flipflopControl?.ViewModel?.Node;
if (node is not null)
{
NodeTreeViewer.RemoteGlobalFlipFlop(node); // 从全局触发器树树视图中移除
}
}
#endregion
this.Dispatcher.Invoke(() =>
{
FlowChartCanvas.Children.Remove(nodeControl);
NodeControls.Remove(nodeControl.ViewModel.Node.Guid);
});
@@ -404,7 +445,6 @@ namespace Serein.WorkBench
return;
}
NodeControls.TryAdd(nodeModelBase.Guid, nodeControl);
if (eventArgs.IsAddInRegion && NodeControls.TryGetValue(eventArgs.RegeionGuid, out NodeControlBase? regionControl))
{
if (regionControl is not null)
@@ -415,13 +455,23 @@ namespace Serein.WorkBench
}
else
{
if (!TryPlaceNodeInRegion(nodeControl, position))
if (!TryPlaceNodeInRegion(nodeControl, position)) // 将节点放置在区域中
{
PlaceNodeOnCanvas(nodeControl, position.X, position.Y);
PlaceNodeOnCanvas(nodeControl, position.X, position.Y); // 将节点放置在画布上
}
}
#region
if (nodeModelBase.ControlType == NodeControlType.Flipflop)
{
var node = nodeControl?.ViewModel?.Node;
if(node is not null)
{
NodeTreeViewer.AddGlobalFlipFlop(FlowEnvironment, node); // 新增的触发器节点添加到全局触发器
}
}
#endregion
});
@@ -450,6 +500,11 @@ namespace Serein.WorkBench
newStartNodeControl.BorderBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#04FC10"));
newStartNodeControl.BorderThickness = new Thickness(2);
var node = newStartNodeControl?.ViewModel?.Node;
if (node is not null)
{
NodeTreeViewer.LoadNodeTreeOfStartNode(FlowEnvironment, node);
}
});
}
@@ -463,10 +518,10 @@ namespace Serein.WorkBench
{
string nodeGuid = eventArgs.NodeGuid;
object monitorKey = MonitorObjectEventArgs.ObjSourceType.NodeFlowData switch
string monitorKey = MonitorObjectEventArgs.ObjSourceType.NodeFlowData switch
{
MonitorObjectEventArgs.ObjSourceType.NodeFlowData => nodeGuid,
_ => eventArgs.NewData,
_ => eventArgs.NewData.GetType().FullName,
};
//NodeControlBase nodeControl = GuidToControl(nodeGuid);
@@ -548,6 +603,15 @@ namespace Serein.WorkBench
}
}
/// <summary>
/// IOC变更
/// </summary>
/// <param name="eventArgs"></param>
/// <exception cref="NotImplementedException"></exception>
private void FlowEnvironment_OnIOCMembersChanged(IOCMembersChangedEventArgs eventArgs)
{
IOCObjectViewer.AddDependenciesInstance(eventArgs.Key, eventArgs.Instance);
}
/// <summary>
/// Guid 转 NodeControl
@@ -569,7 +633,6 @@ namespace Serein.WorkBench
}
#endregion
#region
/// <summary>
@@ -1081,46 +1144,9 @@ namespace Serein.WorkBench
((UIElement)sender).CaptureMouse(); // 捕获鼠标
e.Handled = true; // 防止事件传播影响其他控件
}
}
private void ChangeViewerObjOfNode(NodeControlBase nodeControl)
{
// int i = false;
var node = nodeControl?.ViewModel?.Node;
if (node is not null && node.MethodDetails.ReturnType != typeof(void))
{
if (ViewObjectViewer.MonitorObj is null)
{
FlowEnvironment.SetMonitorObjState(node.Guid, true); // 通知环境该节点的数据更新后需要传到UI
// FlowEnvironment.SetMonitorObjState(nodeObj, true); // 通知环境该节点的数据更新后需要传到UI
return;
}
var nodeObj = node.GetFlowData();
if (nodeObj is null)
{
return;
}
//if (nodeObj.Equals(ViewObjectViewer.MonitorObj) == true)
//{
// // 选择同一个控件,不再监视
// ViewObjectViewer.RefreshObjectTree(nodeObj);
// return;
//}
if (node.Guid.Equals(ViewObjectViewer.MonitorKey) == true)
{
ViewObjectViewer.RefreshObjectTree(nodeObj);
return;
}
else
{
FlowEnvironment.SetMonitorObjState(ViewObjectViewer.MonitorKey, false); // 取消对旧节点的监视
FlowEnvironment.SetMonitorObjState(node.Guid, true); // 通知环境该节点的数据更新后需要传到UI
}
}
}
/// <summary>
/// 控件的鼠标移动事件,根据鼠标拖动更新控件的位置。批量移动计算移动逻辑。
/// </summary>
@@ -1217,6 +1243,41 @@ namespace Serein.WorkBench
startControlDragPoint = currentPosition; // 更新起始点位置
}
}
private void ChangeViewerObjOfNode(NodeControlBase nodeControl)
{
var node = nodeControl?.ViewModel?.Node;
if (node is not null && node.MethodDetails.ReturnType != typeof(void))
{
var key = node.Guid;
var instance = node.GetFlowData();
ViewObjectViewer.LoadObjectInformation(key, instance);
ChangeViewerObj(key, instance);
}
}
public void ChangeViewerObj(string key, object instance)
{
if (ViewObjectViewer.MonitorObj is null)
{
FlowEnvironment.SetMonitorObjState(key, true); // 通知环境该节点的数据更新后需要传到UI
return;
}
if (instance is null)
{
return;
}
if (key.Equals(ViewObjectViewer.MonitorKey) == true)
{
ViewObjectViewer.RefreshObjectTree(instance);
return;
}
else
{
FlowEnvironment.SetMonitorObjState(ViewObjectViewer.MonitorKey,false); // 取消对旧节点的监视
FlowEnvironment.SetMonitorObjState(key, true); // 通知环境该节点的数据更新后需要传到UI
}
}
#region UI连接控件操作
@@ -1229,6 +1290,7 @@ namespace Serein.WorkBench
{
IsControlDragging = false;
((UIElement)sender).ReleaseMouseCapture(); // 释放鼠标捕获
}
if (IsConnecting)
@@ -1240,6 +1302,7 @@ namespace Serein.WorkBench
return;
}
FlowEnvironment.ConnectNode(formNodeGuid, toNodeGuid, currentConnectionType);
}
/*else if (IsConnecting)
{
@@ -2156,7 +2219,28 @@ namespace Serein.WorkBench
#endregion
#region IOC视图管理
#region IOC视图管理
private void JudgmentFlipFlopNode(NodeControlBase nodeControl)
{
if (nodeControl is FlipflopNodeControl flipflopControl) // 判断是否为触发器
{
var nodeModel = flipflopControl?.ViewModel?.Node;
int count = 0;
foreach (var ct in NodeStaticConfig.ConnectionTypes)
{
count += nodeModel.PreviousNodes[ct].Count;
}
if (count == 0)
{
NodeTreeViewer.AddGlobalFlipFlop(FlowEnvironment, nodeModel); // 添加到全局触发器树树视图
}
else
{
NodeTreeViewer.RemoteGlobalFlipFlop(nodeModel); // 从全局触发器树树视图中移除
}
}
}
void LoadIOCObjectViewer()
{
@@ -2366,7 +2450,6 @@ namespace Serein.WorkBench
}
}
}
#region UI层面上显示为 线

View File

@@ -24,8 +24,6 @@ namespace Serein.WorkBench.Node.ViewModel
internal NodeModelBase Node { get; }
private bool isSelect;
/// <summary>
/// 表示节点控件是否被选中

View File

@@ -33,6 +33,7 @@
<Page Remove="Node\FlipflopRegionControl.xaml" />
<Page Remove="Themes\ConditionControl.xaml" />
<Page Remove="Themes\ExplicitDataControl.xaml" />
<Page Remove="Themes\MultiConditionConverter.xaml" />
<Page Remove="Themes\ObjectViewerControl1.xaml" />
</ItemGroup>

View File

@@ -9,15 +9,18 @@
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<!--<RowDefinition Height="*"/>
<RowDefinition Height="*"/>-->
</Grid.RowDefinitions>
<GroupBox Grid.Row="0" Header="已注册待绑定的类型" Margin="5">
<ListBox x:Name="TypeListBox" Background="#E3F6FA"/>
</GroupBox>
<GroupBox Grid.Row="1" Header="完成注入的实例" Margin="5">
<GroupBox Grid.Row="1" Header="实例视图" Margin="5">
<ListBox x:Name="DependenciesListBox" Background="#E3FAE9"/>
</GroupBox>
<!--<GroupBox Grid.Row="0" Header="正在注册的类型" Margin="5">
<ListBox x:Name="TypeListBox" Background="#E3F6FA"/>
</GroupBox>-->
<!--<GroupBox Grid.Row="1" Header="实例视图" Margin="5">
<ListBox x:Name="DependenciesListBox" Background="#E3FAE9"/>
</GroupBox>-->
<!--<GroupBox Grid.Row="3" Header="未完成注入的实例" Margin="5">
<ListBox x:Name="UnfinishedDependenciesListBox" Background="#FFE9D7"/>
</GroupBox>-->

View File

@@ -7,6 +7,7 @@ using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
@@ -14,6 +15,7 @@ using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Xml.Linq;
namespace Serein.WorkBench.Themes
{
@@ -22,18 +24,96 @@ namespace Serein.WorkBench.Themes
/// </summary>
public partial class IOCObjectViewControl : UserControl
{
private IOCObjectViewMoel IOCObjectViewMoel;
private SereinIOC sereinIOC;
public void SetIOC(SereinIOC sereinIOC)
{
this.sereinIOC = sereinIOC;
}
public Action<string,object> SelectObj { get; set; }
public IOCObjectViewControl()
{
InitializeComponent();
IOCObjectViewMoel = new IOCObjectViewMoel();
DataContext = IOCObjectViewMoel;
}
private class IOCObj
{
public string Key { get; set; }
public object Instance { get; set; }
}
/// <summary>
/// 运行环境
/// </summary>
public IFlowEnvironment FlowEnvironment { get; set; }
/// <summary>
/// 添加一个实例
/// </summary>
/// <param name="key"></param>
/// <param name="instance"></param>
public void AddDependenciesInstance(string key,object instance)
{
IOCObj iOCObj = new IOCObj
{
Key = key,
Instance = instance,
};
TextBlock textBlock = new TextBlock();
textBlock.Text = key;
textBlock.Tag = iOCObj;
textBlock.MouseDown += (s, e) =>
{
if(s is TextBlock block && block.Tag is IOCObj iocObj)
{
SelectObj?.Invoke(iocObj.Key, iocObj.Instance);
//FlowEnvironment.SetMonitorObjState(iocObj.Instance, true); // 通知环境该节点的数据更新后需要传到UI
}
};
DependenciesListBox.Items.Add(textBlock);
SortLisbox(DependenciesListBox);
}
/// <summary>
/// 刷新一个实例
/// </summary>
/// <param name="key"></param>
/// <param name="instance"></param>
public void RefreshDependenciesInstance(string key, object instance)
{
foreach (var item in DependenciesListBox.Items)
{
if (item is TextBlock block && block.Tag is IOCObj iocObj && iocObj.Key.Equals(key))
{
iocObj.Instance = instance;
}
}
}
public void ClearObjItem()
{
DependenciesListBox.Items.Clear();
}
private static void SortLisbox(ListBox listBox)
{
var sortedItems = listBox.Items.Cast<TextBlock>().OrderBy(x => x.Text).ToList();
listBox.Items.Clear();
foreach (var item in sortedItems)
{
listBox.Items.Add(item);
}
}
public void RemoveDependenciesInstance(string key)
{
object? itemControl = null;
foreach (var item in DependenciesListBox.Items)
{
if (item is TextBlock block && block.Tag is IOCObj iocObj && iocObj.Key.Equals(key))
{
itemControl = item;
}
}
if (itemControl is not null)
{
DependenciesListBox.Items.Remove(itemControl);
}
}
}

View File

@@ -1,12 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.WorkBench.Themes
{
internal class IOCObjectViewMoel
{
}
}

View File

@@ -1,26 +0,0 @@
<UserControl x:Class="Serein.WorkBench.Themes.LazyTreeView"
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.Themes"
xmlns:converters="clr-namespace:Serein.WorkBench.Tool.Converters"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<converters:TypeToColorConverter x:Key="TypeToColorConverter" />
</UserControl.Resources>
<Grid>
<TreeView Name="treeView"
SelectedItemChanged="treeView_SelectedItemChanged"
ItemsSource="{Binding RootNodes}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding SuccessorNodes}">
<TextBlock Text="{Binding DisplayName}" Foreground="{Binding ControlType, Converter={StaticResource TypeToColorConverter}}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
</UserControl>

View File

@@ -1,58 +0,0 @@
using Serein.Library.Enums;
using Serein.NodeFlow.Base;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
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.Themes
{
/// <summary>
/// LazyTreeView.xaml 的交互逻辑
/// </summary>
public partial class LazyTreeView : UserControl
{
public ObservableCollection<NodeModelBase> RootNodes { get; set; }
public LazyTreeView()
{
InitializeComponent();
RootNodes = new ObservableCollection<NodeModelBase>();
treeView.DataContext = this;
}
private void treeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
if (e.NewValue is NodeModelBase node)
{
// 在这里设置 Expanded 事件
var treeViewItem = (TreeViewItem)treeView.ItemContainerGenerator.ContainerFromItem(node);
treeViewItem.Expanded += (s, args) => LoadChildren(node, treeViewItem);
}
}
private void LoadChildren(NodeModelBase node, TreeViewItem treeViewItem)
{
// 懒加载逻辑
if (node.SuccessorNodes.Count > 0)
{
treeViewItem.Items.Clear();
foreach (var child in node.SuccessorNodes[ConnectionType.IsSucceed]) // 根据类型加载子项
{
treeViewItem.Items.Add(child);
}
}
}
}
}

View File

@@ -0,0 +1,52 @@
<UserControl x:Class="Serein.WorkBench.Themes.NodeTreeItemViewControl"
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.Themes"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0" x:Name="UpstreamTreeGuid" Margin="0,0,0,0" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Rectangle Grid.Column="0" Width="1" x:Name="UpstreamTreeRectangle" Grid.Row="0" Fill="#4A82E4" Margin="4,1,4,1" IsHitTestVisible="False"/>
<TreeView Grid.Column="1" x:Name="UpstreamTreeNodes" BorderThickness="0"/>
</Grid>
<Grid Grid.Row="1" x:Name="IsSucceedTreeGuid" Margin="0,0,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Rectangle Grid.Column="0" Width="1" x:Name="IsSucceedRectangle" Grid.Row="0" Fill="#04FC10" Margin="4,1,4,1" IsHitTestVisible="False"/>
<TreeView Grid.Column="1" x:Name="IsSucceedTreeNodes" BorderThickness="0"/>
</Grid>
<Grid Grid.Row="2" x:Name="IsFailTreeGuid" Margin="0,0,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Rectangle Grid.Column="0" Width="1" x:Name="IsFailRectangle" Grid.Row="0" Fill="#F18905" Margin="4,1,4,1" IsHitTestVisible="False"/>
<TreeView Grid.Column="1" x:Name="IsFailTreeNodes" BorderThickness="0"/>
</Grid>
<Grid Grid.Row="3" x:Name="IsErrorTreeGuid" Margin="0,0,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Rectangle Grid.Column="0" Width="1" x:Name="IsErrorRectangle" Grid.Row="0" Fill="#FE1343" Margin="4,1,4,1" IsHitTestVisible="False"/>
<TreeView Grid.Column="1" x:Name="IsErrorTreeNodes" BorderThickness="0"/>
</Grid>
</Grid>
</UserControl>

View File

@@ -0,0 +1,270 @@
using Serein.Library.Api;
using Serein.Library.Enums;
using Serein.NodeFlow;
using Serein.NodeFlow.Base;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
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;
using System.Xml.Linq;
using static Serein.WorkBench.Themes.TypeViewerWindow;
namespace Serein.WorkBench.Themes
{
/// <summary>
/// NodeTreeVIewControl.xaml 的交互逻辑
/// </summary>
public partial class NodeTreeItemViewControl : UserControl
{
public NodeTreeItemViewControl()
{
InitializeComponent();
foreach (var ct in NodeStaticConfig.ConnectionTypes)
{
var guid = ToGridView(this, ct);
guid.Visibility = Visibility.Collapsed;
}
}
/// <summary>
/// 保存的节点数据
/// </summary>
private NodeModelBase nodeModel;
private IFlowEnvironment flowEnvironment { get; set; }
private class NodeTreeModel
{
public NodeModelBase RootNode { get; set; }
public Dictionary<ConnectionType, List<NodeModelBase>> ChildNodes { get; set; }
}
public void InitAndLoadTree(IFlowEnvironment flowEnvironment, NodeModelBase nodeModel)
{
this.flowEnvironment = flowEnvironment;
this.nodeModel = nodeModel;
RefreshTree();
}
public TreeViewItem RefreshTree()
{
NodeModelBase rootNodeModel = this.nodeModel;
NodeTreeModel nodeTreeModel = new NodeTreeModel
{
RootNode = rootNodeModel,
ChildNodes = new Dictionary<ConnectionType, List<NodeModelBase>>()
{
{ConnectionType.Upstream, []},
{ConnectionType.IsSucceed, [rootNodeModel]},
{ConnectionType.IsFail, []},
{ConnectionType.IsError, []},
}
};
var rootNode = new TreeViewItem
{
Header = rootNodeModel.MethodDetails.MethodTips,
Tag = nodeTreeModel,
};
LoadNodeItem(this, nodeTreeModel);
rootNode.Expanded += TreeViewItem_Expanded; // 监听展开事件
rootNode.IsExpanded = true;
return rootNode;
}
/// <summary>
/// 展开子项事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void TreeViewItem_Expanded(object sender, RoutedEventArgs e)
{
if (sender is TreeViewItem item && item.Tag is NodeTreeModel nodeTreeModel)
{
item.Items.Clear();
NodeTreeItemViewControl? nodeTreeItemViewControl = LoadTNoderee(nodeTreeModel);
if (nodeTreeItemViewControl is not null)
{
LoadNodeItem(nodeTreeItemViewControl, nodeTreeModel);
item.Items.Add(nodeTreeItemViewControl);
}
item.IsSelected = false;
}
e.Handled = true;
}
/// <summary>
/// 加载面板
/// </summary>
/// <param name="nodeTreeItemViewControl"></param>
/// <param name="nodeTreeModel"></param>
private void LoadNodeItem(NodeTreeItemViewControl nodeTreeItemViewControl, NodeTreeModel nodeTreeModel)
{
foreach (var ct in NodeStaticConfig.ConnectionTypes)
{
var treeViewer = ToTreeView(nodeTreeItemViewControl, ct);
var guid = ToGridView(nodeTreeItemViewControl, ct);
treeViewer.Items.Clear(); // 移除对象树的所有节点
var list = nodeTreeModel.ChildNodes[ct];
if (list.Count > 0)
{
foreach (var child in list)
{
NodeTreeModel tmpNodeTreeModel = new NodeTreeModel
{
RootNode = child,
ChildNodes = child.SuccessorNodes,
};
TreeViewItem treeViewItem = new TreeViewItem
{
Header = child.MethodDetails.MethodTips,
Tag = tmpNodeTreeModel
};
treeViewItem.Expanded += TreeViewItem_Expanded;
ContextMenu contextMenu = new ContextMenu();
contextMenu.Items.Add(MainWindow.CreateMenuItem("从此节点执行", (s, e) =>
{
flowEnvironment.StartFlowInSelectNodeAsync(tmpNodeTreeModel.RootNode.Guid);
}));
treeViewItem.ContextMenu = contextMenu;
treeViewer.Items.Add(treeViewItem);
}
guid.Visibility = Visibility.Visible;
}
else
{
guid.Visibility = Visibility.Collapsed;
}
}
}
/// <summary>
/// 加载节点子项
/// </summary>
/// <param name="nodeTreeModel"></param>
/// <returns></returns>
private NodeTreeItemViewControl? LoadTNoderee(NodeTreeModel nodeTreeModel)
{
NodeTreeItemViewControl nodeTreeItemViewControl = null;
foreach (var connectionType in NodeStaticConfig.ConnectionTypes)
{
var childNodeModels = nodeTreeModel.ChildNodes[connectionType];
if (childNodeModels.Count > 0)
{
nodeTreeItemViewControl ??= new NodeTreeItemViewControl();
}
else
{
continue;
}
TreeView treeView = ToTreeView(nodeTreeItemViewControl, connectionType);
foreach (var childNodeModel in childNodeModels)
{
NodeTreeModel tempNodeTreeModel = new NodeTreeModel
{
RootNode = childNodeModel,
ChildNodes = childNodeModel.SuccessorNodes,
};
TreeViewItem treeViewItem = new TreeViewItem
{
Header = childNodeModel.MethodDetails.MethodTips,
Tag = tempNodeTreeModel
};
treeViewItem.Margin = new Thickness(-15, 0, 0, 0);
treeViewItem.Visibility = Visibility.Visible;
treeView.Items.Add(treeViewItem);
}
}
if (nodeTreeItemViewControl is not null)
{
foreach (var connectionType in NodeStaticConfig.ConnectionTypes)
{
var childNodeModels = nodeTreeModel.ChildNodes[connectionType];
if (childNodeModels.Count > 0)
{
nodeTreeItemViewControl ??= new NodeTreeItemViewControl();
}
else
{
continue;
}
}
}
return nodeTreeItemViewControl;
}
/// <summary>
/// 折叠事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void TreeViewItem_Collapsed(object sender, RoutedEventArgs e)
{
if (sender is TreeViewItem item && item.Items.Count > 0)
{
item.Items.Clear();
}
}
public static TreeView ToTreeView(NodeTreeItemViewControl item, ConnectionType connectionType)
{
return connectionType switch
{
ConnectionType.Upstream => item.UpstreamTreeNodes,
ConnectionType.IsError => item.IsErrorTreeNodes,
ConnectionType.IsFail => item.IsFailTreeNodes,
ConnectionType.IsSucceed => item.IsSucceedTreeNodes,
_ => throw new Exception("LoadNodeItem Error ConnectionType is " + connectionType)
};
}
public static Grid ToGridView(NodeTreeItemViewControl item, ConnectionType connectionType)
{
return connectionType switch
{
ConnectionType.Upstream => item.UpstreamTreeGuid,
ConnectionType.IsError => item.IsErrorTreeGuid,
ConnectionType.IsFail => item.IsFailTreeGuid,
ConnectionType.IsSucceed => item.IsSucceedTreeGuid,
_ => throw new Exception("LoadNodeItem Error ConnectionType is " + connectionType)
};
}
//public static System.Windows.Shapes.Rectangle ToRectangle(NodeTreeItemViewControl item, ConnectionType connectionType)
//{
// return connectionType switch
// {
// ConnectionType.Upstream => item.UpstreamTreeRectangle,
// ConnectionType.IsError => item.IsErrorRectangle,
// ConnectionType.IsFail => item.IsFailRectangle,
// ConnectionType.IsSucceed => item.IsSucceedRectangle,
// _ => throw new Exception("LoadNodeItem Error ConnectionType is " + connectionType)
// };
//}
}
}

View File

@@ -1,71 +0,0 @@
using Serein.NodeFlow.Base;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls.Primitives;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows;
using Serein.Library.Enums;
namespace Serein.WorkBench.Themes
{
public class NodeTreeView : TreeView
{
public NodeTreeView()
{
this.ItemContainerGenerator.StatusChanged += OnStatusChanged;
}
private void OnStatusChanged(object sender, EventArgs e)
{
if (this.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
{
foreach (var item in Items)
{
var treeViewItem = (TreeViewItem)this.ItemContainerGenerator.ContainerFromItem(item);
if (treeViewItem != null)
{
treeViewItem.Expanded += TreeViewItem_Expanded;
ApplyColor(treeViewItem, item as NodeModelBase);
}
}
}
}
private void TreeViewItem_Expanded(object sender, RoutedEventArgs e)
{
if (sender is TreeViewItem item && item.DataContext is NodeModelBase node)
{
if (item.Items.Count == 0) // 懒加载
{
foreach (var childNode in node.SuccessorNodes[ConnectionType.Upstream])
{
item.Items.Add(childNode);
}
}
}
}
private void ApplyColor(TreeViewItem item, NodeModelBase node)
{
// 根据 ControlType 设置颜色
switch (node.ControlType)
{
case NodeControlType.Flipflop:
item.Background = Brushes.LightGreen;
break;
case NodeControlType.Action:
item.Background = Brushes.LightCoral;
break;
// 添加更多条件
default:
item.Background = Brushes.Transparent;
break;
}
}
}
}

View File

@@ -0,0 +1,47 @@
<UserControl x:Class="Serein.WorkBench.Themes.NodeTreeViewControl"
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.Themes"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<Style x:Key="ListItemNullFocusContainerStyle" TargetType="ListBoxItem">
<Setter Property="SnapsToDevicePixels" Value="true" />
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border Name="Border" Background="Transparent" SnapsToDevicePixels="True">
<ContentPresenter />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<!--<RowDefinition Height="*"/>-->
</Grid.RowDefinitions>
<StackPanel Grid.Row="0">
<TextBlock Text="起始节点"/>
<ScrollViewer >
<local:NodeTreeItemViewControl x:Name="StartNodeViewer" Margin="4,4,4,4"/>
</ScrollViewer >
</StackPanel>
<StackPanel Grid.Row="1">
<TextBlock Text="全局触发器"/>
<ListBox x:Name="GlobalFlipflopNodeListbox" BorderThickness="0" ItemContainerStyle="{StaticResource ListItemNullFocusContainerStyle}"></ListBox>
</StackPanel>
<!--<StackPanel Grid.Row="2">
<TextBlock Text="无业游民"/>
<ListBox x:Name="UnreachableNodeListbox" BorderThickness="0" ItemContainerStyle="{StaticResource ListItemNullFocusContainerStyle}"></ListBox>
</StackPanel>-->
</Grid>
</UserControl>

View File

@@ -0,0 +1,99 @@
using Serein.Library.Api;
using Serein.NodeFlow.Base;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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.Themes
{
/// <summary>
/// NodeTreeViewControl.xaml 的交互逻辑
/// </summary>
public partial class NodeTreeViewControl : UserControl
{
private IFlowEnvironment FlowEnvironment { get; set; }
public NodeTreeViewControl()
{
InitializeComponent();
}
private string startNodeGuid = string.Empty;
private Dictionary<string, NodeTreeItemViewControl> globalFlipflopNodes = [];
private Dictionary<string, NodeTreeItemViewControl> unemployedNodes = [];
public void LoadNodeTreeOfStartNode(IFlowEnvironment flowEnvironment, NodeModelBase nodeModel)
{
startNodeGuid = nodeModel.Guid;
StartNodeViewer.InitAndLoadTree(flowEnvironment, nodeModel);
}
#region
public void AddGlobalFlipFlop(IFlowEnvironment flowEnvironment, NodeModelBase nodeModel)
{
if (!globalFlipflopNodes.ContainsKey(nodeModel.Guid))
{
NodeTreeItemViewControl flipflopTreeViewer = new NodeTreeItemViewControl();
flipflopTreeViewer.InitAndLoadTree(flowEnvironment, nodeModel);
globalFlipflopNodes.Add(nodeModel.Guid, flipflopTreeViewer);
GlobalFlipflopNodeListbox.Items.Add(flipflopTreeViewer);
}
}
public void RefreshGlobalFlipFlop(NodeModelBase nodeModel)
{
if (globalFlipflopNodes.TryGetValue(nodeModel.Guid, out var viewer))
{
viewer.RefreshTree();
}
}
public void RemoteGlobalFlipFlop(NodeModelBase nodeModel)
{
if (globalFlipflopNodes.TryGetValue(nodeModel.Guid, out var viewer))
{
globalFlipflopNodes.Remove(nodeModel.Guid);
GlobalFlipflopNodeListbox.Items.Remove(viewer);
}
}
#endregion
#region
public void AddUnemployed(IFlowEnvironment flowEnvironment, NodeModelBase nodeModel)
{
if (!unemployedNodes.ContainsKey(nodeModel.Guid))
{
NodeTreeItemViewControl flipflopTreeViewer = new NodeTreeItemViewControl();
flipflopTreeViewer.InitAndLoadTree(flowEnvironment, nodeModel);
unemployedNodes.Add(nodeModel.Guid, flipflopTreeViewer);
GlobalFlipflopNodeListbox.Items.Add(flipflopTreeViewer);
}
}
public void RefreshUnemployed(NodeModelBase nodeModel)
{
if (unemployedNodes.TryGetValue(nodeModel.Guid, out var viewer))
{
viewer.RefreshTree();
}
}
public void RemoteUnemployed(NodeModelBase nodeModel)
{
if (unemployedNodes.TryGetValue(nodeModel.Guid, out var viewer))
{
unemployedNodes.Remove(nodeModel.Guid);
GlobalFlipflopNodeListbox.Items.Remove(viewer);
}
}
#endregion
}
}

View File

@@ -85,7 +85,7 @@ namespace Serein.WorkBench.Themes
/// <summary>
/// 监视对象的键
/// </summary>
public object MonitorKey { get => monitorKey; }
public string MonitorKey { get => monitorKey; }
/// <summary>
/// 正在监视的对象
/// </summary>
@@ -96,7 +96,7 @@ namespace Serein.WorkBench.Themes
/// </summary>
public string MonitorExpression { get => ExpressionTextBox.Text.ToString(); }
private object monitorKey;
private string monitorKey;
private object monitorObj;
// 用于存储当前展开的节点路径
@@ -107,7 +107,7 @@ namespace Serein.WorkBench.Themes
/// 加载对象信息,展示其成员
/// </summary>
/// <param name="obj">要展示的对象</param>
public void LoadObjectInformation(object key, object obj)
public void LoadObjectInformation(string key, object obj)
{
if (obj == null) return;
monitorKey = key;
@@ -171,6 +171,7 @@ namespace Serein.WorkBench.Themes
// 这里创建了一个子项,并给这个子项创建了“正在加载”的子项
// 然后移除了原来对象树的所有项,再把这个新创建的子项添加上去
// 绑定了展开/折叠事件后自动展开第一层开始反射obj的成员并判断obj的成员生成什么样的节点
rootNode.IsExpanded = true;
return rootNode;
}
@@ -454,6 +455,11 @@ namespace Serein.WorkBench.Themes
{
try
{
if(obj is null)
{
value = null;
return "Error";
}
var properties = obj.GetType().GetProperties();
// 获取实例属性值