alielie
2025-07-04 0940f31b7eba1cf6b6a8c1fca72d97cf686e79f2
优化插件,完善TreeListView
46个文件已修改
10个文件已添加
2777 ■■■■■ 已修改文件
HMI.Core/HMI.Core.csproj 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Core/Util/BindingProxy.cs 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Core/Util/LangUtil.cs 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.DataViewer/DataViewer.xaml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.LogViewer/LogViewer.xaml 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.MenuManage/MenuManage.xaml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.MenuManage/RoleManage.xaml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.MenuManage/UserManage.xaml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.ParameterManage/ParameterManage.xaml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.ParameterManage/ParameterSetting.xaml 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.ParameterManage/TestItem.xaml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/Controls/MultiCombox.xaml 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/Controls/MultiCombox.xaml.cs 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/Controls/treegrid/Controls/TreeDataGrid.cs 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/Controls/treegrid/Controls/TreeItemData.cs 280 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/Controls/treegrid/Controls/TreeListView.cs 86 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/Controls/treegrid/Themes/TreeItemContentControl.xaml 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/Controls/treegrid/Themes/TreeListView.xaml 140 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/Converter/IndentConverter.cs 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/HMI.UI.csproj 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/Style/PanuonDataGrid.xaml 88 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UI/Style/PanuonResource.xaml 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.UnitTest/MainWindow.xaml.cs 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
OPCClient/OPCClient.csproj 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.DataBoard/Plugin.DataBoard.csproj 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.JarUI/Plugin.JarUI.csproj 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.SeqEditor/Entity/EditablePO/IEPO.cs 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.SeqEditor/Entity/SeqUI.cs 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.SeqEditor/Entity/StepUI.cs 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.SeqEditor/Plugin.SeqEditor.csproj 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.SeqEditor/SeqEditor.xaml 375 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.SeqEditor/SeqEditor.xaml.cs 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.SeqEditor/Util/ItemData.cs 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.SeqEditor/Util/Step2EPO.cs 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.SeqEditor/ViewModel/SeqEditorViewModel.cs 373 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.SeqEditor/ViewModel/StepDropHandler.cs 139 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.SeqEditor/ViewModel/TreeListViewHandler.cs 119 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/DataTable.xaml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/DataTable.xaml.cs 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TestResult/TestResultView.xaml.cs 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TsExecution/Plugin.TsExecution.csproj 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TsExecution/Scanner.xaml 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TsExecution/Scanner.xaml.cs 209 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TsExecution/ScannerManager.cs 101 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TsExecution/SingleControl.xaml 84 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TsExecution/SingleControl.xaml.cs 91 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Plugin.TsExecution/TsExecution.xaml.cs 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SimpleUtil/HMI.LogUtil/HMI.LogUtil.csproj 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SimpleUtil/HMI.Util.Base/HMI.Util.Base.csproj 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
VisionBuilder.Run/VisionBuilder.Run.csproj 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/App.xaml 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/Lang/en-US/SeqEditor.json 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/Lang/en-US/TestResult.json 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/Lang/zh-CN/SeqEditor.json 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/Lang/zh-CN/TestResult.json 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WPFProject/WPFProject.sln 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HMI.Core/HMI.Core.csproj
@@ -110,6 +110,7 @@
    <Compile Include="Properties\AssemblyInfo.cs" />
    <Compile Include="Setting\SystemSetting.cs" />
    <Compile Include="Util\BeanUtil.cs" />
    <Compile Include="Util\BindingProxy.cs" />
    <Compile Include="Util\DllHelper.cs" />
    <Compile Include="Util\HexUtil.cs" />
    <Compile Include="Util\JsonUtil.cs" />
HMI.Core/Util/BindingProxy.cs
New file
@@ -0,0 +1,22 @@
using System.Windows;
namespace HMI.Core.Util
{
    public class BindingProxy : Freezable
    {
        protected override Freezable CreateInstanceCore()
        {
            return new BindingProxy();
        }
        public object Data
        {
            get { return (object)GetValue(DataProperty); }
            set { SetValue(DataProperty, value); }
        }
        public static readonly DependencyProperty DataProperty =
            DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy),
                                        new UIPropertyMetadata(null));
    }
}
HMI.Core/Util/LangUtil.cs
@@ -63,8 +63,9 @@
                LoadAllDataByXaml();
                LoadLang();
            }
            finally {
            finally
            {
            }
        }
HMI.DataViewer/DataViewer.xaml
@@ -13,6 +13,13 @@
             Background="{StaticResource WorkspaceBackground}"
             Loaded="PluginControl_Loaded"
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="/HMI.UI;component/Style/PanuonDataGrid.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </UserControl.Resources>
    <Border Style="{StaticResource CardBoarder}">
        <Grid dp:GridHelper.Space="10">
            <Grid.RowDefinitions>
HMI.LogViewer/LogViewer.xaml
@@ -13,7 +13,13 @@
             Loaded="UserControl_Loaded"
             Background="{StaticResource WorkspaceBackground}"
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="/HMI.UI;component/Style/PanuonDataGrid.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </UserControl.Resources>
    <Border Style="{StaticResource CardBoarder}">
        <Grid dp:GridHelper.Space="10" Cursor="">
            <Grid.RowDefinitions>
HMI.MenuManage/MenuManage.xaml
@@ -15,6 +15,13 @@
             Loaded="DataGrid_Loaded"
             Background="{StaticResource WorkspaceBackground}"
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="/HMI.UI;component/Style/PanuonDataGrid.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </UserControl.Resources>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="8*"/>
HMI.MenuManage/RoleManage.xaml
@@ -13,6 +13,13 @@
             Padding="10"
             Background="#f0f2f5"
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="/HMI.UI;component/Style/PanuonDataGrid.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </UserControl.Resources>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="6*"/>
HMI.MenuManage/UserManage.xaml
@@ -12,6 +12,13 @@
             Background="{StaticResource WorkspaceBackground}"
             Padding="10"
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="/HMI.UI;component/Style/PanuonDataGrid.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </UserControl.Resources>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="6*"/>
HMI.ParameterManage/ParameterManage.xaml
@@ -15,6 +15,13 @@
             Loaded="UserControl_Loaded"
             Background="{StaticResource WorkspaceBackground}"
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="/HMI.UI;component/Style/PanuonDataGrid.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </UserControl.Resources>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="3*"/>
HMI.ParameterManage/ParameterSetting.xaml
@@ -14,7 +14,13 @@
             Loaded="UserControl_Loaded"
             Background="{StaticResource WorkspaceBackground}"
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="/HMI.UI;component/Style/PanuonDataGrid.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </UserControl.Resources>
    <Border Style="{StaticResource CardBoarder}">
        <Grid dp:GridHelper.Space="10" Cursor="">
            <Grid.RowDefinitions>
HMI.ParameterManage/TestItem.xaml
@@ -13,6 +13,13 @@
             Background="{StaticResource WorkspaceBackground}"
             Loaded="PluginControl_Loaded"
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="/HMI.UI;component/Style/PanuonDataGrid.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </UserControl.Resources>
    <Border Style="{StaticResource CardBoarder}">
        <Grid dp:GridHelper.Space="10">
            <Grid.RowDefinitions>
HMI.UI/Controls/MultiCombox.xaml
@@ -1,10 +1,55 @@
<UserControl x:Class="HMI.UI.Controls.MultiCombox"
             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:HMI.UI.Controls"
             mc:Ignorable="d"
             d:DesignHeight="450" d:DesignWidth="800">
    <ComboBox ></ComboBox>
</UserControl>
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:HMI.UI">
    <Style TargetType="local:MultiComboBox" BasedOn="{StaticResource FormComboxStyle}">
        <Setter Property="ItemTemplate">
            <Setter.Value>
                <DataTemplate DataType="util:ItemData">
                    <StackPanel Orientation="Horizontal">
                        <CheckBox IsChecked="{Binding IsChecked,UpdateSourceTrigger=PropertyChanged}">
                            <!--<CheckBox.Style>
                                <Style TargetType="CheckBox">
                                    <Setter Property="Background" Value="White" />
                                    <Setter Property="BorderBrush" Value="#FF262E34"/>
                                    <Setter Property="Foreground" Value="#FF262E34"/>
                                    <Setter Property="BorderThickness" Value="1"/>
                                    <Setter Property="Template">
                                        <Setter.Value>
                                            <ControlTemplate TargetType="{x:Type CheckBox}">
                                                <StackPanel Orientation="Horizontal" HorizontalAlignment="Stretch" >
                                                    <Border BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}" Width="15" Height="15">
                                                        --><!--                                   your color here --><!--
                                                        <Path Stretch="Uniform" Width="15" Height="10" Fill="Black" Name="eliCheck" Data="F1 M 9.97498,1.22334L 4.6983,9.09834L 4.52164,9.09834L 0,5.19331L 1.27664,3.52165L 4.255,6.08833L 8.33331,1.52588e-005L 9.97498,1.22334 Z " Visibility="Collapsed"/>
                                                    </Border>
                                                    <TextBlock Margin="5,0,0,0"  VerticalAlignment="Center" Foreground="{TemplateBinding Foreground}" Text="{TemplateBinding Content}"></TextBlock>
                                                </StackPanel>
                                                <ControlTemplate.Triggers>
                                                    <Trigger Property="IsMouseOver" Value="True">
                                                        <Setter Property="Background" Value="LightGray" />
                                                    </Trigger>
                                                    <Trigger Property="IsPressed" Value="True">
                                                        <Setter Property="Background" Value="#FF9C9E9F" />
                                                    </Trigger>
                                                    --><!--<Trigger Property="IsEnabled" Value="False">
                                                        <Setter Property="Background" Value="LightGray" />
                                                        <Setter Property="Foreground" Value="Gray" />
                                                        <Setter Property="BorderBrush" Value="Gray"/>
                                                        <Setter TargetName="eliCheck" Property="Opacity" Value="0.5" />
                                                    </Trigger>--><!--
                                                    <Trigger Property="IsChecked" Value="True">
                                                        <Setter TargetName="eliCheck" Property="Visibility" Value="Visible"></Setter>
                                                    </Trigger>
                                                </ControlTemplate.Triggers>
                                            </ControlTemplate>
                                        </Setter.Value>
                                    </Setter>
                                </Style>
                            </CheckBox.Style>-->
                        </CheckBox>
                        <TextBlock Text="{Binding DisplayName}" />
                    </StackPanel>
                </DataTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>
HMI.UI/Controls/MultiCombox.xaml.cs
@@ -1,5 +1,4 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
@@ -53,6 +52,11 @@
        private void MultiComboBoxItem_PreviewMouseUp(object sender, MouseButtonEventArgs e)
        {
            if (e.OriginalSource is CheckBox)
            {
                // 从CheckBox点击传过来的事件要拉回焦点,不然会卡在Popup层当前的Item上
                this.Focus();
            }
            if (DataContext is MultiComboBoxData data)
            {
                data.IsChecked = !data.IsChecked;
@@ -64,17 +68,17 @@
        {
            if (e.OldValue != null && e.OldValue is MultiComboBoxData oldData)
            {
                oldData.IsCheckedChanged -= DataIsExpandedChange;
                oldData.IsCheckedChanged -= DataIsCheckedChangedChange;
            }
            if (DataContext != null && DataContext is MultiComboBoxData newData)
            {
                newData.IsCheckedChanged += DataIsExpandedChange;
                newData.IsCheckedChanged += DataIsCheckedChangedChange;
            }
        }
        private void DataIsExpandedChange(object sender, bool IsExpanded)
        private void DataIsCheckedChangedChange(object sender, bool isChecked)
        {
            parent.Item_IsCheckedChanged((MultiComboBoxData)sender, this, IsExpanded);
            parent.Item_IsCheckedChanged((MultiComboBoxData)sender, this, isChecked);
        }
    }
@@ -151,6 +155,8 @@
    /// </summary>
    public partial class MultiComboBox : ComboBox
    {
        public static readonly string[] splits = new string[] { ", " };
        protected override DependencyObject GetContainerForItemOverride()
        {
            var item = new MultiComboBoxItem(this);
@@ -182,8 +188,10 @@
        {
            var selectedValues = SelectedValues?.ToString() ?? "";
            var showNames = this.Text?.ToString() ?? "";
            List<string> values = string.IsNullOrEmpty(selectedValues) ? new List<string>() : selectedValues.Split(',').ToList();
            List<string> names = string.IsNullOrEmpty(showNames) ? new List<string>() : showNames.Split(',').ToList();
            List<string> values = string.IsNullOrEmpty(selectedValues) ? new List<string>() :
                selectedValues.Split(splits, StringSplitOptions.RemoveEmptyEntries).ToList();
            List<string> names = string.IsNullOrEmpty(showNames) ? new List<string>() :
                showNames.Split(splits, StringSplitOptions.RemoveEmptyEntries).ToList();
            if (isChecked)
            {
                values.Add(data.DataValue);
@@ -194,8 +202,8 @@
                values.Remove(data.DataValue);
                names.Remove(data.DisplayName);
            }
            Text = string.Join(",", names);
            SelectedValues = string.Join(",", values);
            Text = string.Join(", ", names);
            SelectedValues = string.Join(", ", values);
        }
    }
}
HMI.UI/Controls/treegrid/Controls/TreeDataGrid.cs
@@ -19,10 +19,10 @@
        static TreeDataGrid()
        {
            //ScrollViewer.HorizontalScrollBarVisibilityProperty
            //    .OverrideMetadata(typeof(TreeDataGrid), new FrameworkPropertyMetadata(ScrollBarVisibility.Disabled));
            //ScrollViewer.VerticalScrollBarVisibilityProperty
            //    .OverrideMetadata(typeof(TreeDataGrid), new FrameworkPropertyMetadata(ScrollBarVisibility.Disabled));
            ScrollViewer.HorizontalScrollBarVisibilityProperty
                .OverrideMetadata(typeof(TreeDataGrid), new FrameworkPropertyMetadata(ScrollBarVisibility.Disabled));
            ScrollViewer.VerticalScrollBarVisibilityProperty
                .OverrideMetadata(typeof(TreeDataGrid), new FrameworkPropertyMetadata(ScrollBarVisibility.Disabled));
        }
        protected override DependencyObject GetContainerForItemOverride()
@@ -50,10 +50,10 @@
            {
                oldTreeData.IsExpandedChange -= DataIsExpandedChange;
                oldTreeData.VisibleChange -= DataVisiableChange;
            }
            if (DataContext != null && DataContext is TreeItemData treeData)
            {
                Visibility = treeData.IsVisible ? Visibility.Visible : Visibility.Collapsed;
                treeData.IsExpandedChange += DataIsExpandedChange;
                treeData.VisibleChange += DataVisiableChange;
            }
HMI.UI/Controls/treegrid/Controls/TreeItemData.cs
@@ -4,8 +4,10 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Windows.Data;
namespace HMI.UI
@@ -32,10 +34,10 @@
            get => _IsExpanded;
            set
            {
                if (_IsExpanded == value)
                {
                    return;
                }
                //if (_IsExpanded == value)
                //{
                //    return;
                //}
                _IsExpanded = value;
                Set("IsExpanded");
                SetItemsVisible(value);
@@ -43,6 +45,84 @@
            }
        }
        #endregion
        #region 属性 IsVisible
        private bool _IsVisible = true;
        public bool IsVisible
        {
            get
            {
                return _IsVisible;
            }
            set
            {
                if (_IsVisible == value)
                {
                    return;
                }
                _IsVisible = value;
                Set("IsVisible");
                SetItemsVisible(value);
                VisibleChange?.Invoke(this, value);
            }
        }
        /// <summary>
        /// 用于展开的可见
        /// </summary>
        /// <param name="visible"></param>
        private void SetItemsVisible(bool visible)
        {
            if (IsExpanded == false || visible == false)
            {
                Items.ForEach(p => p.IsVisible = false);
            }
            else
            {
                Items.ForEach(p => p.IsVisible = true);
            }
        }
        /// <summary>
        /// 设置筛选是否可见,比如筛选name1,则包含name1的数据和其父节点都可见
        /// </summary>
        public bool SelectVisible
        {
            get
            {
                return _IsVisible;
            }
            set
            {
                if (_IsVisible == value)
                {
                    return;
                }
                _IsVisible = value;
                Set("IsVisible");
                SetItemsVisible(value);
            }
        }
        /// <summary>
        /// 用于筛选的可见
        /// </summary>
        /// <param name="visible"></param>
        private void SetSelectVisible(bool visible)
        {
            if (visible)
            {
                ParentItem.SelectVisible = visible;
            }
            Items.ForEach(p => p.IsVisible = visible);
        }
        #endregion
        public void NotifyUI()
        {
            IsExpanded = _IsExpanded;
        }
        #region 属性 Level
        private int _Level = 0;
@@ -107,6 +187,18 @@
            }
        }
        #endregion
        /// <summary>
        /// 所有子项数量
        /// </summary>
        private int allSubCount;
        public int AllSubCount
        {
            get { return allSubCount; }
            set { allSubCount = value; }
        }
        #region 属性 ParentItem
        private TreeItemData _ParentItem = null;
@@ -196,79 +288,6 @@
                Set("IconType");
            }
        }
        #endregion
        #region 属性 IsVisible
        private bool _IsVisible = true;
        public bool IsVisible
        {
            get
            {
                return _IsVisible;
            }
            set
            {
                if (_IsVisible == value)
                {
                    return;
                }
                _IsVisible = value;
                Set("IsVisible");
                SetItemsVisible(value);
                VisibleChange?.Invoke(this, value);
            }
        }
        /// <summary>
        /// 用于展开的可见
        /// </summary>
        /// <param name="visible"></param>
        private void SetItemsVisible(bool visible)
        {
            if (IsExpanded == false || visible == false)
            {
                Items.ForEach(p => p.IsVisible = false);
            }
            else
            {
                Items.ForEach(p => p.IsVisible = true);
            }
        }
        /// <summary>
        /// 设置筛选是否可见,比如筛选name1,则包含name1的数据和其父节点都可见
        /// </summary>
        public bool SelectVisible
        {
            get
            {
                return _IsVisible;
            }
            set
            {
                if (_IsVisible == value)
                {
                    return;
                }
                _IsVisible = value;
                Set("IsVisible");
                SetItemsVisible(value);
            }
        }
        /// <summary>
        /// 用于筛选的可见
        /// </summary>
        /// <param name="visible"></param>
        private void SetSelectVisible(bool visible)
        {
            if (visible)
            {
                ParentItem.SelectVisible = visible;
            }
            Items.ForEach(p => p.IsVisible = visible);
        }
        #endregion
        int GetIconType()
@@ -310,6 +329,9 @@
        string _ItemsPath = "";
        List<TreeItemData> _RootItems = new List<TreeItemData>();
        List<TreeItemData> levelOneItems = new List<TreeItemData>();
        private PropertyInfo subDataProperty;
        #region 属性 UseTree
        private bool _UseTree = true;
@@ -324,6 +346,12 @@
                _UseTree = value;
            }
        }
        public List<TreeItemData> RootItems
        {
            get => _RootItems;
        }
        #endregion
        public TreeItemDataCollection()
@@ -341,15 +369,68 @@
            }
            UseTree = useTree;
            ResetItems();
            //subDataProperty = typeof(T).GetProperty(itemsPath);
        }
        protected override void MoveItem(int oldIndex, int newIndex)
        {
            CheckReentrancy();
            TreeItemData source = base[oldIndex];
            TreeItemData target = base[newIndex];
            newIndex += target.AllSubCount;
            if (newIndex == oldIndex - 1)
            {
                return;
            }
            target = base[newIndex];
            int times = source.AllSubCount;
            if (oldIndex < newIndex)
            {
                base.RemoveItem(oldIndex);
                base.InsertItem(newIndex, source);
                OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
                /// 从上往下拉
                for (int i = 0; i < times; i++)
                {
                    CheckReentrancy();
                    source = base[oldIndex];
                    target = base[newIndex];
                    base.RemoveItem(oldIndex);
                    base.InsertItem(newIndex, source);
                    OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
                }
            }
            else if (oldIndex > newIndex)
            {
                /// 从下往上拉
                base.RemoveItem(oldIndex);
                base.InsertItem(++newIndex, source);
                OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
                //MessageBox.Show($"source: {source.Data} {oldIndex}; target: {target.Data} {newIndex}");
                for (int i = 0; i < times; i++)
                {
                    oldIndex++;
                    newIndex++;
                    CheckReentrancy();
                    source = base[oldIndex];
                    target = base[newIndex];
                    //MessageBox.Show($"source: {source.Data} {oldIndex}; target: {target.Data} {newIndex}");
                    base.RemoveItem(oldIndex);
                    base.InsertItem(newIndex, source);
                    OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
                }
            }
        }
        private void List_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            ResetItems();
            //ResetItems();
        }
        private void ResetItems()
        public void ResetItems()
        {
            CheckReentrancy();
            if (_SourceItems is null)
            {
                ClearAllItems();
@@ -359,7 +440,8 @@
            var items = new List<TreeItemData>();
            _RootItems.Clear();
            var list = this.Items.ToDictionary(l => l.Data);
            Dictionary<dynamic, TreeItemData> list = this.Items.
                ToLookup(t => t.Data, t => t).ToDictionary(t => t.Key, t => t.First());
            foreach (var item in _SourceItems)
            {
                if (list.TryGetValue(item, out var treeItem))
@@ -375,8 +457,9 @@
                treeItem.Level = 0;
                items.Add(treeItem);
                _RootItems.Add(treeItem);
                foreach (var sitem in GetSubItems(treeItem, _ItemsPath, list).ToList())
                var subItems = GetSubItems(treeItem, _ItemsPath, list);
                treeItem.AllSubCount = subItems.Count();
                foreach (var sitem in subItems)
                {
                    items.Add(sitem);
                }
@@ -385,17 +468,20 @@
            ResetSubItems(_RootItems);
            this.Items.Clear();
            if (UseTree)
            {
                _RootItems.ForEach(t => this.Items.Add(t));
            }
            else
            {
                items.ForEach(t => this.Items.Add(t));
            }
            items.ForEach(t => this.Items.Add(t));
            //if (UseTree)
            //{
            //    _RootItems.ForEach(t => this.Items.Add(t));
            //}
            //else
            //{
            //    items.ForEach(t => this.Items.Add(t));
            //}
            OnPropertyChanged(new PropertyChangedEventArgs("Count"));
            OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
        }
        private void ResetSubItems(List<TreeItemData> items)
        public static void ResetSubItems(IList<TreeItemData> items)
        {
            for (int i = 0; i < items.Count; i++)
            {
@@ -410,6 +496,13 @@
            var data = treeItem.Data;
            var p = data.GetType().GetProperty(itemsPath);
            var pvalue = p.GetValue(data, null);
            if (pvalue is INotifyCollectionChanged inc)
            {
                //inc.CollectionChanged -= List_CollectionChanged;
                //inc.CollectionChanged += List_CollectionChanged;
            }
            treeItem.Items.Clear();
            if (pvalue is IEnumerable subItems)
            {
@@ -436,8 +529,9 @@
                    treeItem.Items.Add(subitem);
                    yield return subitem;
                    foreach (var sitem in GetSubItems(subitem, itemsPath, list))
                    var subSub = GetSubItems(subitem, itemsPath, list);
                    subitem.AllSubCount = subSub.Count();
                    foreach (var sitem in subSub)
                    {
                        yield return sitem;
                    }
HMI.UI/Controls/treegrid/Controls/TreeListView.cs
New file
@@ -0,0 +1,86 @@
using System.Windows;
using System.Windows.Controls;
namespace HMI.UI
{
    public class TreeListView : TreeView
    {
        public static readonly DependencyProperty ColumnsProperty =
            DependencyProperty.Register("Columns", typeof(GridViewColumnCollection), typeof(TreeListView),
                new PropertyMetadata(new GridViewColumnCollection()));
        public GridViewColumnCollection Columns
        {
            get { return (GridViewColumnCollection)GetValue(ColumnsProperty); }
            set { SetValue(ColumnsProperty, value); }
        }
        public static readonly DependencyProperty ColumnHeaderContainerStyleProperty =
            DependencyProperty.Register("ColumnHeaderContainerStyle", typeof(Style), typeof(TreeListView),
                new PropertyMetadata(null));
        public Style ColumnHeaderContainerStyle
        {
            get { return (Style)GetValue(ColumnHeaderContainerStyleProperty); }
            set { SetValue(ColumnHeaderContainerStyleProperty, value); }
        }
        static TreeListView()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(TreeListView),
                new FrameworkPropertyMetadata(typeof(TreeListView)));
        }
        public TreeListView()
        {
            Columns = new GridViewColumnCollection();
        }
        protected override DependencyObject GetContainerForItemOverride()
        {
            return new TreeListViewItem(0, this);
        }
    }
    public class TreeListViewItem : TreeViewItem
    {
        public static readonly DependencyProperty ColumnsProperty =
            DependencyProperty.Register("Columns", typeof(GridViewColumnCollection), typeof(TreeListViewItem),
                new PropertyMetadata(null));
        public int level { get; private set; }
        private TreeListView root;
        public TreeListViewItem(int level, TreeListView root)
        {
            this.level = level;
            this.root = root;
            this.Loaded += TreeListViewItem_Loaded;
        }
        public GridViewColumnCollection Columns
        {
            get { return (GridViewColumnCollection)GetValue(ColumnsProperty); }
            set { SetValue(ColumnsProperty, value); }
        }
        static TreeListViewItem()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(TreeListViewItem),
                new FrameworkPropertyMetadata(typeof(TreeListViewItem)));
        }
        private void TreeListViewItem_Loaded(object sender, RoutedEventArgs e)
        {
            if (root is TreeListView treeListView)
            {
                Columns = treeListView.Columns;
            }
        }
        protected override DependencyObject GetContainerForItemOverride()
        {
            return new TreeListViewItem(level + 1, root);
        }
    }
}
HMI.UI/Controls/treegrid/Themes/TreeItemContentControl.xaml
@@ -145,14 +145,8 @@
                                        <DataTemplate>
                                            <Grid x:Name="lineGrid" Width="15" Margin="0">
                                                <Line x:Name="line" X1="7.5" Y1="0" X2="7.5" Y2="{Binding ElementName=lineGrid,Path=ActualHeight}" StrokeThickness="1"
                                                      Stroke="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=DataGridRow},Path=Foreground}" StrokeDashArray="1,1"/>
                  Stroke="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=local:TreeItemContentControl},Path=IconLineStroke}" StrokeDashArray="1,1"/>
                                            </Grid>
                                             <!--这里放开有个问题,会让只有一个根节点的树列表显示错误-->
                                            <!--<DataTemplate.Triggers>
                                                <DataTrigger Binding="{Binding IsLastItem}" Value="true">
                                                    <Setter Property="Visibility" Value="Collapsed"/>
                                                </DataTrigger>
                                            </DataTemplate.Triggers>-->
                                        </DataTemplate>
                                    </ListBox.ItemTemplate>
                                </ListBox>
HMI.UI/Controls/treegrid/Themes/TreeListView.xaml
New file
@@ -0,0 +1,140 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:controls="clr-namespace:HMI.UI">
    <Style TargetType="{x:Type controls:TreeListView}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type controls:TreeListView}">
                    <Border BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}"
                        Background="{TemplateBinding Background}">
                        <Grid>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto"/>
                                <RowDefinition/>
                            </Grid.RowDefinitions>
                            <GridViewHeaderRowPresenter Grid.Row="0"
                                                        Columns="{TemplateBinding Columns}"/>
                            <ScrollViewer Grid.Row="1" Padding="{TemplateBinding Padding}" VerticalScrollBarVisibility="Auto">
                                <ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                            </ScrollViewer>
                        </Grid>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    <!--<Style x:Key="ExpandCollapseToggleStyle" TargetType="ToggleButton">
        <Setter Property="Width" Value="10"/>
        <Setter Property="Height" Value="10"/>
        <Setter Property="Margin" Value="0"/>
        <Setter Property="Padding" Value="0"/>
        <Setter Property="ToggleButton.RenderTransformOrigin" Value="0.5,0.5"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ToggleButton">
                    <Path x:Name="ExpandPath" Margin="0" Fill="Black" Stroke="Black"
                      Data="M 4 0 L 10 5 L 4 10 Z"/>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsChecked" Value="True">
                            <Setter Property="ToggleButton.RenderTransform">
                                <Setter.Value>
                                    <RotateTransform Angle="90"/>
                                </Setter.Value>
                            </Setter>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>-->
    <Style x:Key="ExpandCollapseToggleStyle" TargetType="ToggleButton">
        <Setter Property="Margin" Value="0"/>
        <Setter Property="Padding" Value="0"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ToggleButton">
                    <Border Background="Transparent" Cursor="Hand">
                        <Grid x:Name="cross" Height="16"  Width="16" >
                            <Path Data="M 0.5,0.5 l 15,0 l 0,15 l -15,0 L 0.5,0" Stroke="{TemplateBinding Foreground}" StrokeThickness="1"/>
                            <Path x:Name="cross_h" Data="M 3,7.5 l 9,0" Stroke="{TemplateBinding Foreground}" StrokeThickness="1"/>
                            <Path x:Name="cross_v" Data="M 7.5,3 l 0,9" Stroke="{TemplateBinding Foreground}" StrokeThickness="1"/>
                        </Grid>
                    </Border>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsChecked" Value="True">
                            <Setter Property="Visibility" TargetName="cross_v" Value="Collapsed"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    <ControlTemplate x:Key="tvi" TargetType="{x:Type controls:TreeListViewItem}">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <Border x:Name="Bd"
            Grid.Column="1"
            Background="{TemplateBinding Background}"
            BorderBrush="{TemplateBinding BorderBrush}"
            BorderThickness="{TemplateBinding BorderThickness}"
            Padding="{TemplateBinding Padding}">
                <!--<GridViewRowPresenter x:Name="sb" Grid.Column="1" Columns="{TemplateBinding Columns}"
                                      Content="{Binding }"/>-->
                <ContentPresenter Grid.Column="0" x:Name="cp"
        Content="{TemplateBinding Header}"
        ContentTemplate="{TemplateBinding HeaderTemplate}"
        ContentTemplateSelector="{TemplateBinding HeaderTemplateSelector}"/>
                <!--<Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                -->
                <!--<GridViewRowPresenter Grid.Column="1" Columns="{TemplateBinding Columns}"
                                      Content="{Binding }"/>-->
                <!--
            </Grid>-->
            </Border>
            <ItemsPresenter x:Name="ItemsHost"
                  Grid.Row="1"
                  Grid.Column="1"/>
        </Grid>
        <ControlTemplate.Triggers>
            <Trigger Property="IsExpanded" Value="False">
                <Setter Property="Visibility" Value="Collapsed" TargetName="ItemsHost"/>
            </Trigger>
            <DataTrigger Binding="{Binding IsSelected,RelativeSource={RelativeSource Mode=Self}}" Value="True">
                <Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
                <Setter Property="TextBlock.Foreground" TargetName="cp"  Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/>
            </DataTrigger>
            <!--<DataTrigger Binding="{Binding IsMouseOver,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=controls:TreeListViewItem,AncestorLevel=1}}" Value="True">
            <Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
            <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/>
        </DataTrigger>-->
        </ControlTemplate.Triggers>
    </ControlTemplate>
    <Style TargetType="{x:Type controls:TreeListViewItem}">
        <Setter Property="Margin" Value="0"/>
        <Setter Property="Padding" Value="0"/>
        <Setter Property="Template" Value="{StaticResource tvi}"/>
    </Style>
</ResourceDictionary>
HMI.UI/Converter/IndentConverter.cs
New file
@@ -0,0 +1,21 @@
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace HMI.UI.Converter
{
    public class IndentConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            int level = (int)value;
            return new Thickness(level * 10, 0, 0, 0);
        }
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return parameter;
        }
    }
}
HMI.UI/HMI.UI.csproj
@@ -93,8 +93,10 @@
    <Compile Include="Controls\treegrid\Controls\TreeGrid.cs" />
    <Compile Include="Controls\treegrid\Controls\TreeItemContentControl.cs" />
    <Compile Include="Controls\treegrid\Controls\TreeItemData.cs" />
    <Compile Include="Controls\treegrid\Controls\TreeListView.cs" />
    <Compile Include="Controls\treegrid\TreeItemConverter.cs" />
    <Compile Include="Controls\treegrid\TreeNodeExtend.cs" />
    <Compile Include="Converter\IndentConverter.cs" />
    <Compile Include="Converter\VisibilityBoolConverter.cs" />
    <Compile Include="Converter\DataGridRowFlagConverter.cs" />
    <Compile Include="Converter\DataGridRowNumberConverter.cs" />
@@ -139,6 +141,10 @@
      <Generator>MSBuild:Compile</Generator>
      <SubType>Designer</SubType>
    </Page>
    <Page Include="Controls\treegrid\Themes\TreeListView.xaml">
      <Generator>MSBuild:Compile</Generator>
      <SubType>Designer</SubType>
    </Page>
    <Page Include="Controls\treegrid\Themes\TreeGrid.xaml">
      <Generator>MSBuild:Compile</Generator>
      <SubType>Designer</SubType>
@@ -159,6 +165,10 @@
      <SubType>Designer</SubType>
      <Generator>MSBuild:Compile</Generator>
    </Page>
    <Page Include="Style\PanuonDataGrid.xaml">
      <SubType>Designer</SubType>
      <Generator>MSBuild:Compile</Generator>
    </Page>
    <Page Include="Style\PanuonResource.xaml">
      <Generator>MSBuild:Compile</Generator>
      <SubType>Designer</SubType>
HMI.UI/Style/PanuonDataGrid.xaml
New file
@@ -0,0 +1,88 @@
<ResourceDictionary
     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"
                    mc:Ignorable="d"
                    xmlns:ui="clr-namespace:HMI.UI"
                    xmlns:sys="clr-namespace:System;assembly=mscorlib"
                    xmlns:pw="https://opensource.panuon.com/wpf"
                    xmlns:pu="https://opensource.panuon.com/wpf-ui"
             xmlns:uic="clr-namespace:HMI.UI.Converter;assembly=HMI.UI"
             xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI">
    <ResourceDictionary.MergedDictionaries>
        <pu:StyleDictionary Includes="DataGrid" />
        <ResourceDictionary Source="/HMI.UI;component/Style/Colors.xaml"/>
        <ResourceDictionary Source="/HMI.UI;component/Resources/Font/Font.xaml"/>
    </ResourceDictionary.MergedDictionaries>
    <Style x:Key="DefaultDataGridRow" TargetType="DataGridRow" BasedOn="{StaticResource {x:Static pu:StyleKeys.DataGridRowStyle}}">
        <Setter Property="Background" Value="White"/>
        <Setter Property="DetailsVisibility" Value="Collapsed"/>
        <Style.Triggers>
            <Trigger Property="AlternationIndex" Value="1">
                <Setter Property="Background" Value="#eeeeee"/>
            </Trigger>
        </Style.Triggers>
    </Style>
    <Style TargetType="DataGridCell"
       BasedOn="{StaticResource {x:Static pu:StyleKeys.DataGridCellStyle}}">
        <Setter Property="Background" Value="Transparent"/>
        <Setter Property="Padding" Value="5"/>
        <Setter Property="Margin" Value="0"/>
        <Setter Property="BorderThickness" Value="0"/>
        <Setter Property="VerticalAlignment" Value="Stretch"/>
    </Style>
    <Style x:Key="DefaultDataGrid" TargetType="DataGrid" BasedOn="{StaticResource {x:Static pu:StyleKeys.DataGridStyle}}">
        <Style.Resources>
            <Style TargetType="DataGridColumnHeader"
       BasedOn="{StaticResource {x:Static pu:StyleKeys.DataGridColumnHeaderStyle}}">
                <Setter Property="Padding" Value="10"/>
                <Setter Property="BorderThickness" Value="0"/>
            </Style>
        </Style.Resources>
        <Setter Property="RowHeight" Value="30"/>
        <Setter Property="ColumnHeaderHeight" Value="36"/>
        <Setter Property="pu:DataGridHelper.ColumnHeaderPadding" Value="0"/>
        <Setter Property="ForceCursor" Value="True"/>
        <Setter Property="SelectionMode" Value="Single"/>
        <Setter Property="RowStyle" Value="{StaticResource DefaultDataGridRow}"/>
        <Setter Property="AlternationCount" Value="2"></Setter>
        <Setter Property="Foreground" Value="#3a3a3a"/>
        <Setter Property="HorizontalGridLinesBrush" Value="{StaticResource TableBoarder}"/>
        <Setter Property="VerticalGridLinesBrush" Value="{StaticResource TableBoarder}"/>
        <Setter Property="BorderBrush" Value="{StaticResource TableBoarder}" />
        <Setter Property="AutoGenerateColumns" Value="False"/>
        <Setter Property="CanUserAddRows" Value="False"/>
        <Setter Property="CanUserSortColumns" Value="False"/>
        <Setter Property="CanUserDeleteRows" Value="False"/>
        <Setter Property="RowHeaderWidth" Value="1"/>
        <Setter Property="IsReadOnly" Value="True"/>
        <Setter Property="GridLinesVisibility" Value="All"/>
        <Setter Property="BorderThickness" Value="0"/>
        <Setter Property="BorderBrush" Value="{StaticResource CommonBoarder}"/>
        <Setter Property="FontSize" Value="{StaticResource ContentFontSize2}"/>
        <Setter Property="pu:DataGridHelper.IsVerticalScrollBarCrossingColumnHeaderPanel" Value="True"/>
        <Setter Property="pu:DataGridHelper.ColumnHeaderForeground" Value="White"/>
        <Setter Property="pu:DataGridHelper.ColumnHeaderBackground" Value="{StaticResource TableHeader}"/>
        <Setter Property="pu:DataGridHelper.ColumnHeaderHoverBackground" Value="{StaticResource TableHeader}"/>
        <Setter Property="pu:DataGridHelper.ColumnHeaderSeparatorBrush" Value="{StaticResource TableBoarder}"/>
        <Setter Property="pu:DataGridHelper.ColumnHeaderSeparatorVisibility" Value="Visible"/>
        <Setter Property="pu:DataGridHelper.ColumnHeaderVerticalContentAlignment" Value="Center"/>
        <Setter Property="pu:DataGridHelper.ColumnHeaderHorizontalContentAlignment" Value="Left"/>
        <Setter Property="pu:DataGridHelper.RowHeaderSeparatorBrush" Value="{StaticResource CommonBoarder}"/>
        <Setter Property="pu:DataGridHelper.RowHoverForeground" Value="{StaticResource SecondPrimaryColorContract}"/>
        <Setter Property="pu:DataGridHelper.RowHoverBackground" Value="{StaticResource SecondPrimaryColor}"/>
        <Setter Property="pu:DataGridHelper.RowSelectedBackground" Value="{StaticResource PrimaryColor}"/>
        <Setter Property="pu:DataGridHelper.RowSelectedForeground" Value="White"/>
        <Setter Property="pu:DataGridHelper.RowHeaderBorderThickness" Value="10,0,0,0"/>
        <Setter Property="pu:DataGridHelper.ColumnHeaderBorderThickness" Value="0"/>
        <Setter Property="pu:DataGridHelper.ColumnHeaderPadding" Value="0"/>
    </Style>
</ResourceDictionary>
HMI.UI/Style/PanuonResource.xaml
@@ -11,8 +11,8 @@
             xmlns:uic="clr-namespace:HMI.UI.Converter;assembly=HMI.UI"
             xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI">
    <ResourceDictionary.MergedDictionaries>
        <pu:StyleDictionary Includes="PasswordBox,TextBox,ComboBox,Button,TreeView,Menu,ListBox,ContextMenu" />
        <ResourceDictionary Source="/HMI.UI;component/Style/Colors.xaml"/>
        <pu:StyleDictionary Includes="PasswordBox,TextBox,ComboBox,Button,DataGrid,TreeView,Menu,ListBox,ContextMenu" />
        <ResourceDictionary Source="/HMI.UI;component/Resources/Font/Font.xaml"/>
    </ResourceDictionary.MergedDictionaries>
    <sys:Double x:Key="TitleFontSize">20</sys:Double>
@@ -55,7 +55,7 @@
        <Setter Property="pu:ButtonHelper.Icon" Value="&#xe643;"/>
        <Setter Property="Content" Value="{DynamicResource App.Edit}"/>
    </Style>
    <Style x:Key="RfhBtn" TargetType="Button" BasedOn="{StaticResource PrimaryButton}">
        <Setter Property="pu:ButtonHelper.Icon" Value="&#xe631;"/>
        <Setter Property="Content" Value="{DynamicResource App.Refresh}"/>
@@ -161,80 +161,11 @@
        <Setter Property="Padding" Value="10"/>
    </Style>
    <Style x:Key="DefaultDataGridRow" TargetType="DataGridRow" BasedOn="{StaticResource {x:Static pu:StyleKeys.DataGridRowStyle}}">
        <Setter Property="Background" Value="White"/>
        <Style.Triggers>
            <Trigger Property="AlternationIndex" Value="1">
                <Setter Property="Background" Value="#eeeeee"/>
            </Trigger>
        </Style.Triggers>
    </Style>
    <Style x:Key="TableTextBlock" TargetType="TextBlock">
        <Setter Property="Padding" Value="0,2"/>
        <Setter Property="Margin" Value="0"/>
        <Setter Property="VerticalAlignment" Value="Center"/>
        <Setter Property="TextWrapping" Value="Wrap"/>
    </Style>
    <Style TargetType="DataGridCell"
       BasedOn="{StaticResource {x:Static pu:StyleKeys.DataGridCellStyle}}">
        <Setter Property="Background" Value="Transparent"/>
        <Setter Property="Padding" Value="5"/>
        <Setter Property="Margin" Value="0"/>
        <Setter Property="BorderThickness" Value="0"/>
        <Setter Property="VerticalAlignment" Value="Stretch"/>
    </Style>
    <Style x:Key="DefaultDataGrid" TargetType="DataGrid" BasedOn="{StaticResource {x:Static pu:StyleKeys.DataGridStyle}}">
        <Style.Resources>
            <Style TargetType="DataGridColumnHeader"
       BasedOn="{StaticResource {x:Static pu:StyleKeys.DataGridColumnHeaderStyle}}">
                <Setter Property="Padding" Value="10"/>
                <Setter Property="BorderThickness" Value="0"/>
            </Style>
        </Style.Resources>
        <Setter Property="RowHeight" Value="30"/>
        <Setter Property="ColumnHeaderHeight" Value="36"/>
        <Setter Property="pu:DataGridHelper.ColumnHeaderPadding" Value="0"/>
        <Setter Property="ForceCursor" Value="True"/>
        <Setter Property="SelectionMode" Value="Single"/>
        <Setter Property="RowStyle" Value="{StaticResource DefaultDataGridRow}"/>
        <Setter Property="AlternationCount" Value="2"></Setter>
        <Setter Property="Foreground" Value="#3a3a3a"/>
        <Setter Property="HorizontalGridLinesBrush" Value="{StaticResource TableBoarder}"/>
        <Setter Property="VerticalGridLinesBrush" Value="{StaticResource TableBoarder}"/>
        <Setter Property="BorderBrush" Value="{StaticResource TableBoarder}" />
        <Setter Property="AutoGenerateColumns" Value="False"/>
        <Setter Property="CanUserAddRows" Value="False"/>
        <Setter Property="CanUserSortColumns" Value="False"/>
        <Setter Property="CanUserDeleteRows" Value="False"/>
        <Setter Property="RowHeaderWidth" Value="1"/>
        <Setter Property="IsReadOnly" Value="True"/>
        <Setter Property="GridLinesVisibility" Value="All"/>
        <Setter Property="BorderThickness" Value="0"/>
        <Setter Property="BorderBrush" Value="{StaticResource CommonBoarder}"/>
        <Setter Property="FontSize" Value="{StaticResource ContentFontSize2}"/>
        <Setter Property="pu:DataGridHelper.ColumnHeaderForeground" Value="White"/>
        <Setter Property="pu:DataGridHelper.ColumnHeaderBackground" Value="{StaticResource TableHeader}"/>
        <Setter Property="pu:DataGridHelper.ColumnHeaderHoverBackground" Value="{StaticResource TableHeader}"/>
        <Setter Property="pu:DataGridHelper.ColumnHeaderSeparatorBrush" Value="{StaticResource TableBoarder}"/>
        <Setter Property="pu:DataGridHelper.ColumnHeaderSeparatorVisibility" Value="Visible"/>
        <Setter Property="pu:DataGridHelper.ColumnHeaderVerticalContentAlignment" Value="Center"/>
        <Setter Property="pu:DataGridHelper.ColumnHeaderHorizontalContentAlignment" Value="Left"/>
        <Setter Property="pu:DataGridHelper.RowHeaderSeparatorBrush" Value="{StaticResource CommonBoarder}"/>
        <Setter Property="pu:DataGridHelper.RowHoverForeground" Value="{StaticResource SecondPrimaryColorContract}"/>
        <Setter Property="pu:DataGridHelper.RowHoverBackground" Value="{StaticResource SecondPrimaryColor}"/>
        <Setter Property="pu:DataGridHelper.RowSelectedBackground" Value="{StaticResource PrimaryColor}"/>
        <Setter Property="pu:DataGridHelper.RowSelectedForeground" Value="White"/>
        <Setter Property="pu:DataGridHelper.RowHeaderBorderThickness" Value="10,0,0,0"/>
        <Setter Property="pu:DataGridHelper.ColumnHeaderBorderThickness" Value="0"/>
        <Setter Property="pu:DataGridHelper.ColumnHeaderPadding" Value="0"/>
    </Style>
    <Style x:Key="DefaultTreeView" TargetType="TreeView" BasedOn="{StaticResource {x:Static pu:StyleKeys.TreeViewStyle}}">
HMI.UnitTest/MainWindow.xaml.cs
@@ -33,21 +33,22 @@
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            Task.Run(() =>
            {
                for (int j = 0; j < 1000; j++)
                {
                    string status = (j % 2 == 0) ? "Passed" : "Failed";
                    string d = (j % 2 == 0) ? "9" : "9999999999999999999999999999999999999999999999999999999999999999995";
                    tsExecution.AddTestData(0, $"ConnectivityCANRec,2,3,4,{d},6,{status}");
                }
            });
            //Task.Run(() =>
            //{
            //    for (int j = 0; j < 1000; j++)
            //    {
            //        string status = (j % 2 == 0) ? "Passed" : "Failed";
            //        string d = (j % 2 == 0) ? "9" : "9999999999999999999999999999999999999999999999999999999999999999995";
            //        tsExecution.AddTestData(0, $"ConnectivityCANRec,2,3,4,{d},6,{status}");
            //    }
            //});
            tsExecution.UpdatePositionStatus(0, "Passed");
            tsExecution.UpdatePositionProcessValue(0, 12);
            tsExecution.PausePosition(0);
            tsExecution.SetPositionBarcode(0, "123456789");
            tsExecution.SavePositionCSV(0);
            //tsExecution.UpdatePositionStatus(0, "Passed");
            tsExecution.UpdatePositionStatus(0, "Failed");
            //tsExecution.UpdatePositionProcessValue(0, 12);
            //tsExecution.PausePosition(0);
            //tsExecution.SetPositionBarcode(0, "123456789");
            //tsExecution.SavePositionCSV(0);
            //tsExecution.UpdatePositionStatus(1, "Failed");
OPCClient/OPCClient.csproj
@@ -30,6 +30,24 @@
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
    <DebugSymbols>true</DebugSymbols>
    <OutputPath>bin\x64\Debug\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <DebugType>full</DebugType>
    <PlatformTarget>x64</PlatformTarget>
    <LangVersion>7.3</LangVersion>
    <ErrorReport>prompt</ErrorReport>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
    <OutputPath>bin\x64\Release\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <Optimize>true</Optimize>
    <DebugType>pdbonly</DebugType>
    <PlatformTarget>x64</PlatformTarget>
    <LangVersion>7.3</LangVersion>
    <ErrorReport>prompt</ErrorReport>
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
      <HintPath>..\WPFProject\packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
Plugin.DataBoard/Plugin.DataBoard.csproj
@@ -33,6 +33,24 @@
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
    <DebugSymbols>true</DebugSymbols>
    <OutputPath>bin\x64\Debug\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <DebugType>full</DebugType>
    <PlatformTarget>x64</PlatformTarget>
    <LangVersion>7.3</LangVersion>
    <ErrorReport>prompt</ErrorReport>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
    <OutputPath>bin\x64\Release\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <Optimize>true</Optimize>
    <DebugType>pdbonly</DebugType>
    <PlatformTarget>x64</PlatformTarget>
    <LangVersion>7.3</LangVersion>
    <ErrorReport>prompt</ErrorReport>
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="log4net, Version=3.0.4.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a, processorArchitecture=MSIL">
      <HintPath>..\WPFProject\packages\log4net.3.0.4\lib\net462\log4net.dll</HintPath>
Plugin.JarUI/Plugin.JarUI.csproj
@@ -31,6 +31,24 @@
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
    <DebugSymbols>true</DebugSymbols>
    <OutputPath>bin\x64\Debug\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <DebugType>full</DebugType>
    <PlatformTarget>x64</PlatformTarget>
    <LangVersion>7.3</LangVersion>
    <ErrorReport>prompt</ErrorReport>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
    <OutputPath>bin\x64\Release\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <Optimize>true</Optimize>
    <DebugType>pdbonly</DebugType>
    <PlatformTarget>x64</PlatformTarget>
    <LangVersion>7.3</LangVersion>
    <ErrorReport>prompt</ErrorReport>
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="log4net, Version=3.0.4.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a, processorArchitecture=MSIL">
      <HintPath>..\WPFProject\packages\log4net.3.0.4\lib\net462\log4net.dll</HintPath>
Plugin.SeqEditor/Entity/EditablePO/IEPO.cs
@@ -40,7 +40,7 @@
        public virtual void CustomPropertyChanged(string name)
        {
            OnPropertyChanged(name);
            //StepUI?.UpdatePropertyChanged("Description");
            StepUI?.UpdatePropertyChanged("Description");
        }
    }
}
Plugin.SeqEditor/Entity/SeqUI.cs
@@ -1,10 +1,19 @@
using CommunityToolkit.Mvvm.ComponentModel;
using NationalInstruments.TestStand.Interop.API;
using System.Runtime.InteropServices;
namespace Plugin.SeqEditor.Entity
{
    public partial class SeqUI : ObservableObject
    {
        ~SeqUI()
        {
            if (sequence != null)
            {
                Marshal.ReleaseComObject(sequence);
            }
        }
        public Sequence sequence { get; private set; }
        public SeqUI(Sequence sequence)
        {
@@ -23,5 +32,13 @@
                }
            }
        }
        [ObservableProperty]
        private bool isChanged;
        public void OnStepSettingChanged(object sender, string propertyName)
        {
            IsChanged = true;
        }
    }
}
Plugin.SeqEditor/Entity/StepUI.cs
@@ -1,21 +1,47 @@
using CommunityToolkit.Mvvm.ComponentModel;
using NationalInstruments.TestStand.Interop.API;
using System.Collections.ObjectModel;
using System.Runtime.InteropServices;
namespace Plugin.SeqEditor.Entity
{
    public partial class StepUI : ObservableObject
    {
        public Step step { get; private set; }
        public StepUI(Step step)
        ~StepUI()
        {
            if (step != null)
            {
                Marshal.ReleaseComObject(step);
            }
        }
        public Step step { get; private set; }
        public Sequence sequence { get; private set; }
        public event EventHandler<string> SettingChanged;
        public int IndexInSequence {
            get {
                return sequence.GetStepIndex(Name, StepGroup);
            }
        }
        public StepUI(Step step, Sequence sequence)
        {
            this.step = step;
            this.sequence = sequence;
        }
        public void UpdatePropertyChanged(string name)
        {
            OnPropertyChanged(name);
        }
        protected new void OnPropertyChanged(string name)
        {
            base.OnPropertyChanged(name);
            SettingChanged?.Invoke(this, name);
        }
        public string Name
@@ -72,13 +98,11 @@
            }
        }
        public StepGroups? StepGroup
        public StepGroups StepGroup
        {
            get
            {
                return step?.StepGroup;
                return step.StepGroup;
            }
        }
@@ -87,5 +111,10 @@
        [ObservableProperty]
        public ObservableCollection<StepUI> children = new();
        public override string ToString()
        {
            return Name;
        }
    }
}
Plugin.SeqEditor/Plugin.SeqEditor.csproj
@@ -9,6 +9,7 @@
    </PropertyGroup>
    <ItemGroup>
      <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
      <PackageReference Include="gong-wpf-dragdrop" Version="4.0.0" />
      <PackageReference Include="log4net" Version="3.0.4" />
      <PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.135" />
      <PackageReference Include="Panuon.WPF.UI" Version="1.3.0.2" />
Plugin.SeqEditor/SeqEditor.xaml
@@ -5,6 +5,7 @@
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:Plugin.SeqEditor"
             mc:Ignorable="d" 
             xmlns:hcu="clr-namespace:HMI.Core.Util;assembly=HMI.Core"
             xmlns:hb="clr-namespace:HMI.Base;assembly=HMI.Base"
             xmlns:uic="clr-namespace:HMI.UI.Converter;assembly=HMI.UI"
xmlns:ui="clr-namespace:HMI.UI;assembly=HMI.UI"
@@ -12,6 +13,7 @@
xmlns:pu="https://opensource.panuon.com/wpf-ui"
xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI"
xmlns:util="clr-namespace:Plugin.SeqEditor.Util"
        xmlns:dd="urn:gong-wpf-dragdrop"
        xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
             d:DesignHeight="450" d:DesignWidth="800">
    <i:Interaction.Triggers>
@@ -22,14 +24,29 @@
    <UserControl.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="/HMI.UI;component/Style/PanuonDataGrid.xaml"/>
                <ResourceDictionary Source="/HMI.UI;component/Controls/MultiCombox.xaml"/>
                <ResourceDictionary Source="/HMI.UI;component/Controls/TreeGrid/Themes/TreeListView.xaml"/>
                <ResourceDictionary Source="/HMI.UI;component/Controls/TreeGrid/Themes/TreeItemContentControl.xaml"/>
                <ResourceDictionary Source="/HMI.UI;component/Controls/TreeGrid/Themes/TreeGrid.xaml"/>
            </ResourceDictionary.MergedDictionaries>
            <uic:IndentConverter x:Key="IndentConverter"/>
            <Style TargetType="pu:FormGroup">
                <Setter Property="Padding" Value="0,10"/>
                <Setter Property="HeaderFontSize" Value="14"/>
                <Setter Property="HeaderFontWeight" Value="Bold"/>
            </Style>
            <Style x:Key="TreeTogg" TargetType="ToggleButton">
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate>
                            <Border Width="0"/>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
            <!-- 树Grid组件 -->
@@ -42,6 +59,92 @@
                </CollectionViewSource.GroupDescriptions>
            </CollectionViewSource>
            <hcu:BindingProxy x:Key="BindingProxy" Data="{Binding}"/>
            <ContextMenu x:Key="stepContextMenu">
                <ContextMenu.Resources>
                    <Style TargetType="MenuItem">
                        <Setter Property="DataContext" Value="{Binding Data,Source={StaticResource BindingProxy}}"/>
                    </Style>
                </ContextMenu.Resources>
                <MenuItem Header="插入Step">
                    <MenuItem Header="Tests">
                        <!--<MenuItem Header="Pass/Fail" Command="{Binding AddStepCommand}"
                              CommandParameter="Tests.PassFail"/>
                        <MenuItem Header="MultiNumber" Command="{Binding AddStepCommand}"
CommandParameter="Tests.NI_MultipleNumericLimitTest"/>-->
                        <MenuItem Header="Number" Command="{Binding AddStepCommand}"
CommandParameter="Tests.NumericLimitTest"/>
                        <MenuItem Header="String" Command="{Binding AddStepCommand}"
CommandParameter="Tests.StringValueTest"/>
                    </MenuItem>
                    <!--<MenuItem Header="Actions">
                        <MenuItem Header="LabVIEW" Command="{Binding AddStepCommand}"
CommandParameter="Actions.LabVIEW"/>
                        <MenuItem Header="LabWindows/CVI" Command="{Binding AddStepCommand}"
CommandParameter="Actions.LabWindows/CVI"/>
                        <MenuItem Header="C/C++ DLL" Command="{Binding AddStepCommand}"
                              CommandParameter="Actions.C/C++ DLL"/>
                        <MenuItem Header=".NET" Command="{Binding AddStepCommand}"
          CommandParameter="Actions..NET"/>
                        <MenuItem Header="ActiveX/COM" Command="{Binding AddStepCommand}"
CommandParameter="Actions.ActiveX/COM"/>
                        <MenuItem Header="None" Command="{Binding AddStepCommand}"
CommandParameter="Actions.None"/>
                        <MenuItem Header="LabVIEW NXG" Command="{Binding AddStepCommand}"
CommandParameter="Actions.LabVIEW NXG"/>
                        <MenuItem Header="Python" Command="{Binding AddStepCommand}"
CommandParameter="Actions.Python"/>
                    </MenuItem>-->
                </MenuItem>
            </ContextMenu>
            <Style x:Key="treeCell" TargetType="DataGridCell">
                <Setter Property="Background" Value="Transparent"/>
                <Setter Property="Padding" Value="5"/>
                <Setter Property="Margin" Value="0"/>
                <Setter Property="BorderThickness" Value="0"/>
                <Setter Property="VerticalAlignment" Value="Stretch"/>
                <Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment,
                    RelativeSource={RelativeSource Mode=FindAncestor,AncestorLevel=1,AncestorType=ui:TreeDataGrid}}"/>
                <Setter Property="HorizontalContentAlignment"
                        Value="{Binding HorizontalContentAlignment,
    RelativeSource={RelativeSource Mode=FindAncestor,AncestorLevel=1,AncestorType=ui:TreeDataGrid}}"/>
            </Style>
            <Style x:Key="stepRowStyle" TargetType="ui:TreeDataGridRow">
                <Setter Property="ContextMenu" Value="{StaticResource stepContextMenu}"/>
                <Setter Property="DetailsVisibility" Value="Collapsed"/>
                <Style.Triggers>
                    <Trigger Property="IsSelected" Value="True">
                        <Setter Property="Background" Value="{StaticResource PrimaryColor}"/>
                    </Trigger>
                </Style.Triggers>
            </Style>
            <Style x:Key="sb" TargetType="ui:TreeDataGrid" BasedOn="{StaticResource DefaultDataGrid}">
                <Setter Property="pu:DataGridHelper.RowHeaderSeparatorVisibility" Value="Visible"/>
                <Setter Property="pu:DataGridHelper.RowHeaderSeparatorBrush" Value="#FFF"/>
                <Setter Property="AreRowDetailsFrozen" Value="False"/>
                <Setter Property="HeadersVisibility" Value="Column"/>
                <Setter Property="DataGridCell.VerticalContentAlignment" Value="Center"/>
                <Setter Property="DataGridCell.HorizontalContentAlignment" Value="Left"/>
            </Style>
            <Style x:Key="gvh" TargetType="GridViewColumnHeader">
                <Setter Property="Background" Value="{DynamicResource VerBar}"/>
            </Style>
            <DataTemplate x:Key="gvht">
                <Border Padding="6">
                    <TextBlock Foreground="#FFF" Text="{Binding }"/>
                </Border>
            </DataTemplate>
        </ResourceDictionary>
    </UserControl.Resources>
    <Border Style="{StaticResource CardBoarder}" Padding="0">
@@ -64,7 +167,7 @@
                    <Button Style="{StaticResource SaveBtn}" Content="{DynamicResource se.save}" Command="{Binding SaveSeqFileCommand}"/>
                    <Button Style="{StaticResource SaveBtn}" pu:ButtonHelper.Icon="&#xe60b;"
                            Content="{DynamicResource se.saveTo}" 
                            Command="{Binding SaveSeqFileCommand}"/>
                            Command="{Binding SaveToFileCommand}" Cursor="Hand"/>
                    <Button Style="{StaticResource DelBtn}" Content="{DynamicResource se.close}"
                            pu:ButtonHelper.Icon="&#xe609;"
                            Command="{Binding CloseSeqFileCommand}"/>
@@ -75,7 +178,20 @@
                <TextBlock DockPanel.Dock="Left" FontSize="{StaticResource ContentFontSize2}" 
                           Margin="20,0,0,0"
VerticalAlignment="Bottom" LineHeight="{StaticResource TitleFontSize}" HorizontalAlignment="Left" Text="{Binding CurrentFileName}"/>
VerticalAlignment="Bottom" LineHeight="{StaticResource TitleFontSize}" HorizontalAlignment="Left">
                <Run Text="{Binding CurrentFileName}"/>
                <Run DataContext="{Binding CurrentSeqUI}">
                    <Run.Style>
                        <Style TargetType="Run">
                            <Style.Triggers>
                                <DataTrigger Binding="{Binding IsChanged}" Value="True">
                                    <Setter Property="Text" Value="*"/>
                                </DataTrigger>
                            </Style.Triggers>
                        </Style>
                    </Run.Style>
                </Run>
                </TextBlock>
            </DockPanel>
            <!--<pu:FormGroup Header="{DynamicResource se.seq}" Orientation="Vertical" Grid.Row="1" Grid.Column="0">
@@ -91,36 +207,11 @@
            </pu:FormGroup>-->
            <pu:FormGroup Grid.Row="1" Grid.Column="1" Header="步骤类型" Style="{StaticResource InputFormGroupStyle}">
                <StackPanel Orientation="Horizontal">
                    <!--<ComboBox Text="{Binding TargetType}"
                              MinWidth="180" Margin="0,0,10,0" x:Name="CbFilter"
                        SelectedValuePath="DataValue" IsEditable="True" >
                        <ComboBox.ItemTemplate>
                            <DataTemplate DataType="util:ItemData">
                                <StackPanel Orientation="Horizontal">
                                    <CheckBox IsChecked="{Binding IsChecked}" />
                                    <TextBlock Text="{Binding DisplayName}" />
                                </StackPanel>
                            </DataTemplate>
                        </ComboBox.ItemTemplate>
                    </ComboBox>-->
                    <ui:MultiComboBox SelectedValues="{Binding TargetType}"
                                      Style="{StaticResource FormComboxStyle}"
                                      pu:ComboBoxHelper.ClearButtonVisibility="Collapsed"
                                      MinWidth="180" Margin="0,0,10,0" x:Name="CbFilter" IsEditable="True" IsReadOnly="True" >
                        <ui:MultiComboBox.ItemTemplate>
                            <DataTemplate DataType="util:ItemData">
                                <StackPanel Orientation="Horizontal">
                                    <CheckBox IsChecked="{Binding IsChecked,UpdateSourceTrigger=PropertyChanged}" />
                                    <TextBlock Text="{Binding DisplayName}" />
                                </StackPanel>
                            </DataTemplate>
                        </ui:MultiComboBox.ItemTemplate>
                                      Width="450" Margin="0,0,10,0" x:Name="CbFilter" IsEditable="True" IsReadOnly="True" >
                    </ui:MultiComboBox>
                    <!--<ComboBox Style="{StaticResource FormComboxStyle}" MinWidth="180" Margin="0,0,10,0" x:Name="CbFilter"/>-->
                    <Button Style="{StaticResource RfhBtn}" Command="{Binding RefreshCommand}"/>
                </StackPanel>
            </pu:FormGroup>
@@ -190,43 +281,46 @@
                        </DataGridTemplateColumn>
                    </DataGrid.Columns>
                                 ItemsSource="{Binding AllSteps,Converter={StaticResource treeC}}"
                                 PreviewMouseWheel="treeDataGrid_PreviewMouseWheel"
                </DataGrid>-->
                <ui:TreeDataGrid Grid.Row="1" x:Name="treeDataGrid"
                                 VirtualizingPanel.IsContainerVirtualizable="True"
                                VirtualizingPanel.IsVirtualizing="True"
                                VirtualizingPanel.VirtualizationMode="Recycling"
                                ScrollViewer.CanContentScroll="False"
                                 VirtualizingPanel.IsVirtualizingWhenGrouping="True"
                                EnableColumnVirtualization="True"
                                EnableRowVirtualization="True"
                                 PreviewMouseWheel="treeDataGrid_PreviewMouseWheel"
                <!--<ui:TreeDataGrid Grid.Row="1" x:Name="treeDataGrid"
                                          dd:DragDrop.IsDragSource="True"
dd:DragDrop.IsDropTarget="True"
dd:DragDrop.ShowAlwaysDropTargetAdorner="True"
dd:DragDrop.UseDefaultDragAdorner="True"
                                 dd:DragDrop.DropHandler="{Binding StepDropHandler}"
                                 SelectionUnit="FullRow"
                                 AutoGenerateColumns="False"
                                 IsReadOnly="True"
                                 CanUserAddRows="False"
                                 Style="{StaticResource DefaultDataGrid}"
                ItemsSource="{Binding Source={StaticResource TreeGridFilter}}"
                                 ItemsSource="{Binding TreeCollection,IsAsync=True}"
                                 SelectedItem="{Binding TreeItemData}">
                    <!--<DataGrid.GroupStyle>
                    <DataGrid.GroupStyle>
                        <GroupStyle>
                            <GroupStyle.Panel>
                                <ItemsPanelTemplate>
                                    <DataGridRowsPresenter/>
                                </ItemsPanelTemplate>
                            </GroupStyle.Panel>
                            <GroupStyle.ContainerStyle>
                                <Style TargetType="{x:Type GroupItem}">
                                    <Setter Property="Margin" Value="0,0,0,5" />
                                    <Setter Property="Template">
                                        <Setter.Value>
                                            <ControlTemplate TargetType="{x:Type GroupItem}">
                                                <GroupBox Header="{Binding Name}">
                                                <Expander BorderThickness="0" Background="DarkCyan"
                                                          Header="{Binding Name}" IsExpanded="True">
                                                    <ItemsPresenter />
                                                </GroupBox>
                                                </Expander>
                                            </ControlTemplate>
                                        </Setter.Value>
                                    </Setter>
                                </Style>
                            </GroupStyle.ContainerStyle>
                        </GroupStyle>
                    </DataGrid.GroupStyle>-->
                    </DataGrid.GroupStyle>
                    <ui:TreeDataGrid.Columns>
                    <DataGrid.Columns>
                        <DataGridTemplateColumn IsReadOnly="True" Width="Auto">
                            <DataGridTemplateColumn.CellTemplate>
                                <DataTemplate>
@@ -235,12 +329,187 @@
                            </DataGridTemplateColumn.CellTemplate>
                        </DataGridTemplateColumn>
                        <DataGridTextColumn Header="名称" Width="1*" Binding="{Binding Data.Name}"></DataGridTextColumn>
                        <DataGridTextColumn Header="类型" Width="1*" Binding="{Binding Data.Type}"></DataGridTextColumn>
                        <DataGridTextColumn Header="描述" Width="7*" Binding="{Binding  Data.Description}"></DataGridTextColumn>
                        <DataGridTextColumn Header="RunMode" Width="*" Binding="{Binding Data.RunMode}"></DataGridTextColumn>
                    </ui:TreeDataGrid.Columns>
                </ui:TreeDataGrid>
                        <DataGridTextColumn Width="1*" Binding="{Binding Data.Name}">
                            <DataGridTextColumn.Header>
                                <TextBlock Text="{DynamicResource se.name}"/>
                            </DataGridTextColumn.Header>
                        </DataGridTextColumn>
                        <DataGridTextColumn Width="1*" Binding="{Binding Data.Type}">
                            <DataGridTextColumn.Header>
                                <TextBlock Text="{DynamicResource se.type}"/>
                            </DataGridTextColumn.Header>
                        </DataGridTextColumn>
                        <DataGridTextColumn Width="6*" Binding="{Binding Data.Description}">
                            <DataGridTextColumn.Header>
                                <TextBlock Text="{DynamicResource se.description}"/>
                            </DataGridTextColumn.Header>
                        </DataGridTextColumn>
                        <DataGridTemplateColumn Width="1*">
                            <DataGridTemplateColumn.Header>
                                <TextBlock Text="{DynamicResource se.runMode}"/>
                            </DataGridTemplateColumn.Header>
                            <DataGridTemplateColumn.CellTemplate>
                                <DataTemplate>
                                    <Grid x:Name="border" Margin="0">
                                        <TextBlock x:Name="text" Margin="-1" Padding="0" VerticalAlignment="Center"/>
                                    </Grid>
                                    <DataTemplate.Triggers>
                                        <DataTrigger Binding="{Binding Data.RunMode}" Value="Normal">
                                            <Setter TargetName="text" Property="Text" Value="{DynamicResource se.normal}"/>
                                        </DataTrigger>
                                        <DataTrigger Binding="{Binding Data.RunMode}" Value="Skip">
                                            <Setter TargetName="text" Property="Text" Value="{DynamicResource se.skip}"/>
                                        </DataTrigger>
                                        <DataTrigger Binding="{Binding Data.RunMode}" Value="Pass">
                                            <Setter TargetName="text" Property="Text" Value="{DynamicResource se.pass}"/>
                                        </DataTrigger>
                                        <DataTrigger Binding="{Binding Data.RunMode}" Value="Fail">
                                            <Setter TargetName="text" Property="Text" Value="{DynamicResource se.fail}"/>
                                        </DataTrigger>
                                    </DataTemplate.Triggers>
                                </DataTemplate>
                            </DataGridTemplateColumn.CellTemplate>
                        </DataGridTemplateColumn>
                    </DataGrid.Columns>
                </ui:TreeDataGrid>-->
                <ui:TreeListView Grid.Row="1" Grid.Column="1" ItemsSource="{Binding AllSteps}"
                                 dd:DragDrop.IsDragSource="True"
                                 dd:DragDrop.IsDropTarget="True"
                                 dd:DragDrop.DropTargetAdornerBrush="Red"
                                 dd:DragDrop.DropHandler="{Binding StepDropHandler}"
                                 SizeChanged="TreeListView_SizeChanged">
                    <i:Interaction.Triggers>
                        <i:EventTrigger EventName="SelectedItemChanged">
                            <i:InvokeCommandAction Command="{Binding SbCommand}" PassEventArgsToCommand="True"/>
                        </i:EventTrigger>
                        <i:EventTrigger EventName="PreviewMouseRightButtonDown">
                            <i:InvokeCommandAction Command="{Binding Sb2Command}" PassEventArgsToCommand="True"/>
                        </i:EventTrigger>
                    </i:Interaction.Triggers>
                    <ui:TreeListView.Resources>
                        <Style x:Key="CellBorder" TargetType="Border">
                            <Setter Property="Padding" Value="5"/>
                            <Setter Property="BorderBrush" Value="{DynamicResource CommonBoarder}"/>
                            <Setter Property="BorderThickness" Value="1,0,0,1"/>
                            <Setter Property="Margin" Value="-6,0"/>
                        </Style>
                    </ui:TreeListView.Resources>
                    <ui:TreeListView.Style>
                        <Style TargetType="ui:TreeListView" BasedOn="{StaticResource DefaultTreeView}">
                            <Setter Property="pu:TreeViewHelper.ToggleArrowToggleButtonStyle" Value="{StaticResource TreeTogg}"/>
                            <Setter Property="pu:TreeViewHelper.ItemsPadding" Value="0"/>
                            <Setter Property="Background" Value="#FFF"/>
                            <Setter Property="ColumnHeaderContainerStyle" Value="{StaticResource gvh}"/>
                            <Setter Property="Template">
                                <Setter.Value>
                                    <ControlTemplate TargetType="ui:TreeListView">
                                        <Border BorderBrush="{TemplateBinding BorderBrush}"
                BorderThickness="{TemplateBinding BorderThickness}"
                Background="{TemplateBinding Background}">
                                            <Grid>
                                                <Grid.RowDefinitions>
                                                    <RowDefinition Height="Auto"/>
                                                    <RowDefinition/>
                                                </Grid.RowDefinitions>
                                                <GridViewHeaderRowPresenter
                                                    Grid.Row="0" Columns="{TemplateBinding Columns}">
                                                    <GridViewHeaderRowPresenter.ColumnHeaderContainerStyle>
                                                        <Style TargetType="GridViewColumnHeader">
                                                            <Setter Property="Background" Value="{DynamicResource VerBar}"/>
                                                        </Style>
                                                    </GridViewHeaderRowPresenter.ColumnHeaderContainerStyle>
                                                </GridViewHeaderRowPresenter>
                                                <ScrollViewer Grid.Row="1" Padding="{TemplateBinding Padding}" VerticalScrollBarVisibility="Auto">
                                                    <ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                                                </ScrollViewer>
                                            </Grid>
                                        </Border>
                                    </ControlTemplate>
                                </Setter.Value>
                            </Setter>
                        </Style>
                    </ui:TreeListView.Style>
                    <ui:TreeListView.Columns>
                        <GridViewColumn Header="名称" HeaderContainerStyle="{StaticResource gvh}"
                                        HeaderTemplate="{StaticResource gvht}">
                            <GridViewColumn.CellTemplate>
                                <DataTemplate>
                                    <Border Style="{StaticResource CellBorder}">
                                        <StackPanel Orientation="Horizontal">
                                            <ToggleButton x:Name="Expander"
      Style="{StaticResource ExpandCollapseToggleStyle}"
                                          Margin="{Binding level,Converter={StaticResource IndentConverter},RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ui:TreeListViewItem,AncestorLevel=1}}"
      Grid.Column="0"
IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ui:TreeListViewItem,AncestorLevel=1}}"
ClickMode="Press"/>
                                            <TextBlock Margin="5,0,0,0" Padding="6,0" Text="{Binding Name}"/>
                                        </StackPanel>
                                    </Border>
                                    <DataTemplate.Triggers>
                                        <DataTrigger Binding="{Binding HasItems,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=ui:TreeListViewItem,AncestorLevel=1}}"
         Value="False">
                                            <Setter Property="Visibility" Value="Hidden" TargetName="Expander"/>
                                        </DataTrigger>
                                    </DataTemplate.Triggers>
                                </DataTemplate>
                            </GridViewColumn.CellTemplate>
                        </GridViewColumn>
                        <GridViewColumn Header="类型" HeaderContainerStyle="{StaticResource gvh}"
                                        HeaderTemplate="{StaticResource gvht}">
                            <GridViewColumn.CellTemplate>
                                <DataTemplate>
                                    <Border Style="{StaticResource CellBorder}">
                                        <TextBlock Padding="6,0" Text="{Binding Type}"/>
                                    </Border>
                                </DataTemplate>
                            </GridViewColumn.CellTemplate>
                        </GridViewColumn>
                        <GridViewColumn Header="描述" HeaderContainerStyle="{StaticResource gvh}"
                                        HeaderTemplate="{StaticResource gvht}">
                            <GridViewColumn.CellTemplate>
                                <DataTemplate>
                                    <Border Style="{StaticResource CellBorder}">
                                        <TextBlock Padding="6,0" Text="{Binding Description}"/>
                                    </Border>
                                </DataTemplate>
                            </GridViewColumn.CellTemplate>
                        </GridViewColumn>
                        <GridViewColumn Header="{DynamicResource se.runMode}"
                                        Width="80"
                                        HeaderContainerStyle="{StaticResource gvh}"
                                        HeaderTemplate="{StaticResource gvht}">
                            <GridViewColumn.CellTemplate>
                                <DataTemplate>
                                    <Border Style="{StaticResource CellBorder}" BorderThickness="1,0,1,1">
                                        <TextBlock Padding="6,0" Text="{Binding RunMode}"/>
                                    </Border>
                                </DataTemplate>
                            </GridViewColumn.CellTemplate>
                        </GridViewColumn>
                    </ui:TreeListView.Columns>
                    <TreeView.ItemTemplate>
                        <HierarchicalDataTemplate ItemsSource="{Binding Children}">
                            <GridViewRowPresenter
                                ContextMenu="{StaticResource stepContextMenu}"
                                          Columns="{Binding Columns, RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ui:TreeListView,AncestorLevel=1}}"
                                          Content="{Binding }"/>
                        </HierarchicalDataTemplate>
                    </TreeView.ItemTemplate>
                </ui:TreeListView>
            </pu:FormGroup>
Plugin.SeqEditor/SeqEditor.xaml.cs
@@ -19,7 +19,7 @@
        InitializeComponent();
        CbFilter.ItemsSource = (IEnumerable<MultiComboBoxData>)
            new MultiComboBoxDataConverter().Convert(FilterTypeData.ItemSource,
            null,null,null
            null, null, null
            );
    }
@@ -66,5 +66,22 @@
    {
        e.Handled = false;
    }
    private void Expander_PreviewMouseDown(object sender, MouseButtonEventArgs e)
    {
        e.Handled = false;
    }
    private void TreeListView_SizeChanged(object sender, SizeChangedEventArgs e)
    {
        var treeListView = sender as TreeListView;
        var columns = treeListView.Columns;
        var width = treeListView.ActualWidth;
        columns[0].Width = columns[0].Width + (width - columns[0].Width - columns[1].Width - columns[2].Width - columns[3].Width);
        //foreach (var col in columns) {
        //    col.Width =
        //}
    }
}
Plugin.SeqEditor/Util/ItemData.cs
@@ -50,12 +50,12 @@
        public static readonly ItemData NumValTest =
            new(LangUtil.Get("Numeric Limit Test"), "NumericLimitTest");
        public static readonly ItemData RECS =
        public static readonly ItemData SequenceCall =
            new(LangUtil.Get("SequenceCall"), "SequenceCall");
        public static readonly List<ItemData> ItemSource =
            new List<ItemData> {
                Wait,Action,StrValTest,//RECS,REIC
                Wait,Action,StrValTest,NumValTest,//RECS,REIC
            };
    }
Plugin.SeqEditor/Util/Step2EPO.cs
@@ -34,10 +34,12 @@
                case StepTypeStr.NumericLimitTest:
                    epo = new NumValTestEPO(step);
                    return new NumValTestPanel((NumValTestEPO)epo);
                case StepTypeStr.Action:
                    return new ActionPanel(step);
                //case StepTypeStr.Action:
                //    return new ActionPanel(step);
                //case StepTypeStr.SequenceCall:
                //    return new ActionPanel(step);
                default:
                    return null;
                    return new ActionPanel(step);
            }
        }
    }
Plugin.SeqEditor/ViewModel/SeqEditorViewModel.cs
@@ -1,5 +1,6 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using GongSolutions.Wpf.DragDrop;
using HMI.Core.Util;
using HMI.UI;
using HMI.UI.Util;
@@ -11,6 +12,11 @@
using Plugin.SeqEditor.Util;
using System.Collections.ObjectModel;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
namespace Plugin.SeqEditor.ViewModel
{
@@ -44,8 +50,14 @@
        [ObservableProperty]
        private string targetType = null;
        [ObservableProperty]
        private string currentFilePath;
        [ObservableProperty]
        private bool canDrop = true;
        public IDropTarget StepDropHandler { get; set; }
        private Engine engine;
        private SequenceFile sequenceFile;
        private bool isLoaded;
@@ -54,15 +66,8 @@
        public List<ItemData> Types = FilterTypeData.ItemSource;
        private HashSet<string> edits = new HashSet<string> {
            StepTypeStr.PassFailTest,
            StepTypeStr.NumericLimitTest,
            StepTypeStr.NI_MultipleNumericLimitTest,
            StepTypeStr.StringValueTest,
            StepTypeStr.Action,
            StepTypeStr.NI_Wait,
            StepTypeStr.SequenceCall,
        };
        private HashSet<string> edits = new HashSet<string>();
        public Dictionary<Sequence, ObservableCollection<StepUI>> sequenceCache { get; private set; } = new();
        public SeqEditorViewModel(SeqEditor control, ILog log)
        {
@@ -70,6 +75,8 @@
            this.control = control;
            engine = new Engine();
            engine.LoadTypePaletteFilesEx();
            TargetType = string.Empty;
            StepDropHandler = new TreeListViewHandler(this);
        }
        ~SeqEditorViewModel()
@@ -88,6 +95,10 @@
        private ObservableCollection<StepUI> LoadSteps(Sequence seq, string group = null)
        {
            if (sequenceCache.ContainsKey(seq))
            {
                return sequenceCache[seq];
            }
            //setupSteps.Clear();
            //var steps = new ObservableCollection<StepUI>();
            //int num = seq.GetNumSteps(StepGroups.StepGroup_Setup);
@@ -133,46 +144,51 @@
            //CurrentStepUI = null;
            var steps = new ObservableCollection<StepUI>();
            StepGroups[] groups = new StepGroups[] { StepGroups.StepGroup_Setup, StepGroups.StepGroup_Main, StepGroups.StepGroup_Cleanup };
            int num = seq.GetNumSteps(StepGroups.StepGroup_Setup);
            for (int i = 0; i < num; i++)
            foreach (var stepGroup in groups)
            {
                Step step = seq.GetStep(i, StepGroups.StepGroup_Setup);
                var ui = Convert2StepUI(step, group);
                if (ui != null)
                int num = seq.GetNumSteps(stepGroup);
                for (int i = 0; i < num; i++)
                {
                    steps.Add(ui);
                    Step step = seq.GetStep(i, stepGroup);
                    var ui = Convert2StepUI(i, step, seq, group);
                    if (ui != null)
                    {
                        steps.Add(ui);
                    }
                }
            }
            num = seq.GetNumSteps(StepGroups.StepGroup_Main);
            for (int i = 0; i < num; i++)
            {
                Step step = seq.GetStep(i, StepGroups.StepGroup_Main);
                var ui = Convert2StepUI(step, group);
                if (ui != null)
                {
                    steps.Add(ui);
                }
            }
            //int num = seq.GetNumSteps(StepGroups.StepGroup_Main);
            //for (int i = 0; i < num; i++)
            //{
            //    Step step = seq.GetStep(i, StepGroups.StepGroup_Main);
            //    var ui = Convert2StepUI(step, seq, group);
            //    if (ui != null)
            //    {
            //        steps.Add(ui);
            //    }
            //}
            num = seq.GetNumSteps(StepGroups.StepGroup_Cleanup);
            for (int i = 0; i < num; i++)
            {
                Step step = seq.GetStep(i, StepGroups.StepGroup_Cleanup);
                var ui = Convert2StepUI(step, group);
                if (ui != null)
                {
                    steps.Add(ui);
                }
            }
            //num = seq.GetNumSteps(StepGroups.StepGroup_Cleanup);
            //for (int i = 0; i < num; i++)
            //{
            //    Step step = seq.GetStep(i, StepGroups.StepGroup_Cleanup);
            //    var ui = Convert2StepUI(step, seq, group);
            //    if (ui != null)
            //    {
            //        steps.Add(ui);
            //    }
            //}
            sequenceCache[seq] = steps;
            return steps;
        }
        private StepUI Convert2StepUI(Step step, string group)
        private StepUI Convert2StepUI(int index, Step step, Sequence sequence, string group)
        {
            var ui = new StepUI(step);
            if (!edits.Contains(ui.Type))
            var ui = new StepUI(step, sequence);
            if (edits != null && edits.Count > 0 && !edits.Contains(ui.Type))
            {
                return null;
            }
@@ -202,16 +218,21 @@
            {
                var po = step.Module.AsPropertyObject();
                bool useCurFile = po.GetValBoolean("UseCurFile", 0);
                if (useCurFile)
                string sfPath = po.GetValString("SFPath", 0);
                if (useCurFile || Path.GetFileName(currentFilePath).Equals(sfPath, StringComparison.CurrentCultureIgnoreCase))
                {
                    string seqName = po.GetValString("SeqName", 0);
                    Sequence sequence = sequenceFile.GetSequenceByName(seqName);
                    if (sequence == null)
                    Sequence sunSequence = sequenceFile.GetSequenceByName(seqName);
                    if (sunSequence == null)
                    {
                        return ui;
                    }
                    ui.Children = LoadSteps(sequence, group);
                    ui.Children = LoadSteps(sunSequence, group);
                }
            }
            if (CurrentSeqUI != null)
            {
                ui.SettingChanged += CurrentSeqUI.OnStepSettingChanged;
            }
            return ui;
        }
@@ -232,20 +253,19 @@
            {
                Filter = $"{LangUtil.Get("se.seq")}|*.seq;"
            };
            if (ofd.ShowDialog() == true)
            if (ofd.ShowDialog() == true && DoClose())
            {
                control.BackgroundAction(() =>
                {
                    try
                    {
                        var start = DateTime.Now;
                        currentFilePath = ofd.FileName;
                        CurrentFileName = $"{LangUtil.Get("se.currentFile")}{Path.GetFileName(currentFilePath)}";
                        CurrentFilePath = ofd.FileName;
                        if (sequenceFile != null)
                        {
                            engine.ReleaseSequenceFileEx(sequenceFile);
                        }
                        sequenceFile = engine.GetSequenceFileEx(currentFilePath);
                        sequenceFile = engine.GetSequenceFileEx(CurrentFilePath);
                        Sequence main = sequenceFile.GetSequenceByName("MainSequence");
                        CurrentSeqUI = new SeqUI(main);
@@ -257,15 +277,19 @@
                        //}
                        //Sequences = seqs;
                        if (Sequences != null && Sequences.Count > 0)
                        {
                            CurrentSeqUI = Sequences[0];
                        }
                        log.Info($"Open File [{currentFilePath}] In {(DateTime.Now - start).TotalMilliseconds}ms");
                        //if (Sequences != null && Sequences.Count > 0)
                        //{
                        //    CurrentSeqUI = Sequences[0];
                        //}
                        log.Info($"Open File [{CurrentFilePath}] In {(DateTime.Now - start).TotalMilliseconds}ms");
                    }
                    catch (Exception ex)
                    {
                        log.Error($"Open Sequence Failed.", ex);
                        control.Dispatcher.Invoke(() =>
                        {
                            MsgBoxHelper2.Error(ex.Message);
                        });
                    }
                }, LangUtil.Get("se.seqIsLoading"));
            }
@@ -274,54 +298,92 @@
        [RelayCommand]
        private void SaveSeqFile()
        {
            sequenceFile?.Save(currentFilePath);
            if (sequenceFile == null) return;
            try
            {
                sequenceFile?.Save(CurrentFilePath);
                CurrentSeqUI.IsChanged = false;
                ToastHelper.Success(LangUtil.Get("App.SaveSuccess"));
            }
            catch (Exception ex)
            {
                log.Error("Save Sequence File Error.", ex);
                ToastHelper.Error($"{LangUtil.Get("App.SaveFailed")}: {ex.Message}");
            }
        }
        [RelayCommand]
        private void SaveToFile()
        {
            if (sequenceFile == null) return;
            try
            {
                SaveFileDialog sfd = new SaveFileDialog()
                {
                    Filter = $"{LangUtil.Get("se.seq")}|*.seq;"
                };
                if (sfd.ShowDialog() == true)
                {
                    sequenceFile.Save(sfd.FileName);
                    CurrentFilePath = sfd.FileName;
                    ToastHelper.Success(LangUtil.Get("App.SaveSuccess"));
                    CurrentSeqUI.IsChanged = false;
                }
            }
            catch (Exception ex)
            {
                log.Error("Save Sequence File Error.", ex);
                ToastHelper.Error($"{LangUtil.Get("App.SaveFailed")}: {ex.Message}");
            }
        }
        [RelayCommand]
        private void Refresh()
        {
            if (CurrentSeqUI == null) return;
            if (string.IsNullOrEmpty(TargetType))
            {
                edits = new HashSet<string> {
                    StepTypeStr.PassFailTest,
                    StepTypeStr.NumericLimitTest,
                    StepTypeStr.NI_MultipleNumericLimitTest,
                    StepTypeStr.StringValueTest,
                    StepTypeStr.Action,
                    StepTypeStr.NI_Wait,
                    StepTypeStr.SequenceCall,
                };
            }
            else
            {
                edits = new HashSet<string>(TargetType.Split(','))
                {
                    StepTypeStr.SequenceCall
                };
            }
            AllSteps = LoadSteps(CurrentSeqUI.sequence);
            RefreshData();
        }
        [RelayCommand]
        private void CloseSeqFile()
        private bool DoClose()
        {
            //AllSteps = new ObservableCollection<StepUI>();
            if (CurrentSeqUI?.IsChanged == true)
            {
                if (MsgBoxHelper2.WarnChoose(LangUtil.Get("se.makeSureClose"))
                    == System.Windows.MessageBoxResult.OK)
                {
                    goto DoClose;
                }
                else
                {
                    return false;
                }
            }
        DoClose:
            if (engine != null && sequenceFile != null)
            {
                engine.ReleaseSequenceFileEx(sequenceFile);
                sequenceFile = null;
            }
            setupSteps.Clear();
            mainSteps.Clear();
            cleanupSteps.Clear();
            AllSteps = new();
            Sequences.Clear();
            currentFilePath = string.Empty;
            CurrentFilePath = string.Empty;
            CurrentFileName = string.Empty;
            CurrentSeqUI = null;
            CurrentStepUI = null;
            CurrentSeqTitle = LangUtil.Get("se.steps");
            sequenceCache.Clear();
            return true;
        }
        [RelayCommand]
        private void CloseSeqFile()
        {
            DoClose();
        }
        [RelayCommand]
@@ -330,21 +392,22 @@
            try
            {
                var group = CurrentStepUI.step.StepGroup;
                var targetSequence = CurrentStepUI.sequence;
                ObservableCollection<StepUI> source;
                if (group == StepGroups.StepGroup_Setup)
                {
                    source = setupSteps;
                }
                else if (group == StepGroups.StepGroup_Main)
                {
                    source = mainSteps;
                }
                else
                {
                    source = cleanupSteps;
                }
                int index = source.IndexOf(CurrentStepUI);
                ObservableCollection<StepUI> source = sequenceCache[targetSequence];
                //if (group == StepGroups.StepGroup_Setup)
                //{
                //    source = setupSteps;
                //}
                //else if (group == StepGroups.StepGroup_Main)
                //{
                //    source = mainSteps;
                //}
                //else
                //{
                //    source = cleanupSteps;
                //}
                int index = CurrentStepUI.IndexInSequence;
                if (index < 0)
                {
                    return;
@@ -355,14 +418,6 @@
                    case "Tests.PassFail":
                        newStep = engine.NewStep(AdapterKeyNames.FlexLVAdapterKeyName, StepTypeStr.PassFailTest);
                        newStep.Name = DefaultStepName.PassFailTest;
                        var p = newStep.AsPropertyObject();
                        string name = p.Name;
                        var ps = p.GetSubProperties("", 0);
                        foreach (var pp in ps)
                        {
                            string sname = pp.Name;
                        }
                        break;
                    case "Tests.NumericLimitTest":
                        newStep = engine.NewStep(AdapterKeyNames.FlexLVAdapterKeyName, StepTypeStr.NumericLimitTest);
@@ -408,12 +463,37 @@
                        newStep = engine.NewStep(AdapterKeyNames.PythonAdapterKeyName, StepTypeStr.Action);
                        newStep.Name = DefaultStepName.Action;
                        break;
                    default:
                        return;
                }
                CurrentSeqUI.sequence.InsertStep(newStep, index + 1, group);
                AllSteps = LoadSteps(CurrentSeqUI.sequence);
                var newUI = new StepUI(newStep, targetSequence);
                targetSequence.InsertStep(newStep, index + 1, group);
                ObservableCollection<StepUI> datasource = sequenceCache[targetSequence];
                int uiIndex = datasource.IndexOf(CurrentStepUI);
                datasource.Insert(uiIndex + 1, newUI);
                //var newItem = new TreeItemData
                //{
                //    Level = TreeItemData.Level,
                //    ParentItem = TreeItemData.ParentItem,
                //    ParentItems = TreeItemData.ParentItems,
                //    Data = newUI
                //};
                //var parentItem = TreeItemData.ParentItem;
                //if (parentItem != null)
                //{
                //    var items = parentItem.Items;
                //    int innerIndex = items.IndexOf(TreeItemData);
                //    items.Insert(innerIndex + 1, newItem);
                //    TreeItemDataCollection.ResetSubItems(items);
                //}
                //int itemIndex = TreeCollection.IndexOf(TreeItemData);
                //TreeCollection.Insert(itemIndex + TreeItemData.AllSubCount + 1, newItem);
            }
            catch (Exception ex)
            {
@@ -422,12 +502,96 @@
            }
        }
        [RelayCommand]
        private void Sb(RoutedPropertyChangedEventArgs<object> e)
        {
            CurrentStepUI = e.NewValue as StepUI;
        }
        static TreeListViewItem VisualUpwardSearch(DependencyObject source)
        {
            while (source != null && !(source is TreeViewItem))
                source = VisualTreeHelper.GetParent(source);
            return source as TreeListViewItem;
        }
        [RelayCommand]
        private void Sb2(MouseButtonEventArgs e)
        {
            var item = VisualUpwardSearch(e.OriginalSource as DependencyObject);
            if (item != null)
            {
                item.Focus();
                e.Handled = true;
            }
        }
        [ObservableProperty]
        private TreeItemData treeItemData;
        public void RefreshView()
        {
            CollectionViewSource cvs = control.Resources["TreeGridFilter"] as CollectionViewSource;
            control.Dispatcher.Invoke(() =>
            {
                cvs.View.Refresh();
            });
        }
        [ObservableProperty]
        private TreeItemDataCollection treeCollection = new TreeItemDataCollection();
        public void RefreshData()
        {
            sequenceCache.Clear();
            AllSteps = LoadSteps(CurrentSeqUI.sequence);
        }
        partial void OnAllStepsChanged(ObservableCollection<StepUI> data)
        {
            TreeCollection = new TreeItemDataCollection(AllSteps, "Children", true);
        }
        partial void OnTreeItemDataChanged(TreeItemData data)
        {
            CurrentStepUI = data?.Data as StepUI;
        }
        partial void OnTargetTypeChanged(string value)
        {
            if (string.IsNullOrEmpty(value))
            {
                edits = new HashSet<string>
                {
                    //StepTypeStr.PassFailTest,
                    //StepTypeStr.NumericLimitTest,
                    //StepTypeStr.NI_MultipleNumericLimitTest,
                    //StepTypeStr.StringValueTest,
                    //StepTypeStr.Action,
                    //StepTypeStr.NI_Wait,
                    //StepTypeStr.SequenceCall,
                };
            }
            else
            {
                edits = new HashSet<string>(value.Split(MultiComboBox.splits, StringSplitOptions.RemoveEmptyEntries))
                {
                    StepTypeStr.SequenceCall
                };
            }
        }
        partial void OnCurrentFilePathChanged(string value)
        {
            if (string.IsNullOrEmpty(value))
            {
                CurrentFileName = string.Empty;
            }
            else
            {
                CurrentFileName = $"{LangUtil.Get("se.currentFile")}{Path.GetFileName(value)}";
            }
        }
        partial void OnCurrentStepUIChanged(StepUI stepUI)
@@ -436,7 +600,6 @@
            control.editPanelContainer.Child = panel;
            if (stepUI != null)
            {
                //window.commonP.RefreshStep(stepUI);
            }
        }
@@ -446,7 +609,7 @@
            if (seqUI != null)
            {
                CurrentStepUI = null;
                AllSteps = LoadSteps(seqUI.sequence);
                RefreshData();
            }
            CurrentSeqTitle = $"{seqUI?.Name} {LangUtil.Get("se.steps")}";
        }
Plugin.SeqEditor/ViewModel/StepDropHandler.cs
New file
@@ -0,0 +1,139 @@
using GongSolutions.Wpf.DragDrop;
using HMI.UI;
using NationalInstruments.TestStand.Interop.API;
using Plugin.SeqEditor.Entity;
using System.Windows;
namespace Plugin.SeqEditor.ViewModel
{
    public class StepDropHandler : DefaultDropHandler
    {
        private SeqEditorViewModel model;
        public StepDropHandler(SeqEditorViewModel model)
        {
            this.model = model;
        }
        public bool CanConvert(IDropInfo dropInfo)
        {
            if (!(dropInfo.TargetItem is TreeItemData targetItem))
            {
                return false;
            }
            if (!(targetItem.Data is StepUI target))
            {
                return false;
            }
            if (!(dropInfo.Data is TreeItemData sourceItem))
            {
                return false;
            }
            if (!(sourceItem.Data is StepUI source))
            {
                return false;
            }
            if (targetItem.ParentItem != sourceItem.ParentItem)
            {
                return false;
            }
            if (source == target)
            {
                return false;
            }
            if (source.sequence != target.sequence)
            {
                return false;
            }
            return true;
        }
        public override void DragOver(IDropInfo dropInfo)
        {
            if (CanConvert(dropInfo))
            {
                dropInfo.Effects = DragDropEffects.Move;
                dropInfo.DropTargetHintState = DropHintState.Active;
            }
            else
            {
                dropInfo.DropTargetHintState = DropHintState.None;
            }
        }
        public override void Drop(IDropInfo dropInfo)
        {
            if (!CanConvert(dropInfo))
            {
                return;
            }
            var sourceItem = (TreeItemData)dropInfo.Data;
            var targetItem = (TreeItemData)dropInfo.TargetItem;
            var source = sourceItem.Data as StepUI;
            var target = targetItem.Data as StepUI;
            int sourceIndex = source.IndexInSequence;
            int targetIndex = target.IndexInSequence;
            TreeItemDataCollection collection = model.TreeCollection;
            List<TreeItemData> datasource;
            if (sourceItem.ParentItem == null)
            {
                datasource = collection.RootItems;
            }
            else
            {
                datasource = sourceItem.ParentItem.Items;
            }
            //ObservableCollection<TreeItemData> datasource =
            //   (ObservableCollection<TreeItemData>)dropInfo.TargetCollection.TryGetList();
            int uiSourceIndex = collection.IndexOf(sourceItem);
            int uiTargetIndex = collection.IndexOf(targetItem);
            if (uiSourceIndex == uiTargetIndex + 1)
            {
                return;
            }
            StepGroups group = target.StepGroup;
            if (sourceIndex == targetIndex) {
                return;
            }
            if (sourceIndex < targetIndex)
            {
                source.sequence.DeleteStep(sourceIndex, source.StepGroup);
                target.sequence.InsertStep(source.step, targetIndex, group);
                collection.Move(uiSourceIndex, uiTargetIndex);
                //datasource.RemoveAt(uiSourceIndex);
                //datasource.Insert(uiTargetIndex, sourceItem);
            }
            else if (sourceIndex > targetIndex)
            {
                source.sequence.DeleteStep(sourceIndex, source.StepGroup);
                target.sequence.InsertStep(source.step, targetIndex + 1, group);
                collection.Move(uiSourceIndex, uiTargetIndex);
            }
            model.CurrentSeqUI.IsChanged = true;
            //var task = Task.Run(() =>
            //{
            //    collection.ResetItems();
            //    model.TreeItemData = sourceItem;
            //});
            //task.Wait();
        }
    }
}
Plugin.SeqEditor/ViewModel/TreeListViewHandler.cs
New file
@@ -0,0 +1,119 @@
using GongSolutions.Wpf.DragDrop;
using NationalInstruments.TestStand.Interop.API;
using Plugin.SeqEditor.Entity;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
namespace Plugin.SeqEditor.ViewModel
{
    public class TreeListViewHandler : DefaultDropHandler
    {
        private SeqEditorViewModel model;
        public TreeListViewHandler(SeqEditorViewModel model)
        {
            this.model = model;
        }
        public bool CanConvert(IDropInfo dropInfo)
        {
            if (!(dropInfo.TargetItem is StepUI target))
            {
                return false;
            }
            if (!(dropInfo.Data is StepUI source))
            {
                return false;
            }
            if (source == target)
            {
                return false;
            }
            if (target.sequence != source.sequence)
            {
                return false;
            }
            return true;
        }
        public override void DragOver(IDropInfo dropInfo)
        {
            if (CanConvert(dropInfo))
            {
                dropInfo.Effects = DragDropEffects.Move;
                if (dropInfo.InsertPosition.HasFlag(RelativeInsertPosition.TargetItemCenter))
                {
                    dropInfo.DropTargetAdorner = null;
                }
                else
                {
                    dropInfo.DropTargetAdorner = DropTargetAdorners.Insert;
                }
                //if (dropInfo.DropTargetAdorner == DropTargetAdorners.Highlight) {
                //}
                //dropInfo.DropTargetAdorner = DropTargetAdorners.Insert;
                dropInfo.DropTargetHintState = DropHintState.Active;
                dropInfo.DropTargetHintAdorner = DropTargetAdorners.Hint;
            }
            else
            {
                dropInfo.Effects = DragDropEffects.None;
                dropInfo.DropTargetHintAdorner = DropTargetAdorners.Hint;
                dropInfo.DropTargetHintState = DropHintState.Error;
            }
        }
        public override void Drop(IDropInfo dropInfo)
        {
            if (!CanConvert(dropInfo))
            {
                return;
            }
            //var sourceItem = (TreeItemData)dropInfo.Data;
            //var targetItem = (TreeItemData)dropInfo.TargetItem;
            var source = dropInfo.Data as StepUI;
            var target = dropInfo.TargetItem as StepUI;
            int sourceIndex = source.IndexInSequence;
            int targetIndex = target.IndexInSequence;
            if (sourceIndex == targetIndex)
            {
                return;
            }
            bool insertBefore = dropInfo.InsertPosition.HasFlag(RelativeInsertPosition.BeforeTargetItem);
            if (!insertBefore && (sourceIndex == targetIndex + 1)) {
                return;
            }
            ObservableCollection<StepUI> datasource = model.sequenceCache[source.sequence];
            int uiSourceIndex = datasource.IndexOf(source);
            int uiTargetIndex = datasource.IndexOf(target);
            StepGroups group = target.StepGroup;
            if (sourceIndex < targetIndex)
            {
                source.sequence.DeleteStep(sourceIndex, source.StepGroup);
                target.sequence.InsertStep(source.step, targetIndex, group);
                datasource.Move(uiSourceIndex, uiTargetIndex);
            }
            else if (sourceIndex > targetIndex)
            {
                source.sequence.DeleteStep(sourceIndex, source.StepGroup);
                target.sequence.InsertStep(source.step, targetIndex, group);
                datasource.Move(uiSourceIndex, uiTargetIndex);
            }
        }
    }
}
Plugin.TestResult/DataTable.xaml
@@ -112,10 +112,13 @@
            <Grid.ColumnDefinitions>
                <ColumnDefinition/>
                <ColumnDefinition/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <Button Click="ScanWindow_Click" Style="{StaticResource PrimaryButton}"
        Content="{DynamicResource Tr.Scan}"/>
            <Button Grid.Column="1" Click="SetStationGlobal_Click" Style="{StaticResource SuccessButton}"
            <Button Grid.Column="1" Click="ClearAllSnr_Click" Style="{StaticResource DangerButton}"
Content="{DynamicResource Tr.ClearAllSNR}"/>
            <Button Grid.Column="2" Click="SetStationGlobal_Click" Style="{StaticResource SuccessButton}"
        Content="{DynamicResource App.Start}"/>
        </Grid>
        
Plugin.TestResult/DataTable.xaml.cs
@@ -1,4 +1,5 @@
using HMI.Core.Util;
using HMI.UI.Util;
using HMI.Util;
using Plugin.TestResult;
using Plugin.TestResult_WPF.Model;
@@ -257,6 +258,7 @@
            dutNo = this.index * this.cellCount + dutNo;
            string LookupStr = $"StationState.SN[{dutNo}]";
            ViewControl.Logger.Info($"PositionCode: [{e.positionCode}], SNR: [{e.snrCode}], LookUpStr: [{LookupStr}]");
            parent.engine.Globals.SetValString(LookupStr, 0, snrCode);
            parent.engine.CommitGlobalsToDisk(promptOnSaveConflicts: false);
@@ -369,6 +371,7 @@
        private void TcpServer_NewRequestReceived(TcpSession session, StringRequestInfo requestInfo)
        {
            string msg = requestInfo.Body;
            ViewControl.Logger.Info($"TCP Data From {session.RemoteEndPoint}: {msg}.");
            string[] datas_10015 = msg.Split(';');
            int cellIndex = int.Parse(datas_10015[1]);
@@ -518,5 +521,28 @@
            };
            setting.Open(true, this.title);
        }
        private void ClearAllSnr_Click(object sender, RoutedEventArgs e)
        {
            if (MsgBoxHelper.WarnMessage(Window.GetWindow(this), LangUtil.Get("Tr.MakeSureClearAllSNR")) == MessageBoxResult.OK)
            {
                try
                {
                    for (int i = 0; i < this.cellCount; i++)
                    {
                        int dutNo = this.index * this.cellCount + i;
                        string LookupStr = $"StationState.SN[{dutNo}]";
                        parent.engine.Globals.SetValString(LookupStr, 0, "");
                        GetSingleCell(i).SetStatus("waiting");
                    }
                    ToastHelper.Success(LangUtil.Get("App.DeleteSuccess"));
                }
                catch (Exception ex)
                {
                    ViewControl.Logger.Error("ClearAllSnr_Click Error.", ex);
                    MsgBoxHelper.Error(Window.GetWindow(this), ex.Message);
                }
            }
        }
    }
}
Plugin.TestResult/TestResultView.xaml.cs
@@ -1,6 +1,5 @@
using HMI.Core.Util;
using HMI.UI.Util;
using NationalInstruments.TestStand.Interop.AdapterAPI;
using NationalInstruments.TestStand.Interop.API;
using NationalInstruments.TestStand.Interop.UI.Ax;
using NationalInstruments.TestStand.Utility;
@@ -173,7 +172,6 @@
                this.testApp.LoginOnStart = false;
                this.testApp.Start();
                this.modeSeq = this.engine.GetSequenceFileEx(@"C:\Program Files\National Instruments\TestStand 2020\Components\Models\TestStandModels\ParallelModel.seq");
            }
            catch (Exception ex)
            {
Plugin.TsExecution/Plugin.TsExecution.csproj
@@ -136,6 +136,10 @@
    <Reference Include="WindowsFormsIntegration" />
  </ItemGroup>
  <ItemGroup>
    <Page Include="Scanner.xaml">
      <Generator>MSBuild:Compile</Generator>
      <SubType>Designer</SubType>
    </Page>
    <Page Include="SingleControl.xaml">
      <SubType>Designer</SubType>
      <Generator>MSBuild:Compile</Generator>
@@ -145,6 +149,10 @@
      <SubType>Designer</SubType>
    </Page>
    <Compile Include="Entity\TestStep.cs" />
    <Compile Include="Scanner.xaml.cs">
      <DependentUpon>Scanner.xaml</DependentUpon>
    </Compile>
    <Compile Include="ScannerManager.cs" />
    <Compile Include="Setting\TsSetting.cs" />
    <Compile Include="SingleControl.xaml.cs">
      <DependentUpon>SingleControl.xaml</DependentUpon>
Plugin.TsExecution/Scanner.xaml
New file
@@ -0,0 +1,77 @@
<pu:WindowX x:Class="Plugin.TsExecution.Scanner"
        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:Plugin.TsExecution"
        WindowStartupLocation="CenterScreen"
        Closing="Window_Closing"
        FontSize="20"
        mc:Ignorable="d"
        Style="{StaticResource ModalWindow}"
        xmlns:hb="clr-namespace:HMI.Base;assembly=HMI.Base"
        xmlns:uic="clr-namespace:HMI.UI.Converter;assembly=HMI.UI"
        xmlns:pw="https://opensource.panuon.com/wpf"
        xmlns:pu="https://opensource.panuon.com/wpf-ui"
        xmlns:dp="clr-namespace:HMI.UI.DpUtil;assembly=HMI.UI"
        xmlns:cc="clr-namespace:HMI.Core;assembly=HMI.Core"
        InteropOwnersMask="False"
        SizeToContent="Height"
        pu:WindowXModalDialog.Buttons="{x:Null}"
        Title="{Binding title}" Width="600">
    <Window.Resources>
        <Style TargetType="Label">
            <Setter Property="HorizontalAlignment" Value="Right"/>
            <Setter Property="FontSize" Value="{Binding ActualWidth,RelativeSource={RelativeSource AncestorType=Window},
Converter={StaticResource fc}, ConverterParameter='80,1920'}" />
        </Style>
        <Style TargetType="TextBox">
            <Setter Property="FontSize" Value="{Binding ActualWidth,RelativeSource={RelativeSource AncestorType=Window},
Converter={StaticResource fc}, ConverterParameter='100,1920'}" />
        </Style>
        <Style TargetType="Button" BasedOn="{StaticResource PrimaryButton}">
            <Setter Property="FontSize" Value="{Binding ActualWidth,RelativeSource={RelativeSource AncestorType=Window},
Converter={StaticResource fc}, ConverterParameter='20,1920'}" />
        </Style>
    </Window.Resources>
    <Grid Margin="10">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <!--<Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" HorizontalAlignment="Stretch" Padding="0"
               Content="{DynamicResource Tr.PleaseScanSnr}"
               Margin="0,0,0,20"
               />-->
        <GroupBox Grid.Row="1" Header="{DynamicResource Tr.PleaseScanSnr}" Padding="0,0,0,10" Grid.ColumnSpan="2" >
            <StackPanel>
                <pu:FormGroup
         Header="{DynamicResource Tr.PositionSnr}"
         dp:FormGroupHelper.IsRequired="True"
         Style="{StaticResource InputFormGroupStyle}">
                    <TextBox Style="{StaticResource EditorTextBoxStyle}" x:Name="positionText" TextWrapping="NoWrap"/>
                </pu:FormGroup>
                <pu:FormGroup
         Header="{DynamicResource Tr.ProductSnr}"
         dp:FormGroupHelper.IsRequired="True" Style="{StaticResource InputFormGroupStyle}">
                    <TextBox Style="{StaticResource EditorTextBoxStyle}" x:Name="snrText" TextWrapping="NoWrap"/>
                </pu:FormGroup>
            </StackPanel>
        </GroupBox>
        <Button Grid.Row="2" Click="Con_Click" Style="{StaticResource PrimaryButton}"
        Content="{DynamicResource Tr.ContinueScan}"/>
        <Button Grid.Row="2" Grid.Column="1" Click="Ok_Click" Style="{StaticResource SuccessButton}"
        Content="{DynamicResource Tr.FinishScan}"/>
    </Grid>
</pu:WindowX>
Plugin.TsExecution/Scanner.xaml.cs
New file
@@ -0,0 +1,209 @@
using HMI.Core.Util;
using HMI.UI.Entity;
using HMI.UI.Util;
using HMI.Util;
using Panuon.WPF.UI;
using System;
using System.ComponentModel;
using System.IO.Ports;
using System.Windows;
namespace Plugin.TsExecution
{
    public class BarcodeEventArg
    {
        public string name { get; private set; }
        public string positionCode { get; private set; }
        public string snrCode { get; private set; }
        public BarcodeEventArg(string name, string positionCode, string snrCode)
        {
            this.name = name;
            this.positionCode = positionCode;
            this.snrCode = snrCode;
        }
    }
    /// <summary>
    /// Scanner.xaml 的交互逻辑
    /// </summary>
    public partial class Scanner : WindowX
    {
        private string _title;
        public string title
        {
            get { return _title; }
            set => Set(ref _title, value);
        }
        public event EventHandler<BarcodeEventArg> FinishBarcode;
        SerialPort serialPort;
        public Scanner(string title)
        {
            this.title = title;
            this.DataContext = this;
            InitializeComponent();
            Loaded += Scanner_Loaded;
        }
        private void Scanner_Loaded(object sender, RoutedEventArgs e)
        {
            InitScanner();
            Loaded -= Scanner_Loaded;
        }
        /// <summary>
        /// 设置扫码枪
        /// </summary>
        private void InitScanner()
        {
            string comStr = "";
            if (serialPort != null) return;
            string section = this.Tag.ToString();
            ScannerConfig common = ScannerConfig.ReadFromIni("", "Config");
            string portName = IniFileHelper.ReadIniData(section, "PortName", "","");
            if (string.IsNullOrEmpty(portName))
            {
                if (section.StartsWith("A"))
                {
                    portName = "COM1";
                }
                else
                {
                    portName = "COM2";
                }
            }
            common.PortName = portName;
            // 配置串口
            serialPort = new SerialPort
            {
                PortName = common.PortName,
                BaudRate = common.BaudRate,
                Parity = Parity.None,
                DataBits = common.DataBits,
                StopBits = common.StopBits,
                Handshake = Handshake.None,
                DtrEnable = common.DtrEnable,
                RtsEnable = common.RtsEnable
                //ReadTimeout = 500,
                //WriteTimeout = 500,
                //ReciveBufferSize=4096
            };
            try
            {
                // 打开串口
                serialPort.Open();
                // 注册接收数据事件
                serialPort.DataReceived += SerialPort_DataReceived;
            }
            catch (Exception ex)
            {
                MsgBoxHelper.Error(this, $"{this.lang("Tr.InitGunFailed")}: {ex.StackTrace}");
            }
        }
        private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            try
            {
                SerialPort sp = (SerialPort)sender;
                string barcode = ReadDataFrom(sp);
                //string barcode =
                Dispatcher.Invoke(() =>
                {
                    if (snrText.IsFocused)
                    {
                        snrText.Text = barcode;
                        Con_Click(null, null);
                    }
                    else
                    {
                        positionText.Text = barcode;
                        //positionText.Focus();
                        //snrText.Text = "";
                        snrText.Focus();
                    }
                });
            }
            catch (Exception ex)
            {
                MessageBox.Show($"{this.lang("Tr.GetGunData")}: {ex.StackTrace}");
            }
        }
        private void Con_Click(object sender, RoutedEventArgs e)
        {
            Dispatcher.Invoke(() =>
            {
                string position = positionText.Text, snr = snrText.Text;
                if (string.IsNullOrEmpty(position) || string.IsNullOrEmpty(snr))
                {
                    ToastHelper.Warning(this, this.lang("Tr.PleaseInputSnrs"));
                    return;
                }
                try
                {
                    FinishBarcode.Invoke(this, new BarcodeEventArg(Tag.ToString(), position, snr));
                    //positionText.Text = "";
                    //snrText.Text = "";
                    positionText.Focus();
                }
                catch (Exception ex)
                {
                    ToastHelper.Warning(this, ex.Message);
                }
            });
        }
        private void Ok_Click(object sender, RoutedEventArgs e)
        {
            string position = positionText.Text, snr = snrText.Text;
            if (string.IsNullOrEmpty(position) || string.IsNullOrEmpty(snr))
            {
                ToastHelper.Warning(this, this.lang("Tr.PleaseInputSnrs"));
                return;
            }
            try
            {
                FinishBarcode.Invoke(this, new BarcodeEventArg(Tag.ToString(), position, snr));
                this.Close();
            }
            catch (Exception ex)
            {
                ToastHelper.Warning(this, ex.Message);
            }
        }
        private string ReadDataFrom(SerialPort sp)
        {
            string barcode = "";
            while (sp.BytesToRead > 0)
            {
                byte[] buffer = new byte[sp.BytesToRead];
                sp.Read(buffer, 0, buffer.Length);
                barcode += System.Text.Encoding.UTF8.GetString(buffer);
                System.Threading.Thread.Sleep(50);
            }
            return barcode;
        }
        private void Window_Closing(object sender, CancelEventArgs e)
        {
            try
            {
                serialPort.Close();
                serialPort = null;
            }
            catch (Exception ex)
            {
            }
        }
    }
}
Plugin.TsExecution/ScannerManager.cs
New file
@@ -0,0 +1,101 @@
using HMI.UI.Entity;
using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Windows.Controls;
namespace Plugin.TsExecution
{
    public class ScannerManager
    {
        public static Dictionary<string, SerialPort> map = new Dictionary<string, SerialPort>();
        public static Dictionary<SerialPort, Queue<TextBox>> controlMap = new Dictionary<SerialPort, Queue<TextBox>>();
        public static Dictionary<SerialPort, TextBox> focusMap = new Dictionary<SerialPort, TextBox>();
        public static Dictionary<TextBox, SerialPort> reverseMap = new Dictionary<TextBox, SerialPort>();
        public static void RegisterCom(ScannerConfig setting, TextBox tb)
        {
            if (!map.ContainsKey(setting.PortName))
            {
                // 配置串口
                var serialPort = new SerialPort
                {
                    PortName = setting.PortName,
                    BaudRate = setting.BaudRate,
                    Parity = Parity.None,
                    DataBits = setting.DataBits,
                    StopBits = setting.StopBits,
                    Handshake = Handshake.None,
                    DtrEnable = setting.DtrEnable,
                    RtsEnable = setting.RtsEnable
                    //ReadTimeout = 500,
                    //WriteTimeout = 500,
                    //ReciveBufferSize=4096
                };
                try
                {
                    // 打开串口
                    serialPort.Open();
                    map[setting.PortName] = serialPort;
                    controlMap[serialPort] = new Queue<TextBox>();
                    reverseMap[tb] = serialPort;
                    tb.GotFocus += Tb_GotFocus;
                    controlMap[serialPort].Enqueue(tb);
                    // 注册接收数据事件
                    serialPort.DataReceived += SerialPort_DataReceived; ;
                }
                catch (Exception ex)
                {
                }
            }
            else {
            }
        }
        private static string ReadDataFrom(SerialPort sp)
        {
            string barcode = "";
            while (sp.BytesToRead > 0)
            {
                byte[] buffer = new byte[sp.BytesToRead];
                sp.Read(buffer, 0, buffer.Length);
                barcode += System.Text.Encoding.UTF8.GetString(buffer);
                System.Threading.Thread.Sleep(50);
            }
            return barcode;
        }
        private static void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            var sp = sender as SerialPort;
            string barcode = ReadDataFrom(sp);
            TextBox tb;
            if (focusMap.ContainsKey(sp))
            {
                tb = focusMap[sp];
            }
            else
            {
                tb = controlMap[sp].Dequeue();
            }
            //string barcode =
            tb.Dispatcher.Invoke(() =>
                {
                    tb.Text = barcode;
                });
        }
        private static void Tb_GotFocus(object sender, System.Windows.RoutedEventArgs e)
        {
            var tb = sender as TextBox;
            SerialPort sp = reverseMap[tb];
            focusMap[sp] = tb;
        }
    }
}
Plugin.TsExecution/SingleControl.xaml
@@ -16,23 +16,28 @@
             x:Name="control"
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Resources>
        <sys:Double x:Key="nanD">NaN</sys:Double>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="/HMI.UI;component/Style/PanuonDataGrid.xaml"/>
            </ResourceDictionary.MergedDictionaries>
            <sys:Double x:Key="nanD">NaN</sys:Double>
        <Style TargetType="Label" x:Key="baseLabel">
            <Setter Property="HorizontalContentAlignment" Value="Left"/>
            <Setter Property="VerticalContentAlignment" Value="Center"/>
            <Setter Property="Padding" Value="5"/>
            <Setter Property="FontSize" Value="{Binding ActualWidth,ElementName=control,
            <Style TargetType="Label" x:Key="baseLabel">
                <Setter Property="HorizontalContentAlignment" Value="Left"/>
                <Setter Property="VerticalContentAlignment" Value="Center"/>
                <Setter Property="Padding" Value="5"/>
                <Setter Property="FontSize" Value="{Binding ActualWidth,ElementName=control,
Converter={StaticResource fc}, ConverterParameter='20,1093,20'}"/>
        </Style>
        <Style TargetType="TextBlock" x:Key="baseText">
            <Setter Property="HorizontalAlignment" Value="Left"/>
            <Setter Property="VerticalAlignment" Value="Center"/>
            <Setter Property="Padding" Value="0,0,2,0"/>
            <Setter Property="FontSize" Value="{Binding ActualWidth,ElementName=control,
            </Style>
            <Style TargetType="TextBlock" x:Key="baseText">
                <Setter Property="HorizontalAlignment" Value="Left"/>
                <Setter Property="VerticalAlignment" Value="Center"/>
                <Setter Property="Padding" Value="0,0,2,0"/>
                <Setter Property="FontSize" Value="{Binding ActualWidth,ElementName=control,
Converter={StaticResource fc}, ConverterParameter='20,1093,20'}"/>
        </Style>
        <Brush x:Key="passedColor">#beed00</Brush>
            </Style>
            <Brush x:Key="passedColor">#beed00</Brush>
        </ResourceDictionary>
    </UserControl.Resources>
    <Grid>
@@ -312,6 +317,19 @@
                    <Label Padding="0" FontSize="12" VerticalContentAlignment="Center" Content="{Binding CurrentStep}"/>
                    <StackPanel DockPanel.Dock="Right" Orientation="Horizontal" Margin="0,0,5,0">
                        <!--<Button FontSize="12" x:Name="scannerSetting"
                                Margin="0,0,10,0" Click="Button_Click" Content="扫码枪设置">
                            <Button.Style>
                                <Style TargetType="Button" BasedOn="{StaticResource LinkButton}">
                                    <Style.Triggers>
                                        <DataTrigger Binding="{Binding IsAutoMode}" Value="True">
                                            <Setter Property="Visibility" Value="Collapsed"/>
                                        </DataTrigger>
                                    </Style.Triggers>
                                </Style>
                            </Button.Style>
                        </Button>-->
                        <Label Padding="0" FontSize="12" 
VerticalAlignment="Center"
VerticalContentAlignment="Center" Content="{DynamicResource Ts.ProbeCount}"/>
@@ -381,6 +399,44 @@
            </Border>
        </Canvas>
        <Canvas Grid.Row="0" Grid.RowSpan="5" Visibility="Collapsed"
        Background="#AAFFFFFF" x:Name="scannerPanel" SizeChanged="pausePanel_Loaded">
            <Border BorderBrush="#000" Background="#FFF" BorderThickness="0.5" CornerRadius="5" x:Name="scannerVisualModal">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="Auto"/>
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto"/>
                        <ColumnDefinition />
                    </Grid.ColumnDefinitions>
                    <Border Padding="10,5,0,5" Grid.ColumnSpan="2" Background="{StaticResource PrimaryColor}"
                   CornerRadius="5,5,0,0">
                        <Label Padding="0" Margin="0" Foreground="#FFF" Content="请扫码"/>
                    </Border>
                    <pu:FormGroup Margin="20,20,20,0" Grid.Row="1" Grid.ColumnSpan="2"
                       FontSize="{Binding ActualWidth,ElementName=control,
Converter={StaticResource fc}, ConverterParameter='20,1093,20'}" Orientation="Horizontal" Header="SNR">
                        <TextBox Width="{Binding ActualWidth,ElementName=control,
Converter={StaticResource fc}, ConverterParameter='180,1093'}"
                                 FontSize="{Binding ActualWidth,ElementName=control,
Converter={StaticResource fc}, ConverterParameter='15,1093,15'}"
                                 x:Name="TbManualSnr" Style="{StaticResource EditorTextBoxStyle}"/>
                    </pu:FormGroup>
                    <StackPanel Margin="10" Grid.Row="2" Grid.ColumnSpan="2" Orientation="Horizontal" HorizontalAlignment="Right">
                        <Button Style="{StaticResource ModalButton}"
                        x:Name="scannerOkBtn"
                        Click="Button_Click"
                        Background="{DynamicResource PrimaryColor}"
                        Tag="OK" Content="{DynamicResource App.Continue}"/>
                    </StackPanel>
                </Grid>
            </Border>
        </Canvas>
        <!--<DockPanel Grid.Row="4">
            <StatusBar>
Plugin.TsExecution/SingleControl.xaml.cs
@@ -1,4 +1,7 @@
using HMI.Core.Util;
using HMI.Core.Manager;
using HMI.Core.Util;
using HMI.UI;
using HMI.UI.Util;
using HMI.Util;
using NationalInstruments.TestStand.Interop.API;
using Plugin.TsExecution.Entity;
@@ -7,6 +10,7 @@
using System.ComponentModel;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Timers;
using System.Windows;
using System.Windows.Controls;
@@ -58,15 +62,15 @@
            }
        }
        private double passNum;
        public double PassNum
        private int passNum;
        public int PassNum
        {
            get => passNum;
            private set { Set(ref passNum, value); }
        }
        private double failNum;
        public double FailNum
        private int failNum;
        public int FailNum
        {
            get => failNum;
            private set { Set(ref failNum, value); }
@@ -135,7 +139,7 @@
        private DateTime TimeNow = new DateTime();
        private TimeSpan TimeCount = new TimeSpan();
        private Timer refreshTimer;
        private System.Timers.Timer refreshTimer;
        private string currentStep = "等待扫码";
        public string CurrentStep
@@ -198,6 +202,16 @@
        public int ProbeWarn { get; private set; }
        private bool isAutoMode;
        public bool IsAutoMode
        {
            get => isAutoMode;
            set
            {
                Set(ref isAutoMode, value);
            }
        }
        public SingleControl(int Index, string CountIniPath, int probeWarn)
        {
            this.CountIniPath = CountIniPath;
@@ -205,7 +219,7 @@
            this.index = Index;
            ProbeWarn = probeWarn;
            refreshTimer = new Timer()
            refreshTimer = new System.Timers.Timer()
            {
                Interval = 100
            };
@@ -213,6 +227,15 @@
            ReadProbeCount();
            ReadCounter();
            InitializeComponent();
            try
            {
                var value = ParameterManager.GetCommonParameterByName("AutoTestMode");
                IsAutoMode = bool.Parse(value.Value);
            }
            catch
            {
                IsAutoMode = true;
            }
            //if (ProbeWarn > 0)
            //{
            //    probeWarnPanel.Visibility = Visibility.Visible;
@@ -328,16 +351,20 @@
            if (result == true)
            {
                PassNum += 1;
                //Interlocked.Increment(ref passNum);
                //Set("PassNum");
            }
            else if (result == false)
            {
                FailNum += 1;
                //Interlocked.Increment(ref failNum);
                //Set("FailNum");
            }
            TotalNum = PassNum + FailNum;
            if (TotalNum != 0)
            {
                PassRate = (PassNum / TotalNum);
                PassRate = ((double)PassNum / TotalNum);
            }
            else
            {
@@ -462,17 +489,57 @@
                    SequenceFileContext?.StationGlobals.SetValBoolean($"StationState.IsContinue_{index}", 1, true);
                    pausePanel.Visibility = Visibility.Collapsed;
                    break;
                case "scannerSetting":
                    //ScannerSetting setting = new ScannerSetting()
                    //{
                    //    Owner = Window.GetWindow(this)
                    //};
                    //setting.Open(true, $"Socket{index}", CountIniPath);
                    ShowScannerPanel();
                    break;
                case "scannerOkBtn":
                    SendSnr2Ts();
                    break;
                default:
                    break;
            }
        }
        public void ShowScannerPanel()
        {
            scannerPanel.Visibility = Visibility.Visible;
        }
        private void SendSnr2Ts()
        {
            try
            {
                string snr = TbManualSnr.Text;
                SequenceFileContext?.StationGlobals.SetValString($"StationState.SN[{index}]", 1, snr);
                scannerPanel.Visibility = Visibility.Collapsed;
                TbManualSnr.Text = string.Empty;
            }
            catch (Exception ex)
            {
                ToastHelper.Error(ex.Message);
            }
        }
        private void pausePanel_Loaded(object sender, RoutedEventArgs e)
        {
            var can = pausePanel;
            var modal = visualModal;
            Canvas.SetLeft(modal, (pausePanel.ActualWidth - visualModal.ActualWidth) / 2);
            Canvas.SetTop(modal, (pausePanel.ActualHeight - visualModal.ActualHeight) / 2);
            if (sender == pausePanel)
            {
                var can = pausePanel;
                var modal = visualModal;
                Canvas.SetLeft(modal, (pausePanel.ActualWidth - visualModal.ActualWidth) / 2);
                Canvas.SetTop(modal, (pausePanel.ActualHeight - visualModal.ActualHeight) / 2);
            }
            else
            {
                var can = scannerPanel;
                var modal = scannerVisualModal;
                Canvas.SetLeft(modal, (scannerPanel.ActualWidth - scannerVisualModal.ActualWidth) / 2);
                Canvas.SetTop(modal, (scannerPanel.ActualHeight - scannerVisualModal.ActualHeight) / 2);
            }
        }
    }
}
Plugin.TsExecution/TsExecution.xaml.cs
@@ -18,12 +18,10 @@
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Timers;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Forms.DataVisualization.Charting;
using System.Xml.Linq;
namespace Plugin.TsExecution
{
SimpleUtil/HMI.LogUtil/HMI.LogUtil.csproj
@@ -30,6 +30,24 @@
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
    <DebugSymbols>true</DebugSymbols>
    <OutputPath>bin\x64\Debug\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <DebugType>full</DebugType>
    <PlatformTarget>x64</PlatformTarget>
    <LangVersion>7.3</LangVersion>
    <ErrorReport>prompt</ErrorReport>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
    <OutputPath>bin\x64\Release\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <Optimize>true</Optimize>
    <DebugType>pdbonly</DebugType>
    <PlatformTarget>x64</PlatformTarget>
    <LangVersion>7.3</LangVersion>
    <ErrorReport>prompt</ErrorReport>
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="System" />
    <Reference Include="System.Core" />
SimpleUtil/HMI.Util.Base/HMI.Util.Base.csproj
@@ -30,6 +30,24 @@
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
    <DebugSymbols>true</DebugSymbols>
    <OutputPath>bin\x64\Debug\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <DebugType>full</DebugType>
    <PlatformTarget>x64</PlatformTarget>
    <LangVersion>7.3</LangVersion>
    <ErrorReport>prompt</ErrorReport>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
    <OutputPath>bin\x64\Release\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <Optimize>true</Optimize>
    <DebugType>pdbonly</DebugType>
    <PlatformTarget>x64</PlatformTarget>
    <LangVersion>7.3</LangVersion>
    <ErrorReport>prompt</ErrorReport>
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="System" />
    <Reference Include="System.Core" />
VisionBuilder.Run/VisionBuilder.Run.csproj
@@ -31,6 +31,24 @@
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
    <DebugSymbols>true</DebugSymbols>
    <OutputPath>bin\x64\Debug\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <DebugType>full</DebugType>
    <PlatformTarget>x64</PlatformTarget>
    <LangVersion>7.3</LangVersion>
    <ErrorReport>prompt</ErrorReport>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
    <OutputPath>bin\x64\Release\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <Optimize>true</Optimize>
    <DebugType>pdbonly</DebugType>
    <PlatformTarget>x64</PlatformTarget>
    <LangVersion>7.3</LangVersion>
    <ErrorReport>prompt</ErrorReport>
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="System" />
    <Reference Include="System.Data" />
WPFProject/App.xaml
@@ -7,6 +7,7 @@
             xmlns:uic="clr-namespace:HMI.UI.Converter;assembly=HMI.UI"
             xmlns:hc="clr-namespace:HMI.Core;assembly=HMI.Core"
             ShutdownMode="OnExplicitShutdown"
             xmlns:sys="clr-namespace:System;assembly=mscorlib"
             Startup="Application_Startup">
    <Application.Resources>
        <ResourceDictionary>
@@ -18,6 +19,7 @@
                <ResourceDictionary Source="/HMI.UI;component/Style/PanuonResource.xaml"/>
                <ResourceDictionary Source="/HMI.UI;component/Style/MsgBoxStyle.xaml"/>
            </ResourceDictionary.MergedDictionaries>
            <sys:Double x:Key="nanD">NaN</sys:Double>
            <Style TargetType="{x:Type pu:FormGroup}" BasedOn="{StaticResource InputFormGroupStyle}"></Style>
            <uic:PathImageConverter x:Key="path2Image"/>
            <uic:DataGridRowNumberConverter x:Key="rowNumber"/>
WPFProject/Lang/en-US/SeqEditor.json
@@ -24,6 +24,7 @@
  "se.seqIsLoading": "Sequence is loading",
  "se.compType": "Comparison Type",
  "se.targetVal": "Target Val",
  "se.makeSureClose": "Current sequence is not saved,make sure you want to close.",
  "se.data.none": "None",
  "se.strComp.SCCS": "String Comparsion - Case Sensitive",
WPFProject/Lang/en-US/TestResult.json
@@ -36,5 +36,7 @@
  "Tr.DtrEnable": "DtrEnable",
  "Tr.RtsEnable": "RtsEnable",
  "Tr.CommonSetting": "Common Setting",
  "Tr.ScannerSetting": " Scanner Setting"
  "Tr.ScannerSetting": " Scanner Setting",
  "Tr.ClearAllSNR": "Clear All SNR",
  "Tr.MakeSureClearAllSNR": "Are you sure you want to clear all SNR?"
}
WPFProject/Lang/zh-CN/SeqEditor.json
@@ -24,6 +24,7 @@
  "se.seqIsLoading": "正在加载测试序列",
  "se.compType": "比较方式",
  "se.targetVal": "期望值",
  "se.makeSureClose": "当前文件未保存,确认要关闭吗?",
  "se.data.none": "无",
  "se.strComp.SCCS": "比较字符串-大小写敏感",
WPFProject/Lang/zh-CN/TestResult.json
@@ -36,5 +36,7 @@
  "Tr.DtrEnable": "启用DTR",
  "Tr.RtsEnable": "启用RTS",
  "Tr.CommonSetting": "通用设置",
  "Tr.ScannerSetting": "扫码枪设置"
  "Tr.ScannerSetting": "扫码枪设置",
  "Tr.ClearAllSNR": "清空条码",
  "Tr.MakeSureClearAllSNR": "确定要清空条码吗?"
}
WPFProject/WPFProject.sln
@@ -123,8 +123,8 @@
        {DE4205C6-5923-46FD-A4B9-65169BF61C75}.Release|x64.Build.0 = Release|Any CPU
        {7C299AED-88CC-437E-A68B-1550AA72E6B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
        {7C299AED-88CC-437E-A68B-1550AA72E6B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
        {7C299AED-88CC-437E-A68B-1550AA72E6B0}.Debug|x64.ActiveCfg = Debug|Any CPU
        {7C299AED-88CC-437E-A68B-1550AA72E6B0}.Debug|x64.Build.0 = Debug|Any CPU
        {7C299AED-88CC-437E-A68B-1550AA72E6B0}.Debug|x64.ActiveCfg = Debug|x64
        {7C299AED-88CC-437E-A68B-1550AA72E6B0}.Debug|x64.Build.0 = Debug|x64
        {7C299AED-88CC-437E-A68B-1550AA72E6B0}.Release|Any CPU.ActiveCfg = Release|Any CPU
        {7C299AED-88CC-437E-A68B-1550AA72E6B0}.Release|Any CPU.Build.0 = Release|Any CPU
        {7C299AED-88CC-437E-A68B-1550AA72E6B0}.Release|x64.ActiveCfg = Release|Any CPU
@@ -235,16 +235,16 @@
        {306A83DC-A7DB-4057-BAA7-477D8756F5D9}.Release|x64.Build.0 = Release|Any CPU
        {D0806FA6-3F6D-4304-81B8-EAD9DA7CB5B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
        {D0806FA6-3F6D-4304-81B8-EAD9DA7CB5B2}.Debug|Any CPU.Build.0 = Debug|Any CPU
        {D0806FA6-3F6D-4304-81B8-EAD9DA7CB5B2}.Debug|x64.ActiveCfg = Debug|Any CPU
        {D0806FA6-3F6D-4304-81B8-EAD9DA7CB5B2}.Debug|x64.Build.0 = Debug|Any CPU
        {D0806FA6-3F6D-4304-81B8-EAD9DA7CB5B2}.Debug|x64.ActiveCfg = Debug|x64
        {D0806FA6-3F6D-4304-81B8-EAD9DA7CB5B2}.Debug|x64.Build.0 = Debug|x64
        {D0806FA6-3F6D-4304-81B8-EAD9DA7CB5B2}.Release|Any CPU.ActiveCfg = Release|Any CPU
        {D0806FA6-3F6D-4304-81B8-EAD9DA7CB5B2}.Release|Any CPU.Build.0 = Release|Any CPU
        {D0806FA6-3F6D-4304-81B8-EAD9DA7CB5B2}.Release|x64.ActiveCfg = Release|Any CPU
        {D0806FA6-3F6D-4304-81B8-EAD9DA7CB5B2}.Release|x64.Build.0 = Release|Any CPU
        {E5740BB5-9FC6-4A20-B7C2-CC90C281ED05}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
        {E5740BB5-9FC6-4A20-B7C2-CC90C281ED05}.Debug|Any CPU.Build.0 = Debug|Any CPU
        {E5740BB5-9FC6-4A20-B7C2-CC90C281ED05}.Debug|x64.ActiveCfg = Debug|Any CPU
        {E5740BB5-9FC6-4A20-B7C2-CC90C281ED05}.Debug|x64.Build.0 = Debug|Any CPU
        {E5740BB5-9FC6-4A20-B7C2-CC90C281ED05}.Debug|x64.ActiveCfg = Debug|x64
        {E5740BB5-9FC6-4A20-B7C2-CC90C281ED05}.Debug|x64.Build.0 = Debug|x64
        {E5740BB5-9FC6-4A20-B7C2-CC90C281ED05}.Release|Any CPU.ActiveCfg = Release|Any CPU
        {E5740BB5-9FC6-4A20-B7C2-CC90C281ED05}.Release|Any CPU.Build.0 = Release|Any CPU
        {E5740BB5-9FC6-4A20-B7C2-CC90C281ED05}.Release|x64.ActiveCfg = Release|Any CPU
@@ -259,8 +259,8 @@
        {30201EC1-9A82-48DB-94B9-BD5C1190228B}.Release|x64.Build.0 = Release|Any CPU
        {BE42AB26-3270-43E1-84B8-6929D862FF8D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
        {BE42AB26-3270-43E1-84B8-6929D862FF8D}.Debug|Any CPU.Build.0 = Debug|Any CPU
        {BE42AB26-3270-43E1-84B8-6929D862FF8D}.Debug|x64.ActiveCfg = Debug|Any CPU
        {BE42AB26-3270-43E1-84B8-6929D862FF8D}.Debug|x64.Build.0 = Debug|Any CPU
        {BE42AB26-3270-43E1-84B8-6929D862FF8D}.Debug|x64.ActiveCfg = Debug|x64
        {BE42AB26-3270-43E1-84B8-6929D862FF8D}.Debug|x64.Build.0 = Debug|x64
        {BE42AB26-3270-43E1-84B8-6929D862FF8D}.Release|Any CPU.ActiveCfg = Release|Any CPU
        {BE42AB26-3270-43E1-84B8-6929D862FF8D}.Release|Any CPU.Build.0 = Release|Any CPU
        {BE42AB26-3270-43E1-84B8-6929D862FF8D}.Release|x64.ActiveCfg = Release|Any CPU
@@ -283,24 +283,24 @@
        {6735FDB8-B471-41C3-93CB-E8ADBD0989F0}.Release|x64.Build.0 = Release|Any CPU
        {6D0AAB08-0F3E-48CE-B755-9D29BC8F1225}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
        {6D0AAB08-0F3E-48CE-B755-9D29BC8F1225}.Debug|Any CPU.Build.0 = Debug|Any CPU
        {6D0AAB08-0F3E-48CE-B755-9D29BC8F1225}.Debug|x64.ActiveCfg = Debug|Any CPU
        {6D0AAB08-0F3E-48CE-B755-9D29BC8F1225}.Debug|x64.Build.0 = Debug|Any CPU
        {6D0AAB08-0F3E-48CE-B755-9D29BC8F1225}.Debug|x64.ActiveCfg = Debug|x64
        {6D0AAB08-0F3E-48CE-B755-9D29BC8F1225}.Debug|x64.Build.0 = Debug|x64
        {6D0AAB08-0F3E-48CE-B755-9D29BC8F1225}.Release|Any CPU.ActiveCfg = Release|Any CPU
        {6D0AAB08-0F3E-48CE-B755-9D29BC8F1225}.Release|Any CPU.Build.0 = Release|Any CPU
        {6D0AAB08-0F3E-48CE-B755-9D29BC8F1225}.Release|x64.ActiveCfg = Release|Any CPU
        {6D0AAB08-0F3E-48CE-B755-9D29BC8F1225}.Release|x64.Build.0 = Release|Any CPU
        {D59E25D1-9B27-46A1-8438-92661DD23FAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
        {D59E25D1-9B27-46A1-8438-92661DD23FAE}.Debug|Any CPU.Build.0 = Debug|Any CPU
        {D59E25D1-9B27-46A1-8438-92661DD23FAE}.Debug|x64.ActiveCfg = Debug|Any CPU
        {D59E25D1-9B27-46A1-8438-92661DD23FAE}.Debug|x64.Build.0 = Debug|Any CPU
        {D59E25D1-9B27-46A1-8438-92661DD23FAE}.Debug|x64.ActiveCfg = Debug|x64
        {D59E25D1-9B27-46A1-8438-92661DD23FAE}.Debug|x64.Build.0 = Debug|x64
        {D59E25D1-9B27-46A1-8438-92661DD23FAE}.Release|Any CPU.ActiveCfg = Release|Any CPU
        {D59E25D1-9B27-46A1-8438-92661DD23FAE}.Release|Any CPU.Build.0 = Release|Any CPU
        {D59E25D1-9B27-46A1-8438-92661DD23FAE}.Release|x64.ActiveCfg = Release|Any CPU
        {D59E25D1-9B27-46A1-8438-92661DD23FAE}.Release|x64.Build.0 = Release|Any CPU
        {968FE602-22D6-42BD-981C-601AD518A896}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
        {968FE602-22D6-42BD-981C-601AD518A896}.Debug|Any CPU.Build.0 = Debug|Any CPU
        {968FE602-22D6-42BD-981C-601AD518A896}.Debug|x64.ActiveCfg = Debug|Any CPU
        {968FE602-22D6-42BD-981C-601AD518A896}.Debug|x64.Build.0 = Debug|Any CPU
        {968FE602-22D6-42BD-981C-601AD518A896}.Debug|x64.ActiveCfg = Debug|x64
        {968FE602-22D6-42BD-981C-601AD518A896}.Debug|x64.Build.0 = Debug|x64
        {968FE602-22D6-42BD-981C-601AD518A896}.Release|Any CPU.ActiveCfg = Release|Any CPU
        {968FE602-22D6-42BD-981C-601AD518A896}.Release|Any CPU.Build.0 = Release|Any CPU
        {968FE602-22D6-42BD-981C-601AD518A896}.Release|x64.ActiveCfg = Release|Any CPU
@@ -311,8 +311,8 @@
        {717FD815-FF95-4F30-B4D9-9CEA978CA40E}.Debug|x64.Build.0 = Debug|x64
        {717FD815-FF95-4F30-B4D9-9CEA978CA40E}.Release|Any CPU.ActiveCfg = Release|Any CPU
        {717FD815-FF95-4F30-B4D9-9CEA978CA40E}.Release|Any CPU.Build.0 = Release|Any CPU
        {717FD815-FF95-4F30-B4D9-9CEA978CA40E}.Release|x64.ActiveCfg = Release|Any CPU
        {717FD815-FF95-4F30-B4D9-9CEA978CA40E}.Release|x64.Build.0 = Release|Any CPU
        {717FD815-FF95-4F30-B4D9-9CEA978CA40E}.Release|x64.ActiveCfg = Release|x64
        {717FD815-FF95-4F30-B4D9-9CEA978CA40E}.Release|x64.Build.0 = Release|x64
    EndGlobalSection
    GlobalSection(SolutionProperties) = preSolution
        HideSolutionNode = FALSE