DynamicTreeView.cs 17 KB

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