DynamicDataGrid.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics.CodeAnalysis;
  4. using System.Drawing;
  5. using System.Linq;
  6. using System.Linq.Expressions;
  7. using System.Reflection;
  8. using System.Threading;
  9. using System.Threading.Tasks;
  10. using System.Windows;
  11. using System.Windows.Controls;
  12. using InABox.Clients;
  13. using InABox.Configuration;
  14. using InABox.Core;
  15. using InABox.WPF;
  16. using Expression = System.Linq.Expressions.Expression;
  17. namespace InABox.DynamicGrid;
  18. public interface IDynamicDataGrid : IDynamicGrid
  19. {
  20. /// <summary>
  21. /// The tag the the DynamicGridColumns are stored against. If set to <see langword="null"/>,
  22. /// the name of <typeparamref name="TEntity"/> is used as a default.
  23. /// </summary>
  24. string? ColumnsTag { get; set; }
  25. IColumns LoadEditorColumns();
  26. }
  27. public class DynamicDataGrid<TEntity> : DynamicGrid<TEntity>, IDynamicDataGrid where TEntity : Entity, IRemotable, IPersistent, new()
  28. {
  29. private Button MergeBtn = null!; //Late-initialised
  30. protected DynamicGridCustomColumnsComponent<TEntity> ColumnsComponent;
  31. public DynamicGridFilterButtonComponent<TEntity> FilterComponent;
  32. public DynamicDataGrid() : base()
  33. {
  34. var fields = DatabaseSchema.Properties(typeof(TEntity));
  35. foreach (var field in fields)
  36. if (!MasterColumns.Any(x => x.ColumnName == field.Name))
  37. MasterColumns.Add(new DynamicGridColumn { ColumnName = field.Name });
  38. var cols = LookupFactory.DefineColumns<TEntity>();
  39. // Minimum Columns for Lookup values
  40. foreach (var col in cols)
  41. HiddenColumns.Add(CoreUtils.CreateLambdaExpression<TEntity>(col.Property));
  42. // Minimum Columns for Successful Saving
  43. // This should be cross-checked with the relevant Store<>
  44. // so that clients will (usually) provide sufficient columns for saving
  45. foreach (var col in LookupFactory.RequiredColumns<TEntity>())
  46. HiddenColumns.Add(CoreUtils.CreateLambdaExpression<TEntity>(col.Property));
  47. //HiddenColumns.Add(x => x.ID);
  48. if (typeof(TEntity).GetInterfaces().Contains(typeof(IIssues)))
  49. {
  50. HiddenColumns.Add(x => (x as IIssues)!.Issues);
  51. var coltype = typeof(DynamicIssuesColumn<>).MakeGenericType(typeof(TEntity));
  52. ActionColumns.Add((Activator.CreateInstance(coltype, this) as DynamicActionColumn)!);
  53. }
  54. }
  55. protected override void Init()
  56. {
  57. FilterComponent = new(this,
  58. new GlobalConfiguration<CoreFilterDefinitions>(GetTag()),
  59. new UserConfiguration<CoreFilterDefinitions>(GetTag()));
  60. FilterComponent.OnFilterRefresh += () => Refresh(false, true);
  61. ColumnsComponent = new DynamicGridCustomColumnsComponent<TEntity>(this, GetTag());
  62. var dataComponent = new DynamicGridClientDataComponent<TEntity>(this);
  63. dataComponent.OnReload += DataComponent_OnReload;
  64. DataComponent = dataComponent;
  65. MergeBtn = AddButton("Merge", Wpf.Resources.merge.AsBitmapImage(Color.White), DoMerge);
  66. }
  67. protected override void SelectItems(CoreRow[]? rows)
  68. {
  69. base.SelectItems(rows);
  70. MergeBtn.Visibility = Options.MultiSelect && typeof(T).IsAssignableTo(typeof(IMergeable)) && Security.CanMerge<TEntity>()
  71. && rows != null && rows.Length > 1
  72. ? Visibility.Visible
  73. : Visibility.Collapsed;
  74. }
  75. private void DataComponent_OnReload(object sender, Filters<TEntity> criteria, Columns<TEntity> columns, ref SortOrder<TEntity>? sortby)
  76. {
  77. criteria.Add(FilterComponent.GetFilter());
  78. }
  79. protected override void OptionsChanged()
  80. {
  81. base.OptionsChanged();
  82. FilterComponent.ShowFilterList = Options.FilterRows && !Options.HideDatabaseFilters;
  83. if (MergeBtn != null)
  84. MergeBtn.Visibility = Visibility.Collapsed;
  85. }
  86. protected override void DoReconfigure(DynamicGridOptions options)
  87. {
  88. if (Security.CanEdit<TEntity>())
  89. {
  90. options.AddRows = true;
  91. options.EditRows = true;
  92. }
  93. if (Security.CanDelete<TEntity>())
  94. options.DeleteRows = true;
  95. if (Security.CanImport<TEntity>() && typeof(TEntity).HasInterface<IImportable>())
  96. options.ImportData = true;
  97. if (Security.CanExport<TEntity>() && typeof(TEntity).HasInterface<IExportable>())
  98. options.ExportData = true;
  99. if (Security.CanMerge<TEntity>())
  100. options.MultiSelect = true;
  101. }
  102. protected override void BeforeLoad(IDynamicEditorForm form, TEntity[] items)
  103. {
  104. form.ReadOnly = form.ReadOnly || !Security.CanEdit<TEntity>();
  105. base.BeforeLoad(form, items);
  106. }
  107. private string? _columnsTag;
  108. public string? ColumnsTag
  109. {
  110. get => _columnsTag;
  111. set
  112. {
  113. _columnsTag = value;
  114. ColumnsComponent.Tag = GetTag();
  115. }
  116. }
  117. public override void LoadEditorButtons(TEntity item, DynamicEditorButtons buttons)
  118. {
  119. base.LoadEditorButtons(item, buttons);
  120. if (ClientFactory.IsSupported<AuditTrail>())
  121. buttons.Add("Audit Trail", Wpf.Resources.view.AsBitmapImage(), item, AuditTrailClick);
  122. }
  123. private void AuditTrailClick(object sender, object? item)
  124. {
  125. var entity = (item as TEntity)!;
  126. var window = new AuditWindow(entity.ID);
  127. window.ShowDialog();
  128. }
  129. protected override DynamicGridColumns LoadColumns()
  130. {
  131. return ColumnsComponent.LoadColumns();
  132. }
  133. protected override void SaveColumns(DynamicGridColumns columns)
  134. {
  135. ColumnsComponent.SaveColumns(columns);
  136. }
  137. protected override void LoadColumnsMenu(ContextMenu menu)
  138. {
  139. base.LoadColumnsMenu(menu);
  140. ColumnsComponent.LoadColumnsMenu(menu);
  141. }
  142. private string GetTag()
  143. {
  144. var tag = typeof(TEntity).Name;
  145. if (!string.IsNullOrWhiteSpace(ColumnsTag))
  146. tag = string.Format("{0}.{1}", tag, ColumnsTag);
  147. return tag;
  148. }
  149. protected override DynamicGridSettings LoadSettings()
  150. {
  151. var tag = GetTag();
  152. var user = Task.Run(() => new UserConfiguration<DynamicGridSettings>(tag).Load());
  153. user.Wait();
  154. //var global = Task.Run(() => new GlobalConfiguration<DynamicGridSettings>(tag).Load());
  155. //global.Wait();
  156. //Task.WaitAll(user, global);
  157. //var columns = user.Result.Any() ? user.Result : global.Result;
  158. return user.Result;
  159. }
  160. protected override void SaveSettings(DynamicGridSettings settings)
  161. {
  162. var tag = GetTag();
  163. new UserConfiguration<DynamicGridSettings>(tag).Save(settings);
  164. }
  165. #region Duplicate
  166. protected bool Duplicate(
  167. CoreRow row,
  168. Expression<Func<TEntity, object?>> codefield,
  169. Type[] childtypes)
  170. {
  171. var id = row.Get<TEntity, Guid>(x => x.ID);
  172. var code = row.Get(codefield) as string;
  173. var tasks = new List<Task>();
  174. var itemtask = Task.Run(() =>
  175. {
  176. var filter = new Filter<TEntity>(x => x.ID).IsEqualTo(id);
  177. var result = new Client<TEntity>().Load(filter).FirstOrDefault()
  178. ?? throw new Exception("Entity does not exist!");
  179. return result;
  180. });
  181. tasks.Add(itemtask);
  182. //itemtask.Wait();
  183. Task<List<string?>>? codetask = null;
  184. if (!string.IsNullOrWhiteSpace(code))
  185. {
  186. codetask = Task.Run(() =>
  187. {
  188. var columns = Columns.None<TEntity>().Add(codefield);
  189. //columns.Add<String>(codefield);
  190. var filter = new Filter<TEntity>(codefield).BeginsWith(code);
  191. var table = new Client<TEntity>().Query(filter, columns);
  192. var result = table.Rows.Select(x => x.Get(codefield) as string).ToList();
  193. return result;
  194. });
  195. tasks.Add(codetask);
  196. }
  197. //codetask.Wait();
  198. var children = new Dictionary<Type, CoreTable>();
  199. foreach (var childtype in childtypes)
  200. {
  201. var childtask = Task.Run(() =>
  202. {
  203. var prop = childtype.GetProperties().FirstOrDefault(x =>
  204. x.PropertyType.GetInterfaces().Contains(typeof(IEntityLink)) &&
  205. x.PropertyType.GetInheritedGenericTypeArguments().FirstOrDefault() == typeof(TEntity));
  206. if (prop is not null)
  207. {
  208. var filter = Core.Filter.Create(childtype);
  209. filter.Expression = CoreUtils.GetMemberExpression(childtype, prop.Name + ".ID");
  210. filter.Operator = Operator.IsEqualTo;
  211. filter.Value = id;
  212. var sort = LookupFactory.DefineSort(childtype);
  213. var client = ClientFactory.CreateClient(childtype);
  214. var table = client.Query(filter, null, sort);
  215. foreach (var r in table.Rows)
  216. {
  217. r["ID"] = Guid.Empty;
  218. r[prop.Name + ".ID"] = Guid.Empty;
  219. }
  220. children[childtype] = table;
  221. }
  222. else
  223. {
  224. Logger.Send(LogType.Error, "", $"DynamicDataGrid<{typeof(TEntity)}>.Duplicate(): No parent property found for child type {childtype}");
  225. }
  226. });
  227. tasks.Add(childtask);
  228. //childtask.Wait();
  229. }
  230. //var manytomanys = CoreUtils.TypeList(
  231. // AppDomain.CurrentDomain.GetAssemblies(),
  232. // x => x.GetInterfaces().Any(intf => intf.IsGenericType && intf.GetGenericTypeDefinition() == typeof(IManyToMany<,>) && intf.GenericTypeArguments.Contains(typeof(T)))
  233. //);
  234. //Task<CoreTable> childtask = Task.Run(() =>
  235. //{
  236. // var result = new CoreTable();
  237. // result.LoadColumns(typeof(TChild));
  238. // var children = new Client<TChild>().Load(new Filter<TChild>(x => linkfield).IsEqualTo(id));
  239. // foreach (var child in children)
  240. // {
  241. // child.ID = Guid.Empty;
  242. // String linkprop = CoreUtils.GetFullPropertyName<TChild, Guid>(linkfield, ".");
  243. // CoreUtils.SetPropertyValue(child, linkprop, Guid.Empty);
  244. // var newrow = result.NewRow();
  245. // result.LoadRow(newrow, child);
  246. // result.Rows.Add(newrow);
  247. // }
  248. // return result;
  249. //});
  250. //tasks.Add(childtask);
  251. Task.WaitAll(tasks.ToArray());
  252. var item = itemtask.Result;
  253. item.ID = Guid.Empty;
  254. if (codetask != null)
  255. {
  256. var codes = codetask.Result;
  257. var i = 1;
  258. while (codes.Contains(string.Format("{0} ({1})", code, i)))
  259. i++;
  260. var codeprop = CoreUtils.GetFullPropertyName(codefield, ".");
  261. CoreUtils.SetPropertyValue(item, codeprop, string.Format("{0} ({1})", code, i));
  262. }
  263. var grid = new DynamicDataGrid<TEntity>();
  264. return grid.EditItems(new[] { item }, t => children.ContainsKey(t) ? children[t] : null, true);
  265. }
  266. protected override IEnumerable<TEntity> LoadDuplicatorItems(CoreRow[] rows)
  267. {
  268. return rows.Select(x => x.ToObject<TEntity>());
  269. }
  270. #endregion
  271. protected override bool BeforePaste(IEnumerable<TEntity> items, ClipAction action)
  272. {
  273. if (action == ClipAction.Copy)
  274. {
  275. foreach (var item in items)
  276. item.ID = Guid.Empty;
  277. return true;
  278. }
  279. return base.BeforePaste(items, action);
  280. }
  281. protected override IEnumerable<Tuple<Type?, CoreTable>> LoadExportTables(Filters<TEntity> filter, IEnumerable<Tuple<Type, IColumns>> tableColumns)
  282. {
  283. var queries = new Dictionary<string, IQueryDef>();
  284. var columns = tableColumns.ToList();
  285. foreach (var table in columns)
  286. {
  287. var tableType = table.Item1;
  288. PropertyInfo? property = null;
  289. var m2m = CoreUtils.GetManyToMany(tableType, typeof(TEntity));
  290. IFilter? queryFilter = null;
  291. if (m2m != null)
  292. {
  293. property = CoreUtils.GetManyToManyThisProperty(tableType, typeof(TEntity));
  294. }
  295. else
  296. {
  297. var o2m = CoreUtils.GetOneToMany(tableType, typeof(TEntity));
  298. if (o2m != null)
  299. {
  300. property = CoreUtils.GetOneToManyProperty(tableType, typeof(TEntity));
  301. }
  302. }
  303. if (property != null)
  304. {
  305. var subQuery = new SubQuery<TEntity>();
  306. subQuery.Filter = filter.Combine();
  307. subQuery.Column = new Column<TEntity>(x => x.ID);
  308. queryFilter = (Activator.CreateInstance(typeof(Filter<>).MakeGenericType(tableType)) as IFilter)!;
  309. queryFilter.Expression = CoreUtils.GetMemberExpression(tableType, property.Name + ".ID");
  310. queryFilter.InQuery(subQuery);
  311. queries[tableType.Name] = new QueryDef(tableType)
  312. {
  313. Filter = queryFilter,
  314. Columns = table.Item2
  315. };
  316. }
  317. }
  318. var results = Client.QueryMultiple(queries);
  319. return columns.Select(x => new Tuple<Type?, CoreTable>(x.Item1, results[x.Item1.Name]));
  320. }
  321. protected override CoreTable LoadImportKeys(String[] fields)
  322. {
  323. return Client.Query(null, Columns.None<TEntity>().Add(fields));
  324. }
  325. #region Merge
  326. private bool DoMerge(Button arg1, CoreRow[] arg2)
  327. {
  328. if (arg2 == null || arg2.Length <= 1)
  329. return false;
  330. var targetid = arg2.Last().Get<T, Guid>(x => x.ID);
  331. var target = arg2.Last().ToObject<T>().ToString();
  332. var otherids = arg2.Select(r => r.Get<T, Guid>(x => x.ID)).Where(x => x != targetid).ToArray();
  333. string[] others = arg2.Where(r => otherids.Contains(r.Get<Guid>("ID"))).Select(x => x.ToObject<T>().ToString()!).ToArray();
  334. var rows = arg2.Length;
  335. if (MessageBox.Show(
  336. string.Format(
  337. "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?",
  338. string.Join("\n- ", others),
  339. target
  340. ),
  341. "Merge Items Warning",
  342. MessageBoxButton.YesNo,
  343. MessageBoxImage.Stop) != MessageBoxResult.Yes
  344. )
  345. return false;
  346. using (new WaitCursor())
  347. {
  348. var types = CoreUtils.Entities.Where(
  349. x =>
  350. x.IsClass
  351. && !x.IsGenericType
  352. && x.IsSubclassOf(typeof(Entity))
  353. && !x.Equals(typeof(AuditTrail))
  354. && !x.Equals(typeof(T))
  355. && x.GetCustomAttribute<AutoEntity>() == null
  356. && x.HasInterface<IRemotable>()
  357. && x.HasInterface<IPersistent>()
  358. ).ToArray();
  359. foreach (var type in types)
  360. {
  361. var props = CoreUtils.PropertyList(
  362. type,
  363. x =>
  364. x.PropertyType.GetInterfaces().Contains(typeof(IEntityLink))
  365. && x.PropertyType.GetInheritedGenericTypeArguments().Contains(typeof(T))
  366. );
  367. foreach (var prop in props)
  368. {
  369. var propname = string.Format(prop.Name + ".ID");
  370. var filter = Core.Filter.Create(type);
  371. filter.Expression = CoreUtils.CreateMemberExpression(type, propname);
  372. filter.Operator = Operator.InList;
  373. filter.Value = otherids;
  374. var columns = Columns.None(type)
  375. .Add("ID")
  376. .Add(propname);
  377. var updates = ClientFactory.CreateClient(type).Query(filter, columns).Rows.Select(r => r.ToObject(type)).ToArray();
  378. if (updates.Any())
  379. {
  380. foreach (var update in updates)
  381. CoreUtils.SetPropertyValue(update, propname, targetid);
  382. ClientFactory.CreateClient(type).Save(updates,
  383. string.Format("Merged {0} Records", typeof(T).EntityName().Split('.').Last()));
  384. }
  385. }
  386. }
  387. var histories = new Client<AuditTrail>()
  388. .Query(
  389. new Filter<AuditTrail>(x => x.EntityID).InList(otherids),
  390. Columns.None<AuditTrail>()
  391. .Add(x => x.ID).Add(x => x.EntityID))
  392. .ToArray<AuditTrail>();
  393. foreach (var history in histories)
  394. history.EntityID = targetid;
  395. if (histories.Length != 0)
  396. new Client<AuditTrail>().Save(histories, "");
  397. var deletes = new List<object>();
  398. foreach (var otherid in otherids)
  399. {
  400. var delete = new T();
  401. CoreUtils.SetPropertyValue(delete, "ID", otherid);
  402. deletes.Add(delete);
  403. }
  404. ClientFactory.CreateClient(typeof(T))
  405. .Delete(deletes, string.Format("Merged {0} Records", typeof(T).EntityName().Split('.').Last()));
  406. return true;
  407. }
  408. }
  409. #endregion
  410. }