DynamicTreeView.cs 16 KB

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