DynamicTreeView.cs 16 KB

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