DynamicEditorGrid.xaml.cs 21 KB

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