You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

471 lines
16 KiB

  1. using BPA.UIControl.Commons.KnownBoxes;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Windows;
  5. using System.Windows.Controls;
  6. using System.Windows.Controls.Primitives;
  7. using System.Windows.Input;
  8. using System.Windows.Media;
  9. namespace BPA.UIControl
  10. {
  11. /// <summary>
  12. /// 对话框
  13. /// </summary>
  14. [TemplatePart(Name = TransitionName, Type = typeof(Transition))]
  15. [TemplatePart(Name = CloseButtonPartName, Type = typeof(Button))]
  16. [TemplatePart(Name = BackgroundBorderPartName, Type = typeof(Border))]
  17. [TemplatePart(Name = ContentPresenterPartName, Type = typeof(ContentPresenter))]
  18. public class DialogContainer : ContentControl
  19. {
  20. /// <summary>
  21. /// 转换动画名称
  22. /// </summary>
  23. public const string TransitionName = "Path_Transition";
  24. /// <summary>
  25. /// 关闭按钮名称
  26. /// </summary>
  27. public const string CloseButtonPartName = "PART_CloseButton";
  28. /// <summary>
  29. /// 背景 Border 名称
  30. /// </summary>
  31. public const string BackgroundBorderPartName = "PART_BackgroundBorder";
  32. /// <summary>
  33. /// Content 内容名称
  34. /// </summary>
  35. public const string ContentPresenterPartName = "PART_ContentPresenter";
  36. /// <summary>
  37. /// 对话框结果路由事件处理
  38. /// </summary>
  39. /// <param name="sender"></param>
  40. /// <param name="e"></param>
  41. public delegate void DialogResultRoutedEventHandler(object sender, DialogResultRoutedEventArgs e);
  42. /// <summary>
  43. /// 打开对话框前事件处理
  44. /// </summary>
  45. public Action<DialogContainer> BeforeOpenHandler;
  46. /// <summary>
  47. /// 打开对话框后事件处理
  48. /// </summary>
  49. public Action<DialogContainer, object> AfterCloseHandler;
  50. private Border rootBorder;
  51. private object closeParameter;
  52. private List<FrameworkElement> focusableElements; // Content 内 focusable 元素,用于打开弹窗使其失效
  53. /// <summary>
  54. /// Initializes a new instance of the <see cref="DialogContainer"/> class.
  55. /// </summary>
  56. static DialogContainer()
  57. {
  58. DefaultStyleKeyProperty.OverrideMetadata(typeof(DialogContainer), new FrameworkPropertyMetadata(typeof(DialogContainer)));
  59. }
  60. /// <inheritdoc/>
  61. public override void OnApplyTemplate()
  62. {
  63. base.OnApplyTemplate();
  64. CommandBindings.Add(new CommandBinding(CloseDialogCommand, CloseDialogHandler));
  65. CommandBindings.Add(new CommandBinding(OpenDialogCommand, OpenDialogHandler));
  66. ButtonBase closeButton = GetTemplateChild(CloseButtonPartName) as ButtonBase;
  67. closeButton.Click += (sender, args) =>
  68. {
  69. closeParameter = null;
  70. IsShow = false;
  71. };
  72. rootBorder = GetTemplateChild(BackgroundBorderPartName) as Border;
  73. rootBorder.MouseLeftButtonDown += RootBorder_MouseLeftButtonDown;
  74. if (GetTemplateChild(TransitionName) is Transition transition)
  75. {
  76. transition.Showed += (sender, e) => IsClosed = false;
  77. transition.Closed += Closed;
  78. }
  79. if (GetTemplateChild(ContentPresenterPartName) is ContentPresenter contentPresenter)
  80. {
  81. contentPresenter.PreviewKeyDown += ContentPresenter_PreviewKeyDown;
  82. }
  83. focusableElements = new List<FrameworkElement>();
  84. }
  85. #region 命令
  86. /// <summary>
  87. /// 关闭对话框命令
  88. /// </summary>
  89. public static RoutedCommand CloseDialogCommand = new RoutedCommand();
  90. /// <summary>
  91. /// 打开对话框命令
  92. /// </summary>
  93. public static RoutedCommand OpenDialogCommand = new RoutedCommand();
  94. /// <summary>
  95. /// 打开前命令
  96. /// </summary>
  97. public static readonly DependencyProperty BeforeOpenCommandProperty = DependencyProperty.Register(
  98. "BeforeOpenCommand", typeof(ICommand), typeof(DialogContainer));
  99. /// <summary>
  100. /// 打开前命令
  101. /// </summary>
  102. public ICommand BeforeOpenCommand
  103. {
  104. get { return (ICommand)GetValue(BeforeOpenCommandProperty); }
  105. set { SetValue(BeforeOpenCommandProperty, value); }
  106. }
  107. /// <summary>
  108. /// 关闭后命令
  109. /// </summary>
  110. public static readonly DependencyProperty AfterCloseCommandProperty = DependencyProperty.Register(
  111. "AfterCloseCommand", typeof(ICommand), typeof(DialogContainer));
  112. /// <summary>
  113. /// 关闭后命令
  114. /// </summary>
  115. public ICommand AfterCloseCommand
  116. {
  117. get { return (ICommand)GetValue(AfterCloseCommandProperty); }
  118. set { SetValue(AfterCloseCommandProperty, value); }
  119. }
  120. #endregion 命令
  121. #region 事件
  122. /// <summary>
  123. /// 打开前事件
  124. /// </summary>
  125. public static readonly RoutedEvent BeforeOpenEvent = EventManager.RegisterRoutedEvent(
  126. "BeforeOpen", RoutingStrategy.Direct, typeof(RoutedEventHandler), typeof(DialogContainer));
  127. /// <summary>
  128. /// 打开前事件
  129. /// </summary>
  130. public event RoutedEventHandler BeforeOpen
  131. {
  132. add { AddHandler(BeforeOpenEvent, value); }
  133. remove { RemoveHandler(BeforeOpenEvent, value); }
  134. }
  135. /// <summary>
  136. /// 关闭后事件
  137. /// </summary>
  138. public static readonly RoutedEvent AfterCloseEvent = EventManager.RegisterRoutedEvent(
  139. "AfterClose", RoutingStrategy.Direct, typeof(DialogResultRoutedEventHandler), typeof(DialogContainer));
  140. /// <summary>
  141. /// 关闭后事件
  142. /// </summary>
  143. public event DialogResultRoutedEventHandler AfterClose
  144. {
  145. add { AddHandler(AfterCloseEvent, value); }
  146. remove { RemoveHandler(AfterCloseEvent, value); }
  147. }
  148. #endregion 事件
  149. #region 依赖属性
  150. /// <summary>
  151. /// 标识
  152. /// </summary>
  153. public static readonly DependencyProperty IdentifierProperty = DependencyProperty.Register(
  154. "Identifier", typeof(string), typeof(DialogContainer), new PropertyMetadata(default(string), OnIdentifierChanged));
  155. private static void OnIdentifierChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  156. {
  157. DialogContainer dialog = d as DialogContainer;
  158. string identifier = e.NewValue.ToString();
  159. Dialog.AddDialogContainer(identifier, dialog);
  160. }
  161. /// <summary>
  162. /// 标识
  163. /// </summary>
  164. public string Identifier
  165. {
  166. get { return (string)GetValue(IdentifierProperty); }
  167. set { SetValue(IdentifierProperty, value); }
  168. }
  169. /// <summary>
  170. /// 对话框内容
  171. /// </summary>
  172. public static readonly DependencyProperty DialogContentProperty = DependencyProperty.Register(
  173. "DialogContent", typeof(object), typeof(DialogContainer), new PropertyMetadata(default(object)));
  174. /// <summary>
  175. /// 对话框内容
  176. /// </summary>
  177. public object DialogContent
  178. {
  179. get { return GetValue(DialogContentProperty); }
  180. set { SetValue(DialogContentProperty, value); }
  181. }
  182. /// <summary>
  183. /// 标题
  184. /// </summary>
  185. public static readonly DependencyProperty TitleProperty = DependencyProperty.Register(
  186. "Title", typeof(string), typeof(DialogContainer), new FrameworkPropertyMetadata(default(string), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
  187. /// <summary>
  188. /// 标题
  189. /// </summary>
  190. public string Title
  191. {
  192. get { return (string)GetValue(TitleProperty); }
  193. set { SetValue(TitleProperty, value); }
  194. }
  195. /// <summary>
  196. /// 圆角半径
  197. /// </summary>
  198. public static readonly DependencyProperty CornerRadiusProperty = DependencyProperty.Register(
  199. "CornerRadius", typeof(CornerRadius), typeof(DialogContainer), new PropertyMetadata(default(CornerRadius)));
  200. /// <summary>
  201. /// 圆角半径
  202. /// </summary>
  203. public CornerRadius CornerRadius
  204. {
  205. get { return (CornerRadius)GetValue(CornerRadiusProperty); }
  206. set { SetValue(CornerRadiusProperty, value); }
  207. }
  208. /// <summary>
  209. /// 是否显示关闭按钮
  210. /// </summary>
  211. public static readonly DependencyProperty IsShowCloseButtonProperty = DependencyProperty.Register(
  212. "IsShowCloseButton", typeof(bool), typeof(DialogContainer), new FrameworkPropertyMetadata(BooleanBoxes.FalseBox, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
  213. /// <summary>
  214. /// 是否显示关闭按钮
  215. /// </summary>
  216. public bool IsShowCloseButton
  217. {
  218. get { return (bool)GetValue(IsShowCloseButtonProperty); }
  219. set { SetValue(IsShowCloseButtonProperty, BooleanBoxes.Box(value)); }
  220. }
  221. /// <summary>
  222. /// 是否 esc 键关闭弹窗 (点击空白关闭)
  223. /// </summary>
  224. public static readonly DependencyProperty IsEscKeyToCloseProperty = DependencyProperty.Register(
  225. "IsEscKeyToClose", typeof(bool), typeof(DialogContainer), new PropertyMetadata(BooleanBoxes.FalseBox, OnIsEscKeyToCloseChanged));
  226. private static void OnIsEscKeyToCloseChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  227. {
  228. if (d is DialogContainer dialog)
  229. {
  230. if (dialog.IsEscKeyToClose)
  231. {
  232. var escKeyBinding = new KeyBinding(DialogContainer.CloseDialogCommand, Key.Escape, ModifierKeys.None);
  233. dialog.InputBindings.Add(escKeyBinding);
  234. }
  235. else
  236. {
  237. foreach (var inputBinding in dialog.InputBindings)
  238. {
  239. if (inputBinding is KeyBinding keyBinding && keyBinding.Key == Key.Escape)
  240. {
  241. dialog.InputBindings.Remove(keyBinding);
  242. }
  243. }
  244. }
  245. }
  246. }
  247. /// <summary>
  248. /// 是否 esc 键关闭弹窗 (点击空白关闭)
  249. /// </summary>
  250. public bool IsEscKeyToClose
  251. {
  252. get { return (bool)GetValue(IsEscKeyToCloseProperty); }
  253. set { SetValue(IsEscKeyToCloseProperty, BooleanBoxes.Box(value)); }
  254. }
  255. /// <summary>
  256. /// 是否显示
  257. /// </summary>
  258. public static readonly DependencyProperty IsShowProperty = DependencyProperty.Register(
  259. "IsShow", typeof(bool), typeof(DialogContainer), new FrameworkPropertyMetadata(BooleanBoxes.FalseBox, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnIsShowChanged));
  260. private static void OnIsShowChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  261. {
  262. DialogContainer dialog = d as DialogContainer;
  263. if (dialog.IsShow)
  264. {
  265. if (dialog.Content is FrameworkElement dialogContent)
  266. {
  267. dialogContent.ForEachVisualChild(x =>
  268. {
  269. if (x is FrameworkElement element && element.Focusable)
  270. {
  271. element.Focusable = false;
  272. dialog.focusableElements.Add(element);
  273. }
  274. });
  275. dialog.OpenAnimiation(dialog);
  276. }
  277. }
  278. else
  279. {
  280. dialog.focusableElements.ForEach(x => x.Focusable = true);
  281. dialog.focusableElements.Clear();
  282. }
  283. }
  284. /// <summary>
  285. /// 是否显示
  286. /// </summary>
  287. public bool IsShow
  288. {
  289. get { return (bool)GetValue(IsShowProperty); }
  290. set { SetValue(IsShowProperty, BooleanBoxes.Box(value)); }
  291. }
  292. /// <summary>
  293. /// 遮罩背景色
  294. /// </summary>
  295. public static readonly DependencyProperty MaskBackgroundProperty = DependencyProperty.Register(
  296. "MaskBackground", typeof(Brush), typeof(DialogContainer), new PropertyMetadata(default(Brush)));
  297. /// <summary>
  298. /// 遮罩背景色
  299. /// </summary>
  300. public Brush MaskBackground
  301. {
  302. get { return (Brush)GetValue(MaskBackgroundProperty); }
  303. set { SetValue(MaskBackgroundProperty, value); }
  304. }
  305. /// <summary>
  306. /// 关闭完成
  307. /// </summary>
  308. public static readonly DependencyProperty IsClosedProperty = DependencyProperty.Register(
  309. "IsClosed", typeof(bool), typeof(DialogContainer), new PropertyMetadata(BooleanBoxes.FalseBox));
  310. /// <summary>
  311. /// 关闭完成
  312. /// </summary>
  313. public bool IsClosed
  314. {
  315. get { return (bool)GetValue(IsClosedProperty); }
  316. set { SetValue(IsClosedProperty, BooleanBoxes.Box(value)); }
  317. }
  318. #endregion 依赖属性
  319. private void OpenDialogHandler(object sender, ExecutedRoutedEventArgs e)
  320. {
  321. IsShow = true;
  322. }
  323. private void CloseDialogHandler(object sender, ExecutedRoutedEventArgs e)
  324. {
  325. closeParameter = e.Parameter;
  326. IsShow = false;
  327. }
  328. private void RootBorder_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
  329. {
  330. if (e.OriginalSource is Border border)
  331. {
  332. if (IsEscKeyToClose && sender.Equals(border))
  333. {
  334. closeParameter = null;
  335. IsShow = false;
  336. }
  337. }
  338. }
  339. /// <summary>
  340. /// 关闭对话框
  341. /// </summary>
  342. /// <param name="parameter">参数</param>
  343. public void Close(object parameter = null)
  344. {
  345. CloseDialogCommand.Execute(parameter, this);
  346. }
  347. // 打开对话框动作
  348. private void OpenAnimiation(DialogContainer dialog)
  349. {
  350. RoutedEventArgs args = new RoutedEventArgs(BeforeOpenEvent);
  351. dialog.RaiseEvent(args);
  352. dialog.BeforeOpenCommand?.Execute(null);
  353. dialog.BeforeOpenHandler?.Invoke(dialog);
  354. _ = dialog.Focus();
  355. }
  356. private void Closed(object sender, RoutedEventArgs e)
  357. {
  358. IsClosed = true;
  359. var args = new DialogResultRoutedEventArgs(AfterCloseEvent, this.closeParameter, this);
  360. args.RoutedEvent = AfterCloseEvent;
  361. this.RaiseEvent(args);
  362. this.AfterCloseCommand?.Execute(this.closeParameter);
  363. this.AfterCloseHandler?.Invoke(this, this.closeParameter);
  364. this.closeParameter = null;
  365. this.BeforeOpenHandler = null;
  366. this.AfterCloseHandler = null;
  367. }
  368. private void ContentPresenter_PreviewKeyDown(object sender, KeyEventArgs e)
  369. {
  370. Key key = e.Key == Key.System ? e.SystemKey : e.Key;
  371. if (key == Key.Tab && IsShow)
  372. {
  373. e.Handled = true;
  374. }
  375. }
  376. }
  377. /// <summary>
  378. /// 对话框结果路由参数
  379. /// </summary>
  380. public class DialogResultRoutedEventArgs : RoutedEventArgs
  381. {
  382. /// <summary>
  383. /// 结果
  384. /// </summary>
  385. public object Result { get; set; }
  386. /// <summary>
  387. /// 对话框
  388. /// </summary>
  389. public DialogContainer Dialog { get; set; }
  390. /// <summary>
  391. /// Initializes a new instance of the <see cref="DialogResultRoutedEventArgs"/> class.
  392. /// </summary>
  393. /// <param name="routedEvent">The routed event.</param>
  394. /// <param name="result">The result.</param>
  395. /// <param name="dialog">The dialog.</param>
  396. public DialogResultRoutedEventArgs(RoutedEvent routedEvent, object result, DialogContainer dialog)
  397. : base(routedEvent)
  398. {
  399. Result = result;
  400. Dialog = dialog;
  401. }
  402. }
  403. }