DynamicGridUtils.cs 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933
  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 System.Linq.Expressions;
  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. pages.AddRange(pageTypes.Select(x => (Activator.CreateInstance(x) as IDynamicEditorPage)!));
  143. }
  144. public static IEnumerable<Type> GetOneToManyTypes(Type type)
  145. {
  146. _allo2mtypes ??= CoreUtils.Entities.Where(
  147. x => x.HasInterface(typeof(IOneToMany<>)) && !x.HasAttribute<ObsoleteAttribute>())
  148. .ToArray();
  149. return _allo2mtypes
  150. .Where(x => x.GetInterfaces().Any(i =>
  151. i.IsGenericType
  152. && i.GetGenericTypeDefinition() == typeof(IOneToMany<>)
  153. && i.GenericTypeArguments.Contains(type)))
  154. .OrderBy(x => x.EntityName());
  155. }
  156. public static void LoadOneToManyPages(Type type, DynamicEditorPages pages)
  157. {
  158. if (!_onetomanypages.TryGetValue(type, out var pageTypes))
  159. {
  160. pageTypes = new List<Type>();
  161. var maps = GetOneToManyTypes(type);
  162. foreach (var map in maps)
  163. {
  164. if (ClientFactory.IsSupported(map))
  165. {
  166. _allo2mpages ??= CoreUtils.Entities.Where(
  167. x =>
  168. x.IsClass
  169. && !x.IsGenericType
  170. && x.HasInterface(typeof(IDynamicOneToManyGrid<,>))
  171. && !x.HasInterface<ISpecificGrid>())
  172. .ToArray();
  173. var subtypes = _allo2mpages.Where(x => x.GetInterfaces().Any(
  174. i => i.IsGenericType
  175. && i.GetGenericTypeDefinition() == typeof(IDynamicOneToManyGrid<,>)
  176. && i.GenericTypeArguments.First().Equals(type)
  177. && i.GenericTypeArguments.Last().Equals(map)
  178. )
  179. );
  180. if (subtypes.Any())
  181. {
  182. pageTypes.Add(subtypes.First());
  183. }
  184. else
  185. {
  186. pageTypes.Add(typeof(DynamicOneToManyGrid<,>).MakeGenericType(type, map));
  187. }
  188. }
  189. }
  190. _onetomanypages[type] = pageTypes.ToArray();
  191. }
  192. pages.AddRange(pageTypes.Select(x => (Activator.CreateInstance(x) as IDynamicEditorPage)!));
  193. }
  194. public static void LoadCustomEditorPages(Type type, DynamicEditorPages pages)
  195. {
  196. if (!_customeditorpages.TryGetValue(type, out var pageTypes))
  197. {
  198. _allcepages ??= CoreUtils.Entities.Where(
  199. x => x.IsClass
  200. && !x.IsGenericType
  201. && x.HasInterface(typeof(IDynamicCustomEditorPage<>)))
  202. .ToArray();
  203. pageTypes = _allcepages.Where(x => x.GetInterfaces().Any(i =>
  204. i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDynamicCustomEditorPage<>) &&
  205. i.GenericTypeArguments.First().Equals(type))).ToArray();
  206. _customeditorpages[type] = pageTypes;
  207. }
  208. pages.AddRange(pageTypes.Select(x => (Activator.CreateInstance(x) as IDynamicEditorPage)!));
  209. }
  210. public static void LoadEnclosedListPages(Type type, DynamicEditorPages pages)
  211. {
  212. if (!_enclosedlistpages.TryGetValue(type, out var pageTypes))
  213. {
  214. pageTypes = new List<Tuple<Type, PropertyInfo>>();
  215. foreach (var property in type.GetProperties())
  216. {
  217. if (property.PropertyType.GetInterfaces().Contains(typeof(IList)))
  218. {
  219. var curtype = property.PropertyType;
  220. var gentype = property.PropertyType.GetGenericArguments().FirstOrDefault();
  221. while (gentype == null && curtype?.BaseType != null)
  222. {
  223. curtype = curtype.BaseType;
  224. gentype = curtype?.GetGenericArguments().FirstOrDefault();
  225. }
  226. if (gentype != null)
  227. if (gentype.IsSubclassOf(typeof(BaseObject)))
  228. {
  229. var editor = property.GetCustomAttributes().FirstOrDefault(x => x is BaseEditor);
  230. if (editor == null || !(editor is NullEditor))
  231. {
  232. _alleltypes ??= CoreUtils.Entities.Where(
  233. x => x.IsClass
  234. && !x.IsGenericType
  235. && x.HasInterface(typeof(IDynamicEnclosedListGrid<,>)))
  236. .ToArray();
  237. var subtypes = _alleltypes.Where(
  238. x => x.GetInterfaces().Any(
  239. i => i.IsGenericType
  240. && i.GetGenericTypeDefinition() == typeof(IDynamicEnclosedListGrid<,>)
  241. && i.GenericTypeArguments.First().Equals(type)
  242. && i.GenericTypeArguments.Last().Equals(gentype)
  243. )
  244. );
  245. if (subtypes.Any())
  246. {
  247. pageTypes.Add(new(subtypes.First(), property));
  248. }
  249. else
  250. {
  251. subtypes = _alleltypes.Where(x => x.GetInterfaces().Any(i => (i.GenericTypeArguments.LastOrDefault()?.Equals(gentype) == true)));
  252. if (subtypes.Any())
  253. {
  254. pageTypes.Add(new(subtypes.First().MakeGenericType(type), property));
  255. }
  256. else
  257. {
  258. try
  259. {
  260. pageTypes.Add(new(typeof(DynamicEnclosedListGrid<,>).MakeGenericType(type, gentype), property));
  261. }
  262. catch (Exception e)
  263. {
  264. Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
  265. }
  266. }
  267. }
  268. }
  269. }
  270. }
  271. }
  272. _enclosedlistpages[type] = pageTypes.ToArray();
  273. }
  274. pages.AddRange(pageTypes.Select(x => (Activator.CreateInstance(x.Item1, x.Item2) as IDynamicEditorPage)!));
  275. }
  276. #endregion
  277. #region Columns
  278. public static IColumns LoadEditorColumns(Type T, IEnumerable<IColumn> additional)
  279. {
  280. var result = Columns.Create(T, ColumnTypeFlags.EditorColumns);
  281. foreach (var col in additional)
  282. result.Add(col.Property);
  283. var newColumns = new List<IColumn>();
  284. foreach (var col in result)
  285. {
  286. var prop = DatabaseSchema.Property(T, col.Property);
  287. if (prop?.Editor is DataLookupEditor dataLookup)
  288. {
  289. foreach (var lookupColumn in LookupFactory.DefineLookupFilterColumns(T, prop.Name))
  290. {
  291. newColumns.Add(lookupColumn);
  292. }
  293. }
  294. }
  295. foreach(var col in newColumns)
  296. {
  297. result.Add(col);
  298. }
  299. return result;
  300. }
  301. public static Columns<T> LoadEditorColumns<T>(Columns<T> additional)
  302. {
  303. var result = new Columns<T>(ColumnTypeFlags.EditorColumns);
  304. foreach (var col in additional)
  305. result.Add(col.Property);
  306. foreach (var col in result.ToArray())
  307. {
  308. var prop = DatabaseSchema.Property(typeof(T), col.Property);
  309. if (prop?.Editor is DataLookupEditor dataLookup)
  310. {
  311. foreach (var lookupColumn in LookupFactory.DefineLookupFilterColumns(typeof(T), prop.Name))
  312. {
  313. result.Add(lookupColumn);
  314. }
  315. }
  316. }
  317. return result;
  318. }
  319. #endregion
  320. #region Editor Values
  321. public static Dictionary<string, object?> UpdateEditorValue(BaseObject[] items, string name, object? value)
  322. {
  323. Logger.Send(LogType.Information, "", string.Format("DynamicGridUtils.UpdateEditorValue({0},{1},{2})", items.Length, name, value));
  324. var sw = new Stopwatch();
  325. var changes = new Dictionary<string, object?>();
  326. var props = DatabaseSchema.Properties(items.First().GetType()).ToArray();
  327. foreach (var item in items)
  328. {
  329. //Dictionary<String, object> previous = new Dictionary<string, object>();
  330. var previous = CoreUtils.GetValues(item, props);
  331. //if (item.OriginalValues != null)
  332. //{
  333. // foreach (var key in item.OriginalValues.Keys)
  334. // previous[key] = item.OriginalValues[key];
  335. //}
  336. var prop = DatabaseSchema.Property(item.GetType(), name);
  337. if (prop is CustomProperty)
  338. {
  339. if (!item.HasOriginalValue(name))
  340. item.SetOriginalValue(name, item.UserProperties[name]);
  341. item.UserProperties[name] = value;
  342. }
  343. else
  344. {
  345. if (prop != null)
  346. try
  347. {
  348. var getter = prop.Getter();
  349. var oldvalue = getter != null ? getter.Invoke(item) : CoreUtils.GetPropertyValue(item, name);
  350. item.OnPropertyChanged(name, oldvalue, value);
  351. var setter = prop.Setter();
  352. if (setter != null && value != null)
  353. setter.Invoke(item, value);
  354. else
  355. CoreUtils.SetPropertyValue(item, name, value);
  356. }
  357. catch (Exception)
  358. {
  359. Logger.Send(LogType.Error, "",
  360. string.Format("Unable to Set Value for [{0}.{1}] (Value is {2})", item.GetType().Name, name, value));
  361. }
  362. }
  363. var current = CoreUtils.GetValues(item, props);
  364. CoreUtils.MergeChanges(previous, current, changes);
  365. }
  366. return changes;
  367. }
  368. public static void UpdateEditorValue(BaseObject[] items, string name, object? value, Dictionary<string, object?> changes)
  369. {
  370. var results = UpdateEditorValue(items, name, value);
  371. foreach (var key in results.Keys)
  372. changes[key] = results[key];
  373. }
  374. #endregion
  375. #region Dynamic Grid Creation
  376. public static IDynamicGrid CreateDynamicGrid(Type gridType, Type entityType)
  377. {
  378. var type = FindDynamicGrid(gridType, entityType);
  379. return (Activator.CreateInstance(type) as IDynamicGrid)
  380. ?? throw new ArgumentException("Argument must be a type of IDynamicGrid", nameof(gridType));
  381. }
  382. public static DynamicGrid<TEntity> CreateDynamicGrid<TEntity>(Type gridType)
  383. where TEntity : BaseObject, new()
  384. {
  385. var type = FindDynamicGrid(gridType, typeof(TEntity));
  386. return (Activator.CreateInstance(type) as DynamicGrid<TEntity>)
  387. ?? throw new ArgumentException("Argument must be a type of IDynamicGrid", nameof(gridType));
  388. }
  389. private static Dictionary<Type, Type[]> _dynamicGrids = new();
  390. public static bool TryFindDynamicGrid(Type gridType, Type entityType, [NotNullWhen(true)] out Type? grid)
  391. {
  392. if (!_dynamicGrids.TryGetValue(gridType, out var grids))
  393. {
  394. grids = CoreUtils.Entities.Where(
  395. myType =>
  396. myType.IsClass
  397. && !myType.IsGenericType
  398. && myType.IsAssignableTo(typeof(IDynamicGrid))
  399. && !myType.IsAssignableTo(typeof(ISpecificGrid))
  400. ).ToArray();
  401. _dynamicGrids[gridType] = grids;
  402. }
  403. grids = grids.Where(x => x.IsSubclassOfRawGeneric(gridType)).ToArray();
  404. var entityGrids = grids.Where(x =>
  405. {
  406. var baseGrid = x.GetSuperclassDefinition(typeof(DynamicGrid<>));
  407. return baseGrid?.GenericTypeArguments[0] == entityType;
  408. }).ToList();
  409. var defaults = entityGrids.Where(x => x.IsAssignableTo(typeof(IDefaultGrid))).ToList();
  410. if (defaults.Count > 0)
  411. {
  412. if (defaults.Count > 1)
  413. {
  414. Logger.Send(LogType.Information, ClientFactory.UserID, $"Error: {defaults.Count} IDefaultGrid derivations for {gridType.Name} of {entityType.Name}");
  415. }
  416. grid = defaults.First();
  417. return true;
  418. }
  419. grid = entityGrids.FirstOrDefault();
  420. return grid is not null;
  421. }
  422. public static Type FindDynamicGrid(Type gridType, Type entityType)
  423. {
  424. if(TryFindDynamicGrid(gridType, entityType, out var grid))
  425. {
  426. return grid;
  427. }
  428. return gridType.MakeGenericType(entityType);
  429. }
  430. public static Window CreateGridWindow(string title, IDynamicGrid dynamicGrid)
  431. {
  432. dynamicGrid.Margin = new Thickness(5);
  433. var window = new ThemableWindow { Title = title, Content = dynamicGrid };
  434. dynamicGrid.Refresh(true, true);
  435. return window;
  436. }
  437. public static Window CreateGridWindow(string title, Type entityType, Type? gridType = null)
  438. {
  439. gridType ??= typeof(DynamicGrid<>);
  440. var grid = CreateDynamicGrid(gridType, entityType);
  441. return CreateGridWindow(title, grid);
  442. }
  443. public static Window CreateGridWindow<TGrid, TEntity>(string title)
  444. where TEntity : BaseObject
  445. where TGrid : IDynamicGrid
  446. {
  447. return CreateGridWindow(title, typeof(TEntity), typeof(TGrid));
  448. }
  449. public static Window CreateGridWindow<TEntity>(string title)
  450. where TEntity : BaseObject
  451. {
  452. return CreateGridWindow(title, typeof(TEntity));
  453. }
  454. #endregion
  455. #region Editing BaseObject
  456. /// <summary>
  457. /// Edit (using <see cref="DynamicItemsListGrid{T}"/>) a list of <see cref="BaseObject"/>s. Use for objects not saved in the database.
  458. /// </summary>
  459. /// <typeparam name="T"></typeparam>
  460. /// <param name="items"></param>
  461. /// <param name="pageDataHandler"></param>
  462. /// <param name="preloadPages"></param>
  463. /// <returns></returns>
  464. public static bool EditObjects<T>(T[] items, Func<Type, CoreTable?>? pageDataHandler = null, bool preloadPages = false, Action<DynamicGrid<T>>? customiseGrid = null)
  465. where T : BaseObject, new()
  466. {
  467. var grid = new DynamicGrid<T>();
  468. customiseGrid?.Invoke(grid);
  469. return grid.EditItems(items, PageDataHandler: pageDataHandler, PreloadPages: preloadPages);
  470. }
  471. /// <summary>
  472. /// Edit (using <see cref="DynamicItemsListGrid{T}"/>) a <see cref="BaseObject"/>. Use for objects not saved in the database.
  473. /// </summary>
  474. /// <typeparam name="T"></typeparam>
  475. /// <param name="items"></param>
  476. /// <param name="pageDataHandler"></param>
  477. /// <param name="preloadPages"></param>
  478. /// <returns></returns>
  479. public static bool EditObject<T>(T item, Func<Type, CoreTable?>? pageDataHandler = null, bool preloadPages = false, Action<DynamicGrid<T>>? customiseGrid = null)
  480. where T : BaseObject, new()
  481. {
  482. var grid = new DynamicGrid<T>();
  483. customiseGrid?.Invoke(grid);
  484. return grid.EditItems(new T[] { item }, PageDataHandler: pageDataHandler, PreloadPages: preloadPages);
  485. }
  486. /// <summary>
  487. /// Edit (using a grid sourced with <see cref="CreateDynamicGrid{T}(Type)"/>) a <typeparamref name="T"/>.
  488. /// </summary>
  489. /// <typeparam name="T"></typeparam>
  490. /// <param name="item"></param>
  491. /// <param name="pageDataHandler"></param>
  492. /// <param name="preloadPages"></param>
  493. /// <param name="customiseGrid"></param>
  494. /// <returns></returns>
  495. public static bool EditEntity<T>(T item, Func<Type, CoreTable?>? pageDataHandler = null, bool preloadPages = false, Action<DynamicGrid<T>>? customiseGrid = null)
  496. where T : Entity, new()
  497. {
  498. var grid = CreateDynamicGrid<T>(typeof(DynamicGrid<>));
  499. customiseGrid?.Invoke(grid);
  500. return grid.EditItems(new T[] { item }, PageDataHandler: pageDataHandler, PreloadPages: preloadPages);
  501. }
  502. /// <summary>
  503. /// Edit (using a grid sourced with <see cref="CreateDynamicGrid{T}(Type)"/>) a list of <typeparamref name="T"/>.
  504. /// </summary>
  505. /// <typeparam name="T"></typeparam>
  506. /// <param name="item"></param>
  507. /// <param name="pageDataHandler"></param>
  508. /// <param name="preloadPages"></param>
  509. /// <param name="customiseGrid"></param>
  510. /// <returns></returns>
  511. public static bool EditEntities<T>(T[] items, Func<Type, CoreTable?>? pageDataHandler = null, bool preloadPages = false, Action<DynamicGrid<T>>? customiseGrid = null)
  512. where T : Entity, new()
  513. {
  514. var grid = CreateDynamicGrid<T>(typeof(DynamicGrid<>));
  515. customiseGrid?.Invoke(grid);
  516. return grid.EditItems(items, PageDataHandler: pageDataHandler, PreloadPages: preloadPages);
  517. }
  518. #endregion
  519. #region Drag + Drop
  520. public static string DragFormat => typeof(DynamicGridDragFormat).FullName ?? "";
  521. /// <summary>
  522. /// Try to get data dragged from a <see cref="DynamicGrid{T}"/> from a <see cref="DragEventArgs"/>, returning <see langword="true"/>
  523. /// if data was present.
  524. /// </summary>
  525. /// <param name="e"></param>
  526. public static bool TryGetDropData(
  527. DragEventArgs e,
  528. [NotNullWhen(true)] out Type? type,
  529. [NotNullWhen(true)] out CoreTable? table)
  530. {
  531. if (e.Data.GetDataPresent(DragFormat))
  532. {
  533. var data = e.Data.GetData(DragFormat) as DynamicGridDragFormat;
  534. if (data is not null)
  535. {
  536. table = new CoreTable();
  537. foreach (var column in data.Table.Columns)
  538. {
  539. if (column is DataColumn dataColumn)
  540. {
  541. table.Columns.Add(new CoreColumn { ColumnName = dataColumn.ColumnName.Replace('_', '.'), DataType = dataColumn.DataType });
  542. }
  543. }
  544. foreach (var row in data.Table.Rows)
  545. {
  546. if (row is DataRow dataRow)
  547. {
  548. var coreRow = table.NewRow();
  549. coreRow.LoadValues(dataRow.ItemArray);
  550. table.Rows.Add(coreRow);
  551. }
  552. }
  553. type = data.Entity;
  554. return true;
  555. }
  556. }
  557. table = null;
  558. type = null;
  559. return false;
  560. }
  561. #endregion
  562. #region Style
  563. public static Brush SelectionBackground { get; set; } = new SolidColorBrush(Colors.Black);
  564. public static Brush SelectionForeground { get; set; } = new SolidColorBrush(Colors.Silver);
  565. public static Brush FilterBackground { get; set; } = new SolidColorBrush(System.Windows.Media.Color.FromArgb(0xFF, 0xE9, 0xF7, 0xC9));
  566. #endregion
  567. public static void PopulateFormMenu<TEntityForm, TEntity, TEntityLink>(
  568. ItemsControl menu,
  569. Guid entityID,
  570. Func<TEntity> loadEntity,
  571. bool editOnAdd = false,
  572. DynamicFormEditWindow.CustomiseDynamicFormEditWindow? customiseEditor = null)
  573. where TEntityForm : EntityForm<TEntity, TEntityLink, TEntityForm>, new()
  574. where TEntity : Entity
  575. where TEntityLink : IEntityLink<TEntity>, new()
  576. {
  577. var task = Task.Run(() =>
  578. {
  579. return new Client<TEntityForm>().Query(
  580. new Filter<TEntityForm>(x => x.Parent.ID).IsEqualTo(entityID),
  581. null).Rows.Select(x => x.ToObject<TEntityForm>()).ToList();
  582. });
  583. var addForm = new MenuItem { Header = "Add Form" };
  584. addForm.Click += (o, e) =>
  585. {
  586. var entity = loadEntity();
  587. var filter = LookupFactory.DefineChildFilter<TEntity, DigitalForm>(new TEntity[] { entity })
  588. ?? LookupFactory.DefineLookupFilter<TEntityForm, DigitalForm, DigitalFormLink>(x => x.Form, Array.Empty<TEntityForm>());
  589. var select = new MultiSelectDialog<DigitalForm>(
  590. filter,
  591. LookupFactory.DefineLookupColumns<TEntityForm, DigitalForm, DigitalFormLink>(x => x.Form).Add(x => x.Description),
  592. false);
  593. if(select.ShowDialog() == true)
  594. {
  595. var digitalForm = select.Data().Rows.FirstOrDefault()?.ToObject<DigitalForm>();
  596. if(digitalForm is not null)
  597. {
  598. var form = new TEntityForm
  599. {
  600. Description = digitalForm.Description
  601. };
  602. form.Parent.ID = entityID;
  603. form.Form.ID = digitalForm.ID;
  604. if (editOnAdd)
  605. {
  606. if (DynamicFormEditWindow.EditDigitalForm(form, out var dataModel, customise: customiseEditor))
  607. {
  608. dataModel.Update(null);
  609. }
  610. }
  611. else
  612. {
  613. new Client<TEntityForm>().Save(form, "Added by user");
  614. }
  615. }
  616. };
  617. };
  618. var manageForms = new MenuItem { Header = "Manage Forms..." };
  619. manageForms.Click += (o, e) =>
  620. {
  621. var window = new ThemableWindow() { Title = $"Manage {typeof(TEntity).Name} Forms" };
  622. var grid = new DynamicEntityFormGrid<TEntityForm, TEntity, TEntityLink>(loadEntity());
  623. grid.Refresh(true, true);
  624. grid.Margin = new Thickness(5);
  625. window.Content = grid;
  626. window.ShowDialog();
  627. };
  628. menu.Items.Add(addForm);
  629. menu.Items.Add(new Separator());
  630. menu.Items.Add(new MenuItem() { Header = "Loading...", IsEnabled = false });
  631. menu.Items.Add(new Separator());
  632. menu.Items.Add(manageForms);
  633. task.ContinueWith((task) =>
  634. {
  635. var entityForms = task.Result;
  636. menu.Items.Clear();
  637. menu.Items.Add(addForm);
  638. menu.Items.Add(new Separator());
  639. if (entityForms.Any())
  640. {
  641. foreach (var entityForm in entityForms)
  642. {
  643. var description = entityForm.Description;
  644. if (string.IsNullOrWhiteSpace(description))
  645. {
  646. description = entityForm.Form.Description;
  647. }
  648. var formItem = new MenuItem { Header = $"{entityForm.Number} : {description}" };
  649. formItem.Click += (o, e) =>
  650. {
  651. if (DynamicFormEditWindow.EditDigitalForm(entityForm, out var dataModel, customise: customiseEditor))
  652. {
  653. dataModel.Update(null);
  654. }
  655. };
  656. menu.Items.Add(formItem);
  657. }
  658. }
  659. else
  660. {
  661. menu.Items.Add(new MenuItem() { Header = "No Forms", IsEnabled = false });
  662. }
  663. menu.Items.Add(new Separator());
  664. menu.Items.Add(manageForms);
  665. }, TaskScheduler.FromCurrentSynchronizationContext());
  666. }
  667. public static bool DuplicateEntity<TEntity>(
  668. CoreRow row,
  669. Expression<Func<TEntity, object?>> codefield,
  670. Type[] childtypes)
  671. where TEntity : Entity, IRemotable, IPersistent, new()
  672. {
  673. var id = row.Get<TEntity, Guid>(x => x.ID);
  674. var code = row.Get(codefield) as string;
  675. var tasks = new List<Task>();
  676. var itemtask = Task.Run(() =>
  677. {
  678. var filter = new Filter<TEntity>(x => x.ID).IsEqualTo(id);
  679. var result = new Client<TEntity>().Load(filter).FirstOrDefault()
  680. ?? throw new Exception("Entity does not exist!");
  681. return result;
  682. });
  683. tasks.Add(itemtask);
  684. //itemtask.Wait();
  685. Task<List<string?>>? codetask = null;
  686. if (!string.IsNullOrWhiteSpace(code))
  687. {
  688. codetask = Task.Run(() =>
  689. {
  690. var columns = Columns.None<TEntity>().Add(codefield);
  691. //columns.Add<String>(codefield);
  692. var filter = new Filter<TEntity>(codefield).BeginsWith(code);
  693. var table = new Client<TEntity>().Query(filter, columns);
  694. var result = table.Rows.Select(x => x.Get(codefield) as string).ToList();
  695. return result;
  696. });
  697. tasks.Add(codetask);
  698. }
  699. //codetask.Wait();
  700. var children = new Dictionary<Type, CoreTable>();
  701. foreach (var childtype in childtypes)
  702. {
  703. var childtask = Task.Run(() =>
  704. {
  705. var prop = childtype.GetProperties().FirstOrDefault(x =>
  706. x.PropertyType.GetInterfaces().Contains(typeof(IEntityLink)) &&
  707. x.PropertyType.GetInheritedGenericTypeArguments().FirstOrDefault() == typeof(TEntity));
  708. if (prop is not null)
  709. {
  710. var filter = Core.Filter.Create(childtype);
  711. filter.Expression = CoreUtils.GetMemberExpression(childtype, prop.Name + ".ID");
  712. filter.Operator = Operator.IsEqualTo;
  713. filter.Value = id;
  714. var sort = LookupFactory.DefineSort(childtype);
  715. var client = ClientFactory.CreateClient(childtype);
  716. var table = client.Query(filter, null, sort);
  717. foreach (var r in table.Rows)
  718. {
  719. r["ID"] = Guid.Empty;
  720. r[prop.Name + ".ID"] = Guid.Empty;
  721. }
  722. children[childtype] = table;
  723. }
  724. else
  725. {
  726. Logger.Send(LogType.Error, "", $"DynamicGridUtils.DuplicateEntity<{typeof(TEntity)}>(): No parent property found for child type {childtype}");
  727. }
  728. });
  729. tasks.Add(childtask);
  730. //childtask.Wait();
  731. }
  732. Task.WaitAll(tasks.ToArray());
  733. var item = itemtask.Result;
  734. item.ID = Guid.Empty;
  735. if (codetask != null)
  736. {
  737. var codes = codetask.Result;
  738. var i = 1;
  739. while (codes.Contains(string.Format("{0} ({1})", code, i)))
  740. i++;
  741. var codeprop = CoreUtils.GetFullPropertyName(codefield, ".");
  742. CoreUtils.SetPropertyValue(item, codeprop, string.Format("{0} ({1})", code, i));
  743. }
  744. var grid = new DynamicDataGrid<TEntity>();
  745. return grid.EditItems(new[] { item }, t => children.ContainsKey(t) ? children[t] : null, true);
  746. }
  747. public static bool MergeEntities<T>(T target, T[]? entities)
  748. where T : Entity, IRemotable, IPersistent, new()
  749. {
  750. if (entities == null || entities.Length == 0)
  751. return false;
  752. if (!MessageWindow.ShowYesNo(
  753. string.Format(
  754. "This will merge the following items:\n\n- {0}\n\n into:\n\n- {1}\n\nAfter this, the items will be permanently removed.\nAre you sure you wish to do this?",
  755. string.Join("\n- ", entities.Select(x => x.ToString())),
  756. target
  757. ),
  758. "Merge Items Warning"))
  759. return false;
  760. using (new WaitCursor())
  761. {
  762. var types = CoreUtils.Entities.Where(
  763. x =>
  764. x.IsClass
  765. && !x.IsGenericType
  766. && x.IsSubclassOf(typeof(Entity))
  767. && !x.Equals(typeof(AuditTrail))
  768. && !x.Equals(typeof(T))
  769. && x.GetCustomAttribute<AutoEntity>() == null
  770. && x.HasInterface<IRemotable>()
  771. && x.HasInterface<IPersistent>()
  772. ).ToArray();
  773. var otherIDs = entities.ToArray(x => x.ID);
  774. foreach (var type in types)
  775. {
  776. var props = CoreUtils.PropertyList(
  777. type,
  778. x =>
  779. x.PropertyType.GetInterfaces().Contains(typeof(IEntityLink))
  780. && x.PropertyType.GetInheritedGenericTypeArguments().Contains(typeof(T))
  781. );
  782. foreach (var prop in props)
  783. {
  784. var propname = string.Format(prop.Name + ".ID");
  785. var filter = Filter.Create(type, propname).InList(otherIDs);
  786. var columns = Columns.None(type)
  787. .Add("ID")
  788. .Add(propname);
  789. var updates = ClientFactory.CreateClient(type).Query(filter, columns).Rows.Select(r => r.ToObject(type)).ToArray();
  790. if (updates.Any())
  791. {
  792. foreach (var update in updates)
  793. CoreUtils.SetPropertyValue(update, propname, target.ID);
  794. ClientFactory.CreateClient(type).Save(updates,
  795. string.Format("Merged {0} Records", typeof(T).EntityName().Split('.').Last()));
  796. }
  797. }
  798. }
  799. var histories = new Client<AuditTrail>()
  800. .Query(
  801. new Filter<AuditTrail>(x => x.EntityID).InList(otherIDs),
  802. Columns.None<AuditTrail>()
  803. .Add(x => x.ID).Add(x => x.EntityID))
  804. .ToArray<AuditTrail>();
  805. foreach (var history in histories)
  806. history.EntityID = target.ID;
  807. if (histories.Length != 0)
  808. new Client<AuditTrail>().Save(histories, "");
  809. Client.Delete(entities, string.Format("Merged {0} Records", typeof(T).EntityName().Split('.').Last()));
  810. return true;
  811. }
  812. }
  813. }