DynamicGridUtils.cs 36 KB

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