DynamicTreeView.cs 16 KB

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