DynamicGridUtils.cs 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Diagnostics;
  5. using System.Linq;
  6. using System.Reflection;
  7. using System.Threading.Tasks;
  8. using System.Windows;
  9. using System.Windows.Controls;
  10. using InABox.Clients;
  11. using InABox.Core;
  12. using InABox.Wpf;
  13. using InABox.Core.Reports;
  14. using Syncfusion.Data.Extensions;
  15. using System.Diagnostics.CodeAnalysis;
  16. using System.Data;
  17. using System.Windows.Media;
  18. using AutoProperties;
  19. using InABox.WPF;
  20. using System.Windows.Threading;
  21. namespace InABox.DynamicGrid;
  22. [Caption("Set Default Column Selections")]
  23. public class CanSetDefaultColumns : EnabledSecurityDescriptor<CoreLicense>
  24. {
  25. }
  26. [LibraryInitializer]
  27. public static class DynamicGridUtils
  28. {
  29. private static IEnumerable<Type>? _allm2mtypes;
  30. private static IEnumerable<Type>? _allm2mpages;
  31. private static IEnumerable<Type>? _allo2mtypes;
  32. private static IEnumerable<Type>? _allo2mpages;
  33. private static IEnumerable<Type>? _allcepages;
  34. private static IEnumerable<Type>? _alleltypes;
  35. private static Dictionary<Type, IList<Type>> _onetomanypages = new();
  36. private static Dictionary<Type, IList<Type>> _manytomanytomanypages = new();
  37. private static Dictionary<Type, IList<Tuple<Type, PropertyInfo>>> _enclosedlistpages = new();
  38. private static Dictionary<Type, IList<Type>> _customeditorpages = new();
  39. // HACK: These are really dumb
  40. public static Action<ReportTemplate, DataModel>? PreviewReport { get; set; }
  41. public static Action<FrameworkElement?, string, DataModel, bool>? PrintMenu { get; set; }
  42. public static readonly MainResources Resources = new();
  43. public static void RegisterClasses()
  44. {
  45. // String assyname = "_" + Assembly.GetExecutingAssembly().GetName().Name;
  46. // AssemblyName assemblyName = new AssemblyName(assyname);
  47. // AppDomain appDomain = Thread.GetDomain();
  48. //
  49. // String assyFile = String.Format("{0}.dll", assemblyName.Name);
  50. // String path = "";
  51. // if (Assembly.GetEntryAssembly() != null)
  52. // {
  53. // path = Path.Combine(
  54. // Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
  55. // Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().Location)
  56. // );
  57. // }
  58. // else
  59. // {
  60. // path = Path.Combine(
  61. // Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
  62. // Path.GetFileNameWithoutExtension(Assembly.GetExecutingAssembly().Location)
  63. // );
  64. // }
  65. //
  66. // if (!Directory.Exists(path))
  67. // Directory.CreateDirectory(path);
  68. // AssemblyBuilder assemblyBuilder = appDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave, path);
  69. //
  70. // ModuleBuilder module = assemblyBuilder.DefineDynamicModule(assyFile); //,true);
  71. //
  72. // if (_allm2mtypes == null)
  73. // {
  74. // _allm2mtypes = CoreUtils.TypeList(
  75. // AppDomain.CurrentDomain.GetAssemblies(),
  76. // x => x.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IManyToMany<,>))
  77. // );
  78. // }
  79. //
  80. // var maps = _allm2mtypes.Where(x => x.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IManyToMany<,>) && i.GenericTypeArguments.Last().Equals(typeof(Document))));
  81. //
  82. // foreach (var map in maps)
  83. // {
  84. // var intf = map.GetInterfaces().FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IManyToMany<,>) && i.GenericTypeArguments.Last().Equals(typeof(Document)));
  85. // Type entity = intf.GenericTypeArguments.First();
  86. // Type basetype = typeof(DynamicDocumentGrid<,>).MakeGenericType(map, entity);
  87. // TypeBuilder tbService = module.DefineType(String.Format("{0}", map.EntityName().Replace(".", "_")), TypeAttributes.Public | TypeAttributes.Class);
  88. // tbService.SetParent(basetype);
  89. // Type final = tbService.CreateType();
  90. // }
  91. //
  92. // try
  93. // {
  94. // assemblyBuilder.Save(assyFile);
  95. // }
  96. // catch (Exception e)
  97. // {
  98. // Logger.Send(LogType.Error, "", String.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
  99. // }
  100. }
  101. #region Pages
  102. public static IEnumerable<Type> GetManyToManyTypes(Type type)
  103. {
  104. _allm2mtypes ??= CoreUtils.Entities.Where(x => x.HasInterface(typeof(IManyToMany<,>))).ToArray();
  105. return _allm2mtypes.Where(x => x.GetInterfaces().Any(
  106. i => i.IsGenericType
  107. && i.GetGenericTypeDefinition() == typeof(IManyToMany<,>)
  108. && i.GenericTypeArguments[0] == type)
  109. );
  110. }
  111. public static void LoadManyToManyPages(Type type, DynamicEditorPages pages)
  112. {
  113. if (!_manytomanytomanypages.TryGetValue(type, out var pageTypes))
  114. {
  115. pageTypes = new List<Type>();
  116. var maps = GetManyToManyTypes(type);
  117. foreach (var map in maps)
  118. {
  119. _allm2mpages ??= CoreUtils.Entities.Where(
  120. x => x.IsClass
  121. && !x.IsGenericType
  122. && x.HasInterface(typeof(IDynamicManyToManyGrid<,>)))
  123. .ToArray();
  124. var subtypes = _allm2mpages.Where(
  125. x => x.GetInterfaces().Any(i =>
  126. i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDynamicManyToManyGrid<,>) &&
  127. i.GenericTypeArguments.First().Equals(map) && i.GenericTypeArguments.Last().Equals(type))
  128. );
  129. if (subtypes.Any())
  130. {
  131. pageTypes.Add(subtypes.First());
  132. }
  133. else
  134. {
  135. pageTypes.Add(typeof(DynamicManyToManyGrid<,>).MakeGenericType(map, type));
  136. }
  137. }
  138. _manytomanytomanypages[type] = pageTypes.ToArray();
  139. }
  140. pages.AddRange(pageTypes.Select(x => (Activator.CreateInstance(x) as IDynamicEditorPage)!));
  141. }
  142. public static IEnumerable<Type> GetOneToManyTypes(Type type)
  143. {
  144. _allo2mtypes ??= CoreUtils.Entities.Where(
  145. x => x.HasInterface(typeof(IOneToMany<>)) && !x.HasAttribute<ObsoleteAttribute>())
  146. .ToArray();
  147. return _allo2mtypes
  148. .Where(x => x.GetInterfaces().Any(i =>
  149. i.IsGenericType
  150. && i.GetGenericTypeDefinition() == typeof(IOneToMany<>)
  151. && i.GenericTypeArguments.Contains(type)))
  152. .OrderBy(x => x.EntityName());
  153. }
  154. public static void LoadOneToManyPages(Type type, DynamicEditorPages pages)
  155. {
  156. if (!_onetomanypages.TryGetValue(type, out var pageTypes))
  157. {
  158. pageTypes = new List<Type>();
  159. var maps = GetOneToManyTypes(type);
  160. foreach (var map in maps)
  161. {
  162. _allo2mpages ??= CoreUtils.Entities.Where(
  163. x =>
  164. x.IsClass
  165. && !x.IsGenericType
  166. && x.HasInterface(typeof(IDynamicOneToManyGrid<,>))
  167. && !x.HasInterface<ISpecificGrid>())
  168. .ToArray();
  169. var subtypes = _allo2mpages.Where(x => x.GetInterfaces().Any(
  170. i => i.IsGenericType
  171. && i.GetGenericTypeDefinition() == typeof(IDynamicOneToManyGrid<,>)
  172. && i.GenericTypeArguments.First().Equals(type)
  173. && i.GenericTypeArguments.Last().Equals(map)
  174. )
  175. );
  176. if (subtypes.Any())
  177. {
  178. pageTypes.Add(subtypes.First());
  179. }
  180. else
  181. {
  182. pageTypes.Add(typeof(DynamicOneToManyGrid<,>).MakeGenericType(type, map));
  183. }
  184. }
  185. _onetomanypages[type] = pageTypes.ToArray();
  186. }
  187. pages.AddRange(pageTypes.Select(x => (Activator.CreateInstance(x) as IDynamicEditorPage)!));
  188. }
  189. public static void LoadCustomEditorPages(Type type, DynamicEditorPages pages)
  190. {
  191. if (!_customeditorpages.TryGetValue(type, out var pageTypes))
  192. {
  193. _allcepages ??= CoreUtils.Entities.Where(
  194. x => x.IsClass
  195. && !x.IsGenericType
  196. && x.HasInterface(typeof(IDynamicCustomEditorPage<>)))
  197. .ToArray();
  198. pageTypes = _allcepages.Where(x => x.GetInterfaces().Any(i =>
  199. i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDynamicCustomEditorPage<>) &&
  200. i.GenericTypeArguments.First().Equals(type))).ToArray();
  201. _customeditorpages[type] = pageTypes;
  202. }
  203. pages.AddRange(pageTypes.Select(x => (Activator.CreateInstance(x) as IDynamicEditorPage)!));
  204. }
  205. public static void LoadEnclosedListPages(Type type, DynamicEditorPages pages)
  206. {
  207. if (!_enclosedlistpages.TryGetValue(type, out var pageTypes))
  208. {
  209. pageTypes = new List<Tuple<Type, PropertyInfo>>();
  210. foreach (var property in type.GetProperties())
  211. {
  212. if (property.PropertyType.GetInterfaces().Contains(typeof(IList)))
  213. {
  214. var curtype = property.PropertyType;
  215. var gentype = property.PropertyType.GetGenericArguments().FirstOrDefault();
  216. while (gentype == null && curtype?.BaseType != null)
  217. {
  218. curtype = curtype.BaseType;
  219. gentype = curtype?.GetGenericArguments().FirstOrDefault();
  220. }
  221. if (gentype != null)
  222. if (gentype.IsSubclassOf(typeof(BaseObject)))
  223. {
  224. var editor = property.GetCustomAttributes().FirstOrDefault(x => x is BaseEditor);
  225. if (editor == null || !(editor is NullEditor))
  226. {
  227. _alleltypes ??= CoreUtils.Entities.Where(
  228. x => x.IsClass
  229. && !x.IsGenericType
  230. && x.HasInterface(typeof(IDynamicEnclosedListGrid<,>)))
  231. .ToArray();
  232. var subtypes = _alleltypes.Where(
  233. x => x.GetInterfaces().Any(
  234. i => i.IsGenericType
  235. && i.GetGenericTypeDefinition() == typeof(IDynamicEnclosedListGrid<,>)
  236. && i.GenericTypeArguments.First().Equals(type)
  237. && i.GenericTypeArguments.Last().Equals(gentype)
  238. )
  239. );
  240. if (subtypes.Any())
  241. {
  242. pageTypes.Add(new(subtypes.First(), property));
  243. }
  244. else
  245. {
  246. subtypes = _alleltypes.Where(x => x.GetInterfaces().Any(i => (i.GenericTypeArguments.LastOrDefault()?.Equals(gentype) == true)));
  247. if (subtypes.Any())
  248. {
  249. pageTypes.Add(new(subtypes.First().MakeGenericType(type), property));
  250. }
  251. else
  252. {
  253. try
  254. {
  255. pageTypes.Add(new(typeof(DynamicEnclosedListGrid<,>).MakeGenericType(type, gentype), property));
  256. }
  257. catch (Exception e)
  258. {
  259. Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
  260. }
  261. }
  262. }
  263. }
  264. }
  265. }
  266. }
  267. _enclosedlistpages[type] = pageTypes.ToArray();
  268. }
  269. pages.AddRange(pageTypes.Select(x => (Activator.CreateInstance(x.Item1, x.Item2) as IDynamicEditorPage)!));
  270. }
  271. #endregion
  272. #region Columns
  273. public static IColumns LoadEditorColumns(Type T, IColumns? additional = null)
  274. {
  275. var result = Columns.Create(T, ColumnTypeFlags.EditorColumns);
  276. if(additional is not null)
  277. {
  278. foreach (var col in additional)
  279. result.Add(col.Property);
  280. }
  281. foreach (var col in result.ColumnNames().ToArray())
  282. {
  283. var prop = DatabaseSchema.Property(T, col);
  284. if (prop?.Editor is DataLookupEditor dataLookup)
  285. {
  286. foreach (var lookupColumn in LookupFactory.DefineLookupFilterColumns(T, prop.Name))
  287. {
  288. result.Add(lookupColumn);
  289. }
  290. }
  291. }
  292. return result;
  293. }
  294. public static Columns<T> LoadEditorColumns<T>(Columns<T>? additional = null) => (LoadEditorColumns(typeof(T), additional) as Columns<T>)!;
  295. #endregion
  296. #region Editor Values
  297. /// <summary>
  298. /// Set the property <paramref name="name"/> of <paramref name="items"/> to <paramref name="value"/>, and return all changed properties.
  299. /// </summary>
  300. /// <returns>A dictionary containing changed properties, with the property name as the key, and the new value as the value.</returns>
  301. public static Dictionary<string, object?> UpdateEditorValue(BaseObject[] items, string name, object? value)
  302. {
  303. Logger.Send(LogType.Information, "", string.Format("DynamicGridUtils.UpdateEditorValue({0},{1},{2})", items.Length, name, value));
  304. var sw = new Stopwatch();
  305. var changes = new Dictionary<string, object?>();
  306. var props = DatabaseSchema.Properties(items.First().GetType()).ToArray();
  307. foreach (var item in items)
  308. {
  309. //Dictionary<String, object> previous = new Dictionary<string, object>();
  310. var previous = CoreUtils.GetValues(item, props);
  311. //if (item.OriginalValues != null)
  312. //{
  313. // foreach (var key in item.OriginalValues.Keys)
  314. // previous[key] = item.OriginalValues[key];
  315. //}
  316. var prop = DatabaseSchema.Property(item.GetType(), name);
  317. if (prop is CustomProperty)
  318. {
  319. if (!item.HasOriginalValue(name))
  320. item.SetOriginalValue(name, item.UserProperties[name]);
  321. item.UserProperties[name] = value;
  322. }
  323. else
  324. {
  325. if (prop != null)
  326. try
  327. {
  328. var getter = prop.Getter();
  329. var oldvalue = getter != null ? getter.Invoke(item) : CoreUtils.GetPropertyValue(item, name);
  330. var setter = prop.Setter();
  331. if (setter != null && value != null)
  332. setter.Invoke(item, value);
  333. else
  334. CoreUtils.SetPropertyValue(item, name, value);
  335. }
  336. catch (Exception)
  337. {
  338. Logger.Send(LogType.Error, "",
  339. string.Format("Unable to Set Value for [{0}.{1}] (Value is {2})", item.GetType().Name, name, value));
  340. }
  341. }
  342. var current = CoreUtils.GetValues(item, props);
  343. CoreUtils.MergeChanges(previous, current, changes);
  344. }
  345. return changes;
  346. }
  347. public static void UpdateEditorValue(BaseObject[] items, string name, object? value, Dictionary<string, object?> changes)
  348. {
  349. var results = UpdateEditorValue(items, name, value);
  350. foreach (var key in results.Keys)
  351. changes[key] = results[key];
  352. }
  353. #endregion
  354. #region Dynamic Grid Creation
  355. public static IDynamicGrid CreateDynamicGrid(Type gridType, Type entityType)
  356. {
  357. var type = FindDynamicGrid(gridType, entityType);
  358. return (Activator.CreateInstance(type) as IDynamicGrid)
  359. ?? throw new ArgumentException("Argument must be a type of IDynamicGrid", nameof(gridType));
  360. }
  361. public static DynamicGrid<TEntity> CreateDynamicGrid<TEntity>(Type gridType)
  362. where TEntity : BaseObject, new()
  363. {
  364. var type = FindDynamicGrid(gridType, typeof(TEntity));
  365. return (Activator.CreateInstance(type) as DynamicGrid<TEntity>)
  366. ?? throw new ArgumentException("Argument must be a type of IDynamicGrid", nameof(gridType));
  367. }
  368. private static Dictionary<Type, Type[]> _dynamicGrids = new();
  369. public static bool TryFindDynamicGrid(Type gridType, Type entityType, [NotNullWhen(true)] out Type? grid)
  370. {
  371. if (!_dynamicGrids.TryGetValue(gridType, out var grids))
  372. {
  373. grids = CoreUtils.Entities.Where(
  374. myType =>
  375. myType.IsClass
  376. && !myType.IsGenericType
  377. && myType.IsAssignableTo(typeof(IDynamicGrid))
  378. && !myType.IsAssignableTo(typeof(ISpecificGrid))
  379. ).ToArray();
  380. _dynamicGrids[gridType] = grids;
  381. }
  382. grids = grids.Where(x => x.IsSubclassOfRawGeneric(gridType)).ToArray();
  383. var entityGrids = grids.Where(x =>
  384. {
  385. var baseGrid = x.GetSuperclassDefinition(typeof(DynamicGrid<>));
  386. return baseGrid?.GenericTypeArguments[0] == entityType;
  387. }).ToList();
  388. var defaults = entityGrids.Where(x => x.IsAssignableTo(typeof(IDefaultGrid))).ToList();
  389. if (defaults.Count > 0)
  390. {
  391. if (defaults.Count > 1)
  392. {
  393. Logger.Send(LogType.Information, ClientFactory.UserID, $"Error: {defaults.Count} IDefaultGrid derivations for {gridType.Name} of {entityType.Name}");
  394. }
  395. grid = defaults.First();
  396. return true;
  397. }
  398. grid = entityGrids.FirstOrDefault();
  399. return grid is not null;
  400. }
  401. public static Type FindDynamicGrid(Type gridType, Type entityType)
  402. {
  403. if(TryFindDynamicGrid(gridType, entityType, out var grid))
  404. {
  405. return grid;
  406. }
  407. return gridType.MakeGenericType(entityType);
  408. }
  409. public static Window CreateGridWindow(string title, IDynamicGrid dynamicGrid, bool showbuttons = false, Func<bool>? okclicked = null)
  410. {
  411. dynamicGrid.Margin = new Thickness(5);
  412. var window = new ThemableWindow { Title = title };
  413. window.WindowStartupLocation = WindowStartupLocation.CenterScreen;
  414. window.SetValue(WindowBehavior.HideCloseButtonProperty, showbuttons);
  415. var grid = new Grid();
  416. grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) });
  417. grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Auto) });
  418. grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Auto) });
  419. grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) });
  420. grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Auto) });
  421. var fe = dynamicGrid as FrameworkElement;
  422. fe.SetValue(Grid.RowProperty,0);
  423. fe.SetValue(Grid.ColumnProperty, 0);
  424. fe.SetValue(Grid.ColumnSpanProperty, 3);
  425. grid.Children.Add(fe);
  426. if (showbuttons)
  427. {
  428. var ok = new Button();
  429. ok.Content = "OK";
  430. ok.SetValue(Grid.RowProperty,1);
  431. ok.SetValue(Grid.ColumnProperty,1);
  432. ok.Margin = new Thickness(0,0,5,5);
  433. ok.Width = 80;
  434. ok.Height = 35;
  435. ok.Click += (sender, args) =>
  436. {
  437. if (okclicked?.Invoke() ?? true)
  438. window.DialogResult = true;
  439. };
  440. grid.Children.Add(ok);
  441. var cancel = new Button();
  442. cancel.Content = "Cancel";
  443. cancel.SetValue(Grid.RowProperty,1);
  444. cancel.SetValue(Grid.ColumnProperty,2);
  445. cancel.Margin = new Thickness(0,0,5,5);
  446. cancel.Width = 80;
  447. cancel.Height = 35;
  448. cancel.Click += (sender, args) =>
  449. {
  450. window.DialogResult = false;
  451. };
  452. grid.Children.Add(cancel);
  453. }
  454. window.Content = grid;
  455. dynamicGrid.Refresh(true, true);
  456. return window;
  457. }
  458. public static Window CreateGridWindow(string title, Type entityType, Type? gridType = null, bool showbuttons = false)
  459. {
  460. gridType ??= typeof(DynamicGrid<>);
  461. var grid = CreateDynamicGrid(gridType, entityType);
  462. return CreateGridWindow(title, grid, showbuttons);
  463. }
  464. public static Window CreateGridWindow<TGrid, TEntity>(string title, bool showbuttons = false)
  465. where TEntity : BaseObject
  466. where TGrid : IDynamicGrid
  467. {
  468. return CreateGridWindow(title, typeof(TEntity), typeof(TGrid), showbuttons);
  469. }
  470. public static Window CreateGridWindow<TEntity>(string title, bool showbuttons = false)
  471. where TEntity : BaseObject
  472. {
  473. return CreateGridWindow(title, typeof(TEntity), null, showbuttons);
  474. }
  475. #endregion
  476. #region Non-modal Editing
  477. public class DynamicGridEntityEditLock(IEnumerable<Guid> ids)
  478. {
  479. public HashSet<Guid> ObjectIDs { get; set; } = ids.ToHashSet();
  480. public ISubPanel? Panel { get; set; }
  481. }
  482. private static Dictionary<Guid, DynamicGridEntityEditLock> CurrentEditLocks = new();
  483. /// <summary>
  484. /// Attempt to begin editing <paramref name="objs"/>, failing if those entities are already being edited elsewhere.
  485. /// </summary>
  486. /// <remarks>
  487. /// If returns <see langword="true"/>, then <paramref name="editLock"/> will contain a new edit lock which contains the
  488. /// new entities. Otherwise, <paramref name="editLock"/> will be the lock on the entities that are already being edited.
  489. /// </remarks>
  490. public static bool TryEdit(BaseObject[] objs, out DynamicGridEntityEditLock editLock)
  491. {
  492. lock (CurrentEditLocks)
  493. {
  494. var ids = new List<Guid>();
  495. foreach(var obj in objs)
  496. {
  497. if(obj is Entity entity && entity.ID != Guid.Empty)
  498. {
  499. if (CurrentEditLocks.TryGetValue(entity.ID, out editLock))
  500. {
  501. return false;
  502. }
  503. else
  504. {
  505. ids.Add(entity.ID);
  506. }
  507. }
  508. }
  509. editLock = new(ids);
  510. foreach(var id in ids)
  511. {
  512. CurrentEditLocks.Add(id, editLock);
  513. }
  514. return true;
  515. }
  516. }
  517. public static void FinishEdit(BaseObject[] objs)
  518. {
  519. lock (CurrentEditLocks)
  520. {
  521. foreach(var obj in objs)
  522. {
  523. if(obj is Entity entity && entity.ID != Guid.Empty)
  524. {
  525. if(CurrentEditLocks.Remove(entity.ID, out var editLock))
  526. {
  527. editLock.ObjectIDs.Remove(entity.ID);
  528. }
  529. }
  530. }
  531. }
  532. }
  533. #endregion
  534. #region Editing BaseObject
  535. /// <summary>
  536. /// Edit (using <see cref="DynamicItemsListGrid{T}"/>) a list of <see cref="BaseObject"/>s. Use for objects not saved in the database.
  537. /// </summary>
  538. /// <typeparam name="T"></typeparam>
  539. /// <param name="items"></param>
  540. /// <param name="pageDataHandler"></param>
  541. /// <param name="preloadPages"></param>
  542. /// <returns></returns>
  543. public static bool EditObjects<T>(T[] items, Func<Type, CoreTable?>? pageDataHandler = null, bool preloadPages = false, Action<DynamicGrid<T>>? customiseGrid = null)
  544. where T : BaseObject, new()
  545. {
  546. var grid = new DynamicItemsListGrid<T>();
  547. customiseGrid?.Invoke(grid);
  548. return grid.EditItems(items, PageDataHandler: pageDataHandler, PreloadPages: preloadPages);
  549. }
  550. /// <summary>
  551. /// Edit (using <see cref="DynamicItemsListGrid{T}"/>) a <see cref="BaseObject"/>. Use for objects not saved in the database.
  552. /// </summary>
  553. /// <typeparam name="T"></typeparam>
  554. /// <param name="items"></param>
  555. /// <param name="pageDataHandler"></param>
  556. /// <param name="preloadPages"></param>
  557. /// <returns></returns>
  558. public static bool EditObject<T>(T item, Func<Type, CoreTable?>? pageDataHandler = null, bool preloadPages = false, Action<DynamicGrid<T>>? customiseGrid = null)
  559. where T : BaseObject, new()
  560. {
  561. var grid = new DynamicItemsListGrid<T>();
  562. customiseGrid?.Invoke(grid);
  563. return grid.EditItems(new T[] { item }, PageDataHandler: pageDataHandler, PreloadPages: preloadPages);
  564. }
  565. /// <summary>
  566. /// Edit (using a grid sourced with <see cref="CreateDynamicGrid{T}(Type)"/>) a <typeparamref name="T"/>.
  567. /// </summary>
  568. /// <typeparam name="T"></typeparam>
  569. /// <param name="item"></param>
  570. /// <param name="pageDataHandler"></param>
  571. /// <param name="preloadPages"></param>
  572. /// <param name="customiseGrid"></param>
  573. /// <returns></returns>
  574. public static bool EditEntity<T>(T item, Func<Type, CoreTable?>? pageDataHandler = null, bool preloadPages = false, Action<DynamicGrid<T>>? customiseGrid = null)
  575. where T : BaseObject, new()
  576. {
  577. var grid = CreateDynamicGrid<T>(typeof(DynamicGrid<>));
  578. customiseGrid?.Invoke(grid);
  579. return grid.EditItems(new T[] { item }, PageDataHandler: pageDataHandler, PreloadPages: preloadPages);
  580. }
  581. /// <summary>
  582. /// Edit (using a grid sourced with <see cref="CreateDynamicGrid{T}(Type)"/>) a list of <typeparamref name="T"/>.
  583. /// </summary>
  584. /// <typeparam name="T"></typeparam>
  585. /// <param name="item"></param>
  586. /// <param name="pageDataHandler"></param>
  587. /// <param name="preloadPages"></param>
  588. /// <param name="customiseGrid"></param>
  589. /// <returns></returns>
  590. public static bool EditEntities<T>(T[] items, Func<Type, CoreTable?>? pageDataHandler = null, bool preloadPages = false, Action<DynamicGrid<T>>? customiseGrid = null)
  591. where T : BaseObject, new()
  592. {
  593. var grid = CreateDynamicGrid<T>(typeof(DynamicGrid<>));
  594. customiseGrid?.Invoke(grid);
  595. return grid.EditItems(items, PageDataHandler: pageDataHandler, PreloadPages: preloadPages);
  596. }
  597. /// <summary>
  598. /// Edit (using a grid sourced with <see cref="CreateDynamicGrid{T}(Type)"/>) a <typeparamref name="T"/>, by loading it from the database.
  599. /// </summary>
  600. public static bool EditEntity<T>(Guid id, Func<Type, CoreTable?>? pageDataHandler = null, bool preloadPages = false, Action<DynamicGrid<T>>? customiseGrid = null)
  601. where T : Entity, IRemotable, IPersistent, new()
  602. {
  603. var grid = (CreateDynamicGrid<T>(typeof(DynamicDataGrid<>)) as DynamicDataGrid<T>)!;
  604. customiseGrid?.Invoke(grid);
  605. var item = Client.Query<T>(
  606. Filter<T>.Where(x => x.ID).IsEqualTo(id),
  607. grid.LoadEditorColumns())
  608. .ToObjects<T>().FirstOrDefault();
  609. if (item is null) return false;
  610. return grid.EditItems(new T[] { item }, PageDataHandler: pageDataHandler, PreloadPages: preloadPages);
  611. }
  612. #endregion
  613. #region Drag + Drop
  614. public static string DragFormat => typeof(DynamicGridDragFormat).FullName ?? "";
  615. /// <summary>
  616. /// Try to get data dragged from a <see cref="DynamicGrid{T}"/> from a <see cref="DragEventArgs"/>, returning <see langword="true"/>
  617. /// if data was present.
  618. /// </summary>
  619. /// <param name="e"></param>
  620. public static bool TryGetDropData(
  621. DragEventArgs e,
  622. [NotNullWhen(true)] out Type? type,
  623. [NotNullWhen(true)] out CoreTable? table)
  624. {
  625. if (e.Data.GetDataPresent(DragFormat))
  626. {
  627. var data = e.Data.GetData(DragFormat) as DynamicGridDragFormat;
  628. if (data is not null)
  629. {
  630. table = new CoreTable();
  631. foreach (var column in data.Table.Columns)
  632. {
  633. if (column is DataColumn dataColumn)
  634. {
  635. table.Columns.Add(new CoreColumn { ColumnName = dataColumn.ColumnName.Replace('_', '.'), DataType = dataColumn.DataType });
  636. }
  637. }
  638. foreach (var row in data.Table.Rows)
  639. {
  640. if (row is DataRow dataRow)
  641. {
  642. var coreRow = table.NewRow();
  643. coreRow.LoadValues(dataRow.ItemArray);
  644. table.Rows.Add(coreRow);
  645. }
  646. }
  647. type = data.Entity;
  648. return true;
  649. }
  650. }
  651. table = null;
  652. type = null;
  653. return false;
  654. }
  655. #endregion
  656. #region Style
  657. public static Brush SelectionBackground { get; set; } = new SolidColorBrush(Colors.Black);
  658. public static Brush SelectionForeground { get; set; } = new SolidColorBrush(Colors.Silver);
  659. public static Brush FilterBackground { get; set; } = new SolidColorBrush(System.Windows.Media.Color.FromArgb(0xFF, 0xE9, 0xF7, 0xC9));
  660. public static Brush FilterForeground{ get; set; } = new SolidColorBrush(Colors.Black);
  661. #endregion
  662. public static void PopulateFormMenu<TEntityForm, TEntity, TEntityLink>(
  663. ItemsControl menu,
  664. Guid entityID,
  665. Func<TEntity> loadEntity,
  666. bool editOnAdd = false,
  667. DynamicFormEditWindow.CustomiseDynamicFormEditWindow? customiseEditor = null,
  668. DynamicEntityFormGrid<TEntityForm, TEntity, TEntityLink>.CustomiseNewFormHandler? customiseNewForm = null,
  669. ISubPanelHost? nonModalHost = null,
  670. Action? onSaved = null)
  671. where TEntityForm : BaseEntityForm<TEntity, TEntityLink, TEntityForm>, new()
  672. where TEntity : Entity
  673. where TEntityLink : BaseObject, IEntityLink<TEntity>, new()
  674. {
  675. if(onSaved is not null)
  676. {
  677. var oldOnSaved = onSaved;
  678. onSaved = () =>
  679. {
  680. Application.Current.Dispatcher.BeginInvoke(oldOnSaved);
  681. };
  682. }
  683. var task = Task.Run(() =>
  684. {
  685. return new Client<TEntityForm>().Query(
  686. Filter<TEntityForm>.Where(x => x.Parent.ID).IsEqualTo(entityID),
  687. null).Rows.Select(x => x.ToObject<TEntityForm>()).ToList();
  688. });
  689. void EditForm(TEntityForm form)
  690. {
  691. if(nonModalHost is not null)
  692. {
  693. DynamicFormEditWindow.EditDigitalFormNonModal(nonModalHost, form, customise: customiseEditor)
  694. .ContinueWith(task =>
  695. {
  696. if (task.Result.shouldSave)
  697. {
  698. task.Result.model!.Update(null);
  699. onSaved?.Invoke();
  700. }
  701. }, TaskContinuationOptions.OnlyOnRanToCompletion);
  702. }
  703. else
  704. {
  705. if (DynamicFormEditWindow.EditDigitalForm(form, out var dataModel, customise: customiseEditor))
  706. {
  707. dataModel.Update(null);
  708. onSaved?.Invoke();
  709. }
  710. }
  711. }
  712. var addForm = new MenuItem { Header = "Add Form" };
  713. addForm.Click += (o, e) =>
  714. {
  715. try
  716. {
  717. var entity = loadEntity();
  718. var filter = LookupFactory.DefineChildFilter<TEntity, DigitalForm>([entity])
  719. ?? LookupFactory.DefineLookupFilter<TEntityForm, DigitalForm, DigitalFormLink>(x => x.Form, []);
  720. var select = new MultiSelectDialog<DigitalForm>(
  721. filter,
  722. LookupFactory.DefineLookupColumns<TEntityForm, DigitalForm, DigitalFormLink>(x => x.Form).Add(x => x.Description),
  723. false);
  724. if(select.ShowDialog() == true)
  725. {
  726. var digitalForm = select.Data().Rows.FirstOrDefault()?.ToObject<DigitalForm>();
  727. if(digitalForm is not null)
  728. {
  729. var form = new TEntityForm
  730. {
  731. Description = digitalForm.Description
  732. };
  733. form.Parent.ID = entityID;
  734. form.Form.ID = digitalForm.ID;
  735. customiseNewForm?.Invoke(entity, form);
  736. if (editOnAdd)
  737. {
  738. EditForm(form);
  739. }
  740. else
  741. {
  742. Client.Save(form, "Added by user");
  743. onSaved?.Invoke();
  744. }
  745. }
  746. };
  747. }
  748. catch(Exception exception)
  749. {
  750. MessageWindow.ShowError("Error adding form.", exception);
  751. }
  752. };
  753. var manageForms = new MenuItem { Header = "Manage Forms..." };
  754. manageForms.Click += (o, e) =>
  755. {
  756. var window = new ThemableWindow() { Title = $"Manage {typeof(TEntity).Name} Forms" };
  757. var grid = new DynamicEntityFormGrid<TEntityForm, TEntity, TEntityLink>(loadEntity());
  758. grid.CustomiseNewForm = customiseNewForm;
  759. grid.OnChanged += (o, e) =>
  760. {
  761. onSaved?.Invoke();
  762. };
  763. grid.Refresh(true, true);
  764. grid.Margin = new Thickness(5);
  765. window.Content = grid;
  766. window.ShowDialog();
  767. };
  768. menu.Items.Add(addForm);
  769. menu.Items.Add(new Separator());
  770. menu.Items.Add(new MenuItem() { Header = "Loading...", IsEnabled = false });
  771. menu.Items.Add(new Separator());
  772. menu.Items.Add(manageForms);
  773. task.ContinueWith((task) =>
  774. {
  775. var entityForms = task.Result;
  776. menu.Items.Clear();
  777. menu.Items.Add(addForm);
  778. menu.Items.Add(new Separator());
  779. if (entityForms.Any())
  780. {
  781. foreach (var entityForm in entityForms)
  782. {
  783. var description = entityForm.Description;
  784. if (string.IsNullOrWhiteSpace(description))
  785. {
  786. description = entityForm.Form.Description;
  787. }
  788. var formItem = new MenuItem { Header = $"{entityForm.Number} : {description}" };
  789. formItem.Click += (o, e) =>
  790. {
  791. EditForm(entityForm);
  792. };
  793. menu.Items.Add(formItem);
  794. }
  795. }
  796. else
  797. {
  798. menu.Items.Add(new MenuItem() { Header = "No Forms", IsEnabled = false });
  799. }
  800. menu.Items.Add(new Separator());
  801. menu.Items.Add(manageForms);
  802. }, TaskScheduler.FromCurrentSynchronizationContext());
  803. }
  804. #region Alignment
  805. public static VerticalAlignment VerticalAlignment(this Alignment alignment)
  806. {
  807. if (alignment.Equals(Alignment.NotSet))
  808. return System.Windows.VerticalAlignment.Center;
  809. if (alignment.Equals(Alignment.TopLeft) || alignment.Equals(Alignment.TopCenter) || alignment.Equals(Alignment.TopRight))
  810. return System.Windows.VerticalAlignment.Top;
  811. if (alignment.Equals(Alignment.MiddleLeft) || alignment.Equals(Alignment.MiddleCenter) || alignment.Equals(Alignment.MiddleRight))
  812. return System.Windows.VerticalAlignment.Center;
  813. return System.Windows.VerticalAlignment.Bottom;
  814. }
  815. public static HorizontalAlignment HorizontalAlignment(this Alignment alignment, Type datatype)
  816. {
  817. if (alignment.Equals(Alignment.NotSet))
  818. return datatype.IsNumeric() ? System.Windows.HorizontalAlignment.Right :
  819. datatype == typeof(bool) ? System.Windows.HorizontalAlignment.Center : System.Windows.HorizontalAlignment.Left;
  820. if (alignment.Equals(Alignment.TopLeft) || alignment.Equals(Alignment.MiddleLeft) || alignment.Equals(Alignment.BottomLeft))
  821. return System.Windows.HorizontalAlignment.Left;
  822. if (alignment.Equals(Alignment.TopCenter) || alignment.Equals(Alignment.MiddleCenter) || alignment.Equals(Alignment.BottomCenter))
  823. return System.Windows.HorizontalAlignment.Center;
  824. return System.Windows.HorizontalAlignment.Right;
  825. }
  826. public static TextAlignment TextAlignment(this Alignment alignment, Type datatype)
  827. {
  828. if (alignment.Equals(Alignment.NotSet))
  829. return datatype.IsNumeric() ? System.Windows.TextAlignment.Right :
  830. datatype == typeof(bool) ? System.Windows.TextAlignment.Center : System.Windows.TextAlignment.Left;
  831. if (alignment.Equals(Alignment.TopLeft) || alignment.Equals(Alignment.MiddleLeft) || alignment.Equals(Alignment.BottomLeft))
  832. return System.Windows.TextAlignment.Left;
  833. if (alignment.Equals(Alignment.TopCenter) || alignment.Equals(Alignment.MiddleCenter) || alignment.Equals(Alignment.BottomCenter))
  834. return System.Windows.TextAlignment.Center;
  835. return System.Windows.TextAlignment.Right;
  836. }
  837. #endregion
  838. }