mirror of
https://gitee.com/langsisi_admin/serein-flow
synced 2026-03-19 16:06:33 +08:00
修改了logwindows输出,避免高频输出时卡死。修改了流程运行上下文,使节点具备终止分支运行的能力。
This commit is contained in:
16
WorkBench.Remote/Themes/Condition/BoolConditionControl.xaml
Normal file
16
WorkBench.Remote/Themes/Condition/BoolConditionControl.xaml
Normal file
@@ -0,0 +1,16 @@
|
||||
<UserControl x:Class="DynamicDemo.Themes.Condition.BoolConditionControl"
|
||||
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:DynamicDemo.Themes.Condition"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<Grid>
|
||||
<ComboBox x:Name="ConditionComboBox"
|
||||
SelectedValue="{Binding Condition, Mode=TwoWay}">
|
||||
<ComboBoxItem Content="Is True" Tag="IsTrue" />
|
||||
<ComboBoxItem Content="Is False" Tag="IsFalse" />
|
||||
</ComboBox>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,28 @@
|
||||
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 DynamicDemo.Themes.Condition
|
||||
{
|
||||
/// <summary>
|
||||
/// BoolConditionControl.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class BoolConditionControl : UserControl
|
||||
{
|
||||
public BoolConditionControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
21
WorkBench.Remote/Themes/Condition/IntConditionControl.xaml
Normal file
21
WorkBench.Remote/Themes/Condition/IntConditionControl.xaml
Normal file
@@ -0,0 +1,21 @@
|
||||
<UserControl x:Class="DynamicDemo.Themes.Condition.IntConditionControl"
|
||||
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:DynamicDemo.Themes.Condition"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<Grid>
|
||||
<ComboBox x:Name="ConditionComboBox"
|
||||
SelectedValue="{Binding Condition, Mode=TwoWay}">
|
||||
<ComboBoxItem Content="Greater Than" Tag="GreaterThan" />
|
||||
<ComboBoxItem Content="Less Than" Tag="LessThan" />
|
||||
<ComboBoxItem Content="Equal To" Tag="EqualTo" />
|
||||
<ComboBoxItem Content="Between" Tag="Between" />
|
||||
<ComboBoxItem Content="Not Between" Tag="NotBetween" />
|
||||
<ComboBoxItem Content="Not In Range" Tag="NotInRange" />
|
||||
</ComboBox>
|
||||
<TextBox x:Name="ValueTextBox" Text="{Binding Value, Mode=TwoWay}" />
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,28 @@
|
||||
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 DynamicDemo.Themes.Condition
|
||||
{
|
||||
/// <summary>
|
||||
/// IntConditionControl.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class IntConditionControl : UserControl
|
||||
{
|
||||
public IntConditionControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
88
WorkBench.Remote/Themes/Condition/Model.cs
Normal file
88
WorkBench.Remote/Themes/Condition/Model.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DynamicDemo.Themes.Condition
|
||||
{
|
||||
//public class IntConditionNode : ConditionNode
|
||||
//{
|
||||
// public int Value { get; set; }
|
||||
// public int MinValue { get; set; }
|
||||
// public int MaxValue { get; set; }
|
||||
// public List<int> ExcludeValues { get; set; }
|
||||
|
||||
// public override bool Evaluate(object value)
|
||||
// {
|
||||
// if (value is int intValue)
|
||||
// {
|
||||
// switch (Condition)
|
||||
// {
|
||||
// case ConditionType.GreaterThan:
|
||||
// return intValue > Value;
|
||||
// case ConditionType.LessThan:
|
||||
// return intValue < Value;
|
||||
// case ConditionType.EqualTo:
|
||||
// return intValue == Value;
|
||||
// case ConditionType.Between:
|
||||
// return intValue >= MinValue && intValue <= MaxValue;
|
||||
// case ConditionType.NotBetween:
|
||||
// return intValue < MinValue || intValue > MaxValue;
|
||||
// case ConditionType.NotInRange:
|
||||
// return !ExcludeValues.Contains(intValue);
|
||||
// default:
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
//}
|
||||
|
||||
//public class BoolConditionNode : ConditionNode
|
||||
//{
|
||||
// public override bool Evaluate(object value)
|
||||
// {
|
||||
// if (value is bool boolValue)
|
||||
// {
|
||||
// switch (Condition)
|
||||
// {
|
||||
// case ConditionType.IsTrue:
|
||||
// return boolValue;
|
||||
// case ConditionType.IsFalse:
|
||||
// return !boolValue;
|
||||
// default:
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
//public class StringConditionNode : ConditionNode
|
||||
//{
|
||||
// public string Substring { get; set; }
|
||||
|
||||
// public override bool Evaluate(object value)
|
||||
// {
|
||||
// if (value is string stringValue)
|
||||
// {
|
||||
// switch (Condition)
|
||||
// {
|
||||
// case ConditionType.Contains:
|
||||
// return stringValue.Contains(Substring);
|
||||
// case ConditionType.DoesNotContain:
|
||||
// return !stringValue.Contains(Substring);
|
||||
// case ConditionType.IsNotEmpty:
|
||||
// return !string.IsNullOrEmpty(stringValue);
|
||||
// default:
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<UserControl x:Class="DynamicDemo.Themes.Condition.StringConditionControl"
|
||||
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:DynamicDemo.Themes.Condition"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<Grid>
|
||||
<ComboBox x:Name="ConditionComboBox"
|
||||
SelectedValue="{Binding Condition, Mode=TwoWay}">
|
||||
<ComboBoxItem Content="Contains" Tag="Contains" />
|
||||
<ComboBoxItem Content="Does Not Contain" Tag="DoesNotContain" />
|
||||
<ComboBoxItem Content="Is Not Empty" Tag="IsNotEmpty" />
|
||||
</ComboBox>
|
||||
<TextBox x:Name="SubstringTextBox" Text="{Binding Substring, Mode=TwoWay}" />
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,28 @@
|
||||
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 DynamicDemo.Themes.Condition
|
||||
{
|
||||
/// <summary>
|
||||
/// StringConditionControl.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class StringConditionControl : UserControl
|
||||
{
|
||||
public StringConditionControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
35
WorkBench.Remote/Themes/ConditionControl.xaml
Normal file
35
WorkBench.Remote/Themes/ConditionControl.xaml
Normal file
@@ -0,0 +1,35 @@
|
||||
<UserControl x:Class="DynamicDemo.Themes.ConditionControl"
|
||||
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:DynamicDemo.Themes"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<!--<ComboBox x:Name="ConditionTypeComboBox" SelectionChanged="ConditionTypeComboBox_SelectionChanged">
|
||||
<ComboBoxItem Content="GreaterThan" Tag="{x:Static local:ConditionType.GreaterThan}"/>
|
||||
<ComboBoxItem Content="LessThan" Tag="{x:Static local:ConditionType.LessThan}"/>
|
||||
<ComboBoxItem Content="EqualTo" Tag="{x:Static local:ConditionType.EqualTo}"/>
|
||||
<ComboBoxItem Content="InRange" Tag="{x:Static local:ConditionType.InRange}"/>
|
||||
<ComboBoxItem Content="NotInRange" Tag="{x:Static local:ConditionType.NotInRange}"/>
|
||||
<ComboBoxItem Content="NotInSpecificRange" Tag="{x:Static local:ConditionType.NotInSpecificRange}"/>
|
||||
<ComboBoxItem Content="IsTrue" Tag="{x:Static local:ConditionType.IsTrue}"/>
|
||||
<ComboBoxItem Content="IsFalse" Tag="{x:Static local:ConditionType.IsFalse}"/>
|
||||
<ComboBoxItem Content="Contains" Tag="{x:Static local:ConditionType.Contains}"/>
|
||||
<ComboBoxItem Content="DoesNotContain" Tag="{x:Static local:ConditionType.DoesNotContain}"/>
|
||||
<ComboBoxItem Content="IsNotEmpty" Tag="{x:Static local:ConditionType.IsNotEmpty}"/>
|
||||
</ComboBox>
|
||||
<TextBox x:Name="ValueTextBox" Visibility="Collapsed"/>
|
||||
<TextBox x:Name="Value2TextBox" Visibility="Collapsed"/>-->
|
||||
|
||||
<StackPanel Grid.Row="0" x:Name="ConditionsPanel" Orientation="Vertical" Height="400"/>
|
||||
<Button Grid.Row="1" Content="Add Condition" Click="OnAddConditionClicked" />
|
||||
<!-- 其他控件 -->
|
||||
</Grid>
|
||||
</UserControl>
|
||||
85
WorkBench.Remote/Themes/ConditionControl.xaml.cs
Normal file
85
WorkBench.Remote/Themes/ConditionControl.xaml.cs
Normal file
@@ -0,0 +1,85 @@
|
||||
using DynamicDemo.Themes.Condition;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace DynamicDemo.Themes
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// ConditionControl.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class ConditionControl : UserControl
|
||||
{
|
||||
public ConditionControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
//private void ConditionTypeComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
//{
|
||||
// var selectedType = (ConditionType)((ComboBoxItem)ConditionTypeComboBox.SelectedItem).Tag;
|
||||
// UpdateInputVisibility(selectedType);
|
||||
//}
|
||||
|
||||
//private void UpdateInputVisibility(ConditionType type)
|
||||
//{
|
||||
// ValueTextBox.Visibility = Visibility.Collapsed;
|
||||
// Value2TextBox.Visibility = Visibility.Collapsed;
|
||||
|
||||
// switch (type)
|
||||
// {
|
||||
// case ConditionType.GreaterThan:
|
||||
// case ConditionType.LessThan:
|
||||
// case ConditionType.EqualTo:
|
||||
// case ConditionType.Contains:
|
||||
// case ConditionType.DoesNotContain:
|
||||
// ValueTextBox.Visibility = Visibility.Visible;
|
||||
// break;
|
||||
// case ConditionType.InRange:
|
||||
// case ConditionType.NotInRange:
|
||||
// ValueTextBox.Visibility = Visibility.Visible;
|
||||
// Value2TextBox.Visibility = Visibility.Visible;
|
||||
// break;
|
||||
// case ConditionType.IsTrue:
|
||||
// case ConditionType.IsFalse:
|
||||
// case ConditionType.IsNotEmpty:
|
||||
// // No additional input needed
|
||||
// break;
|
||||
// case ConditionType.NotInSpecificRange:
|
||||
// // Handle specific range input, possibly with a different control
|
||||
// break;
|
||||
// }
|
||||
//}
|
||||
|
||||
private void OnAddConditionClicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// 示例:添加一个IntConditionNode
|
||||
var intConditionNode = new IntConditionNode { Condition = ConditionType.GreaterThan, Value = 10 };
|
||||
AddConditionNode(intConditionNode);
|
||||
}
|
||||
|
||||
public void AddConditionNode(ConditionNode node)
|
||||
{
|
||||
UserControl control = null;
|
||||
|
||||
if (node is IntConditionNode)
|
||||
{
|
||||
control = new IntConditionControl { DataContext = node };
|
||||
}
|
||||
else if (node is BoolConditionNode)
|
||||
{
|
||||
control = new BoolConditionControl { DataContext = node };
|
||||
}
|
||||
else if (node is StringConditionNode)
|
||||
{
|
||||
control = new StringConditionControl { DataContext = node };
|
||||
}
|
||||
|
||||
if (control != null)
|
||||
{
|
||||
ConditionsPanel.Children.Add(control);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
99
WorkBench.Remote/Themes/ConditionControlModel.cs
Normal file
99
WorkBench.Remote/Themes/ConditionControlModel.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
namespace DynamicDemo.Themes;
|
||||
|
||||
public enum ConditionType
|
||||
{
|
||||
GreaterThan,
|
||||
LessThan,
|
||||
EqualTo,
|
||||
Between,
|
||||
NotBetween,
|
||||
NotInRange,
|
||||
IsTrue,
|
||||
IsFalse,
|
||||
Contains,
|
||||
DoesNotContain,
|
||||
IsNotEmpty
|
||||
}
|
||||
|
||||
public abstract class ConditionNode
|
||||
{
|
||||
public ConditionType Condition { get; set; }
|
||||
public abstract bool Evaluate(object value);
|
||||
}
|
||||
|
||||
public class IntConditionNode : ConditionNode
|
||||
{
|
||||
public int Value { get; set; }
|
||||
public int MinValue { get; set; }
|
||||
public int MaxValue { get; set; }
|
||||
public List<int> ExcludeValues { get; set; }
|
||||
|
||||
public override bool Evaluate(object value)
|
||||
{
|
||||
if (value is int intValue)
|
||||
{
|
||||
switch (Condition)
|
||||
{
|
||||
case ConditionType.GreaterThan:
|
||||
return intValue > Value;
|
||||
case ConditionType.LessThan:
|
||||
return intValue < Value;
|
||||
case ConditionType.EqualTo:
|
||||
return intValue == Value;
|
||||
case ConditionType.Between:
|
||||
return intValue >= MinValue && intValue <= MaxValue;
|
||||
case ConditionType.NotBetween:
|
||||
return intValue < MinValue || intValue > MaxValue;
|
||||
case ConditionType.NotInRange:
|
||||
return !ExcludeValues.Contains(intValue);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public class BoolConditionNode : ConditionNode
|
||||
{
|
||||
public override bool Evaluate(object value)
|
||||
{
|
||||
if (value is bool boolValue)
|
||||
{
|
||||
switch (Condition)
|
||||
{
|
||||
case ConditionType.IsTrue:
|
||||
return boolValue;
|
||||
case ConditionType.IsFalse:
|
||||
return !boolValue;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public class StringConditionNode : ConditionNode
|
||||
{
|
||||
public string Substring { get; set; }
|
||||
|
||||
public override bool Evaluate(object value)
|
||||
{
|
||||
if (value is string stringValue)
|
||||
{
|
||||
switch (Condition)
|
||||
{
|
||||
case ConditionType.Contains:
|
||||
return stringValue.Contains(Substring);
|
||||
case ConditionType.DoesNotContain:
|
||||
return !stringValue.Contains(Substring);
|
||||
case ConditionType.IsNotEmpty:
|
||||
return !string.IsNullOrEmpty(stringValue);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
28
WorkBench.Remote/Themes/IOCObjectViewControl.xaml
Normal file
28
WorkBench.Remote/Themes/IOCObjectViewControl.xaml
Normal 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>
|
||||
120
WorkBench.Remote/Themes/IOCObjectViewControl.xaml.cs
Normal file
120
WorkBench.Remote/Themes/IOCObjectViewControl.xaml.cs
Normal file
@@ -0,0 +1,120 @@
|
||||
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,
|
||||
};
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
16
WorkBench.Remote/Themes/InputDialog.xaml
Normal file
16
WorkBench.Remote/Themes/InputDialog.xaml
Normal 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>
|
||||
42
WorkBench.Remote/Themes/InputDialog.xaml.cs
Normal file
42
WorkBench.Remote/Themes/InputDialog.xaml.cs
Normal 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(); // 关闭窗口
|
||||
}
|
||||
}
|
||||
}
|
||||
115
WorkBench.Remote/Themes/MethodDetailsControl.xaml
Normal file
115
WorkBench.Remote/Themes/MethodDetailsControl.xaml
Normal file
@@ -0,0 +1,115 @@
|
||||
<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:sys="clr-namespace:System;assembly=mscorlib">
|
||||
|
||||
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<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}}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<ContentControl Content="{Binding}">
|
||||
<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 Background="#E3FDFD">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="50"/>
|
||||
<ColumnDefinition Width="30"/>
|
||||
<ColumnDefinition Width="50"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0" Text="{Binding Index,StringFormat=agr{0}}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
<CheckBox Grid.Column="1" IsChecked="{Binding IsExplicitData, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" VerticalContentAlignment="Center"/>
|
||||
<TextBlock Grid.Column="2" MinWidth="50" Text="{Binding Name}" HorizontalAlignment="Left" VerticalAlignment="Center"/>
|
||||
<TextBlock Grid.Column="3" 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 Background="#E3FDFD">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="50"/>
|
||||
<ColumnDefinition Width="30"/>
|
||||
<ColumnDefinition Width="50"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0" Text="{Binding Index,StringFormat=agr{0}}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
<CheckBox Grid.Column="1" IsChecked="{Binding IsExplicitData, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" VerticalContentAlignment="Center"/>
|
||||
<TextBlock Grid.Column="2" MinWidth="50" Text="{Binding Name}" HorizontalAlignment="Left" VerticalAlignment="Center"/>
|
||||
<ComboBox Grid.Column="3"
|
||||
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" />
|
||||
<!--<Condition Binding="{Binding ExplicitTypeName}" Value="{x:Type sys:String}" />
|
||||
<Condition Binding="{Binding ExplicitTypeName}" Value="{x:Type sys:Double}" />-->
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter Property="ContentTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate>
|
||||
<Grid Background="#E3FDFD">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="50"/>
|
||||
<ColumnDefinition Width="30"/>
|
||||
<ColumnDefinition Width="50"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0" Text="{Binding Index,StringFormat=agr{0}}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
<CheckBox Grid.Column="1" IsChecked="{Binding IsExplicitData, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" VerticalContentAlignment="Center"/>
|
||||
<TextBlock Grid.Column="2" MinWidth="50" Text="{Binding Name}" HorizontalAlignment="Left" VerticalAlignment="Center"/>
|
||||
<TextBox Grid.Column="3" MinWidth="50" Text="{Binding DataValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</MultiDataTrigger>
|
||||
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ContentControl.Style>
|
||||
</ContentControl>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
</ResourceDictionary>
|
||||
64
WorkBench.Remote/Themes/MethodDetailsControl.xaml.cs
Normal file
64
WorkBench.Remote/Themes/MethodDetailsControl.xaml.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using Serein.Library.Entity;
|
||||
using Serein.NodeFlow;
|
||||
using System.Collections;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public partial class MethodDetailsControl : UserControl//,ItemsControl
|
||||
{
|
||||
static MethodDetailsControl()
|
||||
{
|
||||
DefaultStyleKeyProperty.OverrideMetadata(typeof(MethodDetailsControl), new FrameworkPropertyMetadata(typeof(MethodDetailsControl)));
|
||||
}
|
||||
|
||||
|
||||
public MethodDetails MethodDetails
|
||||
{
|
||||
get { return (MethodDetails)GetValue(MethodDetailsProperty); }
|
||||
set { SetValue(MethodDetailsProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty MethodDetailsProperty = DependencyProperty.Register("MethodDetails", typeof(MethodDetails),
|
||||
typeof(MethodDetailsControl), new PropertyMetadata(null, new PropertyChangedCallback(OnPropertyChange)));
|
||||
|
||||
static void OnPropertyChange(DependencyObject sender, DependencyPropertyChangedEventArgs args)
|
||||
{
|
||||
|
||||
var MethodDetails = (MethodDetails)args.NewValue;
|
||||
//MethodDetails.ExplicitDatas[0].
|
||||
}
|
||||
}
|
||||
}
|
||||
4
WorkBench.Remote/Themes/MultiConditionConverter.xaml
Normal file
4
WorkBench.Remote/Themes/MultiConditionConverter.xaml
Normal file
@@ -0,0 +1,4 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
|
||||
</ResourceDictionary>
|
||||
59
WorkBench.Remote/Themes/NodeTreeItemViewControl.xaml
Normal file
59
WorkBench.Remote/Themes/NodeTreeItemViewControl.xaml
Normal 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>
|
||||
289
WorkBench.Remote/Themes/NodeTreeItemViewControl.xaml.cs
Normal file
289
WorkBench.Remote/Themes/NodeTreeItemViewControl.xaml.cs
Normal file
@@ -0,0 +1,289 @@
|
||||
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, []},
|
||||
}
|
||||
};
|
||||
string? itemName = rootNodeModel.MethodDetails?.MethodTips;
|
||||
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?.MethodTips;
|
||||
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("从此节点执行", (s, e) =>
|
||||
{
|
||||
flowEnvironment.StartFlowInSelectNodeAsync(tmpNodeTreeModel.RootNode.Guid);
|
||||
}));
|
||||
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?.MethodTips;
|
||||
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, 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)
|
||||
// };
|
||||
//}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
47
WorkBench.Remote/Themes/NodeTreeViewControl.xaml
Normal file
47
WorkBench.Remote/Themes/NodeTreeViewControl.xaml
Normal 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>
|
||||
98
WorkBench.Remote/Themes/NodeTreeViewControl.xaml.cs
Normal file
98
WorkBench.Remote/Themes/NodeTreeViewControl.xaml.cs
Normal file
@@ -0,0 +1,98 @@
|
||||
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
|
||||
{
|
||||
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
|
||||
|
||||
}
|
||||
}
|
||||
31
WorkBench.Remote/Themes/ObjectViewerControl.xaml
Normal file
31
WorkBench.Remote/Themes/ObjectViewerControl.xaml
Normal 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>
|
||||
671
WorkBench.Remote/Themes/ObjectViewerControl.xaml.cs
Normal file
671
WorkBench.Remote/Themes/ObjectViewerControl.xaml.cs
Normal file
@@ -0,0 +1,671 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Serein.Library.Api;
|
||||
using Serein.NodeFlow.Base;
|
||||
using Serein.NodeFlow.Tool.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 void UpMonitorExpressionButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (FlowEnvironment is not null && FlowEnvironment.AddInterruptExpression(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;
|
||||
// });
|
||||
// }
|
||||
|
||||
//}
|
||||
|
||||
8
WorkBench.Remote/Themes/ObjectViewerControl1.xaml
Normal file
8
WorkBench.Remote/Themes/ObjectViewerControl1.xaml
Normal file
@@ -0,0 +1,8 @@
|
||||
<UserControl x:Class="RealTimeObjectViewer.ObjectViewerControl"
|
||||
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">
|
||||
<Grid>
|
||||
<TreeView x:Name="ObjectTreeView" />
|
||||
</Grid>
|
||||
</UserControl>
|
||||
146
WorkBench.Remote/Themes/ObjectViewerControl1.xaml.cs
Normal file
146
WorkBench.Remote/Themes/ObjectViewerControl1.xaml.cs
Normal file
@@ -0,0 +1,146 @@
|
||||
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>
|
||||
/// ObjectViewerWindow.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class ObjectViewerControl : UserControl
|
||||
{
|
||||
public ObjectViewerControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private object _objectInstance;
|
||||
private Action _closeCallback;
|
||||
|
||||
public void LoadObjectInformation(object obj,Action closeCallback)
|
||||
{
|
||||
if (obj == null || closeCallback == null)
|
||||
return;
|
||||
_closeCallback = closeCallback;
|
||||
_objectInstance = obj;
|
||||
var objectType = obj.GetType();
|
||||
var rootNode = new TreeViewItem { Header = objectType.Name, Tag = obj };
|
||||
|
||||
// 添加占位符节点
|
||||
AddPlaceholderNode(rootNode);
|
||||
ObjectTreeView.Items.Clear();
|
||||
ObjectTreeView.Items.Add(rootNode);
|
||||
|
||||
// 监听展开事件
|
||||
rootNode.Expanded += TreeViewItem_Expanded;
|
||||
}
|
||||
|
||||
private void AddPlaceholderNode(TreeViewItem node)
|
||||
{
|
||||
node.Items.Add(new TreeViewItem { Header = "Loading..." });
|
||||
}
|
||||
|
||||
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 object obj)
|
||||
{
|
||||
var objectType = obj.GetType();
|
||||
AddMembersToTreeNode(item, obj, objectType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddMembersToTreeNode(TreeViewItem node, object obj, Type type)
|
||||
{
|
||||
// 获取属性和字段
|
||||
var members = type.GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
|
||||
foreach (var member in members)
|
||||
{
|
||||
TreeViewItem memberNode = ConfigureTreeViewItem(obj, member);
|
||||
node.Items.Add(memberNode);
|
||||
}
|
||||
}
|
||||
|
||||
private TreeViewItem ConfigureTreeViewItem(object obj, MemberInfo member)
|
||||
{
|
||||
TreeViewItem memberNode = new TreeViewItem { Header = member.Name };
|
||||
|
||||
if (member is PropertyInfo property)
|
||||
{
|
||||
string propertyValue = GetPropertyValue(obj, property);
|
||||
memberNode.Header = $"{property.Name} : {property.PropertyType.Name} = {propertyValue}";
|
||||
|
||||
if (!property.PropertyType.IsPrimitive && property.PropertyType != typeof(string))
|
||||
{
|
||||
AddPlaceholderNode(memberNode);
|
||||
memberNode.Expanded += TreeViewItem_Expanded;
|
||||
}
|
||||
}
|
||||
else if (member is FieldInfo field)
|
||||
{
|
||||
string fieldValue = GetFieldValue(obj, field);
|
||||
memberNode.Header = $"{field.Name} : {field.FieldType.Name} = {fieldValue}";
|
||||
|
||||
if (!field.FieldType.IsPrimitive && field.FieldType != typeof(string))
|
||||
{
|
||||
AddPlaceholderNode(memberNode);
|
||||
memberNode.Expanded += TreeViewItem_Expanded;
|
||||
}
|
||||
}
|
||||
|
||||
return memberNode;
|
||||
}
|
||||
|
||||
private string GetPropertyValue(object obj, PropertyInfo property)
|
||||
{
|
||||
try
|
||||
{
|
||||
var value = property.GetValue(obj);
|
||||
return value?.ToString() ?? "null";
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "Error";
|
||||
}
|
||||
}
|
||||
|
||||
private string GetFieldValue(object obj, FieldInfo field)
|
||||
{
|
||||
try
|
||||
{
|
||||
var value = field.GetValue(obj);
|
||||
return value?.ToString() ?? "null";
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "Error";
|
||||
}
|
||||
}
|
||||
|
||||
private void Window_Closed(object sender, EventArgs e)
|
||||
{
|
||||
}
|
||||
|
||||
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
|
||||
{
|
||||
|
||||
_closeCallback?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
16
WorkBench.Remote/Themes/TypeViewerWindow.xaml
Normal file
16
WorkBench.Remote/Themes/TypeViewerWindow.xaml
Normal 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>
|
||||
279
WorkBench.Remote/Themes/TypeViewerWindow.xaml.cs
Normal file
279
WorkBench.Remote/Themes/TypeViewerWindow.xaml.cs
Normal 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,
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user