恢复WPF项目代码

This commit is contained in:
fengjiayi
2025-01-05 08:52:37 +08:00
parent 176e43f834
commit 652707f980
73 changed files with 10202 additions and 1 deletions

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows;
namespace Serein.Workbench.Themes
{
public partial class BindableRichTextBox : RichTextBox
{
public new FlowDocument Document
{
get { return (FlowDocument)GetValue(DocumentProperty); }
set { SetValue(DocumentProperty, value); }
}
// Using a DependencyProperty as the backing store for Document. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DocumentProperty =
DependencyProperty.Register("Document", typeof(FlowDocument), typeof(BindableRichTextBox), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnDucumentChanged)));
private static void OnDucumentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
RichTextBox rtb = (RichTextBox)d;
rtb.Document = (FlowDocument)e.NewValue;
}
}
}

View File

@@ -0,0 +1,28 @@
<UserControl x:Class="Serein.Workbench.Themes.IOCObjectViewControl"
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="*"/>-->
</Grid.RowDefinitions>
<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>-->
</Grid>
</UserControl>

View File

@@ -0,0 +1,128 @@
using Serein.Library.Api;
using Serein.Library.Utils;
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.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;
namespace Serein.Workbench.Themes
{
/// <summary>
/// IOCObjectViewControl.xaml 的交互逻辑
/// </summary>
public partial class IOCObjectViewControl : UserControl
{
public Action<string,object> SelectObj { get; set; }
public IOCObjectViewControl()
{
InitializeComponent();
}
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,
};
Application.Current.Dispatcher.Invoke(() =>
{
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.Dispatcher.Invoke(() =>
{
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

@@ -0,0 +1,16 @@
<Window x:Class="Serein.Workbench.Themes.InputDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Serein.Workbench.Themes"
mc:Ignorable="d"
Title="InputDialog" Height="450" Width="800">
<StackPanel Margin="10">
<TextBox x:Name="InputTextBox" Width="200" Margin="0,0,0,10" />
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<Button Content="确认" Click="ConfirmButton_Click" Margin="5" />
<Button Content="取消" Click="CancelButton_Click" Margin="5" />
</StackPanel>
</StackPanel>
</Window>

View File

@@ -0,0 +1,42 @@
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.Shapes;
namespace Serein.Workbench.Themes
{
/// <summary>
/// InputDialog.xaml 的交互逻辑
/// </summary>
public partial class InputDialog : Window
{
public string InputValue { get; private set; }
public InputDialog()
{
InitializeComponent();
}
private void ConfirmButton_Click(object sender, RoutedEventArgs e)
{
InputValue = InputTextBox.Text;
DialogResult = true; // 设置返回结果为 true
Close(); // 关闭窗口
}
private void CancelButton_Click(object sender, RoutedEventArgs e)
{
DialogResult = false; // 设置返回结果为 false
Close(); // 关闭窗口
}
}
}

View File

@@ -0,0 +1,129 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Serein.Workbench.Themes"
xmlns:view="clr-namespace:Serein.Workbench.Node.View"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:converters="clr-namespace:Serein.Workbench.Tool.Converters">
<ResourceDictionary.MergedDictionaries>
</ResourceDictionary.MergedDictionaries>
<converters:InvertableBooleanToVisibilityConverter x:Key="InvertedBoolConverter"/>
<Style TargetType="{x:Type local:MethodDetailsControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MethodDetailsControl}">
<!--根据方法入参数量生成相应的控件-->
<ItemsControl ItemsSource="{Binding MethodDetails.ParameterDetailss, RelativeSource={RelativeSource TemplatedParent}}" Background="#E3FDFD" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<!--连接控制器-->
<view:ArgJunctionControl x:Name="ArgJunctionControl" Grid.Column="0" ArgIndex="{Binding Index}" MyNode="{Binding NodeModel}" />
<!--参数索引提示-->
<TextBlock Grid.Column="1" Text="{Binding Index,StringFormat=agr{0}}" Margin="2,0,2,0" VerticalAlignment="Center"/>
<!--是否设置为显式参数-->
<CheckBox Grid.Column="2" IsChecked="{Binding IsExplicitData, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="2,0,2,0" VerticalContentAlignment="Center"/>
<!--入参参数名称-->
<TextBlock Grid.Column="3" MinWidth="50" Text="{Binding Name}" Margin="2,0,2,0" HorizontalAlignment="Left" VerticalAlignment="Center"/>
<!--增加可选参数(如果有)-->
<view:ParamsArgControl x:Name="ParamsArgControl"
ArgIndex="{Binding Index}"
MyNode="{Binding NodeModel}"
Width="12"
Grid.Column="5" Margin="2,0,2,0" HorizontalAlignment="Right" VerticalAlignment="Center"
Visibility="{Binding IsParams, Mode=OneWay,
Converter={StaticResource InvertedBoolConverter},ConverterParameter=Normal}"
/>
<ContentControl Content="{Binding}" Grid.Column="4" VerticalAlignment="Center">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<!--无须指定参数-->
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsExplicitData}" Value="false" />
</MultiDataTrigger.Conditions>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" MinWidth="50" Text="无须指定参数"/>
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
</MultiDataTrigger>
<!--指定参数:选项类型-->
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsExplicitData}" Value="true" />
<Condition Binding="{Binding ExplicitTypeName}" Value="Select" />
</MultiDataTrigger.Conditions>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ComboBox Grid.Column="0"
MinWidth="50"
ItemsSource="{Binding Items}"
SelectedItem="{Binding DataValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
</MultiDataTrigger>
<!--指定参数:文本类型(可输入)-->
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsExplicitData}" Value="true" />
<Condition Binding="{Binding ExplicitTypeName}" Value="Value" />
</MultiDataTrigger.Conditions>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" MinWidth="50" Text="{Binding DataValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

View File

@@ -0,0 +1,90 @@
using Serein.Library;
using Serein.Workbench.Node;
using System.Collections;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
namespace Serein.Workbench.Themes
{
public class MultiConditionConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Length == 2 && values[0] is Type valueType && values[1] is bool isEnabled)
{
if (isEnabled)
{
// 返回文本框
if (valueType == typeof(string) || valueType == typeof(int) || valueType == typeof(double))
{
return "TextBoxTemplate";
}
// 返回可选列表框
else if (typeof(IEnumerable).IsAssignableFrom(valueType))
{
return "ComboBoxTemplate";
}
}
}
return DependencyProperty.UnsetValue;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
/// <summary>
/// 方法参数控件
/// </summary>
public partial class MethodDetailsControl : UserControl
{
static MethodDetailsControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MethodDetailsControl), new FrameworkPropertyMetadata(typeof(MethodDetailsControl)));
}
#region
public MethodDetails MethodDetails
{
get { return (MethodDetails)GetValue(MethodDetailsProperty); }
set { SetValue(MethodDetailsProperty, value); }
}
public static readonly DependencyProperty MethodDetailsProperty = DependencyProperty.Register(nameof(MethodDetails), typeof(MethodDetails),
typeof(MethodDetailsControl), new PropertyMetadata(null, new PropertyChangedCallback(OnPropertyChange)));
#endregion
static void OnPropertyChange(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
//var MethodDetails = (MethodDetails)args.NewValue;
//MethodDetails.ExplicitDatas[0].
}
public ICommand CommandAddParams { get; }
public MethodDetailsControl()
{
CommandAddParams = new RelayCommand(ExecuteAddParams);
}
private void ExecuteAddParams(object parameter)
{
// 方法逻辑
this.MethodDetails.AddParamsArg();
}
}
}

View File

@@ -0,0 +1,59 @@
<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="400" d:DesignWidth="200">
<UserControl.Resources>
<Style x:Key="CustomTreeViewItemStyle" TargetType="TreeViewItem">
<Setter Property="SnapsToDevicePixels" Value="true" />
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
</Style>
</UserControl.Resources>
<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" ItemContainerStyle="{StaticResource CustomTreeViewItemStyle}"/>
</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" ItemContainerStyle="{StaticResource CustomTreeViewItemStyle}"/>
</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" ItemContainerStyle="{StaticResource CustomTreeViewItemStyle}"/>
</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" ItemContainerStyle="{StaticResource CustomTreeViewItemStyle}"/>
</Grid>
</Grid>
</UserControl>

View File

@@ -0,0 +1,280 @@
using Serein.Library;
using Serein.Library.Api;
using Serein.Library.Utils;
using System.Windows;
using System.Windows.Controls;
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<ConnectionInvokeType, 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<ConnectionInvokeType, List<NodeModelBase>>()
{
{ConnectionInvokeType.Upstream, []},
{ConnectionInvokeType.IsSucceed, [rootNodeModel]},
{ConnectionInvokeType.IsFail, []},
{ConnectionInvokeType.IsError, []},
}
};
string? itemName = rootNodeModel.MethodDetails?.MethodAnotherName;
if (string.IsNullOrEmpty(itemName))
{
itemName = rootNodeModel.ControlType.ToString();
}
var rootNode = new TreeViewItem
{
Header = itemName,
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,
};
string? itemName = child?.MethodDetails?.MethodAnotherName;
if (string.IsNullOrEmpty(itemName))
{
itemName = child?.ControlType.ToString();
}
TreeViewItem treeViewItem = new TreeViewItem
{
Header = itemName,
Tag = tmpNodeTreeModel
};
treeViewItem.Expanded += TreeViewItem_Expanded;
var contextMenu = new ContextMenu();
contextMenu.Items.Add(MainWindow.CreateMenuItem("从此节点执行", async (s, e) =>
{
try
{
await flowEnvironment.StartAsyncInSelectNode(tmpNodeTreeModel.RootNode.Guid);
}
catch (Exception ex)
{
SereinEnv.WriteLine(ex);
return;
}
}));
contextMenu.Items.Add(MainWindow.CreateMenuItem("定位", (s, e) => flowEnvironment.NodeLocated(tmpNodeTreeModel.RootNode.Guid)));
treeViewItem.ContextMenu = contextMenu;
treeViewItem.Margin = new Thickness(-20, 0, 0, 0);
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,
};
string? itemName = childNodeModel?.MethodDetails?.MethodAnotherName;
if (string.IsNullOrEmpty(itemName))
{
itemName = childNodeModel?.ControlType.ToString();
}
TreeViewItem treeViewItem = new TreeViewItem
{
Header = itemName,
Tag = tempNodeTreeModel
};
treeViewItem.Margin = new Thickness(-20, 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, ConnectionInvokeType connectionType)
{
return connectionType switch
{
ConnectionInvokeType.Upstream => item.UpstreamTreeNodes,
ConnectionInvokeType.IsError => item.IsErrorTreeNodes,
ConnectionInvokeType.IsFail => item.IsFailTreeNodes,
ConnectionInvokeType.IsSucceed => item.IsSucceedTreeNodes,
_ => throw new Exception("LoadNodeItem Error ConnectionType is " + connectionType)
};
}
public static Grid ToGridView(NodeTreeItemViewControl item, ConnectionInvokeType connectionType)
{
return connectionType switch
{
ConnectionInvokeType.Upstream => item.UpstreamTreeGuid,
ConnectionInvokeType.IsError => item.IsErrorTreeGuid,
ConnectionInvokeType.IsFail => item.IsFailTreeGuid,
ConnectionInvokeType.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

@@ -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,85 @@
using Serein.Library;
using Serein.Library.Api;
using System.Windows.Controls;
namespace Serein.Workbench.Themes
{
/// <summary>
/// NodeTreeViewControl.xaml 的交互逻辑
/// </summary>
public partial class NodeTreeViewControl : UserControl
{
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 RemoveGlobalFlipFlop(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

@@ -0,0 +1,31 @@
<UserControl x:Class="Serein.Workbench.Themes.ObjectViewerControl"
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="400" d:DesignWidth="400">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<!-- 按钮 -->
<RowDefinition Height="*" />
<!-- 树视图 -->
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Grid.Row="0" >
<!---->
<!--<Button Grid.Row="0" HorizontalAlignment="Left" Margin="14,2,4,2" Content="监视" Width="100" Height="20" Name="TimerRefreshButton"/>-->
<!--<Button Grid.Row="0" HorizontalAlignment="Left" Margin="14,2,4,2" Content="添加监视表达式" Width="100" Height="20" Name="AddMonitorExpressionButton" Click="AddMonitorExpressionButton_Click"/>-->
<Button Grid.Row="0" HorizontalAlignment="Left" Margin="4,2,4,2" Content="刷新" Width="40" Height="20" Name="RefreshButton" Click="RefreshButton_Click"/>
<Button Grid.Row="0" HorizontalAlignment="Left" Margin="4,2,4,2" Content="添加监视表达式" Width="80" Height="20" Name="UpMonitorExpressionButton" Click="UpMonitorExpressionButton_Click"/>
<TextBox x:Name="ExpressionTextBox" Margin="4,2,4,2" Width="300"/>
</StackPanel>
<!-- 刷新按钮 -->
<!-- 树视图,用于显示对象属性 -->
<TreeView FontSize="14" x:Name="ObjectTreeView" Grid.Row="1" />
</Grid>
</UserControl>

View File

@@ -0,0 +1,670 @@
using Newtonsoft.Json.Linq;
using Serein.Library.Api;
using Serein.Library.Utils.SereinExpression;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Security.Cryptography;
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.Markup.Primitives;
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
{
public class FlowDataDetails
{
/// <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; } = string.Empty;
}
/// <summary>
/// ObjectViewerControl.xaml 的交互逻辑
/// </summary>
public partial class ObjectViewerControl : UserControl
{
public ObjectViewerControl()
{
InitializeComponent();
}
/// <summary>
/// 监视类型
/// </summary>
public enum MonitorType
{
/// <summary>
/// 作用于对象(对象的引用)的监视
/// </summary>
NodeFlowData,
/// <summary>
/// 作用与节点FLowData的监视
/// </summary>
IOCObj,
}
/// <summary>
/// 运行环境
/// </summary>
public IFlowEnvironment? FlowEnvironment { get; set; }
/// <summary>
/// 监视对象的键
/// </summary>
public string? MonitorKey { get => monitorKey; }
/// <summary>
/// 正在监视的对象
/// </summary>
public object? MonitorObj { get => monitorObj; }
/// <summary>
/// 监视表达式
/// </summary>
public string? MonitorExpression { get => ExpressionTextBox.Text.ToString(); }
private string? monitorKey;
private object? monitorObj;
// 用于存储当前展开的节点路径
private HashSet<string> expandedNodePaths = new HashSet<string>();
/// <summary>
/// 加载对象信息,展示其成员
/// </summary>
/// <param name="obj">要展示的对象</param>
public void LoadObjectInformation(string key, object obj)
{
if (obj == null) return;
monitorKey = key;
monitorObj = obj;
expandedNodePaths.Clear();
LoadTree(obj);
}
/// <summary>
/// 刷新对象
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void RefreshButton_Click(object sender, RoutedEventArgs e)
{
RefreshObjectTree(monitorObj);
}
/// <summary>
/// 更新表达式
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void UpMonitorExpressionButton_Click(object sender, RoutedEventArgs e)
{
//if (FlowEnvironment is not null && await FlowEnvironment.AddInterruptExpressionAsync(monitorKey, MonitorExpression)) // 对象预览器尝试添加中断表达式
//{
// if (string.IsNullOrEmpty(MonitorExpression))
// {
// ExpressionTextBox.Text = "表达式已清空";
// }
// else
// {
// UpMonitorExpressionButton.Content = "更新监视表达式";
// }
//}
}
private TreeViewItem? LoadTree(object? obj)
{
if (obj is null) return null;
var objectType = obj.GetType();
FlowDataDetails flowDataDetails = new FlowDataDetails
{
Name = objectType.Name,
DataType = objectType,
DataValue = obj,
DataPath = ""
};
var rootNode = new TreeViewItem
{
Header = objectType.Name,
Tag = flowDataDetails,
};
ObjectTreeView.Items.Clear(); // 移除对象树的所有节点
ObjectTreeView.Items.Add(rootNode); // 添加所有节点
rootNode.Expanded += TreeViewItem_Expanded; // 监听展开事件
rootNode.Collapsed += TreeViewItem_Collapsed; // 监听折叠事件
// 这里创建了一个子项,并给这个子项创建了“正在加载”的子项
// 然后移除了原来对象树的所有项,再把这个新创建的子项添加上去
// 绑定了展开/折叠事件后自动展开第一层开始反射obj的成员并判断obj的成员生成什么样的节点
rootNode.IsExpanded = true;
return rootNode;
}
/// <summary>
/// 刷新对象属性树
/// </summary>
public void RefreshObjectTree(object? obj)
{
monitorObj = obj;
var rootNode = LoadTree(obj);
if (rootNode is not null)
{
ExpandPreviouslyExpandedNodes(rootNode); // 遍历节点,展开之前记录的节点
}
}
/// <summary>
/// 展开父节点,如果路径存在哈希记录,则将其自动展开,并递归展开后的子节点。
/// </summary>
/// <param name="node"></param>
private void ExpandPreviouslyExpandedNodes(TreeViewItem node)
{
if (node == null) return;
if(node.Tag is FlowDataDetails flowDataDetails)
{
if (expandedNodePaths.Contains(flowDataDetails.DataPath))
{
node.IsExpanded = true;
}
}
foreach (TreeViewItem child in node.Items)
{
ExpandPreviouslyExpandedNodes(child);
}
}
/// <summary>
/// 展开子项事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void TreeViewItem_Expanded(object sender, RoutedEventArgs e)
{
if (sender is TreeViewItem item)
{
if (item.Tag is FlowDataDetails flowDataDetails) // FlowDataDetails flowDataDetails object obj
{
if (flowDataDetails.ItemType != TreeItemType.Item && item.Items.Count != 0)
{
return;
}
if(flowDataDetails.DataValue is null || flowDataDetails.DataType is null)
{
return;
}
// 记录当前节点的路径
var path = flowDataDetails.DataPath;
expandedNodePaths.Add(path);
AddMembersToTreeNode(item, flowDataDetails.DataValue, flowDataDetails.DataType);
}
}
}
/// <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)
{
if (item.Tag is FlowDataDetails flowDataDetails)
{
// 记录当前节点的路径
var path = flowDataDetails.DataPath;
if(path != "")
{
expandedNodePaths.Remove(path);
}
}
}
}
/// <summary>
/// 反射对象数据添加子节点
/// </summary>
/// <param name="treeViewNode"></param>
/// <param name="obj"></param>
/// <param name="type"></param>
private void AddMembersToTreeNode(TreeViewItem treeViewNode, object obj, Type type)
{
// 获取公开的属性
var members = type.GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
foreach (var member in members)
{
if (member.Name.StartsWith(".") ||
member.Name.StartsWith("get_") ||
member.Name.StartsWith("set_")
)
{
// 跳过构造函数、属性的get/set方法
continue;
}
TreeViewItem? memberNode = ConfigureTreeViewItem(obj, member); // 根据对象成员生成节点对象
if (memberNode is not null)
{
treeViewNode.Items.Add(memberNode); // 添加到当前节点
// 配置数据路径
FlowDataDetails subFlowDataDetails = (FlowDataDetails)memberNode.Tag;
string superPath = ((FlowDataDetails)treeViewNode.Tag).DataPath;
string subPath = superPath + "." + subFlowDataDetails.Name;
subFlowDataDetails.DataPath = subPath;
// 配置右键菜单
var contextMenu = new ContextMenu();
contextMenu.Items.Add(MainWindow.CreateMenuItem($"表达式", (s, e) =>
{
ExpressionTextBox.Text = subPath; // 获取表达式
}));
memberNode.ContextMenu = contextMenu;
}
}
}
/// <summary>
/// 配置节点子项
/// </summary>
/// <param name="obj"></param>
/// <param name="member"></param>
/// <returns></returns>
private TreeViewItem? ConfigureTreeViewItem(object obj, MemberInfo member)
{
if (obj == null)
{
return null;
}
#region
if (member is PropertyInfo property)
{
#region (
if (property.PropertyType != typeof(string) && typeof(IEnumerable).IsAssignableFrom(property.PropertyType) && property.GetValue(obj) is IEnumerable collection && collection is not null)
{
TreeViewItem memberNode = new TreeViewItem { Header = member.Name };
// 处理集合类型的属性
memberNode.Tag = new FlowDataDetails
{
ItemType = TreeItemType.IEnumerable,
DataType = property.PropertyType,
Name = property.Name,
DataValue = collection,
};
int index = 0;
foreach (var item in collection)
{
var itemNode = new TreeViewItem { Header = $"[{index++}] {item}" ?? "null" };
memberNode.Tag = new FlowDataDetails
{
ItemType = TreeItemType.Item,
DataType = item?.GetType(),
Name = property.Name,
DataValue = itemNode,
};
memberNode.Items.Add(itemNode);
}
memberNode.Header = $"{property.Name} : {property.PropertyType.Name} [{index}]";
if (!property.PropertyType.IsPrimitive && property.PropertyType != typeof(string))
{
memberNode.Expanded += TreeViewItem_Expanded;
memberNode.Collapsed += TreeViewItem_Collapsed;
}
return memberNode;
}
#endregion
#region
else
{
TreeViewItem memberNode = new TreeViewItem { Header = member.Name };
string propertyValue = GetPropertyValue(obj, property, out object? value);
memberNode.Tag = new FlowDataDetails
{
ItemType = TreeItemType.Property,
DataType = property.PropertyType,
Name = property.Name,
DataValue = value,
}; ;
memberNode.Header = $"{property.Name} : {property.PropertyType.Name} = {propertyValue}";
if (!property.PropertyType.IsPrimitive && property.PropertyType != typeof(string))
{
memberNode.Expanded += TreeViewItem_Expanded;
memberNode.Collapsed += TreeViewItem_Collapsed;
}
return memberNode;
}
#endregion
}
#endregion
#region
else if (member is FieldInfo field)
{
#region (
if (field.FieldType != typeof(string) && typeof(IEnumerable).IsAssignableFrom(field.FieldType) && field.GetValue(obj) is IEnumerable collection && collection is not null)
{
TreeViewItem memberNode = new TreeViewItem { Header = member.Name };
// 处理集合类型的字段
memberNode.Tag = new FlowDataDetails
{
ItemType = TreeItemType.IEnumerable,
DataType = field.FieldType,
Name = field.Name,
DataValue = collection,
};
int index = 0;
foreach (var item in collection)
{
var itemNode = new TreeViewItem { Header = $"[{index++}] {item}" ?? "null" };
memberNode.Tag = new FlowDataDetails
{
ItemType = TreeItemType.Item,
DataType = item?.GetType(),
Name = field.Name,
DataValue = itemNode,
};
//collectionNode.Items.Add(itemNode);
memberNode.Items.Add(itemNode);
}
memberNode.Header = $"{field.Name} : {field.FieldType.Name} [{index}]";
if (!field.FieldType.IsPrimitive && field.FieldType != typeof(string))
{
memberNode.Expanded += TreeViewItem_Expanded;
memberNode.Collapsed += TreeViewItem_Collapsed;
}
return memberNode;
}
#endregion
#region
else
{
TreeViewItem memberNode = new TreeViewItem { Header = member.Name };
string fieldValue = GetFieldValue(obj, field, out object? value);
memberNode.Tag = new FlowDataDetails
{
ItemType = TreeItemType.Field,
DataType = field.FieldType,
Name = field.Name,
DataValue = value,
};
memberNode.Header = $"{field.Name} : {field.FieldType.Name} = {fieldValue}";
if (!field.FieldType.IsPrimitive && field.FieldType != typeof(string))
{
memberNode.Expanded += TreeViewItem_Expanded;
memberNode.Collapsed += TreeViewItem_Collapsed;
}
return memberNode;
}
#endregion
}
#endregion
#region null
else
{
return null;
}
#endregion
}
/// <summary>
/// 获取属性类型的成员
/// </summary>
/// <param name="obj"></param>
/// <param name="property"></param>
/// <returns></returns>
private string GetPropertyValue(object obj, PropertyInfo property,out object? value)
{
try
{
if(obj is null)
{
value = null;
return "Error";
}
var properties = obj.GetType().GetProperties();
// 获取实例属性值
value = property.GetValue(obj);
return value?.ToString() ?? "null"; // 返回值或“null”
}
catch
{
value = null;
return "Error";
}
}
/// <summary>
/// 获取字段类型的成员
/// </summary>
/// <param name="obj"></param>
/// <param name="field"></param>
/// <returns></returns>
private string GetFieldValue(object obj, FieldInfo field, out object? value)
{
try
{
value = field.GetValue(obj);
return value?.ToString() ?? "null";
}
catch
{
value = null;
return "Error";
}
}
}
}
/// <summary>
/// 上次刷新时间
/// </summary>
//private DateTime lastRefreshTime = DateTime.MinValue;
/// <summary>
/// 刷新间隔
/// </summary>
//private readonly TimeSpan refreshInterval = TimeSpan.FromSeconds(0.1);
// 当前时间
//var currentTime = DateTime.Now;
//if (currentTime - lastRefreshTime < refreshInterval)
//{
// return; // 跳过过于频繁的刷新调用
//}
//else
//{
// lastRefreshTime = currentTime;// 记录这次的刷新时间
//}
//
/// <summary>
/// 从当前节点获取至父节点的路径,例如 "node1.node2.node3.node4"
/// </summary>
/// <param name="node">目标节点</param>
/// <returns>节点路径</returns>
//private string GetNodeFullPath(TreeViewItem node)
//{
// if (node == null)
// return string.Empty;
// FlowDataDetails flowDataDetails = (FlowDataDetails)node.Tag;
// var parent = GetParentTreeViewItem(node);
// if (parent != null)
// {
// // 递归获取父节点的路径,并拼接当前节点的 Header
// return $"{GetNodeFullPath(parent)}.{flowDataDetails.Name}";
// }
// else
// {
// // 没有父节点,则说明这是根节点,直接返回 Header
// return "";
// }
//}
/// <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;
//}
/// <summary>
/// 根据成员类别配置右键菜单
/// </summary>
/// <param name="memberNode"></param>
/// <param name="member"></param>
/// <param name="contextMenu"></param>
/// <returns></returns>
//private bool ConfigureTreeItemMenu(TreeViewItem memberNode, MemberInfo member, out ContextMenu? contextMenu)
//{
// if (ConfigureTreeItemMenu(memberNode, member, out ContextMenu? contextMenu))
// {
// memberNode.ContextMenu = contextMenu; // 设置子项节点的事件
// }
// bool isChange = false;
// if (member is PropertyInfo property)
// {
// isChange = true;
// contextMenu = new ContextMenu();
// contextMenu.Items.Add(MainWindow.CreateMenuItem($"表达式", (s, e) =>
// {
// string fullPath = GetNodeFullPath(memberNode);
// string copyValue = /*"@Get " + */fullPath;
// ExpressionTextBox.Text = copyValue;
// // Clipboard.SetDataObject(copyValue);
// }));
// }
// 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;
// ExpressionTextBox.Text = copyValue;
// // Clipboard.SetDataObject(copyValue);
// }));
// }
// else
// {
// contextMenu = new ContextMenu();
// }
// return isChange;
//}
///// <summary>
///// 刷新按钮的点击事件
///// </summary>
//private void RefreshButton_Click(object sender, RoutedEventArgs e)
//{
// RefreshObjectTree();
//}
//private bool IsTimerRefres = false;
//private void TimerRefreshButton_Click(object sender, RoutedEventArgs e)
//{
// if (IsTimerRefres)
// {
// IsTimerRefres = false;
// TimerRefreshButton.Content = "定时刷新";
// }
// else
// {
// IsTimerRefres = true;
// TimerRefreshButton.Content = "取消刷新";
// _ = Task.Run(async () => {
// while (true)
// {
// if (IsTimerRefres)
// {
// Application.Current.Dispatcher.Invoke(() =>
// {
// RefreshObjectTree(); // 刷新UI
// });
// await Task.Delay(100);
// }
// else
// {
// break;
// }
// }
// IsTimerRefres = false;
// });
// }
//}

View File

@@ -0,0 +1,16 @@
<Window x:Class="Serein.Workbench.Themes.TypeViewerWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Serein.Workbench.Themes"
mc:Ignorable="d"
Topmost="True"
Title="TypeViewerWindow" Height="300" Width="300">
<Grid>
<Grid>
<TreeView x:Name="TypeTreeView"/>
</Grid>
</Grid>
</Window>

View File

@@ -0,0 +1,279 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
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.Shapes;
namespace Serein.Workbench.Themes
{
/// <summary>
/// TypeViewerWindow.xaml 的交互逻辑
/// </summary>
public partial class TypeViewerWindow : Window
{
public TypeViewerWindow()
{
InitializeComponent();
}
public Type Type { get; set; }
public void LoadTypeInformation()
{
if (Type == null)
return;
NodeFlowDataObjectDetails typeNodeDetails = new NodeFlowDataObjectDetails
{
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 NodeFlowDataObjectDetails typeNodeDetails)
{
AddMembersToTreeNode(item, typeNodeDetails.DataType);
}
}
}
/// <summary>
/// 添加属性节点
/// </summary>
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 = ConfigureTreeViewItem(member); // 生成类型节点的子项
if (ConfigureTreeItemMenu(memberNode,member, out ContextMenu? contextMenu))
{
memberNode.ContextMenu = contextMenu; // 设置子项节点的事件
}
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)
{
NodeFlowDataObjectDetails typeNodeDetails = new NodeFlowDataObjectDetails
{
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)
{
NodeFlowDataObjectDetails typeNodeDetails = new NodeFlowDataObjectDetails
{
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)
{
NodeFlowDataObjectDetails typeNodeDetails = new NodeFlowDataObjectDetails
{
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();
contextMenu.Items.Add(MainWindow.CreateMenuItem($"取值表达式", (s, e) =>
{
string fullPath = GetNodeFullPath(memberNode);
string copyValue = "@Get " + fullPath;
Clipboard.SetDataObject(copyValue);
}));
}
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;
NodeFlowDataObjectDetails typeNodeDetails = (NodeFlowDataObjectDetails)node.Tag;
var parent = GetParentTreeViewItem(node);
if (parent != null)
{
// 递归获取父节点的路径,并拼接当前节点的 Header
return $"{GetNodeFullPath(parent)}.{typeNodeDetails.Name}";
}
else
{
// 没有父节点,则说明这是根节点,直接返回 Header
return "";
// 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 not TreeViewItem)
{
parent = VisualTreeHelper.GetParent(parent);
}
return parent as TreeViewItem;
}
public class NodeFlowDataObjectDetails
{
/// <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,
IEnumerable,
Item,
}
}
}

View File

@@ -0,0 +1,30 @@
<Window x:Class="Serein.Workbench.Themes.WindowEnvRemoteLoginView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Serein.Workbench.Themes"
Title="登录远程环境" Height="150" Width="200">
<Grid Margin="0,10,0,0">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="地址" HorizontalAlignment="Center"></TextBlock>
<TextBox x:Name="TextBlockAddres" Grid.Row="0" Grid.Column="1" Text="127.0.0.1"></TextBox>
<TextBlock Grid.Row="1" Grid.Column="0" Text="端口" HorizontalAlignment="Center"></TextBlock>
<TextBox x:Name="TextBlockPort" Grid.Row="1" Grid.Column="1" Text="7525"></TextBox>
<TextBlock Grid.Row="2" Grid.Column="0" Text="密码" HorizontalAlignment="Center"></TextBlock>
<TextBox x:Name="TextBlockToken" Grid.Row="2" Grid.Column="1" Text="123456"></TextBox>
<StackPanel Grid.Row="3" Grid.Column="1" HorizontalAlignment="Center" Orientation="Horizontal" Margin="4">
<Button Content="测试连接" Margin="2" Click="ButtonTestConnect_Client"></Button>
<Button Content="登录环境" Margin="2" Click="ButtonTestLoginEnv_Client"></Button>
</StackPanel>
</Grid>
</Window>

View File

@@ -0,0 +1,70 @@
using Serein.Library;
using Serein.Library.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
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.Shapes;
namespace Serein.Workbench.Themes
{
/// <summary>
/// WindowDialogInput.xaml 的交互逻辑
/// </summary>
public partial class WindowEnvRemoteLoginView : Window
{
private Action<string, int, string> ConnectRemoteFlowEnv;
/// <summary>
/// 弹窗输入
/// </summary>
/// <param name="connectRemoteFlowEnv"></param>
public WindowEnvRemoteLoginView(Action<string, int, string> connectRemoteFlowEnv)
{
WindowStartupLocation = WindowStartupLocation.CenterScreen;
InitializeComponent();
ConnectRemoteFlowEnv = connectRemoteFlowEnv;
}
private void ButtonTestConnect_Client(object sender, RoutedEventArgs e)
{
var addres = this.TextBlockAddres.Text;
_ = int.TryParse(this.TextBlockPort.Text, out var port);
_ = Task.Run(() => {
bool success = false;
try
{
TcpClient tcpClient = new TcpClient();
var result = tcpClient.BeginConnect(addres, port, null, null);
success = result.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(3));
}
catch
{
success = false;
}
if (!success)
{
SereinEnv.WriteLine(InfoType.ERROR, $"无法连接远程:{addres}:{port}");
}
});
}
private void ButtonTestLoginEnv_Client(object sender, RoutedEventArgs e)
{
var addres = this.TextBlockAddres.Text;
_ = int.TryParse(this.TextBlockPort.Text, out var port);
var token = this.TextBlockToken.Text;
ConnectRemoteFlowEnv?.Invoke(addres, port, token);
}
}
}