DynamicGridUtils.cs 33 KB

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