DynamicTreeView.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Collections.ObjectModel;
  4. using System.ComponentModel;
  5. using System.Linq;
  6. using System.Linq.Expressions;
  7. using System.Windows;
  8. using System.Windows.Controls;
  9. using System.Windows.Media;
  10. using System.Windows.Media.Imaging;
  11. using InABox.Core;
  12. using InABox.WPF;
  13. using Syncfusion.UI.Xaml.Grid;
  14. using Syncfusion.UI.Xaml.TreeGrid;
  15. namespace InABox.DynamicGrid
  16. {
  17. public enum DynamicTreeOption
  18. {
  19. Add,
  20. Edit,
  21. Delete
  22. }
  23. public enum DynamicTreeGridLines
  24. {
  25. Both,
  26. Horizontal,
  27. Vertical,
  28. None
  29. }
  30. public delegate void OnSelectItem(CoreTreeNode node);
  31. public delegate void OnContextMenuOpening(CoreTreeNode node, ContextMenu menu);
  32. public abstract class DynamicTreeView<T> : ContentControl where T : BaseObject, new()
  33. {
  34. protected abstract Expression<Func<T, Guid>> ID { get; }
  35. protected abstract Expression<Func<T, Guid>> ParentID { get; }
  36. protected abstract Expression<Func<T, String>> Description { get; }
  37. public CoreTable Data { get; private set; }
  38. private ContextMenu _menu;
  39. private SfTreeGrid _tree;
  40. private DockPanel _dock;
  41. private Grid _grid;
  42. private Button _add;
  43. private Button _edit;
  44. private Button _delete;
  45. private Label _spacer;
  46. public FluentList<DynamicTreeOption> Options { get; private set; }
  47. public event OnSelectItem OnSelectItem;
  48. public event OnContextMenuOpening OnContextMenuOpening;
  49. private double minRowHeight = 30D;
  50. private double maxRowHeight = 30D;
  51. public double MinRowHeight
  52. {
  53. get => minRowHeight;
  54. set
  55. {
  56. minRowHeight = value;
  57. CalculateRowHeight();
  58. }
  59. }
  60. public double MaxRowHeight
  61. {
  62. get => maxRowHeight;
  63. set
  64. {
  65. maxRowHeight = value;
  66. CalculateRowHeight();
  67. }
  68. }
  69. private bool _shownumbers = false;
  70. public bool ShowNumbers
  71. {
  72. get => _shownumbers;
  73. set
  74. {
  75. _shownumbers = value;
  76. _tree.Columns[1].Width = value ? 50 : 0;
  77. }
  78. }
  79. private DynamicTreeGridLines _gridLines = DynamicTreeGridLines.Both;
  80. public DynamicTreeGridLines GridLines
  81. {
  82. get => _gridLines;
  83. set
  84. {
  85. _gridLines = value;
  86. _tree.GridLinesVisibility = value switch
  87. {
  88. DynamicTreeGridLines.Both => GridLinesVisibility.Both,
  89. DynamicTreeGridLines.Horizontal => GridLinesVisibility.Horizontal,
  90. DynamicTreeGridLines.Vertical => GridLinesVisibility.Vertical,
  91. _ => GridLinesVisibility.None
  92. };
  93. }
  94. }
  95. /*public double RowHeight
  96. {
  97. get => _tree.RowHeight;
  98. set => _tree.RowHeight = value;
  99. }*/
  100. public DynamicTreeView() : base()
  101. {
  102. Options = new FluentList<DynamicTreeOption>();
  103. Options.OnChanged += OptionsChanged;
  104. _grid = new Grid();
  105. _grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1D, GridUnitType.Star) });
  106. _grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1D, GridUnitType.Star) });
  107. _grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1D, GridUnitType.Auto) });
  108. _tree = new SfTreeGrid();
  109. _tree.ChildPropertyName = "Children";
  110. //_tree.ParentPropertyName = "Parent";
  111. _tree.AutoGenerateColumns = false;
  112. _tree.AutoExpandMode = AutoExpandMode.AllNodesExpanded;
  113. //_tree.NodeCollapsing += (o, e) => { e.Cancel = true; };
  114. _tree.HeaderRowHeight = 0D;
  115. _tree.SelectionChanged += (o,e) => OnSelectItem?.Invoke((_tree.SelectedItem as CoreTreeNode)!);
  116. _tree.AllowSelectionOnExpanderClick = false;
  117. _menu = new ContextMenu();
  118. var additem = new MenuItem() { Header = "Add Child Folder" };
  119. additem.Click += (o, e) => { DoAddItem((_tree.SelectedItem as CoreTreeNode)!.ID, true); };
  120. _menu.Items.Add(additem);
  121. _tree.ContextMenuOpening += _tree_ContextMenuOpening;
  122. _tree.ContextMenu = _menu;
  123. _tree.Background = new SolidColorBrush(Colors.DimGray);
  124. var cellStyle = new Style(typeof(TreeGridRowControl));
  125. cellStyle.Setters.Add(new Setter(BackgroundProperty, new SolidColorBrush(Colors.White)));
  126. _tree.RowStyle = cellStyle;
  127. _tree.SelectionBackground = new SolidColorBrush(System.Windows.Media.Color.FromArgb(0xFF, 0x11, 0x9E, 0xD9));
  128. _tree.Columns.Add(new TreeGridTextColumn()
  129. {
  130. MappingName = "Description"
  131. }
  132. );
  133. _tree.Columns.Add(new TreeGridTextColumn()
  134. {
  135. MappingName = "Number",
  136. Width = _shownumbers ? 50 : 0,
  137. TextAlignment = TextAlignment.Right
  138. }
  139. );
  140. _tree.ColumnSizer = TreeColumnSizer.Star;
  141. _tree.RowHeight = 30D;
  142. _tree.SetValue(Grid.RowProperty, 0);
  143. _grid.Children.Add(_tree);
  144. _dock = new DockPanel();
  145. _dock.SetValue(Grid.RowProperty, 1);
  146. _grid.Children.Add(_dock);
  147. _add = CreateButton(Wpf.Resources.add.AsBitmapImage(System.Drawing.Color.White), "", "Add Item", (o) => DoAddItem(Guid.Empty, true));
  148. _add.Margin = new Thickness(0, 2, 2, 0);
  149. _add.Visibility = Visibility.Collapsed;
  150. _add.SetValue(DockPanel.DockProperty, Dock.Left);
  151. _dock.Children.Add(_add);
  152. _edit = CreateButton(Wpf.Resources.pencil.AsBitmapImage(System.Drawing.Color.White), "", "Edit Item", EditItem);
  153. _edit.Margin = new Thickness(0, 2, 2, 0);
  154. _edit.Visibility = Visibility.Collapsed;
  155. _edit.SetValue(DockPanel.DockProperty, Dock.Left);
  156. _dock.Children.Add(_edit);
  157. _delete = CreateButton(Wpf.Resources.delete.AsBitmapImage(System.Drawing.Color.White), "", "Delete Item", DeleteItem);
  158. _delete.Margin = new Thickness(2, 2, 0, 0);
  159. _delete.Visibility = Visibility.Collapsed;
  160. _delete.SetValue(DockPanel.DockProperty, Dock.Right);
  161. _dock.Children.Add(_delete);
  162. _spacer = new Label();
  163. _spacer.SetValue(DockPanel.DockProperty, Dock.Left);
  164. _dock.Children.Add(_spacer);
  165. Content = _grid;
  166. SizeChanged += DynamicTreeView_SizeChanged;
  167. }
  168. #region Public Interface
  169. public void AddItem(CoreTreeNode? parentNode = null, bool edit = true)
  170. {
  171. var id = parentNode?.ID ?? Guid.Empty;
  172. DoAddItem(id, edit);
  173. }
  174. #endregion
  175. private void _tree_ContextMenuOpening(object sender, ContextMenuEventArgs e)
  176. {
  177. _menu.Items.Clear();
  178. if (OnContextMenuOpening is not null)
  179. {
  180. OnContextMenuOpening.Invoke((_tree.SelectedItem as CoreTreeNode)!, _menu);
  181. if(_menu.Items.Count == 0)
  182. {
  183. e.Handled = true;
  184. }
  185. }
  186. else
  187. {
  188. if (Options.Contains(DynamicTreeOption.Add))
  189. {
  190. _menu.AddItem("Add Item", null, (_tree.SelectedItem as CoreTreeNode)!.ID, (id) => DoAddItem(id, true));
  191. }
  192. }
  193. }
  194. private void DynamicTreeView_SizeChanged(object sender, SizeChangedEventArgs e)
  195. {
  196. CalculateRowHeight();
  197. }
  198. private void CalculateRowHeight()
  199. {
  200. if(Data != null && Data.Rows.Count > 0)
  201. {
  202. var contentHeight = _tree.ActualHeight - (_tree.Padding.Top + _tree.Padding.Bottom) - 2; // Two extra pixels of space
  203. var targetHeight = contentHeight / Data.Rows.Count;
  204. _tree.RowHeight = Math.Max(Math.Min(targetHeight, MaxRowHeight), MinRowHeight);
  205. }
  206. }
  207. private Button CreateButton(BitmapImage? image = null, string? text = null, string? tooltip = null, Action<Button>? action = null)
  208. {
  209. var button = new Button();
  210. button.SetValue(BorderBrushProperty, new SolidColorBrush(Colors.Gray));
  211. button.SetValue(BorderThicknessProperty, new Thickness(0.75));
  212. button.Height = 30;
  213. button.MinWidth = 30;
  214. button.Click += (o, e) => action?.Invoke(button);
  215. UpdateButton(button, image, text, tooltip);
  216. return button;
  217. }
  218. protected void UpdateButton(Button button, BitmapImage? image, string? text, string? tooltip = null)
  219. {
  220. var stackPnl = new StackPanel();
  221. stackPnl.Orientation = Orientation.Horizontal;
  222. if (image != null)
  223. {
  224. var img = new Image();
  225. img.Source = image;
  226. img.Margin = new Thickness(2);
  227. img.ToolTip = tooltip;
  228. stackPnl.Children.Add(img);
  229. }
  230. if (!string.IsNullOrEmpty(text))
  231. {
  232. var lbl = new Label();
  233. lbl.Content = text;
  234. lbl.VerticalAlignment = VerticalAlignment.Stretch;
  235. lbl.VerticalContentAlignment = VerticalAlignment.Center;
  236. lbl.Margin = new Thickness(2, 0, 5, 0);
  237. lbl.ToolTip = ToolTip;
  238. stackPnl.Children.Add(lbl);
  239. }
  240. button.Content = stackPnl;
  241. button.ToolTip = tooltip;
  242. }
  243. private void OptionsChanged(object sender, EventArgs args)
  244. {
  245. _add.Visibility = Options.Contains(DynamicTreeOption.Add) ? Visibility.Visible : Visibility.Collapsed;
  246. _edit.Visibility = Options.Contains(DynamicTreeOption.Edit) ? Visibility.Visible : Visibility.Collapsed;
  247. _delete.Visibility = Options.Contains(DynamicTreeOption.Delete) ? Visibility.Visible : Visibility.Collapsed;
  248. }
  249. protected virtual T DoCreateItem(Guid parent)
  250. {
  251. T result = new T();
  252. CoreUtils.SetPropertyValue(result, CoreUtils.GetFullPropertyName(ParentID, "."), parent);
  253. return result;
  254. }
  255. protected abstract T? DoLoadItem(Guid id);
  256. protected virtual bool DoEditItem(T item)
  257. {
  258. var form = new DynamicEditorForm(typeof(T));
  259. form.OnEditorValueChanged += (sender, name, value) =>
  260. {
  261. CoreUtils.SetPropertyValue(item, name, value);
  262. return new Dictionary<string, object?>();
  263. };
  264. form.Items = new T[] { item };
  265. return form.ShowDialog() == true;
  266. }
  267. protected abstract void DoSaveItem(T item);
  268. protected abstract bool DoDeleteItem(Guid id);
  269. protected virtual void DoAddItem(Guid id, bool edit)
  270. {
  271. try
  272. {
  273. T item = DoCreateItem(id);
  274. if (edit)
  275. {
  276. if (DoEditItem(item))
  277. {
  278. DoSaveItem(item);
  279. Refresh();
  280. }
  281. }
  282. else
  283. {
  284. DoSaveItem(item);
  285. Refresh();
  286. }
  287. }
  288. catch (Exception e)
  289. {
  290. MessageBox.Show(e.Message);
  291. }
  292. }
  293. private void EditItem(Button button)
  294. {
  295. var node = _tree.SelectedItem as CoreTreeNode;
  296. if (node == null)
  297. {
  298. MessageBox.Show("Please Select an item to edit!");
  299. return;
  300. }
  301. var item = DoLoadItem(node.ID);
  302. if (item != null && DoEditItem(item))
  303. {
  304. DoSaveItem(item);
  305. Refresh();
  306. }
  307. }
  308. private void DeleteItem(Button button)
  309. {
  310. var node = _tree.SelectedItem as CoreTreeNode;
  311. if (node == null)
  312. {
  313. MessageBox.Show("Please Select an item to edit!");
  314. return;
  315. }
  316. if (DoDeleteItem(node.ID))
  317. {
  318. Refresh();
  319. }
  320. }
  321. public CoreTreeNodes Nodes { get; set; }
  322. protected abstract void DoRefresh(Action<CoreTable?, Exception?> action);
  323. private void AfterRefresh()
  324. {
  325. var nodes = new CoreTreeNodes();
  326. foreach (var row in Data.Rows)
  327. {
  328. var _id = row.Get(ID);
  329. var _parent = row.Get(ParentID);
  330. var _description = row.Get(Description);
  331. nodes.Add(_id, _parent).Description = _description;
  332. }
  333. Nodes = nodes;
  334. _tree.ItemsSource = nodes.Nodes;
  335. CalculateRowHeight();
  336. }
  337. public void Refresh()
  338. {
  339. _tree.ItemsSource = null;
  340. DoRefresh((table, exception) =>
  341. {
  342. if(exception != null)
  343. {
  344. Dispatcher.Invoke(() =>
  345. {
  346. MessageBox.Show(String.Format("Error: {0}", exception.Message));
  347. });
  348. }
  349. else if(table is not null)
  350. {
  351. Data = table;
  352. Dispatcher.Invoke(() =>
  353. {
  354. AfterRefresh();
  355. });
  356. }
  357. });
  358. }
  359. }
  360. }