DynamicGrid.cs 43 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Data;
  5. using System.Diagnostics;
  6. using System.Globalization;
  7. using System.Linq;
  8. using System.Linq.Expressions;
  9. using System.Runtime.CompilerServices;
  10. using System.Threading.Tasks;
  11. using System.Windows;
  12. using System.Windows.Controls;
  13. using System.Windows.Data;
  14. using System.Windows.Input;
  15. using System.Windows.Media;
  16. using System.Windows.Media.Animation;
  17. using System.Windows.Media.Imaging;
  18. using InABox.Clients;
  19. using InABox.Core;
  20. using InABox.Wpf;
  21. using InABox.WPF;
  22. using Syncfusion.Data;
  23. using Syncfusion.UI.Xaml.Grid;
  24. using Syncfusion.UI.Xaml.Grid.Helpers;
  25. using static InABox.DynamicGrid.IDynamicGrid;
  26. using Color = System.Drawing.Color;
  27. using Columns = InABox.Core.Columns;
  28. using Image = System.Windows.Controls.Image;
  29. using RowColumnIndex = Syncfusion.UI.Xaml.ScrollAxis.RowColumnIndex;
  30. using SolidColorBrush = System.Windows.Media.SolidColorBrush;
  31. using String = System.String;
  32. using VerticalAlignment = System.Windows.VerticalAlignment;
  33. using VirtualizingCellsControl = Syncfusion.UI.Xaml.Grid.VirtualizingCellsControl;
  34. using System.Threading;
  35. using System.Diagnostics.CodeAnalysis;
  36. namespace InABox.DynamicGrid;
  37. public abstract class DynamicGrid<T> : BaseDynamicGrid, IDynamicGridUIComponentParent<T>, IDynamicGrid<T>,
  38. IImportDynamicGrid, IExportDynamicGrid, IDuplicateDynamicGrid, IHelpDynamicGrid
  39. where T : BaseObject, new()
  40. {
  41. #region Events
  42. public ValidateEvent<T>? OnValidate { get; set; }
  43. public event OnDefineFilter? OnDefineFilter;
  44. public event OnCreateItem? OnCreateItem;
  45. public event OnAfterCreateItem? OnAfterCreateItem;
  46. public event EditorValueChangedHandler? OnEditorValueChanged;
  47. public event OnCustomiseEditor<T>? OnCustomiseEditor;
  48. public event EntitySaveEvent? OnBeforeSave;
  49. public event EntitySaveEvent? OnAfterSave;
  50. public delegate void EditorLoaded(IDynamicEditorForm editor, T[] items);
  51. public event EditorLoaded? OnEditorLoaded;
  52. public event OnLoadEditorButtons<T>? OnLoadEditorButtons;
  53. #endregion
  54. protected virtual string HelpSlug()
  55. {
  56. return typeof(T).Name.Split('.').Last().SplitCamelCase().Replace(" ", "_");
  57. }
  58. string IHelpDynamicGrid.HelpSlug() => HelpSlug();
  59. protected override bool CanDuplicate => typeof(T).IsAssignableTo(typeof(IDuplicatable));
  60. public DynamicGrid() : base()
  61. {
  62. }
  63. protected sealed override void PreInit()
  64. {
  65. MasterColumns = new DynamicGridColumns();
  66. MasterColumns.ExtractColumns(typeof(T));
  67. HiddenColumns = new HiddenColumnsList();
  68. if (typeof(T).IsAssignableTo(typeof(ISequenceable)))
  69. {
  70. HiddenColumns.Add(x => (x as ISequenceable)!.Sequence);
  71. }
  72. }
  73. protected override void Init()
  74. {
  75. }
  76. #region IDynamicGridUIComponentParent
  77. protected override IDynamicGridUIComponent CreateUIComponent()
  78. {
  79. return new DynamicGridGridUIComponent<T>
  80. {
  81. Parent = this
  82. };
  83. }
  84. T IDynamicGrid<T>.LoadItem(CoreRow row) => LoadItem(row);
  85. void IDynamicGridUIComponentParent<T>.EntityChanged(T obj, CoreRow row, string changedColumn, Dictionary<string, object?> changes)
  86. => EntityChanged(obj, row, changedColumn, changes);
  87. void IDynamicGridUIComponentParent<T>.UpdateData(T obj, CoreRow row, string changedColumn, Dictionary<CoreColumn, object?> updates)
  88. {
  89. var result = new Dictionary<string, object?>();
  90. foreach (var (col, value) in updates)
  91. {
  92. UpdateRow(row, col.ColumnName, value, refresh: false);
  93. DynamicGridUtils.UpdateEditorValue(new BaseObject[] { obj }, col.ColumnName, value, result);
  94. }
  95. EntityChanged(obj, row, changedColumn, result);
  96. }
  97. #endregion
  98. protected override DynamicGridRowStyleSelector GetRowStyleSelector()
  99. {
  100. return new DynamicGridRowStyleSelector<T, DynamicGridRowStyle>();
  101. }
  102. public DynamicGridColumns MasterColumns { get; protected set; }
  103. public override void OnItemSourceChanged(object value)
  104. {
  105. Data = value as CoreTable;
  106. Refresh(true, true);
  107. }
  108. public class HiddenColumnsList
  109. {
  110. private List<string> Columns { get; set; } = new();
  111. public IEnumerable<string> ColumnNames => Columns;
  112. public void Add(Expression<Func<T, object?>> column) => Add(CoreUtils.GetFullPropertyName(column, "."));
  113. public void Add(IColumn column) => Add(column.Property);
  114. public void Add(string column)
  115. {
  116. if (!Contains(column))
  117. Columns.Add(column);
  118. }
  119. public bool Contains(string column) => Columns.Contains(column);
  120. }
  121. public void AddHiddenColumn(string column) => HiddenColumns.Add(column);
  122. public HiddenColumnsList HiddenColumns { get; private set; }
  123. private static bool IsSequenced => typeof(T).GetInterfaces().Any(x => x.Equals(typeof(ISequenceable)));
  124. #region Options
  125. protected override void DoReconfigure(DynamicGridOptions options)
  126. {
  127. options.ReorderRows = IsSequenced;
  128. }
  129. #endregion
  130. private void EntityChanged(T obj, CoreRow row, string changedColumn, Dictionary<string, object?> changes)
  131. {
  132. OnAfterEditorValueChanged(null, [obj], new AfterEditorValueChangedArgs(changedColumn, changes), changes);
  133. SaveItem(obj);
  134. foreach (var (key, value) in changes)
  135. {
  136. if (row.Table.GetColumnIndex(key) > -1)
  137. row[key] = value;
  138. }
  139. GetUIComponent().UpdateRow(row);
  140. }
  141. #region Column Handling
  142. /// <summary>
  143. /// Create a set of <see cref="DynamicGridColumns"/> according to the natural default sizes and formats and captions, based on the contents of <paramref name="columns"/>.
  144. /// </summary>
  145. /// <param name="columns"></param>
  146. /// <returns></returns>
  147. protected DynamicGridColumns ExtractColumns(Columns<T> columns)
  148. {
  149. var cols = new DynamicGridColumns();
  150. foreach (var col in columns)
  151. {
  152. var mc = MasterColumns.FirstOrDefault(x => x.ColumnName.Equals(col.Property));
  153. if (mc != null && mc.Editor is not NullEditor && mc.Editor.Visible != Visible.Hidden)
  154. cols.Add(mc);
  155. }
  156. return cols;
  157. }
  158. DynamicGridColumns IDynamicGrid.ExtractColumns(IColumns columns)
  159. {
  160. var cols = new DynamicGridColumns();
  161. foreach (var col in columns)
  162. {
  163. var mc = MasterColumns.FirstOrDefault(x => x.ColumnName.Equals(col.Property));
  164. if (mc != null && mc.Editor is not NullEditor && mc.Editor.Visible != Visible.Hidden)
  165. cols.Add(mc);
  166. }
  167. return cols;
  168. }
  169. public override DynamicGridColumns GenerateColumns()
  170. {
  171. var cols = IsDirectEditMode()
  172. ? new Columns<T>(ColumnTypeFlags.IncludeVisible | ColumnTypeFlags.IncludeForeignKeys)
  173. : new Columns<T>(ColumnTypeFlags.IncludeVisible | ColumnTypeFlags.IncludeLinked);
  174. var columns = ExtractColumns(cols);
  175. OnGenerateColumns?.Invoke(this, new GenerateColumnsEventArgs(columns));
  176. return columns;
  177. }
  178. public event GenerateColumnsEvent? OnGenerateColumns;
  179. public event SaveColumnsEvent? OnSaveColumns;
  180. public event GetAvailableColumnsEvent? GetAvailableColumns;
  181. protected override void SaveColumns(DynamicGridColumns columns)
  182. {
  183. OnSaveColumns?.Invoke(this, new(columns));
  184. }
  185. #endregion
  186. #region Refresh / Reload
  187. protected abstract void Reload(
  188. Filters<T> criteria, Columns<T> columns, ref SortOrder<T>? sort,
  189. CancellationToken token, Action<CoreTable?, Exception?> action);
  190. public Filter<T>? DefineFilter()
  191. {
  192. if (OnDefineFilter is null)
  193. return null;
  194. var result = OnDefineFilter.Invoke(typeof(T)) as Filter<T>;
  195. return result;
  196. }
  197. public IEnumerable<TType> ExtractValues<TType>(Expression<Func<T, TType>> column, Selection selection)
  198. {
  199. var result = selection == Selection.None
  200. ? Enumerable.Empty<TType>()
  201. : selection == Selection.Selected
  202. ? SelectedRows.Select(r => r.Get(column))
  203. : Data.ExtractValues(column);
  204. return result;
  205. }
  206. protected override void ReloadData(CancellationToken token, Action<CoreTable?, Exception?> action)
  207. {
  208. _lookupcache.Clear();
  209. var criteria = new Filters<T>();
  210. var filter = DefineFilter();
  211. if (filter != null)
  212. criteria.Add(filter);
  213. var columns = DataColumns();
  214. var sort = LookupFactory.DefineSort<T>();
  215. if (sort == null && IsSequenced)
  216. sort = new SortOrder<T>("Sequence");
  217. Reload(criteria, columns, ref sort, token, action);
  218. }
  219. public Columns<T> DataColumns()
  220. {
  221. var columns = Columns.None<T>();
  222. foreach (var column in VisibleColumns)
  223. columns.Add(column.ColumnName);
  224. foreach (var column in HiddenColumns.ColumnNames)
  225. columns.Add(new Column<T>(column));
  226. if (!Options.ReadOnly)
  227. {
  228. foreach (var column in LookupFactory.RequiredColumns<T>())
  229. {
  230. columns.Add(column);
  231. }
  232. }
  233. return columns;
  234. }
  235. public void UpdateRow(CoreRow row, T obj, bool invalidateRow = true)
  236. {
  237. ObjectToRow(obj, row);
  238. ObjectToRow(obj, _recordmap[row]);
  239. if (invalidateRow)
  240. {
  241. InvalidateRow(row);
  242. }
  243. }
  244. public void UpdateRows(CoreRow[] rows, T[] objs, bool invalidateRows = true)
  245. {
  246. for(var i = 0; i < objs.Length; ++i)
  247. {
  248. UpdateRow(rows[i], objs[i], invalidateRow: invalidateRows);
  249. }
  250. }
  251. public void AddRow(T data)
  252. {
  253. if (MasterData is null) return;
  254. MasterData.LoadRow(data);
  255. Refresh(false, false);
  256. }
  257. #endregion
  258. #region Item Manipulation
  259. #region Load/Save/Delete
  260. public virtual T[] LoadItems(IList<CoreRow> rows)
  261. {
  262. return rows.ToArray(LoadItem);
  263. }
  264. public abstract T LoadItem(CoreRow row);
  265. public abstract void SaveItem(T item);
  266. public virtual void SaveItems(IEnumerable<T> items)
  267. {
  268. foreach (var item in items)
  269. SaveItem(item);
  270. }
  271. protected virtual bool CanDeleteItems(params CoreRow[] rows)
  272. {
  273. return true;
  274. }
  275. public abstract void DeleteItems(params CoreRow[] rows);
  276. protected override bool CanDeleteRows(params CoreRow[] rows) => CanDeleteItems(rows);
  277. public override void DeleteRows(params CoreRow[] rows) => DeleteItems(rows);
  278. #endregion
  279. #region Edit
  280. protected override void NewRow()
  281. {
  282. CreateItems(null);
  283. }
  284. protected void CreateItems(Func<IEnumerable<T>>? create)
  285. {
  286. var newRows = new List<CoreRow>();
  287. var items = create?.Invoke() ?? CoreUtils.One(CreateItem());
  288. foreach (var item in items)
  289. {
  290. if (!AfterCreate(item))
  291. return;
  292. SaveItem(item);
  293. var datarow = Data.NewRow();
  294. ObjectToRow(item, datarow);
  295. Data.Rows.Add(datarow);
  296. newRows.Add(datarow);
  297. var masterrow = MasterData.NewRow();
  298. ObjectToRow(item, masterrow);
  299. MasterData.Rows.Add(masterrow);
  300. _recordmap[datarow] = masterrow;
  301. }
  302. InvalidateGrid();
  303. SelectedRows = newRows.ToArray();
  304. DoChanged();
  305. }
  306. public virtual DynamicEditorPages LoadEditorPages(T item)
  307. {
  308. DynamicEditorPages pages = new DynamicEditorPages();
  309. DynamicGridUtils.LoadOneToManyPages(typeof(T), pages);
  310. DynamicGridUtils.LoadEnclosedListPages(typeof(T), pages);
  311. DynamicGridUtils.LoadManyToManyPages(typeof(T), pages);
  312. DynamicGridUtils.LoadCustomEditorPages(typeof(T), pages);
  313. pages.RemoveAll(x => !x.Visible);
  314. foreach (var page in pages)
  315. page.Ready = false;
  316. return pages;
  317. }
  318. public virtual void LoadEditorButtons(T item, DynamicEditorButtons buttons)
  319. {
  320. buttons.Clear();
  321. buttons.Add(
  322. "",
  323. Wpf.Resources.help.AsBitmapImage(),
  324. item,
  325. (f, i) =>
  326. {
  327. Process.Start(new ProcessStartInfo("https://prsdigital.com.au/wiki/index.php/" + typeof(T).Name.SplitCamelCase().Replace(" ", "_"))
  328. { UseShellExecute = true });
  329. }
  330. );
  331. OnLoadEditorButtons?.Invoke(item, buttons);
  332. }
  333. protected virtual void BeforeLoad(IDynamicEditorForm form, T[] items)
  334. {
  335. form.BeforeLoad();
  336. }
  337. void IDynamicGrid.InitialiseEditorForm(IDynamicEditorForm editor, object[] items, Func<Type, CoreTable>? pageDataHandler, bool preloadPages)
  338. {
  339. InitialiseEditorForm(editor, items.Cast<T>().ToArray(), pageDataHandler, preloadPages);
  340. }
  341. public virtual bool EditItems(object[] items, Func<Type, CoreTable?>? PageDataHandler = null, bool PreloadPages = false)
  342. {
  343. var values = items.Cast<T>().ToArray();
  344. return EditItems(values, PageDataHandler, PreloadPages);
  345. }
  346. public virtual void InitialiseEditorForm(IDynamicEditorForm editor, T[] items, Func<Type, CoreTable?>? pageDataHandler = null, bool preloadPages = false)
  347. {
  348. var pages = items.Length == 1 ? LoadEditorPages(items.First()) : new DynamicEditorPages();
  349. var buttons = new DynamicEditorButtons();
  350. if (items.Length == 1)
  351. LoadEditorButtons(items.First(), buttons);
  352. editor.Setup(items.Any() ? items.First().GetType() : typeof(T), pages, buttons, pageDataHandler, preloadPages);
  353. editor.OnCustomiseColumns = (sender, columns) =>
  354. {
  355. columns.Clear();
  356. columns.AddRange(MasterColumns);
  357. };
  358. editor.OnDefineEditor = (o, c) =>
  359. {
  360. var result = GetEditor(o, c);
  361. if (result != null)
  362. result = result.CloneEditor();
  363. return result;
  364. };
  365. editor.OnFormCustomiseEditor += DoCustomiseEditor;
  366. editor.OnDefineFilter = (type, column) => { return DefineLookupFilter(type, column, items); };
  367. //editor.OnDefineFilter += (o, e) => { return DefineFilter(items, e); };
  368. editor.OnDefineLookups = editor => DefineLookups(editor, items);
  369. editor.OnEditorValueChanged += (s, n, v) => EditorValueChanged(editor, items, n, v);
  370. editor.OnAfterEditorValueChanged += (g, args) => AfterEditorValueChanged(g, items, args);
  371. editor.OnReconfigureEditors = g => DoReconfigureEditors(g, items);
  372. editor.OnValidateData += (o, i) => ValidateData(items);
  373. editor.OnSelectPage += SelectPage;
  374. editor.OnSaveItem = (o, e) =>
  375. {
  376. try
  377. {
  378. using var Wait = new WaitCursor();
  379. DoBeforeSave(editor, items);
  380. if (items.Length == 1)
  381. editor.UnloadEditorPages(false);
  382. foreach (var item in items)
  383. SaveItem(item);
  384. if (items.Length == 1)
  385. editor.UnloadEditorPages(true);
  386. DoAfterSave(editor, items);
  387. }
  388. catch (Exception err)
  389. {
  390. MessageBox.Show(err.Message);
  391. e.Cancel = true;
  392. }
  393. };
  394. BeforeLoad(editor, items);
  395. editor.Items = items;
  396. AfterLoad(editor, items);
  397. }
  398. // BaseEditor IDynamicGridUIComponentParent<T>.CustomiseEditor(DynamicGridColumn column, BaseEditor editor)
  399. // {
  400. // var result = editor.CloneEditor();
  401. // CustomiseEditor(new T[] { }, column, result);
  402. // return result;
  403. // }
  404. private void DoCustomiseEditor(IDynamicEditorForm sender, object[] items, DynamicGridColumn column, BaseEditor editor)
  405. {
  406. CustomiseEditor(sender, (T[])items, column, editor);
  407. OnCustomiseEditor?.Invoke(sender, (T[])items, column, editor);
  408. }
  409. protected virtual void CustomiseEditor(IDynamicEditorForm form, T[] items, DynamicGridColumn column, BaseEditor editor)
  410. {
  411. }
  412. protected virtual void DoAfterSave(IDynamicEditorForm editor, T[] items)
  413. {
  414. OnAfterSave?.Invoke(editor, items);
  415. }
  416. protected virtual void DoBeforeSave(IDynamicEditorForm editor, T[] items)
  417. {
  418. OnBeforeSave?.Invoke(editor, items);
  419. }
  420. /// <summary>
  421. /// Edit the <paramref name="items"/> with a non-modal editor window, attaching them to the <paramref name="host"/> provided.
  422. /// </summary>
  423. /// <param name="items">List of objects to edit.</param>
  424. /// <param name="callback">A callback for when the items are finished being edited.</param>
  425. public virtual void EditItemsNonModal(ISubPanelHost host, T[] items, Action<T[], bool> callback, Func<Type, CoreTable?>? PageDataHandler = null, bool PreloadPages = false)
  426. {
  427. if (!DynamicGridUtils.TryEdit(items, out var editLock))
  428. {
  429. if(editLock.Panel is Window window)
  430. {
  431. Task.Delay(100).ContinueWith(task =>
  432. {
  433. window.WindowState = WindowState.Normal;
  434. window.Activate();
  435. }, TaskScheduler.FromCurrentSynchronizationContext());
  436. }
  437. else
  438. {
  439. MessageWindow.ShowMessage("One or more items are already being edited in an open window. You cannot edit the same entity multiple times simultaneously.", "Simultaneous edit.");
  440. }
  441. return;
  442. }
  443. DynamicEditorForm editor;
  444. using (var cursor = new WaitCursor())
  445. {
  446. editor = new DynamicEditorForm();
  447. editor.SetValue(Panel.ZIndexProperty, 999);
  448. editor.Form.DisableOKIfUnchanged = true;
  449. InitialiseEditorForm(editor, items, PageDataHandler, PreloadPages);
  450. OnEditorLoaded?.Invoke(editor, items);
  451. }
  452. editLock.Panel = editor;
  453. host.AddSubPanel(editor);
  454. editor.SubPanelClosed += o =>
  455. {
  456. try
  457. {
  458. DynamicGridUtils.FinishEdit(items);
  459. callback(items, editor.Result == true);
  460. }
  461. catch(Exception e)
  462. {
  463. MessageWindow.ShowError("Error occurred while closing editor.", e);
  464. }
  465. };
  466. editor.Show();
  467. }
  468. /// <summary>
  469. /// Edit the <paramref name="items"/> with a modal editor window.
  470. /// </summary>
  471. /// <param name="items">List of objects to edit.</param>
  472. /// <returns><see langword="true"/> if the items were saved.</returns>
  473. public virtual bool EditItems(T[] items, Func<Type, CoreTable?>? PageDataHandler = null, bool PreloadPages = false)
  474. {
  475. DynamicEditorForm editor;
  476. using (var cursor = new WaitCursor())
  477. {
  478. editor = new DynamicEditorForm()
  479. {
  480. WindowStartupLocation = WindowStartupLocation.CenterScreen
  481. };
  482. editor.SetValue(Panel.ZIndexProperty, 999);
  483. InitialiseEditorForm(editor, items, PageDataHandler, PreloadPages);
  484. OnEditorLoaded?.Invoke(editor, items);
  485. }
  486. return editor.ShowDialog() == true;
  487. }
  488. private Dictionary<String, object?> AfterEditorValueChanged(DynamicEditorGrid grid, T[] items, AfterEditorValueChangedArgs args)
  489. {
  490. var changes = new Dictionary<string, object?>();
  491. OnAfterEditorValueChanged(grid, items, args, changes);
  492. return changes;
  493. }
  494. protected virtual void OnAfterEditorValueChanged(DynamicEditorGrid? grid, T[] items, AfterEditorValueChangedArgs args, Dictionary<String, object?> changes)
  495. {
  496. }
  497. protected virtual void DoReconfigureEditors(DynamicEditorGrid grid, T[] items)
  498. {
  499. /*if (items.First() is IDimensioned dimensioned)
  500. {
  501. UpdateEditor(grid, x => x.Dimensions.Quantity, dimensioned.Dimensions.GetUnit().HasQuantity);
  502. UpdateEditor(grid, x => x.Dimensions.Length, dimensioned.Dimensions.GetUnit().HasLength);
  503. UpdateEditor(grid, x => x.Dimensions.Width, dimensioned.Dimensions.GetUnit().HasWidth);
  504. UpdateEditor(grid, x => x.Dimensions.Height, dimensioned.Dimensions.GetUnit().HasHeight);
  505. UpdateEditor(grid, x => x.Dimensions.Weight, dimensioned.Dimensions.GetUnit().HasWeight);
  506. }*/
  507. }
  508. private List<string>? ValidateData(T[] items)
  509. {
  510. var errors = new List<string>();
  511. DoValidate(items, errors);
  512. OnValidate?.Invoke(this, items, errors);
  513. return errors.Count != 0 ? errors : null;
  514. }
  515. protected virtual void DoValidate(T[] items, List<string> errors)
  516. {
  517. }
  518. protected virtual void AfterLoad(IDynamicEditorForm editor, T[] items)
  519. {
  520. editor.AfterLoad();
  521. }
  522. protected virtual void SelectPage(object sender, BaseObject[]? items)
  523. {
  524. }
  525. protected virtual Dictionary<string, object?> EditorValueChanged(IDynamicEditorForm editor, T[] items, string name, object value)
  526. {
  527. var result = DynamicGridUtils.UpdateEditorValue(items, name, value);
  528. if (OnEditorValueChanged != null)
  529. {
  530. var newchanges = OnEditorValueChanged(editor, name, value);
  531. foreach (var key in newchanges.Keys)
  532. result[key] = newchanges[key];
  533. }
  534. return result;
  535. }
  536. private readonly Dictionary<Tuple<Type, Type>, Dictionary<object, object>> _lookupcache = new();
  537. protected virtual void DefineLookups(ILookupEditorControl sender, T[] items, bool async = true)
  538. {
  539. if (sender.EditorDefinition is not ILookupEditor editor)
  540. return;
  541. var colname = sender.ColumnName;
  542. if (async)
  543. {
  544. Task.Run(() =>
  545. {
  546. try
  547. {
  548. var values = editor.Values(colname, items);
  549. Dispatcher.Invoke(
  550. () =>
  551. {
  552. try
  553. {
  554. //Logger.Send(LogType.Information, typeof(T).Name, "Dispatching Results" + colname);
  555. sender.LoadLookups(values);
  556. }
  557. catch (Exception e2)
  558. {
  559. Logger.Send(LogType.Information, typeof(T).Name,
  560. "Exception (2) in LoadLookups: " + e2.Message + "\n" + e2.StackTrace);
  561. }
  562. }
  563. );
  564. }
  565. catch (Exception e)
  566. {
  567. Logger.Send(LogType.Information, typeof(T).Name,
  568. "Exception (1) in LoadLookups: " + e.Message + "\n" + e.StackTrace);
  569. }
  570. });
  571. }
  572. else
  573. {
  574. var values = editor.Values(colname, items);
  575. sender.LoadLookups(values);
  576. }
  577. }
  578. /// <summary>
  579. /// Retrieves an editor to display for the given column of <paramref name="item"/>.
  580. /// </summary>
  581. /// <param name="item">The object being edited.</param>
  582. /// <param name="column">The column of the editor.</param>
  583. /// <returns>A new editor, or <see langword="null"/> if no editor defined and no sensible default exists.</returns>
  584. protected virtual BaseEditor? GetEditor(object item, DynamicGridColumn column)
  585. {
  586. return column.Editor ?? DatabaseSchema.Property(item.GetType(), column.ColumnName)?.Editor;
  587. }
  588. protected IFilter? DefineLookupFilter(Type type, string column, T[] items)
  589. {
  590. return LookupFactory.DefineLookupFilter(typeof(T), type, column, items);
  591. }
  592. protected virtual void SetEditorValue(object item, string name, object value)
  593. {
  594. try
  595. {
  596. CoreUtils.SetPropertyValue(item, name, value);
  597. }
  598. catch (Exception e)
  599. {
  600. Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
  601. }
  602. }
  603. protected virtual object? GetEditorValue(object item, string name)
  604. {
  605. return CoreUtils.GetPropertyValue(item, name);
  606. }
  607. protected override bool CanCreateRows() => CanCreateItems();
  608. protected virtual bool CanCreateItems()
  609. {
  610. return true;
  611. }
  612. protected override bool EditRows(CoreRow[]? rows)
  613. {
  614. if(rows is null)
  615. {
  616. if (!CanCreateItems())
  617. return false;
  618. var item = CreateItem();
  619. if (!AfterCreate(item))
  620. return false;
  621. if(Options.NonModalEditorHost is not null)
  622. {
  623. EditItemsNonModal(Options.NonModalEditorHost, [item], (items, result) =>
  624. {
  625. if (result)
  626. {
  627. var row = Data.NewRow();
  628. ObjectToRow(item, row);
  629. Data.Rows.Add(row);
  630. var masterrow = MasterData.NewRow();
  631. ObjectToRow(item, masterrow);
  632. MasterData.Rows.Add(masterrow);
  633. _recordmap[row] = masterrow;
  634. InvalidateGrid();
  635. SelectedRows = [row];
  636. DoChanged();
  637. }
  638. });
  639. return false;
  640. }
  641. else
  642. {
  643. if (EditItems([item]))
  644. {
  645. //_CurrentRow = Data.Rows.Count;
  646. var row = Data.NewRow();
  647. ObjectToRow(item, row);
  648. Data.Rows.Add(row);
  649. var masterrow = MasterData.NewRow();
  650. ObjectToRow(item, masterrow);
  651. MasterData.Rows.Add(masterrow);
  652. _recordmap[row] = masterrow;
  653. InvalidateGrid();
  654. SelectedRows = [row];
  655. DoChanged();
  656. return true;
  657. }
  658. return false;
  659. }
  660. }
  661. else
  662. {
  663. var items = Array.Empty<T>();
  664. using (new WaitCursor())
  665. {
  666. Stopwatch sw = new Stopwatch();
  667. sw.Start();
  668. items = LoadItems(rows);
  669. //Logger.Send(LogType.Information, "DG:LoadItems", String.Format("Loaded Items: {0}ms", sw.ElapsedMilliseconds));
  670. sw.Stop();
  671. }
  672. if (items.Length != 0)
  673. {
  674. var snaps = items.ToArray(x => x.TakeSnapshot());
  675. if(Options.NonModalEditorHost is not null)
  676. {
  677. EditItemsNonModal(Options.NonModalEditorHost, items, (items, result) =>
  678. {
  679. if (result)
  680. {
  681. var sel = SelectedRows;
  682. UpdateRows(rows, items, invalidateRows: false);
  683. InvalidateGrid();
  684. SelectedRows = sel;
  685. DoChanged();
  686. }
  687. else
  688. {
  689. foreach(var snap in snaps)
  690. {
  691. snap.ResetObject();
  692. }
  693. }
  694. });
  695. return false;
  696. }
  697. else
  698. {
  699. if (EditItems(items))
  700. {
  701. var sel = SelectedRows;
  702. UpdateRows(rows, items, invalidateRows: false);
  703. InvalidateGrid();
  704. SelectedRows = sel;
  705. DoChanged();
  706. return true;
  707. }
  708. else
  709. {
  710. foreach(var snap in snaps)
  711. {
  712. snap.ResetObject();
  713. }
  714. }
  715. }
  716. return false;
  717. }
  718. return false;
  719. }
  720. }
  721. #endregion
  722. #region Duplicate
  723. protected virtual IEnumerable<T> LoadDuplicatorItems(CoreRow[] rows)
  724. {
  725. return LoadItems(rows);
  726. }
  727. bool IDuplicateDynamicGrid.DoDuplicate(CoreRow[] rows) => DoDuplicate(rows);
  728. protected virtual bool DoDuplicate(CoreRow[] rows)
  729. {
  730. if (rows.Length == 0)
  731. {
  732. MessageBox.Show("Please select at least one record to duplicate!");
  733. return false;
  734. }
  735. /*var ids = ExtractValues(x => x.ID, Selection.Selected).ToArray();
  736. if (!ids.Any())
  737. {
  738. MessageBox.Show("Please select at least one record to duplicate!");
  739. return false;
  740. }*/
  741. var duplicator = (new T() as IDuplicatable)?.GetDuplicator();
  742. if (duplicator is null)
  743. {
  744. MessageBox.Show($"Cannot duplicate {typeof(T)}");
  745. return false;
  746. }
  747. duplicator.Duplicate(LoadDuplicatorItems(rows));// new Filter<T>(x => x.ID).InList(ids));
  748. return true;
  749. }
  750. #endregion
  751. public virtual T CreateItem()
  752. {
  753. var result = new T();
  754. OnCreateItem?.Invoke(this, result);
  755. return result;
  756. }
  757. public virtual bool AfterCreate(T item)
  758. {
  759. return OnAfterCreateItem?.Invoke(this, item) ?? true;
  760. }
  761. protected void ReloadForms<TTargetType, TTargetForm, TSourceForm>(IDynamicEditorForm editor, TTargetType item,
  762. Expression<Func<TSourceForm, object?>> sourcekey, Guid sourceid)
  763. where TTargetType : Entity, new()
  764. where TTargetForm : Entity, IRemotable, IPersistent, IDigitalFormInstance, new()
  765. where TSourceForm : Entity, IRemotable, IPersistent, IDigitalForm<TTargetType>, new()
  766. {
  767. var type = typeof(IDynamicOneToManyGrid<,>).MakeGenericType(typeof(TTargetType), typeof(TTargetForm));
  768. var page =
  769. editor.Pages?.FirstOrDefault(x => x.GetType().GetInterfaces().Contains(type)) as IDynamicOneToManyGrid<TTargetType, TTargetForm>;
  770. if (page != null && item != null)
  771. {
  772. if (!page.Ready)
  773. page.Load(item, null);
  774. CoreTable table;
  775. if (sourceid == Guid.Empty)
  776. {
  777. table = new CoreTable();
  778. table.LoadColumns(typeof(TSourceForm));
  779. }
  780. else
  781. {
  782. table = new Client<TSourceForm>().Query(
  783. new Filter<TSourceForm>(sourcekey).IsEqualTo(sourceid).And(x => x.Form.AppliesTo)
  784. .IsEqualTo(typeof(TTargetType).EntityName().Split('.').Last())
  785. );
  786. }
  787. var newforms = new List<TTargetForm>();
  788. foreach (var row in table.Rows)
  789. {
  790. var sourceform = row.ToObject<TSourceForm>();
  791. var targetform = new TTargetForm();
  792. targetform.Form.ID = sourceform.Form.ID;
  793. targetform.Form.Synchronise(sourceform.Form);
  794. newforms.Add(targetform);
  795. }
  796. page.Items.Clear();
  797. page.LoadItems(newforms.ToArray());
  798. }
  799. }
  800. #region ClipBuffer
  801. protected virtual bool BeforeCopy(IList<T> items)
  802. {
  803. return true;
  804. }
  805. protected override void MoveRows(CoreRow[] rows, int index, bool isCopy = false)
  806. {
  807. if (!Options.ReorderRows || !IsSequenced) return;
  808. var items = LoadItems(rows);
  809. if (isCopy)
  810. {
  811. if (!BeforeCopy(items))
  812. {
  813. return;
  814. }
  815. }
  816. var sequence = index < Data.Rows.Count
  817. ? Data.Rows[index].Get<ISequenceable, long>(x => x.Sequence)
  818. : Data.Rows[^1].Get<ISequenceable, long>(x => x.Sequence) + 1;
  819. var postRows = Data.Rows.Where(r => !rows.Contains(r) && r.Get<ISequenceable, long>(x => x.Sequence) >= sequence);
  820. var updates = items.Concatenate(LoadItems(postRows.ToArray()));
  821. foreach (var update in updates)
  822. {
  823. ((ISequenceable)update).Sequence = sequence;
  824. sequence++;
  825. }
  826. if (updates.Length != 0)
  827. {
  828. SaveItems(updates);
  829. DoChanged();
  830. Refresh(false, true);
  831. }
  832. }
  833. #endregion
  834. protected virtual void ObjectToRow(T obj, CoreRow row)
  835. {
  836. row.Table.FillRow(row, obj);
  837. }
  838. #region Import / Export
  839. protected virtual CoreTable LoadImportKeys(String[] fields)
  840. {
  841. var result = new CoreTable();
  842. result.LoadColumns(Columns.None<T>().Add(fields));
  843. return result;
  844. }
  845. protected virtual Guid GetImportID()
  846. {
  847. return Guid.Empty;
  848. }
  849. protected virtual bool CustomiseImportItem(T item)
  850. {
  851. if (IsSequenced)
  852. ((ISequenceable)item).Sequence = CoreUtils.GenerateSequence();
  853. return true;
  854. }
  855. protected virtual string CustomiseImportFileName(string filename)
  856. {
  857. return filename;
  858. }
  859. void IImportDynamicGrid.DoImport() => DoImport();
  860. protected virtual void DoImport()
  861. {
  862. var list = new DynamicImportList(
  863. typeof(T),
  864. GetImportID()
  865. );
  866. list.OnImportItem += o => { return CustomiseImportItem((T)o); };
  867. list.OnCustomiseImport += (o, args) => { args.FileName = CustomiseImportFileName(args.FileName); };
  868. list.OnSave += (sender, entity) => SaveItem(entity as T);
  869. list.OnLoad += (sender, type, fields, id) => LoadImportKeys(fields);
  870. list.ShowDialog();
  871. Refresh(false, true);
  872. }
  873. protected virtual void CustomiseExportColumns(List<string> columnnames)
  874. {
  875. }
  876. protected virtual string CustomiseExportFileName(string filename)
  877. {
  878. return filename;
  879. }
  880. protected virtual void CustomiseExportFilters(Filters<T> filters, CoreRow[] visiblerows)
  881. {
  882. }
  883. protected virtual void ApplyExportFilter(CoreTable table, object data)
  884. {
  885. }
  886. void IExportDynamicGrid.DoExport() => DoExport();
  887. protected virtual void DoExport()
  888. {
  889. var columnnames = VisibleColumns.Select(x => x.ColumnName).ToList();
  890. CustomiseExportColumns(columnnames);
  891. var form = new DynamicExportForm(typeof(T), columnnames);
  892. if (form.ShowDialog() != true)
  893. return;
  894. var filters = new Filters<T>();
  895. filters.Add(DefineFilter());
  896. var predicates = GetUIComponent().GetFilterPredicates();
  897. var visiblerows = GetVisibleRows();
  898. CustomiseExportFilters(filters, visiblerows);
  899. var columns = Columns.None<T>().Add(form.Fields);
  900. var otherColumns = form.GetChildFields()
  901. .Select(x => new Tuple<Type, IColumns>(
  902. x.Key,
  903. Columns.None(x.Key).Add(x.Value)))
  904. .Where(x => x.Item2.ColumnNames().Any()).ToList();
  905. var reloadColumns = Columns.None<T>();
  906. foreach (var column in columns)
  907. {
  908. reloadColumns.Add(column);
  909. }
  910. foreach (var column in HiddenColumns.ColumnNames)
  911. {
  912. reloadColumns.Add(column);
  913. }
  914. foreach (var column in LookupFactory.RequiredColumns<T>())
  915. {
  916. columns.Add(column);
  917. }
  918. foreach (var (column, _) in predicates)
  919. {
  920. reloadColumns.Add(column);
  921. }
  922. CoreTable? data = null;
  923. void LoadExport()
  924. {
  925. var newData = new CoreTable();
  926. newData.LoadColumns(columns);
  927. FilterRows(data.Rows, newData, filter: row =>
  928. {
  929. foreach(var (_, predicate) in predicates)
  930. {
  931. if (!predicate(row))
  932. {
  933. return false;
  934. }
  935. }
  936. return true;
  937. });
  938. var list = new List<Tuple<Type?, CoreTable>>() { new(typeof(T), newData) };
  939. list.AddRange(LoadExportTables(filters, otherColumns));
  940. DoExportTables(list);
  941. }
  942. var sort = LookupFactory.DefineSort<T>();
  943. Reload(filters, reloadColumns, ref sort, CancellationToken.None, (table, err) =>
  944. {
  945. if (table is not null)
  946. {
  947. if (table.Offset == 0 || data is null)
  948. {
  949. data = table;
  950. if (!IsPaging)
  951. {
  952. Dispatcher.Invoke(LoadExport);
  953. }
  954. }
  955. else
  956. {
  957. data.AddPage(table);
  958. if (!IsPaging)
  959. {
  960. Dispatcher.Invoke(LoadExport);
  961. }
  962. }
  963. }
  964. else if (err is not null)
  965. {
  966. Dispatcher.Invoke(() =>
  967. {
  968. MessageWindow.ShowError("Error in export.", err);
  969. });
  970. }
  971. });
  972. }
  973. /// <summary>
  974. /// Loads the child tables for an export, based on the filter of the parent table.
  975. /// </summary>
  976. /// <remarks>
  977. /// If not overriden, defaults to creating empty tables with no records.
  978. /// </remarks>
  979. /// <param name="filter">Filter for the parent table.</param>
  980. /// <param name="tableColumns">A list of the child table types, with columns to load for each</param>
  981. /// <returns>A list of tables, in the same order as they came in <paramref name="tableColumns"/></returns>
  982. protected virtual IEnumerable<Tuple<Type?, CoreTable>> LoadExportTables(Filters<T> filter, IEnumerable<Tuple<Type, IColumns>> tableColumns)
  983. {
  984. return tableColumns.Select(x =>
  985. {
  986. var table = new CoreTable();
  987. table.LoadColumns(x.Item2);
  988. return new Tuple<Type?, CoreTable>(x.Item1, table);
  989. });
  990. }
  991. private void DoExportTables(List<Tuple<Type?, CoreTable>> data)
  992. {
  993. var filename = CustomiseExportFileName(typeof(T).EntityName().Split('.').Last());
  994. ExcelExporter.DoExport(data, filename);
  995. }
  996. #endregion
  997. #endregion
  998. #region Header Actions
  999. protected override bool SelectColumns([NotNullWhen(true)] out DynamicGridColumns? columns)
  1000. {
  1001. var schema = new DynamicGridObjectColumnSchema(typeof(T));
  1002. var editor = new DynamicGridColumnsEditor(schema, typeof(T)) { WindowStartupLocation = WindowStartupLocation.CenterScreen };
  1003. editor.DirectEdit = IsDirectEditMode();
  1004. editor.Columns.AddRange(VisibleColumns);
  1005. schema.OnProcessColumns += args =>
  1006. {
  1007. GetAvailableColumns?.Invoke(args);
  1008. };
  1009. if (editor.ShowDialog().Equals(true))
  1010. {
  1011. columns = editor.Columns;
  1012. return true;
  1013. }
  1014. else
  1015. {
  1016. columns = null;
  1017. return false;
  1018. }
  1019. }
  1020. #endregion
  1021. #region Drag + Drop
  1022. protected DragDropEffects DragTable(Type entity, CoreTable table)
  1023. {
  1024. Logger.Send(LogType.Information, "", "DragTable");
  1025. var data = new DataObject();
  1026. data.SetData(DynamicGridUtils.DragFormat, new DynamicGridDragFormat(table.ToDataTable(), entity));
  1027. var effect = DragDrop.DoDragDrop(this, data, DragDropEffects.All);
  1028. return effect;
  1029. }
  1030. protected override DragDropEffects OnRowsDragStart(CoreRow[] rows)
  1031. {
  1032. Logger.Send(LogType.Information, "", "OnRowsDragStart");
  1033. var table = new CoreTable();
  1034. table.LoadColumns(Data.Columns);
  1035. table.LoadRows(rows);
  1036. return DragTable(typeof(T), table);
  1037. }
  1038. #endregion
  1039. }
  1040. #region Styling
  1041. public class DynamicGridRowStyle : DynamicGridStyle<VirtualizingCellsControl>
  1042. {
  1043. public DynamicGridRowStyle() : base(null)
  1044. {
  1045. }
  1046. public DynamicGridRowStyle(IDynamicGridStyle source) : base(source)
  1047. {
  1048. }
  1049. public override DependencyProperty FontSizeProperty => Control.FontSizeProperty;
  1050. public override DependencyProperty FontStyleProperty => Control.FontStyleProperty;
  1051. public override DependencyProperty FontWeightProperty => Control.FontWeightProperty;
  1052. public override DependencyProperty BackgroundProperty => Control.BackgroundProperty;
  1053. public override DependencyProperty ForegroundProperty => Control.ForegroundProperty;
  1054. }
  1055. public class DynamicGridCellStyle : DynamicGridStyle<Control>
  1056. {
  1057. public DynamicGridCellStyle() : base(null)
  1058. {
  1059. }
  1060. public DynamicGridCellStyle(IDynamicGridStyle source) : base(source)
  1061. {
  1062. }
  1063. public override DependencyProperty FontSizeProperty => Control.FontSizeProperty;
  1064. public override DependencyProperty FontStyleProperty => Control.FontStyleProperty;
  1065. public override DependencyProperty FontWeightProperty => Control.FontWeightProperty;
  1066. public override DependencyProperty BackgroundProperty => Control.BackgroundProperty;
  1067. public override DependencyProperty ForegroundProperty => Control.ForegroundProperty;
  1068. }
  1069. // Used to render boolean columns (the default "false" value shows what appears to be an intermediate state, which is ugly
  1070. // This should show nothing for false, and a tick in a box for true
  1071. public class BoolToImageConverter : AbstractConverter<bool, ImageSource?>
  1072. {
  1073. public ImageSource TrueValue { get; set; }
  1074. public ImageSource? FalseValue { get; set; }
  1075. public BoolToImageConverter()
  1076. {
  1077. TrueValue = Wpf.Resources.Bullet_Tick.AsBitmapImage();
  1078. }
  1079. public override ImageSource? Convert(bool value)
  1080. {
  1081. return value ? TrueValue : FalseValue;
  1082. }
  1083. public override bool Deconvert(ImageSource? value)
  1084. {
  1085. return ImageUtils.IsEqual(value as BitmapImage, TrueValue as BitmapImage);
  1086. }
  1087. }
  1088. public class StringToColorImageConverter : IValueConverter
  1089. {
  1090. private readonly int _height = 50;
  1091. private readonly int _width = 25;
  1092. private readonly Dictionary<string, BitmapImage> cache = new();
  1093. public StringToColorImageConverter(int width, int height)
  1094. {
  1095. _width = width;
  1096. _height = height;
  1097. }
  1098. public object? Convert(object value, Type targetType, object parameter, CultureInfo culture)
  1099. {
  1100. var str = value?.ToString();
  1101. if (str is null)
  1102. return null;
  1103. var colorcode = str.TrimStart('#');
  1104. if (!cache.ContainsKey(colorcode))
  1105. {
  1106. var col = ImageUtils.StringToColor(colorcode);
  1107. var bmp = ImageUtils.BitmapFromColor(col, _width, _height, Color.Black);
  1108. cache[colorcode] = bmp.AsBitmapImage();
  1109. }
  1110. var result = cache[colorcode];
  1111. return result;
  1112. }
  1113. public object? ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
  1114. {
  1115. return null;
  1116. }
  1117. }
  1118. public class StringArrayConverter : IValueConverter
  1119. {
  1120. object? IValueConverter.Convert(object value, Type targetType, object parameter, CultureInfo culture)
  1121. {
  1122. if (value is string[] strArray)
  1123. {
  1124. return string.Join("\n", strArray);
  1125. }
  1126. Logger.Send(LogType.Error, "", $"Attempt to convert an object which is not a string array: {value}.");
  1127. return null;
  1128. }
  1129. object IValueConverter.ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
  1130. {
  1131. return value;
  1132. }
  1133. }
  1134. #endregion
  1135. [Serializable]
  1136. class DynamicGridDragFormat
  1137. {
  1138. private string entity;
  1139. public DataTable Table { get; set; }
  1140. public Type Entity
  1141. {
  1142. get => CoreUtils.GetEntity(entity);
  1143. [MemberNotNull(nameof(entity))]
  1144. set => entity = value.EntityName();
  1145. }
  1146. public DynamicGridDragFormat(DataTable table, Type entity)
  1147. {
  1148. Table = table;
  1149. Entity = entity;
  1150. }
  1151. }