using AddInPlugin;
|
using AddInPlugin.Entity;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.Input;
|
using GongSolutions.Wpf.DragDrop;
|
using OpenTap;
|
using OpenTap.Plugins.BasicSteps;
|
using OpenTapEditor.UI;
|
using OpenTapEditor.Util;
|
using System.Collections.Concurrent;
|
using System.Collections.ObjectModel;
|
using System.IO;
|
using System.Text;
|
using System.Windows;
|
using System.Windows.Controls;
|
using System.Windows.Input;
|
using System.Windows.Media;
|
using UILib;
|
|
namespace OpenTapEditor
|
{
|
/// <summary>
|
/// StepTree.xaml 的交互逻辑
|
/// </summary>
|
[ObservableObject]
|
public partial class StepTree : UserControl
|
{
|
public event RoutedPropertyChangedEventHandler<object> SelectedItemChanged;
|
|
[ObservableProperty]
|
private TestStepList datasource;
|
|
partial void OnDatasourceChanged(TestStepList value)
|
{
|
DebugIds.CollectionChanged -= DebugIds_CollectionChanged;
|
DebugIds = value?.DebugIds ?? new ObservableCollection<Guid>();
|
DebugIds.CollectionChanged += DebugIds_CollectionChanged;
|
}
|
|
private void DebugIds_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
{
|
OnPropertyChanged(nameof(DebugIds));
|
}
|
|
[ObservableProperty]
|
ObservableCollection<Guid> debugIds = new ObservableCollection<Guid>();
|
|
[ObservableProperty]
|
private Guid debugStepId = Guid.Empty;
|
|
[ObservableProperty]
|
private Guid runningStepId = Guid.Empty;
|
|
[ObservableProperty]
|
private ConcurrentDictionary<Guid, bool> complatedIds = new ConcurrentDictionary<Guid, bool>();
|
|
public void UpdateStepStatus()
|
{
|
OnPropertyChanged(nameof(ComplatedIds));
|
}
|
|
public TestPlan Plan { get; set; }
|
|
public IDropTarget DragHandler { get; set; } = new StepTreeDragHandler();
|
|
private ITestStep SelectedStep;
|
|
[ObservableProperty]
|
private TestStepList selectedSteps = new TestStepList();
|
|
public StepTree()
|
{
|
DataContext = this;
|
InitializeComponent();
|
}
|
|
[RelayCommand]
|
public void CopyStep()
|
{
|
//if (SelectedStep != null)
|
//{
|
// CopyStep(SelectedStep);
|
//}
|
CopyStep(null);
|
}
|
|
[RelayCommand]
|
public void PasteStep()
|
{
|
PasteStep(SelectedStep);
|
}
|
|
[RelayCommand]
|
public void DeleteStep()
|
{
|
if (SelectedStep != null)
|
{
|
DeleteStep(SelectedStep);
|
}
|
}
|
|
public List<object> RefreshMenuItems(ITestStep selectedStep, bool addChild)
|
{
|
var allSteps = StepTypeLoader.Steps;
|
var StepMenuItems = new List<object>(allSteps.Count);
|
Dictionary<string, MenuItem> groupMap = new Dictionary<string, MenuItem>();
|
Dictionary<string, MenuItem> groupAdapMap = new Dictionary<string, MenuItem>();
|
|
foreach (ITypeData stepType in allSteps)
|
{
|
var display = stepType.GetAttribute<DisplayAttribute>();
|
if (display == null)
|
{
|
continue;
|
}
|
MenuItem groupItem = null;
|
|
var groups = display.Group;
|
if (groups.Length > 0)
|
{
|
var group = groups[groups.Length - 1];
|
if (!string.IsNullOrEmpty(group))
|
{
|
if (!groupMap.ContainsKey(group))
|
{
|
groupMap[group] = new MenuItem() { Header = group };
|
StepMenuItems.Add(groupMap[group]);
|
}
|
groupItem = groupMap[group];
|
}
|
}
|
|
|
TypeData data = stepType as TypeData;
|
StepTypeMenuItem menuItem = new StepTypeMenuItem();
|
string des = string.IsNullOrEmpty(display.Description) ? "" : $" ({display.Description})";
|
menuItem.Header = $"{display.Name}{des}";
|
menuItem.Click += addChild ? Add_Child_Step_Click : Add_Generaic_Step_Click;
|
menuItem.TypeData = stepType;
|
menuItem.Tag = selectedStep;
|
|
if (!addChild)
|
{
|
var parent = selectedStep?.Parent?.GetType() ?? typeof(TestPlan);
|
menuItem.IsEnabled = parent == null || TestStepList.AllowChild(parent, data.Type);
|
}
|
else
|
{
|
menuItem.IsEnabled = (selectedStep != null && TestStepList.AllowChild(selectedStep.GetType(), data.Type));
|
}
|
if (groupItem != null)
|
{
|
groupItem.Items.Add(menuItem);
|
}
|
else
|
{
|
StepMenuItems.Add(menuItem);
|
}
|
}
|
|
if (StepTypeLoader.CustomSteps != null && StepTypeLoader.CustomSteps.Count > 0)
|
{
|
StepMenuItems.Add(new Separator { });
|
|
foreach (AdapterData stepType in StepTypeLoader.CustomSteps)
|
{
|
MenuItem groupItem = null;
|
if (!string.IsNullOrEmpty(stepType.GroupBy))
|
{
|
if (!groupAdapMap.ContainsKey(stepType.GroupBy))
|
{
|
groupAdapMap[stepType.GroupBy] = new MenuItem
|
{
|
Header = stepType.GroupBy
|
};
|
StepMenuItems.Add(groupAdapMap[stepType.GroupBy]);
|
}
|
groupItem = groupAdapMap[stepType.GroupBy];
|
}
|
|
|
StepTypeMenuItem menuItem = new StepTypeMenuItem();
|
menuItem.Header = stepType.Name;
|
menuItem.Click += addChild ? Add_Child_Step_Click : Add_Generaic_Step_Click;
|
menuItem.TypeData = stepType;
|
menuItem.Tag = selectedStep;
|
|
menuItem.IsEnabled = !addChild || (selectedStep != null && TestStepList.AllowChild(selectedStep?.GetType(), typeof(AdapterData)));
|
|
if (groupItem != null)
|
{
|
groupItem.Items.Add(menuItem);
|
}
|
else
|
{
|
StepMenuItems.Add(menuItem);
|
}
|
}
|
}
|
return StepMenuItems;
|
}
|
|
private void treeList_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
|
{
|
}
|
|
/// <summary>
|
/// 显示菜单
|
/// </summary>
|
/// <param name="positionItem"></param>
|
private void CreateContextMenu(ITestStep obj)
|
{
|
ContextMenu contextMenu = new ContextMenu();
|
MenuItem menuItem = new MenuItem();
|
menuItem.SetValue(MenuItem.HeaderProperty, new DynamicResourceExtension("插入Step").ProvideValue(null));
|
menuItem.Tag = obj;
|
foreach (var item in RefreshMenuItems(obj, false))
|
{
|
menuItem.Items.Add(item);
|
}
|
contextMenu.Items.Add(menuItem);
|
|
if (obj != null)
|
{
|
menuItem = new MenuItem();
|
menuItem.SetValue(MenuItem.HeaderProperty, new DynamicResourceExtension("插入子Step").ProvideValue(null));
|
menuItem.Tag = obj;
|
foreach (var item in RefreshMenuItems(obj, true))
|
{
|
menuItem.Items.Add(item);
|
}
|
contextMenu.Items.Add(menuItem);
|
}
|
|
if (obj != null)
|
{
|
menuItem = new MenuItem();
|
menuItem.SetValue(MenuItem.HeaderProperty, new DynamicResourceExtension("复制").ProvideValue(null));
|
menuItem.InputGestureText = "Ctrl + C";
|
menuItem.Click += Copy_Step_Click;
|
menuItem.Tag = obj;
|
contextMenu.Items.Add(menuItem);
|
}
|
|
menuItem = new MenuItem();
|
menuItem.SetValue(MenuItem.HeaderProperty, new DynamicResourceExtension("粘贴").ProvideValue(null));
|
menuItem.InputGestureText = "Ctrl + V";
|
menuItem.Click += Paste_Step_Click;
|
menuItem.Tag = obj;
|
contextMenu.Items.Add(menuItem);
|
|
if (obj != null)
|
{
|
menuItem = new MenuItem();
|
menuItem.SetValue(MenuItem.HeaderProperty, new DynamicResourceExtension("删除").ProvideValue(null));
|
menuItem.InputGestureText = "Delete";
|
menuItem.Click += Delete_Step_Click;
|
menuItem.Tag = obj;
|
contextMenu.Items.Add(menuItem);
|
}
|
|
this.treeList.ContextMenu = contextMenu;
|
}
|
|
private void AddStep2Plan(ITestStep newStep, ITestStep insertAfter, TestPlan plan)
|
{
|
if (insertAfter != null)
|
{
|
var parent = insertAfter.Parent;
|
if (parent is TestPlan pplan)
|
{
|
if (TestStepList.AllowChild(typeof(TestPlan), newStep.GetType()))
|
{
|
pplan.InsertStep(Datasource.SequenceName,
|
pplan.GetStepsByName(Datasource.SequenceName).IndexOf(insertAfter) + 1, newStep);
|
}
|
}
|
else if (parent != null && TestStepList.AllowChild(parent.GetType(), newStep.GetType()))
|
{
|
parent.InsertStep(Datasource.SequenceName, parent.ChildTestSteps.IndexOf(insertAfter) + 1, newStep);
|
}
|
}
|
else
|
{
|
if (TestStepList.AllowChild(typeof(TestPlan), newStep.GetType()))
|
{
|
plan.AddStep(Datasource.SequenceName, newStep);
|
}
|
}
|
}
|
|
|
#region add
|
|
/// <summary>
|
/// 添加同级Step
|
/// </summary>
|
/// <param name="sender"></param>
|
/// <param name="e"></param>
|
private void Add_Generaic_Step_Click(object sender, RoutedEventArgs e)
|
{
|
ITestStep step = ((StepTypeMenuItem)sender).Tag as ITestStep;
|
ITestStep newStep = null;
|
if (((StepTypeMenuItem)sender).TypeData is ITypeData type)
|
{
|
newStep = type.CreateInstance() as ITestStep;
|
}
|
else if (((StepTypeMenuItem)sender).TypeData is AdapterData data)
|
{
|
newStep = new AdapterStep()
|
{
|
Name = data.Name,
|
XmlString = data.Xml
|
};
|
}
|
if (newStep != null)
|
{
|
AddStep2Plan(newStep, step, Plan);
|
}
|
}
|
|
/// <summary>
|
/// 添加子Step
|
/// </summary>
|
/// <param name="sender"></param>
|
/// <param name="e"></param>
|
private void Add_Child_Step_Click(object sender, RoutedEventArgs e)
|
{
|
ITestStep step = ((StepTypeMenuItem)sender).Tag as ITestStep;
|
if (step == null)
|
{
|
return;
|
}
|
ITestStep newStep = null;
|
if (((StepTypeMenuItem)sender).TypeData is ITypeData type)
|
{
|
newStep = type.CreateInstance() as ITestStep;
|
}
|
else if (((StepTypeMenuItem)sender).TypeData is AdapterData data)
|
{
|
newStep = new AdapterStep()
|
{
|
Name = data.Name,
|
XmlString = data.Xml
|
};
|
}
|
if (newStep != null)
|
{
|
step.AddStep(Datasource.SequenceName, newStep);
|
}
|
}
|
|
private void DeleteStep(ITestStep step)
|
{
|
foreach (var child in SelectedSteps)
|
{
|
if (child.Parent is ITestStep)
|
{
|
child.Parent.ChildTestSteps.Remove(child);
|
}
|
else
|
{
|
Datasource.Remove(child);
|
}
|
}
|
SelectedSteps.Clear();
|
SelectedStep = null;
|
}
|
|
private void Delete_Step_Click(object sender, RoutedEventArgs e)
|
{
|
ITestStep step = ((MenuItem)sender).Tag as ITestStep;
|
DeleteStep(step);
|
}
|
|
private void CopyStep(ITestStep step)
|
{
|
try
|
{
|
var target = new TestStepList();
|
foreach (var child in Datasource.RecursivelyGetAllTestSteps(TestStepSearch.All))
|
{
|
if (SelectedSteps.Contains(child))
|
{
|
target.Add(child);
|
}
|
}
|
|
TapSerializer serializer = new TapSerializer();
|
using (var str = new MemoryStream())
|
{
|
serializer.Serialize(str, target);
|
Clipboard.SetText(Encoding.UTF8.GetString(str.ToArray()));
|
}
|
}
|
catch (Exception ex)
|
{
|
MessageBox.Show($"{ex.Message}");
|
}
|
}
|
|
private void Copy_Step_Click(object sender, RoutedEventArgs e)
|
{
|
ITestStep step = ((MenuItem)sender).Tag as ITestStep;
|
CopyStep(step);
|
}
|
|
private void PasteStep(ITestStep targetStep)
|
{
|
try
|
{
|
TapSerializer serializer = new TapSerializer();
|
TestStepList newSteps = (TestStepList)serializer.DeserializeFromString(Clipboard.GetText());
|
if (newSteps == null)
|
{
|
return;
|
}
|
var last = targetStep;
|
foreach (var child in newSteps.RecursivelyGetAllTestSteps(TestStepSearch.All))
|
{
|
child.Id = Guid.NewGuid();
|
}
|
foreach (var child in newSteps)
|
{
|
AddStep2Plan(child, last, Plan);
|
last = child;
|
}
|
}
|
catch (Exception ex)
|
{
|
MessageBox.Show($"粘贴错误: {ex.Message}", "错误");
|
}
|
}
|
|
private void Paste_Step_Click(object sender, RoutedEventArgs e)
|
{
|
ITestStep step = ((MenuItem)sender).Tag as ITestStep;
|
PasteStep(step);
|
}
|
#endregion
|
|
private void DebuggerBtn_HandleClick(object sender, EventArgs e)
|
{
|
DebuggerBtn btn = (DebuggerBtn)sender;
|
if (Datasource.DebugIds.Contains(btn.StepId))
|
{
|
Datasource.DebugIds.Remove(btn.StepId);
|
}
|
else
|
{
|
Datasource.DebugIds.Add(btn.StepId);
|
}
|
}
|
|
public bool NeedBreak(Guid id)
|
{
|
if (Datasource == null) return false;
|
return Datasource.DebugIds.Contains(id);
|
}
|
|
private void TreeViewItem_PreviewMouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
{
|
DependencyObject current = e.OriginalSource as DependencyObject;
|
while (current != null)
|
{
|
// 如果找到需要处理双击的控件(例如Button、TextBox等),则不阻止事件
|
if (current is EditBox eb && eb.CanEdit)
|
{
|
return; // 让事件继续传递
|
}
|
current = VisualTreeHelper.GetParent(current);
|
}
|
e.Handled = true;
|
}
|
|
private void treeList_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
{
|
TreeListViewItem item = FindControl<TreeListViewItem>(e.OriginalSource);
|
if (item == null) return;
|
|
ITestStep step = (ITestStep)item.DataContext;
|
if ((Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control)
|
{
|
if (SelectedSteps.Contains(step))
|
{
|
SelectedSteps.Remove(step);
|
}
|
else
|
{
|
var parents = step.GetParents();
|
foreach (var parent in parents)
|
{
|
if (parent is ITestStep pStep)
|
{
|
SelectedSteps.Remove(pStep);
|
}
|
}
|
foreach (var child in step.RecursivelyGetChildSteps(TestStepSearch.All))
|
{
|
SelectedSteps.Remove(child);
|
}
|
SelectedSteps.Add(step);
|
}
|
return;
|
}
|
|
var old = SelectedStep;
|
SelectedStep = step;
|
SelectedItemChanged?.Invoke(sender, new RoutedPropertyChangedEventArgs<object>(old, SelectedStep));
|
|
SelectedSteps = new TestStepList { SelectedStep };
|
}
|
|
private void treeList_PreviewMouseRightButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
{
|
TreeListViewItem item = FindControl<TreeListViewItem>(e.OriginalSource);
|
if (item != null)
|
{
|
item.Focus();
|
var step = item.DataContext as ITestStep;
|
SelectedStep = step;
|
if (!SelectedSteps.Contains(step))
|
{
|
SelectedSteps = new TestStepList { SelectedStep };
|
}
|
CreateContextMenu(step);
|
}
|
else
|
{
|
CreateContextMenu(null);
|
}
|
}
|
|
private T FindControl<T>(object obj)
|
{
|
if (obj == null)
|
{
|
return default(T);
|
}
|
|
if (obj is T target)
|
{
|
return target;
|
}
|
return FindControl<T>(VisualTreeHelper.GetParent((DependencyObject)obj));
|
}
|
|
public void Unload()
|
{
|
if (Datasource != null)
|
{
|
Datasource.DebugIds.CollectionChanged -= DebugIds_CollectionChanged;
|
}
|
Datasource = null;
|
}
|
}
|
}
|