WPFUtils.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Drawing;
  5. using System.Globalization;
  6. using System.Windows;
  7. using System.Windows.Controls;
  8. using System.Windows.Input;
  9. using System.Windows.Media;
  10. using InABox.Core;
  11. using Microsoft.Xaml.Behaviors;
  12. using Brush = System.Windows.Media.Brush;
  13. using FontStyle = System.Windows.FontStyle;
  14. using Image = System.Windows.Controls.Image;
  15. namespace InABox.WPF
  16. {
  17. public static class WPFUtils
  18. {
  19. public static T? FindLogicalParent<T>(this DependencyObject dependencyObject)
  20. where T : DependencyObject
  21. {
  22. DependencyObject? parent = dependencyObject;
  23. do
  24. {
  25. parent = LogicalTreeHelper.GetParent(parent);
  26. } while(parent != null && parent is not T);
  27. return parent as T;
  28. }
  29. public static int GetRow(this Grid grid, DependencyObject dependencyObject)
  30. {
  31. while (true)
  32. {
  33. var parent = LogicalTreeHelper.GetParent(dependencyObject);
  34. if (parent == null)
  35. return -1;
  36. if (parent == grid)
  37. return Grid.GetRow(dependencyObject as UIElement);
  38. dependencyObject = parent;
  39. }
  40. }
  41. public static int GetRowSpan(this Grid grid, DependencyObject dependencyObject)
  42. {
  43. while (true)
  44. {
  45. var parent = LogicalTreeHelper.GetParent(dependencyObject);
  46. if (parent == null)
  47. return -1;
  48. if (parent == grid)
  49. return Grid.GetRowSpan(dependencyObject as UIElement);
  50. dependencyObject = parent;
  51. }
  52. }
  53. public static int GetColumn(this Grid grid, DependencyObject dependencyObject)
  54. {
  55. while (true)
  56. {
  57. var parent = LogicalTreeHelper.GetParent(dependencyObject);
  58. if (parent == null)
  59. return -1;
  60. if (parent == grid)
  61. return Grid.GetColumn(dependencyObject as UIElement);
  62. dependencyObject = parent;
  63. }
  64. }
  65. public static int GetColumnSpan(this Grid grid, DependencyObject dependencyObject)
  66. {
  67. while (true)
  68. {
  69. var parent = LogicalTreeHelper.GetParent(dependencyObject);
  70. if (parent == null)
  71. return -1;
  72. if (parent == grid)
  73. return Grid.GetColumnSpan(dependencyObject as UIElement);
  74. dependencyObject = parent;
  75. }
  76. }
  77. public static void SetGridPosition(this FrameworkElement element, int row, int column, int rowspan = 1, int colspan = 1)
  78. {
  79. element.SetValue(Grid.ColumnProperty, column);
  80. element.SetValue(Grid.ColumnSpanProperty, Math.Max(1, colspan));
  81. element.SetValue(Grid.RowProperty, row);
  82. element.SetValue(Grid.RowSpanProperty, Math.Max(1, rowspan));
  83. }
  84. public static IEnumerable<T> FindVisualChildren<T>(this DependencyObject depObj)
  85. {
  86. if (depObj != null)
  87. //ContentControl cc = depObj as ContentControl;
  88. //if (cc != null)
  89. //{
  90. // if (cc.Content == null)
  91. // yield return null;
  92. // if (cc.Content is T)
  93. // yield return cc.Content as T;
  94. // foreach (var child in FindVisualChildren<T>(cc.Content as DependencyObject))
  95. // yield return child;
  96. //}
  97. //else
  98. for (var i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
  99. {
  100. var child = VisualTreeHelper.GetChild(depObj, i);
  101. if (child is null)
  102. continue;
  103. if (child is T t)
  104. yield return t;
  105. foreach (var childOfChild in FindVisualChildren<T>(child))
  106. yield return childOfChild;
  107. }
  108. }
  109. #region Menu Utils
  110. private static void ItemsControlInsert(ItemsControl menu, FrameworkElement item, int index)
  111. {
  112. if (index != -1)
  113. {
  114. menu.Items.Insert(index, item);
  115. }
  116. else
  117. {
  118. menu.Items.Add(item);
  119. }
  120. }
  121. private static MenuItem DoAddMenuItem(ItemsControl menu, string caption, Bitmap? image, bool enabled, int index = -1)
  122. {
  123. var item = new MenuItem { Header = caption, IsEnabled = enabled };
  124. if (image != null)
  125. item.Icon = new Image() { Source = enabled ? image.AsBitmapImage(24, 24) : image.AsGrayScale().AsBitmapImage(24, 24) };
  126. ItemsControlInsert(menu, item, index);
  127. return item;
  128. }
  129. private static MenuItem DoAddMenuItem(ItemsControl menu, string caption, Bitmap? image, Action? click, bool enabled, int index = -1)
  130. {
  131. var item = DoAddMenuItem(menu, caption, image, enabled, index);
  132. if (click != null)
  133. {
  134. item.Click += (o, e) =>
  135. {
  136. click();
  137. };
  138. }
  139. return item;
  140. }
  141. private static MenuItem DoAddMenuItem<T>(ItemsControl menu, string caption, Bitmap? image, T tag, Action<T> click, bool enabled, int index = -1)
  142. {
  143. var item = DoAddMenuItem(menu, caption, image, enabled, index);
  144. item.Tag = tag;
  145. item.Click += (o, e) =>
  146. {
  147. click((T)(o as MenuItem)!.Tag);
  148. };
  149. return item;
  150. }
  151. public delegate void CheckToggleAction<T>(T tag, bool isChecked);
  152. private static MenuItem DoAddCheckItem<T>(ItemsControl menu, string caption, T tag, CheckToggleAction<T> click, bool isChecked, bool enabled, int index = -1)
  153. {
  154. var item = new MenuItem { Header = caption, IsEnabled = enabled, IsCheckable = true, IsChecked = isChecked };
  155. item.Tag = tag;
  156. item.Click += (o, e) =>
  157. {
  158. click((T)(o as MenuItem)!.Tag, item.IsChecked);
  159. };
  160. ItemsControlInsert(menu, item, index);
  161. return item;
  162. }
  163. private static Separator DoAddSeparator(ItemsControl menu, int index)
  164. {
  165. var separator = new Separator();
  166. ItemsControlInsert(menu, separator, index);
  167. return separator;
  168. }
  169. private static Separator DoAddSeparatorIfNeeded(ItemsControl menu, int index)
  170. {
  171. if (menu.Items.Count == 0) return null;
  172. var lastIndex = index != -1 ? index - 1 : menu.Items.Count - 1;
  173. if (lastIndex < 0 || lastIndex >= menu.Items.Count) return null;
  174. var last = menu.Items[lastIndex];
  175. if (last is Separator) return null;
  176. var separator = new Separator();
  177. ItemsControlInsert(menu, separator, index);
  178. return separator;
  179. }
  180. private static void DoRemoveUnnecessarySeparators(ItemsControl menu)
  181. {
  182. while(menu.Items.Count > 0 && menu.Items[0] is Separator)
  183. {
  184. menu.Items.RemoveAt(0);
  185. }
  186. while(menu.Items.Count > 0 && menu.Items[^1] is Separator)
  187. {
  188. menu.Items.RemoveAt(menu.Items.Count - 1);
  189. }
  190. }
  191. public static Separator AddSeparator(this ContextMenu menu, int index = -1) => DoAddSeparator(menu, index);
  192. public static Separator AddSeparator(this MenuItem menu, int index = -1) => DoAddSeparator(menu, index);
  193. public static Separator? AddSeparatorIfNeeded(this ContextMenu menu, int index = -1) => DoAddSeparatorIfNeeded(menu, index);
  194. public static Separator? AddSeparatorIfNeeded(this MenuItem menu, int index = -1) => DoAddSeparatorIfNeeded(menu, index);
  195. public static void RemoveUnnecessarySeparators(this ContextMenu menu) => DoRemoveUnnecessarySeparators(menu);
  196. public static void RemoveUnnecessarySeparators(this MenuItem menu) => DoRemoveUnnecessarySeparators(menu);
  197. public static MenuItem AddItem(this ContextMenu menu, string caption, Bitmap? image, Action? click, bool enabled = true, int index = -1)
  198. => DoAddMenuItem(menu, caption, image, click, enabled, index);
  199. public static MenuItem AddItem(this MenuItem menu, string caption, Bitmap? image, Action? click, bool enabled = true, int index = -1)
  200. => DoAddMenuItem(menu, caption, image, click, enabled, index);
  201. public static MenuItem AddItem<T>(this ContextMenu menu, string caption, Bitmap? image, T tag, Action<T> click, bool enabled = true, int index = -1)
  202. => DoAddMenuItem(menu, caption, image, tag, click, enabled, index);
  203. public static MenuItem AddItem<T>(this MenuItem menu, string caption, Bitmap? image, T tag, Action<T> click, bool enabled = true, int index = -1)
  204. => DoAddMenuItem(menu, caption, image, tag, click, enabled, index);
  205. public static MenuItem AddCheckItem<T>(this ContextMenu menu, string caption, T tag, CheckToggleAction<T> click, bool isChecked = false, bool enabled = true, int index = -1)
  206. => DoAddCheckItem<T>(menu, caption, tag, click, isChecked, enabled, index);
  207. #endregion
  208. }
  209. public class TextBoxPlaceholderBehaviour : Behavior<TextBox>
  210. {
  211. public static readonly DependencyProperty TextProperty =
  212. DependencyProperty.Register(
  213. nameof(Text),
  214. typeof(String),
  215. typeof(TextBoxPlaceholderBehaviour),
  216. new PropertyMetadata(""));
  217. public String Text
  218. {
  219. get => (String)GetValue(TextProperty);
  220. set
  221. {
  222. SetValue(TextProperty, value);
  223. UpdateAssociatedObject();
  224. }
  225. }
  226. public static readonly DependencyProperty TextColorProperty =
  227. DependencyProperty.Register(
  228. nameof(TextColor),
  229. typeof(System.Windows.Media.Color),
  230. typeof(TextBoxPlaceholderBehaviour),
  231. new PropertyMetadata(Colors.Gray));
  232. public System.Windows.Media.Color TextColor
  233. {
  234. get => (System.Windows.Media.Color)GetValue(TextColorProperty);
  235. set
  236. {
  237. SetValue(TextColorProperty, value);
  238. UpdateAssociatedObject();
  239. }
  240. }
  241. private readonly PropertyDescriptor _propertyDescriptor =
  242. DependencyPropertyDescriptor.FromProperty(TextBox.BackgroundProperty, typeof(TextBox));
  243. private Brush? _originalBackground;
  244. private Brush? _placeholderBackground;
  245. protected override void OnAttached()
  246. {
  247. base.OnAttached();
  248. // Store the Background that has been set on the Text Box (usu through XAML)
  249. _originalBackground = AssociatedObject.Background;
  250. // Start Monitoring changes to the Associated object Background property
  251. _propertyDescriptor.AddValueChanged(AssociatedObject, OnBackgroundChanged);
  252. AssociatedObject.TextChanged += OnTextChanged;
  253. AssociatedObject.SizeChanged += OnSizeChanged;
  254. UpdateAssociatedObject();
  255. }
  256. protected override void OnDetaching()
  257. {
  258. AssociatedObject.SizeChanged -= OnSizeChanged;
  259. AssociatedObject.TextChanged -= OnTextChanged;
  260. _propertyDescriptor?.RemoveValueChanged(AssociatedObject,OnBackgroundChanged);
  261. AssociatedObject.Background = _originalBackground;
  262. base.OnDetaching();
  263. }
  264. private void OnBackgroundChanged(object? sender, EventArgs e)
  265. {
  266. // Update the Saved Background (usu a color, but might not be?)
  267. _originalBackground = AssociatedObject.Background;
  268. _placeholderBackground = null;
  269. UpdateAssociatedObject();
  270. }
  271. private void OnTextChanged(object sender, TextChangedEventArgs e)
  272. {
  273. UpdateAssociatedObject();
  274. }
  275. private void SetBackground(Brush? brush)
  276. {
  277. _propertyDescriptor.RemoveValueChanged(AssociatedObject, OnBackgroundChanged);
  278. AssociatedObject.Background = brush;
  279. _propertyDescriptor.AddValueChanged(AssociatedObject, OnBackgroundChanged);
  280. }
  281. private void OnSizeChanged(object sender, SizeChangedEventArgs e)
  282. {
  283. _placeholderBackground = null;
  284. UpdateAssociatedObject();
  285. }
  286. private Brush CreatePlaceholder()
  287. {
  288. return new VisualBrush()
  289. {
  290. Visual = new Label()
  291. {
  292. Content = Text,
  293. Margin = new Thickness(0),
  294. Padding = new Thickness(4, 0, 2, 0),
  295. HorizontalAlignment = HorizontalAlignment.Stretch,
  296. VerticalAlignment = VerticalAlignment.Stretch,
  297. HorizontalContentAlignment = AssociatedObject.HorizontalContentAlignment,
  298. VerticalContentAlignment = AssociatedObject.VerticalContentAlignment,
  299. FontFamily = AssociatedObject.FontFamily,
  300. FontStretch = AssociatedObject.FontStretch,
  301. FontSize = AssociatedObject.FontSize,
  302. FontWeight = AssociatedObject.FontWeight,
  303. FontStyle = FontStyles.Italic,
  304. Background = _originalBackground?.Clone(),
  305. Foreground = new SolidColorBrush(TextColor),
  306. Width = AssociatedObject.ActualWidth,
  307. Height = AssociatedObject.ActualHeight
  308. },
  309. Stretch = Stretch.None,
  310. TileMode = TileMode.None,
  311. AlignmentX = AlignmentX.Left,
  312. AlignmentY = AlignmentY.Center
  313. };
  314. }
  315. private void UpdateAssociatedObject()
  316. {
  317. if (String.IsNullOrWhiteSpace(AssociatedObject.Text) && _placeholderBackground == null)
  318. {
  319. _placeholderBackground = CreatePlaceholder();
  320. SetBackground(_placeholderBackground);
  321. }
  322. else if (!String.IsNullOrWhiteSpace(AssociatedObject.Text) && _placeholderBackground != null)
  323. {
  324. _placeholderBackground = null;
  325. SetBackground(_originalBackground);
  326. }
  327. }
  328. }
  329. // public class TextBoxPlaceholderBehaviour : Behavior<TextBox>
  330. // {
  331. //
  332. // public static readonly DependencyProperty TextProperty =
  333. // DependencyProperty.Register(
  334. // nameof(Text),
  335. // typeof(String),
  336. // typeof(TextBoxPlaceholderBehaviour),
  337. // new PropertyMetadata(""));
  338. //
  339. //
  340. // public String Text
  341. // {
  342. // get => (String)GetValue(TextProperty);
  343. // set => SetValue(TextProperty, value);
  344. // }
  345. //
  346. // protected override void OnAttached()
  347. // {
  348. // base.OnAttached();
  349. // AssociatedObject.GotFocus += AssociatedObjectOnGotFocus;
  350. // AssociatedObject.LostFocus += AssociatedObjectOnLostFocus;
  351. // }
  352. //
  353. // protected override void OnDetaching()
  354. // {
  355. // AssociatedObject.GotFocus -= AssociatedObjectOnGotFocus;
  356. // AssociatedObject.LostFocus -= AssociatedObjectOnLostFocus;
  357. // base.OnDetaching();
  358. // }
  359. //
  360. // private void AssociatedObjectOnGotFocus(object sender, RoutedEventArgs e)
  361. // {
  362. // if (String.Equals(AssociatedObject.Text, Text))
  363. // {
  364. // AssociatedObject.Text = "";
  365. // AssociatedObject.Foreground = new SolidColorBrush(Colors.Black);
  366. // }
  367. // }
  368. //
  369. // private void AssociatedObjectOnLostFocus(object sender, RoutedEventArgs e)
  370. // {
  371. // if (String.IsNullOrWhiteSpace(AssociatedObject.Text))
  372. // {
  373. // AssociatedObject.Text = Text;
  374. // AssociatedObject.Foreground = new SolidColorBrush(Colors.Gray);
  375. // }
  376. // }
  377. // }
  378. public class TextBoxEnterAsTabBehavior :
  379. Behavior<TextBox>
  380. {
  381. protected override void OnAttached()
  382. {
  383. base.OnAttached();
  384. AssociatedObject.PreviewKeyDown += AssociatedObjectOnPreviewKeyDown;
  385. }
  386. protected override void OnDetaching()
  387. {
  388. AssociatedObject.PreviewKeyDown -= AssociatedObjectOnPreviewKeyDown;
  389. base.OnDetaching();
  390. }
  391. private void AssociatedObjectOnPreviewKeyDown(object sender, KeyEventArgs args)
  392. {
  393. if (args.Key != Key.Enter) { return; }
  394. args.Handled = true;
  395. AssociatedObject.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
  396. }
  397. }
  398. }