From 53e656200368a983e563550e2cc1acbc6d86b729 Mon Sep 17 00:00:00 2001
From: chr <chrry550@outlook.com>
Date: 星期三, 08 四月 2026 19:57:14 +0800
Subject: [PATCH] 完善代码

---
 OpenTap/Engine/TestStepList.cs                             |    3 
 Editor/UI/VariableSelector.xaml                            |   23 ++
 OpenTap/BasicSteps/SequenceCallStep.cs                     |   33 ++-
 Plugin/AddInPlugin/Util/TestPlanRunAddIn.cs                |    3 
 OpenTap/Engine/TestStep.cs                                 |   20 ++
 Editor/SequencePanel.xaml.cs                               |    3 
 OpenTap/Engine/Addin/VariableContainer.cs                  |    6 
 OpenTap/Engine/Addin/FileGlobalsContext.cs                 |   26 +++
 Editor/Provider/GridControlProvider.cs                     |   25 ++
 OpenTap/Engine/TestPlanRun.cs                              |   37 +++-
 OpenTap/Engine/Addin/Annotation/TreeDataAttribute.cs       |    4 
 OpenTap/Engine/Addin/LocalsContext.cs                      |   17 ++
 OpenTap/Engine/TestPlanExecution.cs                        |    7 
 Editor/UI/VariableTextBox.xaml.cs                          |   60 +++++++
 Editor/UI/VariablesControl.xaml.cs                         |    8 
 Editor/MainWindow.xaml.cs                                  |    2 
 Editor/UI/VariableTextBox.xaml                             |   17 ++
 OpenTap/Engine/Addin/SequenceContext.cs                    |   14 +
 OpenTap/Engine/Addin/VariableContext.cs                    |   15 +
 OpenTap/Engine/SerializerPlugins/TestStepListSerializer.cs |   69 ++++++--
 Plugin/AddInPlugin/AddInPlugin.csproj                      |    6 
 OpenTap/Engine/TestPlan.cs                                 |   17 ++
 Editor/OpenTapEditor.csproj                                |   15 +
 Editor/UI/VariableSelector.xaml.cs                         |   50 ++++++
 24 files changed, 420 insertions(+), 60 deletions(-)

diff --git a/Editor/MainWindow.xaml.cs b/Editor/MainWindow.xaml.cs
index 5709e96..1a70347 100644
--- a/Editor/MainWindow.xaml.cs
+++ b/Editor/MainWindow.xaml.cs
@@ -88,7 +88,7 @@
         };
         child.Click += (sender, e) =>
         {
-            var testPlan = new TestPlan();
+            var testPlan = new TestPlan() { VisualPath = new Guid().ToString() };
             var tabItem = new TabItemData
             {
                 Content = new SequencePanel("New Seq.TapPlan", testPlan)
diff --git a/Editor/OpenTapEditor.csproj b/Editor/OpenTapEditor.csproj
index 7f4eb51..25797ee 100644
--- a/Editor/OpenTapEditor.csproj
+++ b/Editor/OpenTapEditor.csproj
@@ -3,10 +3,12 @@
   <PropertyGroup>
     <OutputType>WinExe</OutputType>
     <TargetFramework>net8.0-windows7.0</TargetFramework>
+	<!--<TargetFramework>netstandard2.0</TargetFramework>-->
     <Nullable>enable</Nullable>
     <LangVersion>10.0</LangVersion>
     <ImplicitUsings>enable</ImplicitUsings>
     <UseWPF>true</UseWPF>
+    <SupportedOSPlatformVersion>7.0</SupportedOSPlatformVersion>
   </PropertyGroup>
 
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
@@ -28,11 +30,11 @@
   </ItemGroup>
 
   <ItemGroup>
+    <ProjectReference Include="..\..\..\VisualStudio\UILib\UILib.csproj" />
+    <ProjectReference Include="..\..\..\VisualStudio\UtilLib\UtilLib.csproj" />
     <ProjectReference Include="..\OpenTap\BasicSteps\Tap.Plugins.BasicSteps.csproj" />
     <ProjectReference Include="..\opentap\Engine\Tap.Engine.csproj" />
     <ProjectReference Include="..\Plugin\AddInPlugin\AddInPlugin.csproj" />
-    <ProjectReference Include="..\UILib\UILib.csproj" />
-    <ProjectReference Include="..\UtilLib\UtilLib.csproj" />
   </ItemGroup>
 
   <ItemGroup>
@@ -42,9 +44,18 @@
   </ItemGroup>
 
   <ItemGroup>
+    <Compile Update="UI\VariableTextBox.xaml.cs">
+      <SubType>Code</SubType>
+    </Compile>
+  </ItemGroup>
+
+  <ItemGroup>
     <None Update="App.config">
       <CopyToOutputDirectory>Never</CopyToOutputDirectory>
     </None>
+    <None Update="Packages\涓嶅姞杩欎釜鎶ヤ竴鍫哾ebug鐨勯敊.txt">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </None>
   </ItemGroup>
 
 </Project>
diff --git a/Editor/Provider/GridControlProvider.cs b/Editor/Provider/GridControlProvider.cs
index 417cb6f..aa2dd6a 100644
--- a/Editor/Provider/GridControlProvider.cs
+++ b/Editor/Provider/GridControlProvider.cs
@@ -190,18 +190,31 @@
                 return tree;
             }
 
-            TextBox tb = new TextBox
+            if (property.GetCustomAttribute<IgnoreVariable>() is IgnoreVariable iv)
             {
-                IsReadOnly = !(property.CanWrite && property.SetMethod?.IsPublic == true)
-            };
-            BindingOperations.SetBinding(tb, TextBox.TextProperty,
+                TextBox tb = new TextBox
+                {
+                    IsReadOnly = !(property.CanWrite && property.SetMethod?.IsPublic == true)
+                };
+                BindingOperations.SetBinding(tb, TextBox.TextProperty,
+                    new Binding(property.Name)
+                    {
+                        Source = source,
+                        Mode = tb.IsReadOnly ? BindingMode.OneWay : BindingMode.TwoWay,
+                        UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
+                    });
+                return tb;
+            }
+
+            VariableTextBox vtb = new VariableTextBox() { Step = (ITestStep)source };
+            BindingOperations.SetBinding(vtb, VariableTextBox.ValueProperty,
                 new Binding(property.Name)
                 {
                     Source = source,
-                    Mode = tb.IsReadOnly ? BindingMode.OneWay : BindingMode.TwoWay,
                     UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
                 });
-            return tb;
+
+            return vtb;
         }
 
         public static bool FilterMember(IMemberData member)
diff --git a/Editor/SequencePanel.xaml.cs b/Editor/SequencePanel.xaml.cs
index 7002a55..439ff46 100644
--- a/Editor/SequencePanel.xaml.cs
+++ b/Editor/SequencePanel.xaml.cs
@@ -1,6 +1,7 @@
 using CommunityToolkit.Mvvm.ComponentModel;
 using Microsoft.Win32;
 using OpenTap;
+using OpenTap.Addin;
 using System.IO;
 using System.Windows;
 using System.Windows.Controls;
@@ -110,6 +111,7 @@
             };
             if (sfd.ShowDialog() == true)
             {
+                UIStationGlobalsManager.Instance.WriteToLocal(UIStationGlobalsManager.Instance.Datasource);
                 Plan.Save(sfd.FileName);
                 HasChanged = false;
                 TabName = Path.GetFileName(Plan.Path);
@@ -128,6 +130,7 @@
         };
         if (sfd.ShowDialog() == true)
         {
+            UIStationGlobalsManager.Instance.WriteToLocal(UIStationGlobalsManager.Instance.Datasource);
             Plan.Save(sfd.FileName);
             HasChanged = false;
             TabName = Path.GetFileName(Plan.Path);
diff --git a/Editor/UI/VariableSelector.xaml b/Editor/UI/VariableSelector.xaml
new file mode 100644
index 0000000..b3a3d18
--- /dev/null
+++ b/Editor/UI/VariableSelector.xaml
@@ -0,0 +1,23 @@
+锘�<Window x:Class="OpenTapEditor.VariableSelector"
+        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:OpenTapEditor"
+        mc:Ignorable="d"
+        Title="閫夋嫨鍙橀噺" Height="450" Width="800">
+    <Grid>
+        <Grid.RowDefinitions>
+            <RowDefinition/>
+            <RowDefinition/>
+        </Grid.RowDefinitions>
+
+        <local:VariableTree Datasource="{Binding Variables}">
+
+        </local:VariableTree>
+
+        <GroupBox Grid.Row="1" Header="缁撴灉">
+            <TextBox TextWrapping="Wrap" Text="{Binding Result}" />
+        </GroupBox>
+    </Grid>
+</Window>
diff --git a/Editor/UI/VariableSelector.xaml.cs b/Editor/UI/VariableSelector.xaml.cs
new file mode 100644
index 0000000..fc7d773
--- /dev/null
+++ b/Editor/UI/VariableSelector.xaml.cs
@@ -0,0 +1,50 @@
+锘縰sing CommunityToolkit.Mvvm.ComponentModel;
+using OpenTap;
+using OpenTap.Addin;
+using System.Collections.ObjectModel;
+using System.Windows;
+
+namespace OpenTapEditor
+{
+    /// <summary>
+    /// VariableSelector.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    [ObservableObject]
+    public partial class VariableSelector : Window
+    {
+        [ObservableProperty]
+        private string result;
+
+        [ObservableProperty]
+        private ObservableCollection<TestVariable> variables;
+
+        public VariableSelector()
+        {
+            DataContext = this;
+            Variables = new ObservableCollection<TestVariable>() {
+                new TestVariable(){
+                    Name = "Locals",
+                    Type = TestVariableType.Container
+                },
+                new TestVariable(){
+                    Name = "FileGlobals",
+                    Type = TestVariableType.Container
+                },
+                new TestVariable(){
+                    Name = "StationGlobals",
+                    Type = TestVariableType.Container,
+                    Children = UIStationGlobalsManager.Instance.Datasource
+                },
+            };
+            InitializeComponent();
+        }
+
+        public bool? Open(TestPlan plan, TestStepList seq)
+        {
+            Variables[0].Children = seq.Variables;
+            Variables[1].Children = plan.Variables;
+            return this.ShowDialog();
+        }
+
+    }
+}
diff --git a/Editor/UI/VariableTextBox.xaml b/Editor/UI/VariableTextBox.xaml
new file mode 100644
index 0000000..418297a
--- /dev/null
+++ b/Editor/UI/VariableTextBox.xaml
@@ -0,0 +1,17 @@
+锘�<UserControl x:Class="OpenTapEditor.UI.VariableTextBox"
+             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:OpenTapEditor.UI"
+             mc:Ignorable="d" 
+             d:DesignHeight="450" d:DesignWidth="800">
+    <Grid>
+        <Grid.ColumnDefinitions>
+            <ColumnDefinition/>
+            <ColumnDefinition Width="Auto"/>
+        </Grid.ColumnDefinitions>
+        <TextBox x:Name="TbPath" Text="{Binding FilePath,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
+        <Button Grid.Column="1" Click="Button_Click" Content="f(x)"/>
+    </Grid>
+</UserControl>
diff --git a/Editor/UI/VariableTextBox.xaml.cs b/Editor/UI/VariableTextBox.xaml.cs
new file mode 100644
index 0000000..0dc87ce
--- /dev/null
+++ b/Editor/UI/VariableTextBox.xaml.cs
@@ -0,0 +1,60 @@
+锘縰sing OpenTap;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace OpenTapEditor.UI
+{
+    /// <summary>
+    /// FileSelector.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    public partial class VariableTextBox : UserControl
+    {
+        public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
+                            "Value",
+                            typeof(string),
+                            typeof(VariableTextBox),
+                            new FrameworkPropertyMetadata());
+
+        public static readonly DependencyProperty StepProperty = DependencyProperty.Register(
+                            "Step",
+                            typeof(ITestStep),
+                            typeof(VariableTextBox),
+                            new FrameworkPropertyMetadata());
+
+        public ITestStep Step
+        {
+            get => (ITestStep)GetValue(StepProperty);
+            set
+            {
+                SetValue(StepProperty, value);
+            }
+        }
+
+        public string Value
+        {
+            get => (string)GetValue(ValueProperty);
+            set
+            {
+                SetValue(ValueProperty, value);
+            }
+        }
+
+        public VariableTextBox()
+        {
+            DataContext = this;
+            InitializeComponent();
+        }
+
+        private void Button_Click(object sender, RoutedEventArgs e)
+        {
+            var win = new VariableSelector()
+            {
+                Owner = Window.GetWindow(this)
+            };
+            if (win.Open(Step.Root, new TestStepList()) == true)
+            {
+                Value = win.Result;
+            }
+        }
+    }
+}
diff --git a/Editor/UI/VariablesControl.xaml.cs b/Editor/UI/VariablesControl.xaml.cs
index a11b5c5..62be44c 100644
--- a/Editor/UI/VariablesControl.xaml.cs
+++ b/Editor/UI/VariablesControl.xaml.cs
@@ -46,6 +46,10 @@
             DataContext = this;
             Variables = new ObservableCollection<TestVariable>() {
                 new TestVariable(){
+                    Name = "Locals",
+                    Type = TestVariableType.Container
+                },
+                new TestVariable(){
                     Name = "FileGlobals",
                     Type = TestVariableType.Container
                 },
@@ -61,9 +65,10 @@
         public void SetPlan(TestPlan plan)
         {
             this.plan = plan;
-            Variables[0].Children = plan.Variables;
+            Variables[1].Children = plan.Variables;
             Sequences = plan.Sequences;
             SelectedSequence = Sequences[0];
+            Variables[0].Children = SelectedSequence.Variables;
         }
 
         private void Button_Click(object sender, System.Windows.RoutedEventArgs e)
@@ -94,6 +99,7 @@
 
         partial void OnSelectedSequenceChanged(TestStepList value)
         {
+            Variables[0].Children = value.Variables;
             selectedSequenceChanged?.Invoke(this, value);
         }
     }
diff --git a/OpenTap/BasicSteps/SequenceCallStep.cs b/OpenTap/BasicSteps/SequenceCallStep.cs
index 325d804..b92061c 100644
--- a/OpenTap/BasicSteps/SequenceCallStep.cs
+++ b/OpenTap/BasicSteps/SequenceCallStep.cs
@@ -30,6 +30,11 @@
             public Guid Guid2 { get; set; }
         }
 
+        protected override void OnRootChanged(TestPlan plan)
+        {
+            LoadTestPlan();
+        }
+
         /// <summary>
         /// This is the list of path of test plan loaded and used to prevent recursive TestPlan references.
         /// </summary>
@@ -87,7 +92,6 @@
                 {
                     realPath = this.GetParent<TestPlan>()?.Path;
                 }
-                LoadTestPlan();
             }
         }
 
@@ -107,7 +111,7 @@
             }
         }
 
-        [Display("共享变量")]
+        //[Display("共享变量")]
         public bool ShareGlobals { get; set; }
 
         [Browsable(false)]
@@ -268,25 +272,30 @@
                 var xml = plan.SerializeToString();
                 var listeners = OpenTap.Log.GetListeners();
                 var resultSetting = ResultSettings.Current;
+                var fileGlobalsContext = PlanRun.fileGlobalsContext;
                 using (Session.Create())
                 {
+                    SequenceContext.SetLocals(new VariableContext(null));
                     var plan2 = Utils.DeserializeFromString<TestPlan>(xml);
                     plan2.PrintTestPlanRunSummary = false;
+                    plan2.VisualPath = plan.Path;
                     foreach (var listener in listeners)
                     {
                         OpenTap.Log.AddListener(listener);
                     }
                     TestPlanRun subRun;
-                    if (ShareGlobals)
-                    {
-                        subRun = plan2.Execute(SequenceName, resultSetting, null, null,
-                            new VariableContainer(GetMergeContainer(), this.PlanRun.FileGlobalsRuntime, this.PlanRun.StationGlobalsRuntime));
-                    }
-                    else
-                    {
-                        subRun = plan2.Execute(SequenceName, resultSetting, null, null, new VariableContainer(GetMergeContainer(), null,
-                            this.PlanRun.StationGlobalsRuntime));
-                    }
+                    subRun = plan2.Execute(SequenceName, resultSetting, null, null,
+                           new VariableContainer(GetMergeContainer(), fileGlobalsContext, this.PlanRun.StationGlobalsRuntime));
+                    //if (ShareGlobals)
+                    //{
+                    //    subRun = plan2.Execute(SequenceName, resultSetting, null, null,
+                    //        new VariableContainer(GetMergeContainer(), fileGlobalsContext, this.PlanRun.StationGlobalsRuntime));
+                    //}
+                    //else
+                    //{
+                    //    subRun = plan2.Execute(SequenceName, resultSetting, null, null, new VariableContainer(GetMergeContainer(), null,
+                    //        this.PlanRun.StationGlobalsRuntime));
+                    //}
                     UpgradeVerdict(subRun.Verdict);
                 }
             }
diff --git a/OpenTap/Engine/Addin/Annotation/TreeDataAttribute.cs b/OpenTap/Engine/Addin/Annotation/TreeDataAttribute.cs
index c1d20f7..826fff5 100644
--- a/OpenTap/Engine/Addin/Annotation/TreeDataAttribute.cs
+++ b/OpenTap/Engine/Addin/Annotation/TreeDataAttribute.cs
@@ -11,4 +11,8 @@
     public class TreeDataAttribute : Attribute, IAnnotation
     {
     }
+
+    public class IgnoreVariable : Attribute, IAnnotation
+    {
+    }
 }
diff --git a/OpenTap/Engine/Addin/FileGlobalsContext.cs b/OpenTap/Engine/Addin/FileGlobalsContext.cs
new file mode 100644
index 0000000..289037d
--- /dev/null
+++ b/OpenTap/Engine/Addin/FileGlobalsContext.cs
@@ -0,0 +1,26 @@
+锘縰sing System.Collections.Concurrent;
+
+namespace OpenTap.Addin
+{
+    public class FileGlobalsContext
+    {
+        ConcurrentDictionary<string, VariableContext> map = new ConcurrentDictionary<string, VariableContext>();
+
+        public VariableContext GetFileGlobals(TestPlan plan)
+        {
+            string path = plan.VisualPath;
+            if (!map.ContainsKey(path))
+            {
+                var runtimeVariablePool = new ConcurrentDictionary<string, RuntimeVariable>();
+                foreach (var data in plan.Variables)
+                {
+                    RuntimeVariable runTime = data.ToRuntime();
+                    if (runTime == null) continue;
+                    runtimeVariablePool[runTime.Name] = runTime;
+                }
+                map[path] = new VariableContext(runtimeVariablePool);
+            }
+            return map[path];
+        }
+    }
+}
diff --git a/OpenTap/Engine/Addin/LocalsContext.cs b/OpenTap/Engine/Addin/LocalsContext.cs
new file mode 100644
index 0000000..0d99001
--- /dev/null
+++ b/OpenTap/Engine/Addin/LocalsContext.cs
@@ -0,0 +1,17 @@
+锘縰sing System.Collections.Concurrent;
+
+namespace OpenTap.Addin
+{
+    public class LocalsContext
+    {
+        //public VariableContext GetLocals(TestPlan plan)
+        //{
+        //    string path = plan.Path;
+        //    if (!map.ContainsKey(path))
+        //    {
+
+        //    }
+        //    return map[path];
+        //}
+    }
+}
diff --git a/OpenTap/Engine/Addin/SequenceContext.cs b/OpenTap/Engine/Addin/SequenceContext.cs
new file mode 100644
index 0000000..9129d8b
--- /dev/null
+++ b/OpenTap/Engine/Addin/SequenceContext.cs
@@ -0,0 +1,14 @@
+锘縩amespace OpenTap.Addin
+{
+    public class SequenceContext
+    {
+        static readonly SessionLocal<VariableContext> LocalsSession = new SessionLocal<VariableContext>(null);
+
+        public static VariableContext Locals => LocalsSession.Value;
+
+        public static void SetLocals(VariableContext locals)
+        {
+            LocalsSession.Value = locals;
+        }
+    }
+}
diff --git a/OpenTap/Engine/Addin/VariableContainer.cs b/OpenTap/Engine/Addin/VariableContainer.cs
index 87cc525..518b984 100644
--- a/OpenTap/Engine/Addin/VariableContainer.cs
+++ b/OpenTap/Engine/Addin/VariableContainer.cs
@@ -3,13 +3,13 @@
     public class VariableContainer
     {
         public VariableContext Parameters;
-        public VariableContext FileGlobals;
+        public FileGlobalsContext fileGlobalsContext;
         public VariableContext StationGlobals;
 
-        public VariableContainer(VariableContext parameters, VariableContext fileGlobals, VariableContext stationGlobals)
+        public VariableContainer(VariableContext parameters, FileGlobalsContext fileGlobalsContext, VariableContext stationGlobals)
         {
             Parameters = parameters;
-            FileGlobals = fileGlobals;
+            this.fileGlobalsContext = fileGlobalsContext;
             StationGlobals = stationGlobals;
         }
     }
diff --git a/OpenTap/Engine/Addin/VariableContext.cs b/OpenTap/Engine/Addin/VariableContext.cs
index dce9c8e..c5a2cd4 100644
--- a/OpenTap/Engine/Addin/VariableContext.cs
+++ b/OpenTap/Engine/Addin/VariableContext.cs
@@ -1,5 +1,8 @@
 锘縰sing System.Collections.Concurrent;
+using System.Collections.Generic;
 using System.Dynamic;
+using System.IO;
+using System.Numerics;
 
 namespace OpenTap.Addin
 {
@@ -69,5 +72,17 @@
                 }
             }
         }
+
+        public static VariableContext FromTestVariables(IEnumerable<TestVariable> variables)
+        {
+            var runtimeVariablePool = new ConcurrentDictionary<string, RuntimeVariable>();
+            foreach (var data in variables)
+            {
+                RuntimeVariable runTime = data.ToRuntime();
+                if (runTime == null) continue;
+                runtimeVariablePool[runTime.Name] = runTime;
+            }
+            return new VariableContext(runtimeVariablePool);
+        }
     }
 }
diff --git a/OpenTap/Engine/SerializerPlugins/TestStepListSerializer.cs b/OpenTap/Engine/SerializerPlugins/TestStepListSerializer.cs
index b7d886d..d28bef9 100644
--- a/OpenTap/Engine/SerializerPlugins/TestStepListSerializer.cs
+++ b/OpenTap/Engine/SerializerPlugins/TestStepListSerializer.cs
@@ -2,6 +2,7 @@
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
 // file, you can obtain one at http://mozilla.org/MPL/2.0/.
+using OpenTap.Addin;
 using System;
 using System.Xml.Linq;
 
@@ -10,7 +11,7 @@
     /// <summary> Serializer implementation for TestStepList. </summary>
     internal class TestStepListSerializer : TapSerializerPlugin
     {
-        
+
 
         /// <summary> The order of this serializer. </summary>
         public override double Order
@@ -26,33 +27,57 @@
             steps.SequenceName = sequenceNameAttr?.Value ?? string.Empty;
             foreach (var subnode in elem.Elements())
             {
-                ITestStep result = null;
-                try
+                if (subnode.Name == varName)
                 {
-                    if (!Serializer.Deserialize(subnode, x => result = (ITestStep)x))
+                    TestVariable v = null;
+                    try
                     {
-                        Serializer.PushError(subnode, "Unable to deserialize test step.");
-                        continue; // skip to next step.
+                        if (!Serializer.Deserialize(subnode, x => v = (TestVariable)x))
+                        {
+                            Serializer.PushError(subnode, "Unable to deserialize test step.");
+                            continue; // skip to next step.
+                        }
                     }
-                }
-                catch(Exception ex)
-                {
-                    Serializer.PushError(subnode, "Unable to deserialize test step.", ex);
-                    continue;
-                }
+                    catch (Exception ex)
+                    {
+                        Serializer.PushError(subnode, "Unable to deserialize test step.", ex);
+                        continue;
+                    }
 
-                if (result != null)
-                    steps.Add(result);
+                    if (v != null)
+                        steps.Variables.Add(v);
+                }
+                else
+                {
+                    ITestStep result = null;
+                    try
+                    {
+                        if (!Serializer.Deserialize(subnode, x => result = (ITestStep)x))
+                        {
+                            Serializer.PushError(subnode, "Unable to deserialize test step.");
+                            continue; // skip to next step.
+                        }
+                    }
+                    catch (Exception ex)
+                    {
+                        Serializer.PushError(subnode, "Unable to deserialize test step.", ex);
+                        continue;
+                    }
+
+                    if (result != null)
+                        steps.Add(result);
+                }
             }
             setResult(steps);
             return true;
         }
 
         static XName testStepName = "TestStep";
+        static XName varName = "Variable";
         /// <summary> Serialization implementation. </summary>
-        public override bool Serialize( XElement elem, object target, ITypeData expectedType)
+        public override bool Serialize(XElement elem, object target, ITypeData expectedType)
         {
-            if(target is TestStepList)
+            if (target is TestStepList)
             {
                 TestStepList list = (TestStepList)target;
                 if (!string.IsNullOrEmpty(list.SequenceName))
@@ -66,6 +91,18 @@
                     Serializer.Serialize(newelem, item, null, true);
                     elem.Add(newelem);
                 }
+                var vars = list.Variables;
+                if (vars != null && vars.Count > 0)
+                {
+                    for (int i = 0; i < vars.Count; i++)
+                    {
+                        var item = vars[i];
+                        var newelem = new XElement(varName);
+                        Serializer.Serialize(newelem, item, null, true);
+                        elem.Add(newelem);
+                    }
+                }
+
                 return true;
             }
             return false;
diff --git a/OpenTap/Engine/TestPlan.cs b/OpenTap/Engine/TestPlan.cs
index 39544d6..9abd59d 100644
--- a/OpenTap/Engine/TestPlan.cs
+++ b/OpenTap/Engine/TestPlan.cs
@@ -480,12 +480,27 @@
         }
         #endregion
 
+        private string path;
         /// <summary>
         /// Gets where this plan was last saved or loaded from. It might be null.
         /// </summary>
         [XmlIgnore]
         [AnnotationIgnore]
-        public string Path { get; internal set; }
+        public string Path
+        {
+            get => path; internal set
+            {
+                path = value;
+                VisualPath = value;
+            }
+        }
+
+        /// <summary>
+        /// 虚拟路径
+        /// </summary>
+        [XmlIgnore]
+        [AnnotationIgnore]
+        public string VisualPath { get; set; }
 
         /// <summary> The directory where the test plan is stored.</summary>
         [MetaData(macroName: "TestPlanDir")]
diff --git a/OpenTap/Engine/TestPlanExecution.cs b/OpenTap/Engine/TestPlanExecution.cs
index 9e0eeac..1cfbb93 100644
--- a/OpenTap/Engine/TestPlanExecution.cs
+++ b/OpenTap/Engine/TestPlanExecution.cs
@@ -568,9 +568,12 @@
                 resultListeners = resultListeners.Concat(new IResultListener[] { summaryListener });
             resultListeners = resultListeners.Where(r => r is IEnabledResource ? ((IEnabledResource)r).IsEnabled : true);
             IList<ITestStep> steps;
+            TestStepList list;
+
             if (stepsOverride == null)
             {
-                steps = GetStepsByName(sequenceName);
+                list = GetStepsByName(sequenceName);
+                steps = list;
             }
             else
             {
@@ -579,6 +582,7 @@
                 {
                     throw new ArgumentException("No Name");
                 }
+                list = tempStep;
 
                 // Remove steps that are already included via their parent steps.
                 foreach (var step in stepsOverride)
@@ -669,6 +673,7 @@
                     execStage.Parameters.AddRange(PluginManager.GetPluginVersions(new List<object> { r }));
                 };
             }
+            execStage.LocalsRuntime = VariableContext.FromTestVariables(list.Variables);
 
 
             if (metaDataParameters != null)
diff --git a/OpenTap/Engine/TestPlanRun.cs b/OpenTap/Engine/TestPlanRun.cs
index ccd9810..c705591 100644
--- a/OpenTap/Engine/TestPlanRun.cs
+++ b/OpenTap/Engine/TestPlanRun.cs
@@ -49,15 +49,18 @@
         static readonly TraceSource log = Log.CreateSource("TestPlan");
         static readonly TraceSource resultLog = Log.CreateSource("Resources");
 
+        public VariableContext LocalsRuntime { get; internal set; }
+
         /// <summary>
         /// FileGlobals运行时
         /// </summary>
         public VariableContext ParametersRuntime { get; private set; }
 
+        public FileGlobalsContext fileGlobalsContext { get; private set; }
         /// <summary>
         /// FileGlobals运行时
         /// </summary>
-        public VariableContext FileGlobalsRuntime { get; private set; }
+        public VariableContext FileGlobalsRuntime { get => fileGlobalsContext?.GetFileGlobals(plan); }
 
         /// <summary>
         /// StationGlobals运行时
@@ -675,21 +678,31 @@
 
         void RefreshFileGlobals()
         {
-            if (variableContainer?.FileGlobals != null)
+            //if (variableContainer?.FileGlobals != null)
+            //{
+            //    FileGlobalsRuntime = variableContainer.FileGlobals;
+            //}
+            //else
+            //{
+            //    var runtimeVariablePool = new ConcurrentDictionary<string, RuntimeVariable>();
+            //    foreach (var data in plan.Variables)
+            //    {
+            //        RuntimeVariable runTime = data.ToRuntime();
+            //        if (runTime == null) continue;
+            //        runtimeVariablePool[runTime.Name] = runTime;
+            //    }
+            //    this.FileGlobalsRuntime = new VariableContext(runtimeVariablePool);
+            //}
+
+            if (variableContainer?.fileGlobalsContext != null)
             {
-                FileGlobalsRuntime = variableContainer.FileGlobals;
+                fileGlobalsContext = variableContainer.fileGlobalsContext;
             }
             else
             {
-                var runtimeVariablePool = new ConcurrentDictionary<string, RuntimeVariable>();
-                foreach (var data in plan.Variables)
-                {
-                    RuntimeVariable runTime = data.ToRuntime();
-                    if (runTime == null) continue;
-                    runtimeVariablePool[runTime.Name] = runTime;
-                }
-                this.FileGlobalsRuntime = new VariableContext(runtimeVariablePool);
-            }            
+                fileGlobalsContext = new FileGlobalsContext();
+                var _ = fileGlobalsContext.GetFileGlobals(plan);
+            }
         }
 
         void RefreshStationGlobals()
diff --git a/OpenTap/Engine/TestStep.cs b/OpenTap/Engine/TestStep.cs
index b5edd3e..ce5bde0 100644
--- a/OpenTap/Engine/TestStep.cs
+++ b/OpenTap/Engine/TestStep.cs
@@ -16,6 +16,7 @@
 using System.Threading;
 using System.Runtime.CompilerServices;
 using System.Runtime.ExceptionServices;
+using OpenTap.Addin.Annotation;
 
 
 namespace OpenTap
@@ -34,9 +35,22 @@
     {
         #region Properties
 
-        private TestPlan root;
+        protected TestPlan root;
         [XmlIgnore]
-        public TestPlan Root { get => root; set => root = value; }
+        public TestPlan Root
+        {
+            get => root;
+            set
+            {
+                root = value;
+                OnRootChanged(value);
+            }
+        }
+
+        protected virtual void OnRootChanged(TestPlan plan)
+        {
+
+        }
 
         public StepResult Result { get; set; }
 
@@ -97,6 +111,7 @@
                               "replaced with it's current value in some views.", Group: "Common", Order: 20001, Collapsed: true)]
         [Unsweepable]
         [MetaData(Frozen = true)]
+        [IgnoreVariable]
         public string Name
         {
             get => name;
@@ -206,6 +221,7 @@
 
         private string description;
         [Display("描述")]
+        [IgnoreVariable]
         public string Description
         {
             get => description;
diff --git a/OpenTap/Engine/TestStepList.cs b/OpenTap/Engine/TestStepList.cs
index 1c52187..f88a8c3 100644
--- a/OpenTap/Engine/TestStepList.cs
+++ b/OpenTap/Engine/TestStepList.cs
@@ -2,6 +2,7 @@
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
 // file, you can obtain one at http://mozilla.org/MPL/2.0/.
+using OpenTap.Addin;
 using System;
 using System.Collections.Generic;
 using System.Collections.ObjectModel;
@@ -83,6 +84,8 @@
             }
         }
 
+        public ObservableCollection<TestVariable> Variables { get; set; } = new ObservableCollection<TestVariable>();
+
         /// <summary>
         /// When true, the nesting rules defined by <see cref="AllowAsChildInAttribute"/> and 
         /// <see cref="AllowAnyChildAttribute"/> are checked when trying to insert a step into 
diff --git a/Plugin/AddInPlugin/AddInPlugin.csproj b/Plugin/AddInPlugin/AddInPlugin.csproj
index 6cf0361..93c2c42 100644
--- a/Plugin/AddInPlugin/AddInPlugin.csproj
+++ b/Plugin/AddInPlugin/AddInPlugin.csproj
@@ -45,14 +45,14 @@
   </ItemGroup>
 
   <ItemGroup>
+    <ProjectReference Include="..\..\..\..\VisualStudio\UtilLib\UtilLib.csproj" />
+    <ProjectReference Include="..\..\..\ViHelper\ViHelper.csproj" />
     <ProjectReference Include="..\..\OpenTap\Engine\Tap.Engine.csproj" />
-    <ProjectReference Include="..\..\UtilLib\UtilLib.csproj" />
-    <ProjectReference Include="..\..\ViHelper\ViHelper.csproj" />
   </ItemGroup>
 
   <ItemGroup>
     <Reference Include="LabViewRunner">
-      <HintPath>..\..\ViHelper\Libs\LabViewRunner.dll</HintPath>
+      <HintPath>..\..\..\..\..\Users\chrry\Desktop\builds\Untitled Project 1\My Interop Assembly\LabViewRunner.dll</HintPath>
     </Reference>
     <Reference Include="NationalInstruments.LabVIEW.Interop">
       <HintPath>..\..\..\..\..\Program Files\National Instruments\Shared\LabVIEW Run-Time\2020\NationalInstruments.LabVIEW.Interop.dll</HintPath>
diff --git a/Plugin/AddInPlugin/Util/TestPlanRunAddIn.cs b/Plugin/AddInPlugin/Util/TestPlanRunAddIn.cs
index 87fb8ec..5d8d951 100644
--- a/Plugin/AddInPlugin/Util/TestPlanRunAddIn.cs
+++ b/Plugin/AddInPlugin/Util/TestPlanRunAddIn.cs
@@ -1,5 +1,6 @@
 锘縰sing DynamicExpresso;
 using OpenTap;
+using OpenTap.Addin;
 using System;
 
 namespace AddInPlugin.Util
@@ -15,6 +16,8 @@
             target.SetVariable("Parameters", run.ParametersRuntime);
             target.SetVariable("Step", step);
 
+            target.SetVariable("Locals", run.LocalsRuntime);
+
             var context = step?.GetParent<ContextStep>();
             if (context != null)
             {

--
Gitblit v1.9.1