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 { /// /// StepTree.xaml 的交互逻辑 /// [ObservableObject] public partial class StepTree : UserControl { public event RoutedPropertyChangedEventHandler SelectedItemChanged; [ObservableProperty] private TestStepList datasource; partial void OnDatasourceChanged(TestStepList value) { DebugIds.CollectionChanged -= DebugIds_CollectionChanged; DebugIds = value?.DebugIds ?? new ObservableCollection(); DebugIds.CollectionChanged += DebugIds_CollectionChanged; } private void DebugIds_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { OnPropertyChanged(nameof(DebugIds)); } [ObservableProperty] ObservableCollection debugIds = new ObservableCollection(); [ObservableProperty] private Guid debugStepId = Guid.Empty; [ObservableProperty] private Guid runningStepId = Guid.Empty; [ObservableProperty] private ConcurrentDictionary complatedIds = new ConcurrentDictionary(); 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 RefreshMenuItems(ITestStep selectedStep, bool addChild) { var allSteps = StepTypeLoader.Steps; var StepMenuItems = new List(allSteps.Count); Dictionary groupMap = new Dictionary(); Dictionary groupAdapMap = new Dictionary(); foreach (ITypeData stepType in allSteps) { var display = stepType.GetAttribute(); 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 e) { } /// /// 显示菜单 /// /// 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 /// /// 添加同级Step /// /// /// 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); } } /// /// 添加子Step /// /// /// 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(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(old, SelectedStep)); SelectedSteps = new TestStepList { SelectedStep }; } private void treeList_PreviewMouseRightButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e) { TreeListViewItem item = FindControl(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(object obj) { if (obj == null) { return default(T); } if (obj is T target) { return target; } return FindControl(VisualTreeHelper.GetParent((DependencyObject)obj)); } public void Unload() { if (Datasource != null) { Datasource.DebugIds.CollectionChanged -= DebugIds_CollectionChanged; } Datasource = null; } } }