DynamicEditorForm.xaml.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Collections.ObjectModel;
  4. using System.ComponentModel;
  5. using System.Diagnostics;
  6. using System.Diagnostics.CodeAnalysis;
  7. using System.Linq;
  8. using System.Reflection;
  9. using System.Runtime.InteropServices;
  10. using System.Windows;
  11. using System.Windows.Controls;
  12. using System.Windows.Input;
  13. using System.Windows.Interop;
  14. using System.Windows.Media;
  15. using System.Windows.Media.Imaging;
  16. using InABox.Core;
  17. using InABox.DynamicGrid.Properties;
  18. using InABox.Wpf;
  19. using InABox.WPF;
  20. using Syncfusion.Windows.Shared;
  21. using Syncfusion.Windows.Tools.Controls;
  22. namespace InABox.DynamicGrid
  23. {
  24. public delegate BaseEditor? DefineEditorEventHandler(object item, DynamicGridColumn column);
  25. public delegate void DynamicGridSaveEvent(object sender, CancelEventArgs args);
  26. public interface IDynamicEditorForm
  27. {
  28. }
  29. public class UtilityItem
  30. {
  31. public string Name { get; set; }
  32. public ImageSource Icon { get; set; }
  33. public string Text { get; set; }
  34. public SizeMode Mode { get; set; }
  35. public ICommand Command { get; set; }
  36. }
  37. public class UtilityViewModel
  38. {
  39. /// <summary>
  40. /// Constructor of the UtilityViewModel class.
  41. /// </summary>
  42. public UtilityViewModel()
  43. {
  44. var utilities = new ObservableCollection<UtilityItem>();
  45. utilities.Add(new UtilityItem
  46. { Name = "Help", Icon = Resources.help.AsBitmapImage(), Text = "", Mode = SizeMode.Normal, Command = HelpCommand });
  47. Utilities = utilities;
  48. }
  49. /// <summary>
  50. /// Collection containing the complete details of the items to be bound in the title bar.
  51. /// </summary>
  52. public ObservableCollection<UtilityItem> Utilities { get; }
  53. /// <summary>
  54. /// Commmand for the Help button.
  55. /// </summary>
  56. public DelegateCommand HelpCommand => new(HelpCommandAction);
  57. public static string Slug { get; set; }
  58. /// <summary>
  59. /// Action that is performed when clicking the help button.
  60. /// </summary>
  61. private void HelpCommandAction(object param)
  62. {
  63. Process.Start("https://prs-software.com.au/wiki/index.php/" + Slug);
  64. }
  65. }
  66. /// <summary>
  67. /// Interaction logic for DynamicEditor.xaml
  68. /// </summary>
  69. public partial class DynamicEditorForm : ThemableChromelessWindow, IDynamicEditorForm
  70. {
  71. public delegate Document? FindDocumentEvent(string FileName);
  72. public delegate Document? GetDocumentEvent(Guid id);
  73. public delegate void SaveDocumentEvent(Document document);
  74. private BaseObject[] _items;
  75. public DynamicEditorGrid Editor;
  76. public DynamicEditorForm(Type type, DynamicEditorPages? pages = null, DynamicEditorButtons? buttons = null,
  77. Func<Type, CoreTable>? PageDataHandler = null, bool PreloadPages = false)
  78. {
  79. ReadOnly = false;
  80. InitializeComponent();
  81. //this.Loaded += new RoutedEventHandler(ConfigureSystemMenu);
  82. var grid = Content as Grid;
  83. Editor = grid.Children.OfType<DynamicEditorGrid>().FirstOrDefault();
  84. Editor.UnderlyingType = type;
  85. Editor.OnCustomiseColumns += Editor_OnCustomiseColumns;
  86. Editor.OnDefineFilter += (sender, t) => OnDefineFilter?.Invoke(sender, t);
  87. Editor.OnEditorCreated += Editor_OnEditorCreated;
  88. Editor.OnLoadPage += page => { page.Load(Items.First(), PageDataHandler); };
  89. Editor.OnSelectPage += (tab, items) => { OnSelectPage?.Invoke(tab, items); };
  90. Editor.PreloadPages = PreloadPages;
  91. Editor.OnUnloadPage += (page, saved) =>
  92. {
  93. if (!saved)
  94. page.BeforeSave(Items.First());
  95. else
  96. page.AfterSave(Items.First());
  97. };
  98. //Editor.OnGetPropertyInfo += (o, c) => { return CoreUtils.GetProperty(_item.GetType(), c); };
  99. Editor.OnAfterEditorValueChanged += (g,n) => { return OnAfterEditorValueChanged?.Invoke(g, n); };
  100. Editor.OnReconfigureEditors += g => { OnReconfigureEditors?.Invoke(g); };
  101. Editor.OnGetEditor += c =>
  102. {
  103. if (_items != null && _items.Any())
  104. {
  105. var property = DatabaseSchema.Property(type, c.ColumnName);
  106. if (property == null) return new NullEditor();
  107. if (property.Editor is NullEditor)
  108. return property.Editor;
  109. BaseEditor editor;
  110. if (property is CustomProperty)
  111. {
  112. editor = property.Editor.CloneEditor();
  113. }
  114. else
  115. {
  116. editor = OnDefineEditor?.Invoke(_items[0], c) ?? c.Editor.CloneEditor();
  117. var propEditor = property.Editor;
  118. editor.Page = propEditor.Page;
  119. editor.Caption = propEditor.Caption;
  120. }
  121. //defaultEditor.EditorSequence
  122. //EditorUtils.GetPropertyEditor(type, property, defaultEditor);
  123. /*BaseEditor editor = new NullEditor();
  124. var caption = "";
  125. var page = "";
  126. try
  127. {
  128. var comps = c.ColumnName.Split('.');
  129. for (var i = 0; i < comps.Length; i++)
  130. {
  131. var column = string.Join(".", comps.Take(i + 1));
  132. var prop = CoreUtils.GetProperty(type, column);
  133. if (column.Equals(c.ColumnName))
  134. {
  135. if (OnDefineEditor != null)
  136. editor = OnDefineEditor(_items[0], c);
  137. else
  138. editor = c.Editor != null ? c.Editor : new NullEditor();
  139. }
  140. else
  141. {
  142. var pedit = prop.GetEditor();
  143. if (pedit is NullEditor)
  144. return pedit;
  145. }
  146. editor = editor == null ? new NullEditor() : editor.Clone() as BaseEditor;
  147. var capattr = prop.GetCustomAttribute<Caption>();
  148. var subcap = capattr != null ? capattr.Text : comps[i];
  149. var path = capattr != null ? capattr.IncludePath : true;
  150. if (!string.IsNullOrWhiteSpace(subcap))
  151. caption = string.IsNullOrWhiteSpace(caption) || path == false ? subcap : string.Format("{0} {1}", caption, subcap);
  152. if (string.IsNullOrWhiteSpace(page))
  153. {
  154. var pageattr = prop.GetCustomAttribute<EditorSequence>();
  155. if (pageattr != null)
  156. page = pageattr.Page;
  157. }
  158. }
  159. editor.Caption = caption;
  160. editor.Page = page;
  161. }
  162. catch (Exception e)
  163. {
  164. var dmprop = DatabaseSchema.Property(_items[0].GetType(), c.ColumnName);
  165. if (dmprop is CustomProperty)
  166. {
  167. editor = dmprop.Editor.Clone() as BaseEditor;
  168. editor.Caption = dmprop.Caption;
  169. editor.Page = string.IsNullOrWhiteSpace(dmprop.Page) ? "Custom Fields" : dmprop.Page;
  170. }
  171. }*/
  172. if (ReadOnly && editor.Editable.Equals(Editable.Enabled))
  173. editor.Editable = Editable.Disabled;
  174. return editor;
  175. }
  176. return new NullEditor();
  177. };
  178. Editor.OnGridCustomiseEditor += (sender, column, editor) => OnFormCustomiseEditor?.Invoke(this, Items, column, editor);
  179. Editor.OnGetSequence += c => CoreUtils.GetPropertySequence(_items.First().GetType(), c.ColumnName);
  180. Editor.OnGetPropertyValue += (o, c) =>
  181. {
  182. if (!_items.Any())
  183. return null;
  184. object? result;
  185. try
  186. {
  187. result = CoreUtils.GetPropertyValue(_items.First(), c);
  188. }
  189. catch
  190. {
  191. result = _items.First().UserProperties.ContainsKey(c) ? _items.First().UserProperties[c] : null;
  192. }
  193. if (result == null)
  194. return null;
  195. foreach (var _item in _items)
  196. {
  197. object? curvalue;
  198. try
  199. {
  200. curvalue = CoreUtils.GetPropertyValue(_item, c);
  201. }
  202. catch
  203. {
  204. curvalue = _item.UserProperties.ContainsKey(c) ? _item.UserProperties[c] : null;
  205. }
  206. if (curvalue == null)
  207. return null;
  208. if (!curvalue.Equals(result))
  209. return null;
  210. }
  211. return result;
  212. };
  213. Editor.OnSetPropertyValue += (o, c, v) =>
  214. {
  215. foreach (var _item in _items)
  216. if (_item.UserProperties.ContainsKey(c))
  217. _item.UserProperties[c] = v;
  218. else
  219. CoreUtils.SetPropertyValue(_item, c, v);
  220. };
  221. Editor.OnEditorValueChanged += EditorValueChanged;
  222. Editor.OnDefineLookups += sender => { OnDefineLookups?.Invoke(sender); };
  223. Editor.OnLookupsDefined += sender => { OnLookupsDefined?.Invoke(sender); };
  224. Editor.OnGetDocument += id => { return OnGetDocument?.Invoke(id); };
  225. Editor.OnSaveDocument += doc => { OnSaveDocument?.Invoke(doc); };
  226. Editor.OnFindDocument += file => { return OnFindDocument?.Invoke(file); };
  227. Editor.GetItems += () => _items;
  228. Pages = pages;
  229. if (Pages == null || Pages.Count == 0)
  230. Editor.Margin = new Thickness(5, 5, 5, 0);
  231. if (buttons != null)
  232. foreach (var button in buttons)
  233. {
  234. var btn = new Button();
  235. UpdateButton(btn, button.Image, button.Name);
  236. btn.Tag = button;
  237. btn.Margin = new Thickness(5, 5, 0, 5);
  238. btn.Padding = new Thickness(5, 0, 5, 0);
  239. btn.Click += Btn_Click;
  240. Buttons.Children.Add(btn);
  241. button.Button = btn;
  242. button.Form = this;
  243. }
  244. }
  245. public DynamicEditorPages? Pages { get; }
  246. public BaseObject[] Items
  247. {
  248. get => _items;
  249. set
  250. {
  251. _items = value;
  252. UtilityViewModel.Slug = Items != null ? Items.Any() ? Items.First().GetType().EntityName().Split('.').Last() : "" : "";
  253. Editor.Load(_items.First().GetType().EntityName(), Pages);
  254. }
  255. }
  256. public bool ReadOnly { get; set; }
  257. public OnValidateData? OnValidateData;
  258. public event OnCustomiseColumns? OnCustomiseColumns;
  259. public event OnDefineFilter? OnDefineFilter;
  260. public event DefineEditorEventHandler? OnDefineEditor;
  261. public event OnFormCustomiseEditor? OnFormCustomiseEditor;
  262. public event OnAfterEditorValueChanged? OnAfterEditorValueChanged;
  263. public event OnReconfigureEditors? OnReconfigureEditors;
  264. //public delegate void EditorValueChangedHandler(object sender, String name, object value, List<String> changes);
  265. //public event EditorValueChangedHandler OnEditorValueChanged;
  266. public event EditorValueChangedHandler? OnEditorValueChanged;
  267. //public event DefineFilter OnDefineFilter;
  268. public event OnDefineLookup? OnDefineLookups;
  269. public event OnLookupsDefined? OnLookupsDefined;
  270. public event GetDocumentEvent? OnGetDocument;
  271. public event FindDocumentEvent? OnFindDocument;
  272. public event SaveDocumentEvent? OnSaveDocument;
  273. public event OnSelectPage? OnSelectPage;
  274. public event DynamicGridSaveEvent? OnSaveItem;
  275. public void UnloadEditorPages(DynamicEditorPages pages, object item, bool saved)
  276. {
  277. Editor.UnloadPages(saved);
  278. }
  279. protected void UpdateButton(Button button, BitmapImage? image, string text)
  280. {
  281. var stackPnl = new StackPanel();
  282. stackPnl.Orientation = Orientation.Horizontal;
  283. //stackPnl.Margin = new Thickness(2);
  284. if (image != null)
  285. {
  286. var img = new Image();
  287. img.Source = image;
  288. img.Margin = new Thickness(2);
  289. stackPnl.Children.Add(img);
  290. }
  291. if (!string.IsNullOrEmpty(text))
  292. {
  293. var lbl = new Label();
  294. lbl.Content = text;
  295. lbl.VerticalAlignment = VerticalAlignment.Stretch;
  296. lbl.VerticalContentAlignment = VerticalAlignment.Center;
  297. lbl.Margin = new Thickness(2, 0, 5, 0);
  298. stackPnl.Children.Add(lbl);
  299. }
  300. button.Content = stackPnl;
  301. }
  302. private Dictionary<string, object?> EditorValueChanged(object sender, string name, object value)
  303. {
  304. if (OnEditorValueChanged != null)
  305. return OnEditorValueChanged(sender, name, value);
  306. return DynamicGridUtils.UpdateEditorValue(_items, name, value);
  307. }
  308. private void Editor_OnEditorCreated(object sender, double height, double width)
  309. {
  310. var screen = WpfScreen.GetScreenFrom(new Point(Left, Top));
  311. double spareheight = 90;
  312. double sparewidth = 25;
  313. var desiredheight = height + spareheight;
  314. var desiredwidth = width + sparewidth;
  315. if (Pages != null)
  316. foreach (var page in Pages)
  317. {
  318. if (desiredheight < page.MinimumSize().Height)
  319. desiredheight = page.MinimumSize().Height;
  320. if (desiredwidth < page.MinimumSize().Width)
  321. desiredwidth = page.MinimumSize().Width;
  322. }
  323. var maxheight = screen.WorkingArea.Height - 0;
  324. Height = desiredheight > maxheight ? maxheight : desiredheight;
  325. var maxwidth = screen.WorkingArea.Width - 0;
  326. Width = desiredwidth > maxwidth ? maxwidth : desiredwidth;
  327. Left = screen.DeviceBounds.Left + (screen.DeviceBounds.Width - Width) / 2.0F;
  328. Top = screen.DeviceBounds.Top + (screen.DeviceBounds.Height - Height) / 2.0F;
  329. Editor.VerticalAlignment = VerticalAlignment.Stretch;
  330. Editor.HorizontalAlignment = HorizontalAlignment.Stretch;
  331. var scaption = _items[0].GetType().GetCaption();
  332. Title = "Edit " + scaption.SplitCamelCase();
  333. if (Editor.IsCustomLayout)
  334. Title = Title + "*";
  335. OKButton.IsEnabled = !ReadOnly;
  336. }
  337. private DynamicGridColumns Editor_OnCustomiseColumns(object sender, DynamicGridColumns? source)
  338. {
  339. var columns = new DynamicGridColumns();
  340. if (_items != null && _items.Any())
  341. columns.ExtractColumns(_items.First().GetType(), "");
  342. if (OnCustomiseColumns != null)
  343. columns = OnCustomiseColumns.Invoke(this, columns);
  344. return columns;
  345. }
  346. private void Btn_Click(object sender, RoutedEventArgs e)
  347. {
  348. var button = (Button)sender;
  349. var deb = (DynamicEditorButton)button.Tag;
  350. deb.Click();
  351. }
  352. private void OKButton_Click(object sender, RoutedEventArgs e)
  353. {
  354. var errors = OnValidateData?.Invoke(this, Items);
  355. if (errors != null && errors.Any())
  356. {
  357. MessageBox.Show(
  358. string.Format("The following errors have been found with your data!\nPlease correct them and try again.\n\n- {0}",
  359. string.Join("\n- ", errors)), "Validation Error");
  360. return;
  361. }
  362. // Don't Commit the changes here, because we want to refer back to thos changes when we save the item
  363. // to trigger specific processes in the database
  364. DialogResult = true;
  365. //Close();
  366. }
  367. private void CancelButton_Click(object sender, RoutedEventArgs e)
  368. {
  369. // However, if we cancel the edits, then we can safely revert the items back to their original (loaded) state
  370. foreach (var item in _items)
  371. item.CancelChanges();
  372. DialogResult = false;
  373. //Close();
  374. }
  375. public bool TryFindEditor(string columnname, [NotNullWhen(true)] out IDynamicEditorControl? editor)
  376. {
  377. return Editor.TryFindEditor(columnname, out editor);
  378. }
  379. public IDynamicEditorControl? FindEditor(string columnname)
  380. {
  381. return Editor.FindEditor(columnname);
  382. }
  383. private void Window_Closing(object sender, CancelEventArgs e)
  384. {
  385. if (DialogResult == true)
  386. OnSaveItem?.Invoke(this, e);
  387. }
  388. #region Win32 API Stuff
  389. // Define the Win32 API methods we are going to use
  390. [DllImport("user32.dll")]
  391. private static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);
  392. [DllImport("user32.dll")]
  393. private static extern bool InsertMenu(IntPtr hMenu, int wPosition, int wFlags, int wIDNewItem, string lpNewItem);
  394. /// Define our Constants we will use
  395. public const int WM_SYSCOMMAND = 0x112;
  396. public const int MF_SEPARATOR = 0x800;
  397. public const int MF_BYPOSITION = 0x400;
  398. public const int MF_STRING = 0x0;
  399. public const int _EditLayoutID = 1000;
  400. public const int _ResetLayoutID = 1001;
  401. public IntPtr Handle => new WindowInteropHelper(this).Handle;
  402. private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
  403. {
  404. // Check if a System Command has been executed
  405. if (msg == WM_SYSCOMMAND)
  406. // Execute the appropriate code for the System Menu item that was clicked
  407. switch (wParam.ToInt32())
  408. {
  409. case _EditLayoutID:
  410. Editor.EditLayout();
  411. handled = true;
  412. break;
  413. case _ResetLayoutID:
  414. if (MessageBox.Show(
  415. "WARNING: This will delete any customisations you have made!\n\nAre you sure you wish to reset this layout?",
  416. "Confirm Layout Reset", MessageBoxButton.OKCancel) == MessageBoxResult.OK)
  417. Editor.ResetLayout();
  418. handled = true;
  419. break;
  420. }
  421. return IntPtr.Zero;
  422. }
  423. private void ConfigureSystemMenu(object sender, RoutedEventArgs e)
  424. {
  425. /// Get the Handle for the Forms System Menu
  426. var systemMenuHandle = GetSystemMenu(Handle, false);
  427. /// Create our new System Menu items just before the Close menu item
  428. InsertMenu(systemMenuHandle, 5, MF_BYPOSITION | MF_SEPARATOR, 0, string.Empty); // <-- Add a menu seperator
  429. InsertMenu(systemMenuHandle, 6, MF_BYPOSITION, _EditLayoutID, "Edit Layout");
  430. InsertMenu(systemMenuHandle, 7, MF_BYPOSITION, _ResetLayoutID, "Reset Layout");
  431. // Attach our WndProc handler to this Window
  432. var source = HwndSource.FromHwnd(Handle);
  433. source.AddHook(WndProc);
  434. }
  435. #endregion
  436. //private void Wiki_Click(object sender, RoutedEventArgs e)
  437. //{
  438. // System.Diagnostics.Process.Start("https://prs-software.com.au/wiki/index.php/" + CurrentPanelSlug());
  439. //}
  440. //private string CurrentPanelSlug()
  441. //{
  442. // if ((Items != null) && Items.Any())
  443. // return Items.First().GetType().EntityName().Split('.').Last();
  444. // return "";
  445. //}
  446. }
  447. }