DynamicGridUtils.cs 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607
  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.Reports.Common;
  14. using Syncfusion.Data.Extensions;
  15. namespace InABox.DynamicGrid
  16. {
  17. [Caption("Set Default Column Selections")]
  18. public class CanSetDefaultColumns : EnabledSecurityDescriptor<CoreLicense>
  19. {
  20. }
  21. [LibraryInitializer]
  22. public static class DynamicGridUtils
  23. {
  24. private static IEnumerable<Type>? _allm2mtypes;
  25. private static IEnumerable<Type>? _allm2mpages;
  26. private static IEnumerable<Type>? _allo2mtypes;
  27. private static IEnumerable<Type>? _allo2mpages;
  28. private static IEnumerable<Type>? _allcepages;
  29. private static IEnumerable<Type>? _alleltypes;
  30. private static Dictionary<Type, IEnumerable<IDynamicEditorPage>> _onetomanypages = new Dictionary<Type, IEnumerable<IDynamicEditorPage>>();
  31. private static Dictionary<Type, IEnumerable<IDynamicEditorPage>> _manytomanytomanypages = new Dictionary<Type, IEnumerable<IDynamicEditorPage>>();
  32. private static Dictionary<Type, IEnumerable<IDynamicEditorPage>> _enclosedlistpages = new Dictionary<Type, IEnumerable<IDynamicEditorPage>>();
  33. private static Dictionary<Type, IEnumerable<IDynamicEditorPage>> _customeditorpages = new Dictionary<Type, IEnumerable<IDynamicEditorPage>>();
  34. // HACK: These are really dumb
  35. public static Action<ReportTemplate, DataModel>? PreviewReport { get; set; }
  36. public static Action<FrameworkElement?, string, DataModel, bool>? PrintMenu { get; set; }
  37. public static readonly MainResources Resources = new();
  38. public static void RegisterClasses()
  39. {
  40. // String assyname = "_" + Assembly.GetExecutingAssembly().GetName().Name;
  41. // AssemblyName assemblyName = new AssemblyName(assyname);
  42. // AppDomain appDomain = Thread.GetDomain();
  43. //
  44. // String assyFile = String.Format("{0}.dll", assemblyName.Name);
  45. // String path = "";
  46. // if (Assembly.GetEntryAssembly() != null)
  47. // {
  48. // path = Path.Combine(
  49. // Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
  50. // Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().Location)
  51. // );
  52. // }
  53. // else
  54. // {
  55. // path = Path.Combine(
  56. // Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
  57. // Path.GetFileNameWithoutExtension(Assembly.GetExecutingAssembly().Location)
  58. // );
  59. // }
  60. //
  61. // if (!Directory.Exists(path))
  62. // Directory.CreateDirectory(path);
  63. // AssemblyBuilder assemblyBuilder = appDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave, path);
  64. //
  65. // ModuleBuilder module = assemblyBuilder.DefineDynamicModule(assyFile); //,true);
  66. //
  67. // if (_allm2mtypes == null)
  68. // {
  69. // _allm2mtypes = CoreUtils.TypeList(
  70. // AppDomain.CurrentDomain.GetAssemblies(),
  71. // x => x.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IManyToMany<,>))
  72. // );
  73. // }
  74. //
  75. // var maps = _allm2mtypes.Where(x => x.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IManyToMany<,>) && i.GenericTypeArguments.Last().Equals(typeof(Document))));
  76. //
  77. // foreach (var map in maps)
  78. // {
  79. // var intf = map.GetInterfaces().FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IManyToMany<,>) && i.GenericTypeArguments.Last().Equals(typeof(Document)));
  80. // Type entity = intf.GenericTypeArguments.First();
  81. // Type basetype = typeof(DynamicDocumentGrid<,>).MakeGenericType(map, entity);
  82. // TypeBuilder tbService = module.DefineType(String.Format("{0}", map.EntityName().Replace(".", "_")), TypeAttributes.Public | TypeAttributes.Class);
  83. // tbService.SetParent(basetype);
  84. // Type final = tbService.CreateType();
  85. // }
  86. //
  87. // try
  88. // {
  89. // assemblyBuilder.Save(assyFile);
  90. // }
  91. // catch (Exception e)
  92. // {
  93. // Logger.Send(LogType.Error, "", String.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
  94. // }
  95. }
  96. #region Pages
  97. public static IEnumerable<Type> GetManyToManyTypes(Type type)
  98. {
  99. _allm2mtypes ??= CoreUtils.TypeList(
  100. AppDomain.CurrentDomain.GetAssemblies(),
  101. x => x.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IManyToMany<,>))
  102. );
  103. return _allm2mtypes.Where(x => x.GetInterfaces().Any(
  104. i => i.IsGenericType
  105. && i.GetGenericTypeDefinition() == typeof(IManyToMany<,>)
  106. && i.GenericTypeArguments[0] == type)
  107. );
  108. }
  109. public static void LoadManyToManyPages(Type type, DynamicEditorPages pages)
  110. {
  111. if (!_manytomanytomanypages.ContainsKey(type))
  112. {
  113. var cache = new List<IDynamicEditorPage>();
  114. var maps = GetManyToManyTypes(type);
  115. foreach (var map in maps)
  116. {
  117. if (ClientFactory.IsSupported(map))
  118. {
  119. _allm2mpages ??= CoreUtils.TypeList(
  120. AppDomain.CurrentDomain.GetAssemblies(),
  121. x => x.IsClass
  122. && !x.IsAbstract
  123. && !x.IsGenericType
  124. && x.GetInterfaces().Any(
  125. i => i.IsGenericType
  126. && i.GetGenericTypeDefinition() == typeof(IDynamicManyToManyGrid<,>)
  127. )
  128. );
  129. var subtypes = _allm2mpages.Where(
  130. x => x.GetInterfaces().Any(i =>
  131. i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDynamicManyToManyGrid<,>) &&
  132. i.GenericTypeArguments.First().Equals(map) && i.GenericTypeArguments.Last().Equals(type))
  133. );
  134. IDynamicEditorPage page;
  135. if (subtypes.Any())
  136. {
  137. page = (Activator.CreateInstance(subtypes.First()) as IDynamicEditorPage)!;
  138. }
  139. else
  140. {
  141. var defType = typeof(DynamicManyToManyGrid<,>).MakeGenericType(map, type);
  142. page = (Activator.CreateInstance(defType) as IDynamicEditorPage)!;
  143. }
  144. cache.Add(page);
  145. }
  146. }
  147. _manytomanytomanypages[type] = cache;
  148. }
  149. pages.AddRange(_manytomanytomanypages[type]);
  150. }
  151. public static IEnumerable<Type> GetOneToManyTypes(Type type)
  152. {
  153. _allo2mtypes ??= CoreUtils.TypeList(
  154. AppDomain.CurrentDomain.GetAssemblies(),
  155. x => x.GetInterfaces().Any(i =>
  156. i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IOneToMany<>) && x.GetCustomAttribute<ObsoleteAttribute>() == null)
  157. );
  158. return _allo2mtypes
  159. .Where(x => x.GetInterfaces().Any(i =>
  160. i.IsGenericType
  161. && i.GetGenericTypeDefinition() == typeof(IOneToMany<>)
  162. && i.GenericTypeArguments.Contains(type)))
  163. .OrderBy(x => x.EntityName());
  164. }
  165. public static void LoadOneToManyPages(Type type, DynamicEditorPages pages)
  166. {
  167. if (!_onetomanypages.ContainsKey(type))
  168. {
  169. var cache = new List<IDynamicEditorPage>();
  170. var maps = GetOneToManyTypes(type);
  171. foreach (var map in maps)
  172. {
  173. if (ClientFactory.IsSupported(map))
  174. {
  175. _allo2mpages ??= CoreUtils.TypeList(
  176. AppDomain.CurrentDomain.GetAssemblies(),
  177. x =>
  178. x.IsClass
  179. && !x.IsAbstract
  180. && !x.IsGenericType
  181. && x.GetInterfaces().Any(i =>
  182. i.IsGenericType
  183. && i.GetGenericTypeDefinition() == typeof(IDynamicOneToManyGrid<,>)
  184. )
  185. );
  186. var subtypes = _allo2mpages.Where(x => x.GetInterfaces().Any(
  187. i => i.IsGenericType
  188. && i.GetGenericTypeDefinition() == typeof(IDynamicOneToManyGrid<,>)
  189. && i.GenericTypeArguments.First().Equals(type)
  190. && i.GenericTypeArguments.Last().Equals(map)
  191. )
  192. );
  193. IDynamicEditorPage page;
  194. if (subtypes.Any())
  195. {
  196. page = (Activator.CreateInstance(subtypes.First()) as IDynamicEditorPage)!;
  197. }
  198. else
  199. {
  200. var defType = typeof(DynamicOneToManyGrid<,>).MakeGenericType(type, map);
  201. page = (Activator.CreateInstance(defType) as IDynamicEditorPage)!;
  202. }
  203. cache.Add(page);
  204. }
  205. }
  206. _onetomanypages[type] = cache;
  207. }
  208. pages.AddRange(_onetomanypages[type]);
  209. }
  210. public static void LoadCustomEditorPages(Type type, DynamicEditorPages pages)
  211. {
  212. if (!_customeditorpages.ContainsKey(type))
  213. {
  214. var cache = new List<IDynamicEditorPage>();
  215. _allcepages ??= CoreUtils.TypeList(
  216. AppDomain.CurrentDomain.GetAssemblies(),
  217. x => x.IsClass
  218. && !x.IsAbstract
  219. && !x.IsGenericType
  220. && x.GetInterfaces().Any(i =>
  221. i.IsGenericType
  222. && i.GetGenericTypeDefinition() == typeof(IDynamicCustomEditorPage<>)
  223. )
  224. );
  225. var pagetypes = _allcepages.Where(x => x.GetInterfaces().Any(i =>
  226. i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDynamicCustomEditorPage<>) &&
  227. i.GenericTypeArguments.First().Equals(type)));
  228. foreach (var pagetype in pagetypes)
  229. {
  230. var page = (Activator.CreateInstance(pagetype) as IDynamicEditorPage)!;
  231. cache.Add(page);
  232. }
  233. _customeditorpages[type] = cache;
  234. }
  235. pages.AddRange(_customeditorpages[type]);
  236. }
  237. public static void LoadEnclosedListPages(Type type, DynamicEditorPages pages)
  238. {
  239. if (!_enclosedlistpages.ContainsKey(type))
  240. {
  241. var cache = new List<IDynamicEditorPage>();
  242. foreach (var property in type.GetProperties())
  243. {
  244. if (property.PropertyType.GetInterfaces().Contains(typeof(IList)))
  245. {
  246. var curtype = property.PropertyType;
  247. var gentype = property.PropertyType.GetGenericArguments().FirstOrDefault();
  248. while (gentype == null && curtype?.BaseType != null)
  249. {
  250. curtype = curtype.BaseType;
  251. gentype = curtype?.GetGenericArguments().FirstOrDefault();
  252. }
  253. if (gentype != null)
  254. if (gentype.IsSubclassOf(typeof(BaseObject)))
  255. {
  256. var editor = property.GetCustomAttributes().FirstOrDefault(x => x is BaseEditor);
  257. if (editor == null || !(editor is NullEditor))
  258. {
  259. _alleltypes ??= CoreUtils.TypeList(
  260. AppDomain.CurrentDomain.GetAssemblies(),
  261. x => x.IsClass
  262. && !x.IsAbstract
  263. && !x.IsGenericType
  264. && x.GetInterfaces().Any(i =>
  265. i.IsGenericType
  266. && i.GetGenericTypeDefinition() == typeof(IDynamicEnclosedListGrid<,>)
  267. )
  268. );
  269. var subtypes = _alleltypes.Where(
  270. x => x.GetInterfaces().Any(
  271. i => i.IsGenericType
  272. && i.GetGenericTypeDefinition() == typeof(IDynamicEnclosedListGrid<,>)
  273. && i.GenericTypeArguments.First().Equals(type)
  274. && i.GenericTypeArguments.Last().Equals(gentype)
  275. )
  276. );
  277. IDynamicEditorPage page;
  278. if (subtypes.Any())
  279. {
  280. page = (Activator.CreateInstance(subtypes.First(), property) as IDynamicEditorPage)!;
  281. cache.Add(page);
  282. }
  283. else
  284. {
  285. subtypes = _alleltypes.Where(x => x.GetInterfaces().Any(i => i.GenericTypeArguments.Last().Equals(gentype)));
  286. if (subtypes.Any())
  287. {
  288. var defType = subtypes.First().MakeGenericType(type);
  289. page = (Activator.CreateInstance(defType, property) as IDynamicEditorPage)!;
  290. cache.Add(page);
  291. }
  292. else
  293. {
  294. try
  295. {
  296. var defType = typeof(DynamicEnclosedListGrid<,>).MakeGenericType(type, gentype);
  297. page = (Activator.CreateInstance(defType, property) as IDynamicEditorPage)!;
  298. cache.Add(page);
  299. }
  300. catch (Exception e)
  301. {
  302. Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
  303. }
  304. }
  305. }
  306. }
  307. }
  308. }
  309. }
  310. _enclosedlistpages[type] = cache;
  311. }
  312. pages.AddRange(_enclosedlistpages[type]);
  313. }
  314. #endregion
  315. #region Editor Values
  316. public static Dictionary<string, object?> GetValues(BaseObject item, string name, IEnumerable<IProperty>? props = null)
  317. {
  318. var result = new Dictionary<string, object?>();
  319. props ??= DatabaseSchema.Properties(item.GetType()).Where(x => x.Editor is not NullEditor);
  320. foreach (var prop in props)
  321. if (prop is CustomProperty)
  322. try
  323. {
  324. result[prop.Name] = item.UserProperties[prop.Name];
  325. }
  326. catch (Exception e)
  327. {
  328. Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
  329. }
  330. else
  331. try
  332. {
  333. object value;
  334. var getter = prop.Getter();
  335. if (getter != null)
  336. value = getter.Invoke(item);
  337. else
  338. value = CoreUtils.GetPropertyValue(item, prop.Name);
  339. result[prop.Name] = value;
  340. }
  341. catch (Exception e)
  342. {
  343. Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
  344. }
  345. return result;
  346. }
  347. public static Dictionary<string, object?> UpdateEditorValue(BaseObject[] items, string name, object value)
  348. {
  349. Logger.Send(LogType.Information, "", string.Format("DynamicGridUtils.UpdateEditorValue({0},{1},{2})", items.Length, name, value));
  350. var sw = new Stopwatch();
  351. var changes = new Dictionary<string, object?>();
  352. var props = DatabaseSchema.Properties(items.First().GetType()).Where(x => x.Editor is not NullEditor);
  353. foreach (var item in items)
  354. {
  355. //Dictionary<String, object> previous = new Dictionary<string, object>();
  356. var previous = GetValues(item, name, props);
  357. //if (item.OriginalValues != null)
  358. //{
  359. // foreach (var key in item.OriginalValues.Keys)
  360. // previous[key] = item.OriginalValues[key];
  361. //}
  362. var prop = DatabaseSchema.Property(item.GetType(), name);
  363. if (prop is CustomProperty)
  364. {
  365. if (!item.HasOriginalValue(name))
  366. item.SetOriginalValue(name, item.UserProperties[name]);
  367. item.UserProperties[name] = value;
  368. }
  369. else
  370. {
  371. if (prop != null)
  372. try
  373. {
  374. var getter = prop.Getter();
  375. var oldvalue = getter != null ? getter.Invoke(item) : CoreUtils.GetPropertyValue(item, name);
  376. item.OnPropertyChanged(name, oldvalue, value);
  377. var setter = prop.Setter();
  378. if (setter != null && value != null)
  379. setter.Invoke(item, value);
  380. else
  381. CoreUtils.SetPropertyValue(item, name, value);
  382. }
  383. catch (Exception)
  384. {
  385. Logger.Send(LogType.Error, "",
  386. string.Format("Unable to Set Value for [{0}.{1}] (Value is {2})", item.GetType().Name, name, value));
  387. }
  388. }
  389. var current = GetValues(item, name, props);
  390. foreach (var key in previous.Keys)
  391. if (previous[key] == null)
  392. {
  393. if (current[key] != null)
  394. changes[key] = current[key];
  395. }
  396. else
  397. {
  398. if (current[key] == null)
  399. changes[key] = current[key];
  400. else if (!object.Equals(previous[key], current[key]))
  401. changes[key] = current[key];
  402. }
  403. }
  404. return changes;
  405. }
  406. public static void UpdateEditorValue(BaseObject[] items, string name, object value, Dictionary<string, object?> changes)
  407. {
  408. var results = UpdateEditorValue(items, name, value);
  409. foreach (var key in results.Keys)
  410. changes[key] = results[key];
  411. }
  412. #endregion
  413. #region Dynamic Grid Creation
  414. public static IDynamicGrid CreateDynamicGrid(Type gridType, Type entityType)
  415. {
  416. var type = FindDynamicGrid(gridType, entityType);
  417. return (Activator.CreateInstance(type) as IDynamicGrid)
  418. ?? throw new ArgumentException("Argument must be a type of IDynamicGrid", nameof(gridType));
  419. }
  420. private static Dictionary<Type, Type[]> _dynamicGrids = new();
  421. public static Type FindDynamicGrid(Type gridType, Type entityType)
  422. {
  423. if(!_dynamicGrids.TryGetValue(gridType, out var grids))
  424. {
  425. grids = CoreUtils.TypeList(
  426. AppDomain.CurrentDomain.GetAssemblies(),
  427. myType =>
  428. myType.IsClass
  429. && !myType.IsAbstract
  430. && !myType.IsGenericType
  431. && CoreUtils.IsSubclassOfRawGeneric(myType, gridType)
  432. && !myType.IsAssignableTo(typeof(ISpecificGrid))
  433. ).ToArray();
  434. _dynamicGrids[gridType] = grids;
  435. }
  436. var entityGrids = grids.Where(x => x.ContainsInheritedGenericType(entityType)).ToList();
  437. var defaults = entityGrids.Where(x => x.IsAssignableTo(typeof(IDefaultGrid))).ToList();
  438. if(defaults.Count > 0)
  439. {
  440. if(defaults.Count > 1)
  441. {
  442. Logger.Send(LogType.Information, ClientFactory.UserID, $"Error: {defaults.Count} IDefaultGrid derivations for {gridType.Name} of {entityType.Name}");
  443. }
  444. return defaults.First();
  445. }
  446. return entityGrids.FirstOrDefault()
  447. ?? gridType.MakeGenericType(entityType);
  448. }
  449. public static Window CreateGridWindow(string title, BaseDynamicGrid dynamicGrid)
  450. {
  451. dynamicGrid.Margin = new Thickness(5);
  452. var window = new ThemableWindow { Title = title, Content = dynamicGrid };
  453. (dynamicGrid as IDynamicGrid)!.Refresh(true, true);
  454. return window;
  455. }
  456. public static Window CreateGridWindow(string title, Type entityType, Type? gridType = null)
  457. {
  458. gridType ??= typeof(DynamicGrid<>);
  459. var grid = CreateDynamicGrid(gridType, entityType) as BaseDynamicGrid;
  460. return CreateGridWindow(title, grid!);
  461. }
  462. public static Window CreateGridWindow<TGrid, TEntity>(string title)
  463. where TEntity : BaseObject
  464. where TGrid : IDynamicGrid
  465. {
  466. return CreateGridWindow(title, typeof(TEntity), typeof(TGrid));
  467. }
  468. public static Window CreateGridWindow<TEntity>(string title)
  469. where TEntity : BaseObject
  470. {
  471. return CreateGridWindow(title, typeof(TEntity));
  472. }
  473. #endregion
  474. public static void PopulateFormMenu<TEntityForm, TEntity, TEntityLink>(ItemsControl menu, Guid entityID)
  475. where TEntityForm : EntityForm<TEntity, TEntityLink>, new()
  476. where TEntity : Entity
  477. where TEntityLink : IEntityLink<TEntity>, new()
  478. {
  479. var task = Task.Run(() =>
  480. {
  481. return new Client<TEntityForm>().Query(
  482. new Filter<TEntityForm>(x => x.Parent.ID).IsEqualTo(entityID),
  483. null).Rows.Select(x => x.ToObject<TEntityForm>()).ToList();
  484. });
  485. var manageForms = new MenuItem { Header = "Manage Forms..." };
  486. manageForms.Click += (o, e) =>
  487. {
  488. var window = new ThemableWindow() { Title = $"Manage {typeof(TEntity).Name} Forms" };
  489. var grid = new DynamicEntityFormGrid<TEntityForm, TEntity, TEntityLink>(entityID);
  490. grid.Refresh(true, true);
  491. grid.Margin = new Thickness(5);
  492. window.Content = grid;
  493. window.ShowDialog();
  494. };
  495. menu.Items.Add(new MenuItem() { Header = "Loading...", IsEnabled = false });
  496. menu.Items.Add(new Separator());
  497. menu.Items.Add(manageForms);
  498. task.ContinueWith((task) =>
  499. {
  500. var entityForms = task.Result;
  501. menu.Items.Clear();
  502. if (entityForms.Any())
  503. {
  504. foreach (var entityForm in entityForms)
  505. {
  506. var description = entityForm.Description;
  507. if (string.IsNullOrWhiteSpace(description))
  508. {
  509. description = entityForm.Form.Description;
  510. }
  511. var formItem = new MenuItem { Header = description };
  512. formItem.Click += (o, e) =>
  513. {
  514. if (DynamicFormEditWindow.EditDigitalForm(entityForm, out var dataModel))
  515. {
  516. dataModel.Update(null);
  517. }
  518. };
  519. menu.Items.Add(formItem);
  520. }
  521. }
  522. else
  523. {
  524. menu.Items.Add(new MenuItem() { Header = "No Forms", IsEnabled = false });
  525. }
  526. menu.Items.Add(new Separator());
  527. menu.Items.Add(manageForms);
  528. }, TaskScheduler.FromCurrentSynchronizationContext());
  529. }
  530. }
  531. }