DynamicEditorGrid.xaml.cs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics.CodeAnalysis;
  4. using System.Linq;
  5. using System.Windows;
  6. using System.Windows.Controls;
  7. using System.Windows.Media;
  8. using InABox.Clients;
  9. using InABox.Core;
  10. using InABox.WPF;
  11. using NPOI.HSSF.Record.Aggregates;
  12. using RoslynPad.Editor;
  13. namespace InABox.DynamicGrid
  14. {
  15. public delegate void OnUpdateOtherEditorHandler(string columnname, object value);
  16. public delegate Dictionary<string, object?> EditorValueChangedHandler(IDynamicEditorForm sender, string name, object value);
  17. /// <summary>
  18. /// Interaction logic for DynamicEditorGrid.xaml
  19. /// </summary>
  20. public partial class DynamicEditorGrid : UserControl, IDynamicEditorHost
  21. {
  22. public delegate void EditorCreatedHandler(object sender, double height, double width);
  23. public delegate object? GetPropertyValueHandler(object sender, string name);
  24. public delegate void SetPropertyValueHandler(object sender, string name, object value);
  25. public delegate object?[] GetItemsEvent();
  26. // Column Definitions as defined by calling model
  27. private DynamicGridColumns _columns = new();
  28. private Type? LayoutType;
  29. private DynamicEditorGridLayout? Layout;
  30. private bool _tabStripVisible = true;
  31. public bool TabStripVisible
  32. {
  33. get { return _tabStripVisible; }
  34. set
  35. {
  36. _tabStripVisible = value;
  37. if (Layout != null)
  38. Layout.TabStripVisible = value;
  39. }
  40. }
  41. public DynamicEditorGrid()
  42. {
  43. InitializeComponent();
  44. Loaded += DynamicEditorGrid_Loaded;
  45. }
  46. private DynamicEditorPages _pages = new();
  47. public IEnumerable<IDynamicEditorPage> Pages => _pages;
  48. private void AddPage(IDynamicEditorPage page)
  49. {
  50. page.ReadOnly = ReadOnly;
  51. _pages.Add(page);
  52. }
  53. private void SetPages(DynamicEditorPages pages)
  54. {
  55. _pages = pages;
  56. foreach (var page in _pages)
  57. {
  58. page.ReadOnly = ReadOnly;
  59. }
  60. }
  61. public bool PreloadPages { get; set; }
  62. public Type UnderlyingType { get; set; }
  63. public OnLoadPage? OnLoadPage { get; set; }
  64. public event OnSelectPage? OnSelectPage;
  65. public event OnUnloadPage? OnUnloadPage;
  66. public DynamicGridColumns Columns => _columns;
  67. private bool _readOnly;
  68. public bool ReadOnly
  69. {
  70. get => _readOnly;
  71. set
  72. {
  73. _readOnly = value;
  74. foreach(var page in Pages)
  75. {
  76. page.ReadOnly = value;
  77. }
  78. }
  79. }
  80. public bool TryFindEditor(string columnname, [NotNullWhen(true)] out IDynamicEditorControl? editor)
  81. {
  82. foreach (var page in Pages)
  83. {
  84. if (page is DynamicEditPage editPage)
  85. {
  86. if (editPage.TryFindEditor(columnname, out editor))
  87. return true;
  88. }
  89. }
  90. editor = null;
  91. return false;
  92. }
  93. public IDynamicEditorControl? FindEditor(string columnname)
  94. {
  95. TryFindEditor(columnname, out var editor);
  96. return editor;
  97. }
  98. public virtual void ReconfigureEditors()
  99. {
  100. OnReconfigureEditors?.Invoke(this);
  101. }
  102. public object? GetPropertyValue(string columnname)
  103. {
  104. return OnGetPropertyValue?.Invoke(this, columnname);
  105. }
  106. public event EditorCreatedHandler? OnEditorCreated;
  107. public event OnCustomiseColumns? OnCustomiseColumns;
  108. public event OnGetEditor? OnGetEditor;
  109. public event OnGridCustomiseEditor? OnGridCustomiseEditor;
  110. public event OnGetEditorSequence? OnGetSequence;
  111. public event GetPropertyValueHandler? OnGetPropertyValue;
  112. public event SetPropertyValueHandler? OnSetPropertyValue;
  113. public delegate Dictionary<string, object?> EditorGridValueChangedHandler(DynamicEditorGrid sender, string name, object? value);
  114. public event EditorGridValueChangedHandler? OnEditorValueChanged;
  115. public event OnAfterEditorValueChanged? OnAfterEditorValueChanged;
  116. public event OnReconfigureEditors? OnReconfigureEditors;
  117. public event OnDefineFilter? OnDefineFilter;
  118. public event OnDefineLookup? OnDefineLookups;
  119. public event GetItemsEvent? GetItems;
  120. private void DynamicEditorGrid_Loaded(object sender, RoutedEventArgs e)
  121. {
  122. //Reload();
  123. }
  124. public void Reload()
  125. {
  126. LoadPages();
  127. ReconfigureEditors();
  128. }
  129. #region Host Implementation
  130. IEnumerable<DynamicGridColumn> IDynamicEditorHost.Columns => Columns;
  131. public void LoadColumns(string column, Dictionary<string, string> columns)
  132. {
  133. columns.Clear();
  134. var comps = column.Split('.').ToList();
  135. comps.RemoveAt(comps.Count - 1);
  136. var prefix = string.Format("{0}.", string.Join(".", comps));
  137. var cols = Columns.Where(x => !x.ColumnName.Equals(column) && x.ColumnName.StartsWith(prefix));
  138. foreach (var col in cols)
  139. {
  140. var subColumn = col.ColumnName[prefix.Length..];
  141. columns[subColumn] = col.ColumnName;
  142. }
  143. }
  144. public IFilter? DefineFilter(Type type) => OnDefineFilter?.Invoke(type);
  145. public void LoadLookups(ILookupEditorControl editor)
  146. {
  147. OnDefineLookups?.Invoke(editor);
  148. }
  149. object?[] IDynamicEditorHost.GetItems() => GetItems?.Invoke() ?? Array.Empty<object?>();
  150. public BaseEditor? GetEditor(DynamicGridColumn column) => OnGetEditor?.Invoke(column);
  151. #endregion
  152. #region Edit Page
  153. public class DynamicEditPage : ContentControl, IDynamicEditorPage
  154. {
  155. private Grid Grid;
  156. public DynamicEditorGrid EditorGrid { get; set; } = null!; // Set by DynamicEditorGrid
  157. public bool Ready { get; set; }
  158. private List<BaseDynamicEditorControl> Editors { get; set; }
  159. public PageType PageType => PageType.Editor;
  160. public int PageOrder { get; set; }
  161. public string Header { get; set; }
  162. private double GeneralHeight = 30;
  163. private bool _readOnly;
  164. public bool ReadOnly
  165. {
  166. get => _readOnly;
  167. set
  168. {
  169. if(_readOnly != value)
  170. {
  171. _readOnly = value;
  172. foreach(var editor in Editors)
  173. {
  174. editor.IsEnabled = !value && editor.EditorDefinition.Editable.IsEditable();
  175. }
  176. }
  177. }
  178. }
  179. public DynamicEditPage(string header)
  180. {
  181. Header = header;
  182. Editors = new List<BaseDynamicEditorControl>();
  183. InitialiseContent();
  184. }
  185. public void AddEditor(string columnName, BaseEditor editor)
  186. {
  187. BaseDynamicEditorControl? element = DynamicEditorControlFactory.CreateControl(editor, EditorGrid);
  188. if (element != null)
  189. {
  190. element.EditorDefinition = editor;
  191. element.IsEnabled = !ReadOnly && editor.Editable.IsEditable();
  192. if (!string.IsNullOrWhiteSpace(editor.ToolTip))
  193. {
  194. element.ToolTip = new ToolTip() { Content = editor.ToolTip };
  195. }
  196. var label = new Label();
  197. label.Content = CoreUtils.Neatify(editor.Caption); // 2
  198. label.Margin = new Thickness(0F, 0F, 0F, 0F);
  199. label.HorizontalAlignment = HorizontalAlignment.Stretch;
  200. label.VerticalAlignment = VerticalAlignment.Stretch;
  201. label.HorizontalContentAlignment = HorizontalAlignment.Left;
  202. label.VerticalContentAlignment = VerticalAlignment.Center;
  203. label.SetValue(Grid.RowProperty, Grid.RowDefinitions.Count);
  204. label.SetValue(Grid.ColumnProperty, 0);
  205. label.Visibility = string.IsNullOrWhiteSpace(editor.Caption) ? Visibility.Collapsed : Visibility.Visible;
  206. Grid.Children.Add(label);
  207. element.ColumnName = columnName;
  208. element.Color = editor is UniqueCodeEditor ? Color.FromArgb(0xFF, 0xF6, 0xC9, 0xE8) : Colors.LightYellow;
  209. Editors.Add(element);
  210. element.Margin = new Thickness(5F, 2.5F, 5F, 2.5F);
  211. double iHeight = element.DesiredHeight();
  212. if (iHeight == int.MaxValue)
  213. {
  214. Grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
  215. GeneralHeight += element.MinHeight + 5.0F;
  216. }
  217. else
  218. {
  219. Grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(iHeight + 5.0F) });
  220. GeneralHeight += iHeight + 5.0F;
  221. }
  222. double iWidth = element.DesiredWidth();
  223. if (iWidth == int.MaxValue)
  224. {
  225. element.HorizontalAlignment = HorizontalAlignment.Stretch;
  226. }
  227. else
  228. {
  229. element.HorizontalAlignment = HorizontalAlignment.Left;
  230. element.Width = iWidth;
  231. }
  232. element.SetValue(Grid.RowProperty, Grid.RowDefinitions.Count - 1);
  233. element.SetValue(Grid.ColumnProperty, 1);
  234. Grid.Children.Add(element);
  235. }
  236. }
  237. [MemberNotNull(nameof(Grid))]
  238. private void InitialiseContent()
  239. {
  240. Grid = new Grid
  241. {
  242. HorizontalAlignment = HorizontalAlignment.Stretch,
  243. VerticalAlignment = VerticalAlignment.Stretch,
  244. Margin = new Thickness(0, 2.5, 0, 2.5)
  245. };
  246. Grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) });
  247. Grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
  248. var scroll = new ScrollViewer
  249. {
  250. HorizontalAlignment = HorizontalAlignment.Stretch,
  251. VerticalAlignment = VerticalAlignment.Stretch,
  252. VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
  253. Padding = new Thickness(2),
  254. Content = Grid
  255. };
  256. var border = new Border
  257. {
  258. BorderBrush = new SolidColorBrush(Colors.Gray),
  259. Background = new SolidColorBrush(Colors.White),
  260. BorderThickness = new Thickness(0.75),
  261. Child = scroll
  262. };
  263. Content = border;
  264. }
  265. public void AfterSave(object item)
  266. {
  267. }
  268. public event EventHandler? OnChanged;
  269. public void BeforeSave(object item)
  270. {
  271. }
  272. public string Caption() => Header;
  273. public bool TryFindEditor(string columnname, [NotNullWhen(true)] out IDynamicEditorControl? editor)
  274. {
  275. editor = Editors.FirstOrDefault(x => x.ColumnName.Equals(columnname));
  276. editor ??= Editors.FirstOrDefault(x => columnname.StartsWith(x.ColumnName + '.'));
  277. return editor is not null;
  278. }
  279. public IEnumerable<BaseDynamicEditorControl> FindEditors(DynamicGridColumn column)
  280. {
  281. return Editors.Where(x => string.Equals(x.ColumnName, column.ColumnName));
  282. }
  283. #region Configure Editors
  284. private void Lookup_OnUpdateOtherEditor(string columnname, object value)
  285. {
  286. var editor = Editors.FirstOrDefault(x => x.ColumnName.Equals(columnname));
  287. if (editor != null)
  288. CoreUtils.SetPropertyValue(editor, "Value", value);
  289. }
  290. private void ConfigureEditors()
  291. {
  292. foreach (var Editor in Editors)
  293. {
  294. var editor = Editor.EditorDefinition;
  295. var column = Editor.ColumnName;
  296. Editor.Configure();
  297. if (!Editors.Any(x => x.ColumnName.Equals(Editor.ColumnName)))
  298. Editors.Add(Editor);
  299. Editor.Loaded = true;
  300. }
  301. }
  302. #endregion
  303. private void EditorValueChanged(IDynamicEditorControl sender, Dictionary<string, object?> values)
  304. {
  305. //Logger.Send(LogType.Information, "", string.Format("DynamicEditorGrid.EditorValueChanged({0})", values.Keys.Count));
  306. var changededitors = new Dictionary<string, object?>();
  307. void ExtractChanged(Dictionary<string, object?>? columns)
  308. {
  309. if (columns != null)
  310. foreach (var (change, value) in columns)
  311. if (!changededitors.ContainsKey(change) && !change.Equals(sender.ColumnName))
  312. changededitors[change] = value;
  313. }
  314. var name = sender.ColumnName;
  315. var resetAll = false;
  316. if(values.Remove(name, out var value))
  317. {
  318. var changedcolumns = EditorGrid.OnEditorValueChanged?.Invoke(EditorGrid, name, value);
  319. resetAll = changedcolumns?.ContainsKey(name) ?? false;
  320. ExtractChanged(changedcolumns);
  321. }
  322. else
  323. {
  324. }
  325. foreach (var (key, otherValue) in values)
  326. {
  327. var changes = new Dictionary<string, object?>();
  328. if (resetAll)
  329. {
  330. var prop = DatabaseSchema.Property(EditorGrid.UnderlyingType, key);
  331. if(prop is not null)
  332. {
  333. var def = CoreUtils.GetDefault(prop.PropertyType);
  334. var resetChanges = EditorGrid.OnEditorValueChanged?.Invoke(EditorGrid, key, def);
  335. if(resetChanges is not null)
  336. {
  337. changes = resetChanges;
  338. }
  339. }
  340. else
  341. {
  342. }
  343. }
  344. var changedOtherColumns = EditorGrid.OnEditorValueChanged?.Invoke(EditorGrid, key, otherValue);
  345. if(changedOtherColumns is not null)
  346. {
  347. foreach (var (k, v) in changedOtherColumns)
  348. {
  349. changes[k] = v;
  350. }
  351. }
  352. ExtractChanged(changes);
  353. }
  354. var afterchanged = EditorGrid.OnAfterEditorValueChanged?.Invoke(EditorGrid, new AfterEditorValueChangedArgs(sender.ColumnName, changededitors));
  355. ExtractChanged(afterchanged);
  356. if (changededitors.Any())
  357. LoadEditorValues(changededitors);
  358. EditorGrid.ReconfigureEditors();
  359. }
  360. private void LoadEditorValues(Dictionary<string, object?>? changededitors = null)
  361. {
  362. var columnnames = changededitors != null ? changededitors.Keys.ToArray() : Editors.Select(x => x.ColumnName).ToArray();
  363. foreach (var columnname in columnnames)
  364. {
  365. if (!TryFindEditor(columnname, out var editor))
  366. continue;
  367. var bLoaded = editor.Loaded;
  368. editor.Loaded = false;
  369. if (changededitors != null && changededitors.ContainsKey(columnname))
  370. {
  371. editor.SetValue(columnname, changededitors[columnname]);
  372. }
  373. else
  374. {
  375. var curvalue = EditorGrid.GetPropertyValue(columnname);
  376. try
  377. {
  378. editor.SetValue(columnname, curvalue);
  379. }
  380. catch (Exception e)
  381. {
  382. MessageBox.Show($"Unable to set editor value for {columnname} -> {curvalue}: {CoreUtils.FormatException(e)}");
  383. }
  384. editor.Changed = false;
  385. }
  386. editor.Loaded = bLoaded;
  387. editor.OnEditorValueChanged += EditorValueChanged;
  388. }
  389. }
  390. public void Load(object item, Func<Type, CoreTable?>? PageDataHandler)
  391. {
  392. ConfigureEditors();
  393. LoadEditorValues();
  394. foreach (var editor in Editors)
  395. {
  396. foreach(var (column, editorValue) in editor.GetValues())
  397. {
  398. var entityValue = EditorGrid.GetPropertyValue(column);
  399. if (!Equals(editorValue, entityValue))
  400. {
  401. bool bLoaded = editor.Loaded;
  402. editor.Loaded = false;
  403. editor.SetValue(column, entityValue);
  404. editor.Loaded = bLoaded;
  405. }
  406. }
  407. }
  408. Editors.FirstOrDefault()?.SetFocus();
  409. Ready = true;
  410. }
  411. public void DoChanged()
  412. {
  413. OnChanged?.Invoke(this, EventArgs.Empty);
  414. }
  415. public Size MinimumSize() => new Size(800, GeneralHeight);
  416. public int Order() => PageOrder;
  417. }
  418. #endregion
  419. #region Loading + Editing Layout
  420. private decimal GetSequence(DynamicGridColumn column)
  421. {
  422. if (OnGetSequence != null)
  423. return OnGetSequence.Invoke(column);
  424. return 999;
  425. }
  426. private DynamicEditPage GetEditPage(string name)
  427. {
  428. var page = Pages.Where(x => x is DynamicEditPage page && page.Header == name).FirstOrDefault() as DynamicEditPage;
  429. if(page is null)
  430. {
  431. page = new DynamicEditPage(name)
  432. {
  433. // Setting this here because it's needed now to be able to create the layout.
  434. EditorGrid = this
  435. };
  436. if (name == "General")
  437. {
  438. page.PageOrder = -1;
  439. }
  440. else
  441. {
  442. page.PageOrder = 0;
  443. }
  444. AddPage(page);
  445. }
  446. return page;
  447. }
  448. public void SetLayoutType<T>() where T : DynamicEditorGridLayout
  449. {
  450. LayoutType = typeof(T);
  451. }
  452. private void InitialiseLayout()
  453. {
  454. Layout = (Activator.CreateInstance(LayoutType ?? typeof(DefaultDynamicEditorGridLayout)) as DynamicEditorGridLayout)!;
  455. Layout.OnSelectPage += Layout_SelectPage;
  456. Layout.TabStripVisible = _tabStripVisible;
  457. Content = Layout;
  458. }
  459. private void CreateLayout()
  460. {
  461. if(Layout is null)
  462. {
  463. InitialiseLayout();
  464. }
  465. foreach (var column in _columns.OrderBy(x => GetSequence(x)))
  466. {
  467. var iProp = DatabaseSchema.Property(UnderlyingType, column.ColumnName);
  468. var editor = OnGetEditor?.Invoke(column);
  469. if (editor != null && iProp?.ShouldShowEditor() != true)
  470. {
  471. editor.Visible = Visible.Hidden;
  472. editor.Editable = Editable.Hidden;
  473. }
  474. if(editor is not null)
  475. {
  476. foreach(var security in editor.Security)
  477. {
  478. if (!Security.IsAllowed(security.SecurityDescriptor))
  479. {
  480. if (!security.Visible)
  481. {
  482. editor.Visible = Visible.Hidden;
  483. }
  484. if (!security.Editable)
  485. {
  486. editor.Editable = Editable.Hidden;
  487. }
  488. break;
  489. }
  490. }
  491. }
  492. if(editor is not null)
  493. {
  494. OnGridCustomiseEditor?.Invoke(this, column, editor);
  495. }
  496. if (editor != null && editor.Editable.EditorVisible())
  497. {
  498. var page = string.IsNullOrWhiteSpace(editor.Page) ? iProp is StandardProperty ? "General" : "Custom Fields" : editor.Page;
  499. var editPage = GetEditPage(page);
  500. editPage.AddEditor(column.ColumnName, editor);
  501. }
  502. else if (iProp?.HasParentEditor() == true)
  503. {
  504. var parent = iProp.GetParentWithEditor();
  505. if(parent is not null)
  506. {
  507. var parentEditor = parent.Editor;
  508. if(parentEditor is not null)
  509. {
  510. OnGridCustomiseEditor?.Invoke(this, new DynamicGridColumn { ColumnName = parent.Name }, parentEditor);
  511. }
  512. if(parentEditor is not null && parentEditor.Editable.EditorVisible())
  513. {
  514. var page = string.IsNullOrWhiteSpace(parentEditor.Page)
  515. ? parent is StandardProperty
  516. ? "General"
  517. : "Custom Fields"
  518. : parentEditor.Page;
  519. var editPage = GetEditPage(page);
  520. if (!editPage.TryFindEditor(parent.Name, out var editorControl))
  521. {
  522. editPage.AddEditor(parent.Name, parentEditor);
  523. }
  524. }
  525. }
  526. }
  527. }
  528. OnEditorCreated?.Invoke(this, 0, 800);
  529. }
  530. #endregion
  531. #region Pages
  532. private void Layout_SelectPage(IDynamicEditorPage page)
  533. {
  534. if (!page.Ready)
  535. using (new WaitCursor())
  536. {
  537. OnLoadPage?.Invoke(page);
  538. }
  539. OnSelectPage?.Invoke(this, null);
  540. }
  541. public void UnloadPages(bool saved)
  542. {
  543. if(Pages is not null)
  544. foreach (var page in Pages)
  545. if (page.Ready)
  546. OnUnloadPage?.Invoke(page, saved);
  547. }
  548. private void LoadPages()
  549. {
  550. if (Pages != null && Layout is not null)
  551. using (new WaitCursor())
  552. {
  553. foreach (var page in Pages)
  554. {
  555. page.Ready = false;
  556. page.EditorGrid = this;
  557. }
  558. Layout.LoadPages(Pages);
  559. if (PreloadPages)
  560. {
  561. foreach(var page in Pages)
  562. {
  563. OnLoadPage?.Invoke(page);
  564. }
  565. }
  566. }
  567. }
  568. public void Load(DynamicEditorPages pages)
  569. {
  570. SetPages(pages);
  571. _columns = new DynamicGridColumns();
  572. OnCustomiseColumns?.Invoke(this, _columns);
  573. CreateLayout();
  574. Reload();
  575. }
  576. #endregion
  577. }
  578. }