PluginViewDialog.xaml.cs 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. using System;
  2. using System.Threading;
  3. using System.Threading.Tasks;
  4. using System.Windows;
  5. using HandyControl.Controls;
  6. namespace TeamAAS.Dialogs.Dialogs
  7. {
  8. /// <summary>
  9. /// 通用插件视图对话框 - 承载插件自定义 UserControl。
  10. /// 三个按钮:执行(可选)、取消、确定。
  11. /// 用法与 PropertyGridDialog 一致,区别是用 ContentControl 替代 PropertyGrid。
  12. /// </summary>
  13. public partial class PluginViewDialog
  14. {
  15. /// <summary>用户是否点击了确定</summary>
  16. public bool Result { get; private set; }
  17. /// <summary>执行回调(对象, 取消令牌),传入则显示执行按钮</summary>
  18. public Action<object, CancellationToken> ExecuteAction { get; set; }
  19. /// <summary>当前编辑的对象(即视图的 DataContext)</summary>
  20. public object DataContext2 { get; private set; }
  21. private CancellationTokenSource _cts;
  22. private bool _isExecuting;
  23. /// <summary>最近一次持久提示(执行结果 CT 等)。悬停提示结束后回落到这里。</summary>
  24. private string _lastTip = string.Empty;
  25. public PluginViewDialog()
  26. {
  27. InitializeComponent();
  28. UiScaler.Attach(this); // 分辨率/DPI 自适应
  29. Closing += (s, e) => { if (_isExecuting) e.Cancel = true; };
  30. // 回车 = 确认(执行中除外)
  31. PreviewKeyDown += (s, e) =>
  32. {
  33. if (e.Key == System.Windows.Input.Key.Enter && !_isExecuting && !IsFocusInCodeEditor(e.OriginalSource))
  34. {
  35. BtnOK.RaiseEvent(new RoutedEventArgs(System.Windows.Controls.Button.ClickEvent));
  36. e.Handled = true;
  37. }
  38. };
  39. }
  40. /// <summary>
  41. /// 设置插件视图、标题。
  42. /// </summary>
  43. /// <param name="view">插件自定义 UserControl</param>
  44. /// <param name="title">窗口标题</param>
  45. public void Setup(FrameworkElement view, string title = null)
  46. {
  47. ViewHost.Content = view;
  48. DataContext2 = view.DataContext;
  49. Title = title ?? "插件编辑";
  50. }
  51. /// <summary>
  52. /// 设置插件视图、标题和执行回调。
  53. /// </summary>
  54. public void Setup(FrameworkElement view, string title, Action<object, CancellationToken> executeAction)
  55. {
  56. ViewHost.Content = view;
  57. DataContext2 = view.DataContext;
  58. Title = title ?? "插件编辑";
  59. ExecuteAction = executeAction;
  60. if (executeAction != null)
  61. BtnExecute.Visibility = Visibility.Visible;
  62. }
  63. /// <summary>
  64. /// 设置底部提示栏的持久提示(线程安全)。
  65. /// 用于执行结果 CT、状态信息等;悬停提示结束后自动回落到最近一次持久提示。
  66. /// </summary>
  67. public void ShowTip(string message)
  68. {
  69. void Apply()
  70. {
  71. if (string.IsNullOrWhiteSpace(message))
  72. {
  73. TxtTip.Text = _lastTip;
  74. return;
  75. }
  76. _lastTip = message;
  77. TxtTip.Text = message;
  78. }
  79. if (Dispatcher.CheckAccess()) Apply(); else Dispatcher.Invoke(Apply);
  80. }
  81. /// <summary>
  82. /// 显示鼠标悬停临时提示(线程安全)。message 为 null/空时结束悬停,回落到最近一次持久提示。
  83. /// </summary>
  84. public void ShowHoverHint(string message)
  85. {
  86. void Apply()
  87. {
  88. TxtTip.Text = string.IsNullOrWhiteSpace(message) ? _lastTip : message;
  89. }
  90. if (Dispatcher.CheckAccess()) Apply(); else Dispatcher.Invoke(Apply);
  91. }
  92. /// <summary>窗体自带按钮的悬停提示:把 Tag 文本写入底部提示栏</summary>
  93. private void BtnTip_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
  94. {
  95. if ((sender as FrameworkElement)?.Tag is string tip && !string.IsNullOrWhiteSpace(tip))
  96. TxtTip.Text = tip;
  97. }
  98. /// <summary>悬停结束:回落到最近一次持久提示</summary>
  99. private void BtnTip_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
  100. {
  101. TxtTip.Text = _lastTip;
  102. }
  103. /// <summary>
  104. /// 焦点是否落在代码编辑器(ICSharpCode.AvalonEdit —— CodeForge 内部所用)内。
  105. /// 脚本节点里敲回车是换行,不能被窗体的"回车=确定"劫持,否则一换行就关窗。
  106. /// </summary>
  107. private static bool IsFocusInCodeEditor(object originalSource)
  108. {
  109. var dep = originalSource as DependencyObject
  110. ?? System.Windows.Input.Keyboard.FocusedElement as DependencyObject;
  111. int guard = 0;
  112. while (dep != null && guard++ < 40)
  113. {
  114. var ns = dep.GetType().Namespace;
  115. if (!string.IsNullOrEmpty(ns) && ns.StartsWith("ICSharpCode.AvalonEdit", StringComparison.Ordinal))
  116. return true;
  117. try
  118. {
  119. dep = System.Windows.Media.VisualTreeHelper.GetParent(dep)
  120. ?? LogicalTreeHelper.GetParent(dep);
  121. }
  122. catch { break; }
  123. }
  124. return false;
  125. }
  126. private void BtnExecute_Click(object sender, RoutedEventArgs e)
  127. {
  128. if (_isExecuting)
  129. {
  130. _cts?.Cancel();
  131. return;
  132. }
  133. _cts = new CancellationTokenSource();
  134. _isExecuting = true;
  135. BtnExecute.Content = "停止";
  136. BtnExecute.Style = FindResource("ButtonDanger") as Style;
  137. ViewHost.IsEnabled = false;
  138. BtnOK.IsEnabled = false;
  139. BtnCancel.IsEnabled = false;
  140. var token = _cts.Token;
  141. var obj = DataContext2;
  142. Task.Run(() =>
  143. {
  144. try
  145. {
  146. ExecuteAction?.Invoke(obj, token);
  147. }
  148. catch (OperationCanceledException) { }
  149. catch (Exception ex)
  150. {
  151. Dispatcher.Invoke(() => Growl.Error($"执行失败: {ex.Message}"));
  152. }
  153. }).ContinueWith(t =>
  154. {
  155. Dispatcher.Invoke(() =>
  156. {
  157. _isExecuting = false;
  158. BtnExecute.Content = "执行";
  159. BtnExecute.Style = FindResource("ButtonInfo") as Style;
  160. ViewHost.IsEnabled = true;
  161. BtnOK.IsEnabled = true;
  162. BtnCancel.IsEnabled = true;
  163. if (t.IsCanceled)
  164. Growl.Info("已停止");
  165. });
  166. });
  167. }
  168. private void BtnOK_Click(object sender, RoutedEventArgs e)
  169. {
  170. _cts?.Cancel();
  171. Result = true;
  172. Close();
  173. }
  174. private void BtnCancel_Click(object sender, RoutedEventArgs e)
  175. {
  176. _cts?.Cancel();
  177. Result = false;
  178. Close();
  179. }
  180. }
  181. }