DynamicEditorForm.xaml.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559
  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.ReconfigureEditors += g => { ReconfigureEditors?.Invoke(g); };
  100. Editor.OnGetEditor += c =>
  101. {
  102. if (_items != null && _items.Any())
  103. {
  104. var property = DatabaseSchema.Property(type, c.ColumnName);
  105. if (property == null) return new NullEditor();
  106. if (property.Editor is NullEditor)
  107. return property.Editor;
  108. BaseEditor editor;
  109. if (property is CustomProperty)
  110. {
  111. editor = property.Editor.CloneEditor();
  112. }
  113. else
  114. {
  115. editor = OnDefineEditor?.Invoke(_items[0], c) ?? c.Editor.CloneEditor();
  116. var propEditor = property.Editor;
  117. editor.Page = propEditor.Page;
  118. editor.Caption = propEditor.Caption;
  119. }
  120. //defaultEditor.EditorSequence
  121. //EditorUtils.GetPropertyEditor(type, property, defaultEditor);
  122. /*BaseEditor editor = new NullEditor();
  123. var caption = "";
  124. var page = "";
  125. try
  126. {
  127. var comps = c.ColumnName.Split('.');
  128. for (var i = 0; i < comps.Length; i++)
  129. {
  130. var column = string.Join(".", comps.Take(i + 1));
  131. var prop = CoreUtils.GetProperty(type, column);
  132. if (column.Equals(c.ColumnName))
  133. {
  134. if (OnDefineEditor != null)
  135. editor = OnDefineEditor(_items[0], c);
  136. else
  137. editor = c.Editor != null ? c.Editor : new NullEditor();
  138. }
  139. else
  140. {
  141. var pedit = prop.GetEditor();
  142. if (pedit is NullEditor)
  143. return pedit;
  144. }
  145. editor = editor == null ? new NullEditor() : editor.Clone() as BaseEditor;
  146. var capattr = prop.GetCustomAttribute<Caption>();
  147. var subcap = capattr != null ? capattr.Text : comps[i];
  148. var path = capattr != null ? capattr.IncludePath : true;
  149. if (!string.IsNullOrWhiteSpace(subcap))
  150. caption = string.IsNullOrWhiteSpace(caption) || path == false ? subcap : string.Format("{0} {1}", caption, subcap);
  151. if (string.IsNullOrWhiteSpace(page))
  152. {
  153. var pageattr = prop.GetCustomAttribute<EditorSequence>();
  154. if (pageattr != null)
  155. page = pageattr.Page;
  156. }
  157. }
  158. editor.Caption = caption;
  159. editor.Page = page;
  160. }
  161. catch (Exception e)
  162. {
  163. var dmprop = DatabaseSchema.Property(_items[0].GetType(), c.ColumnName);
  164. if (dmprop is CustomProperty)
  165. {
  166. editor = dmprop.Editor.Clone() as BaseEditor;
  167. editor.Caption = dmprop.Caption;
  168. editor.Page = string.IsNullOrWhiteSpace(dmprop.Page) ? "Custom Fields" : dmprop.Page;
  169. }
  170. }*/
  171. if (ReadOnly && editor.Editable.Equals(Editable.Enabled))
  172. editor.Editable = Editable.Disabled;
  173. return editor;
  174. }
  175. return new NullEditor();
  176. };
  177. Editor.OnGridCustomiseEditor += (sender, column, editor) => OnFormCustomiseEditor?.Invoke(this, Items, column, editor);
  178. Editor.OnGetSequence += c => CoreUtils.GetPropertySequence(_items.First().GetType(), c.ColumnName);
  179. Editor.OnGetPropertyValue += (o, c) =>
  180. {
  181. if (!_items.Any())
  182. return null;
  183. object? result;
  184. try
  185. {
  186. result = CoreUtils.GetPropertyValue(_items.First(), c);
  187. }
  188. catch
  189. {
  190. result = _items.First().UserProperties.ContainsKey(c) ? _items.First().UserProperties[c] : null;
  191. }
  192. if (result == null)
  193. return null;
  194. foreach (var _item in _items)
  195. {
  196. object? curvalue;
  197. try
  198. {
  199. curvalue = CoreUtils.GetPropertyValue(_item, c);
  200. }
  201. catch
  202. {
  203. curvalue = _item.UserProperties.ContainsKey(c) ? _item.UserProperties[c] : null;
  204. }
  205. if (curvalue == null)
  206. return null;
  207. if (!curvalue.Equals(result))
  208. return null;
  209. }
  210. return result;
  211. };
  212. Editor.OnSetPropertyValue += (o, c, v) =>
  213. {
  214. foreach (var _item in _items)
  215. if (_item.UserProperties.ContainsKey(c))
  216. _item.UserProperties[c] = v;
  217. else
  218. CoreUtils.SetPropertyValue(_item, c, v);
  219. };
  220. Editor.OnEditorValueChanged += EditorValueChanged;
  221. Editor.OnDefineLookups += sender => { OnDefineLookups?.Invoke(sender); };
  222. Editor.OnLookupsDefined += sender => { OnLookupsDefined?.Invoke(sender); };
  223. Editor.OnGetDocument += id => { return OnGetDocument?.Invoke(id); };
  224. Editor.OnSaveDocument += doc => { OnSaveDocument?.Invoke(doc); };
  225. Editor.OnFindDocument += file => { return OnFindDocument?.Invoke(file); };
  226. Editor.GetItems += () => _items;
  227. Pages = pages;
  228. if (Pages == null || Pages.Count == 0)
  229. Editor.Margin = new Thickness(5, 5, 5, 0);
  230. if (buttons != null)
  231. foreach (var button in buttons)
  232. {
  233. var btn = new Button();
  234. UpdateButton(btn, button.Image, button.Name);
  235. btn.Tag = button;
  236. btn.Margin = new Thickness(5, 5, 0, 5);
  237. btn.Padding = new Thickness(5, 0, 5, 0);
  238. btn.Click += Btn_Click;
  239. Buttons.Children.Add(btn);
  240. button.Button = btn;
  241. button.Form = this;
  242. }
  243. }
  244. public DynamicEditorPages? Pages { get; }
  245. public BaseObject[] Items
  246. {
  247. get => _items;
  248. set
  249. {
  250. _items = value;
  251. UtilityViewModel.Slug = Items != null ? Items.Any() ? Items.First().GetType().EntityName().Split('.').Last() : "" : "";
  252. Editor.Load(_items.First().GetType().EntityName(), Pages);
  253. }
  254. }
  255. public bool ReadOnly { get; set; }
  256. public OnValidateData? OnValidateData;
  257. public event OnCustomiseColumns? OnCustomiseColumns;
  258. public event OnDefineFilter? OnDefineFilter;
  259. public event DefineEditorEventHandler? OnDefineEditor;
  260. public event OnFormCustomiseEditor? OnFormCustomiseEditor;
  261. public event OnReconfigureEditors? ReconfigureEditors;
  262. //public delegate void EditorValueChangedHandler(object sender, String name, object value, List<String> changes);
  263. //public event EditorValueChangedHandler OnEditorValueChanged;
  264. public event EditorValueChangedHandler? OnEditorValueChanged;
  265. //public event DefineFilter OnDefineFilter;
  266. public event OnDefineLookup? OnDefineLookups;
  267. public event OnLookupsDefined? OnLookupsDefined;
  268. public event GetDocumentEvent? OnGetDocument;
  269. public event FindDocumentEvent? OnFindDocument;
  270. public event SaveDocumentEvent? OnSaveDocument;
  271. public event OnSelectPage? OnSelectPage;
  272. public event DynamicGridSaveEvent? OnSaveItem;
  273. public void UnloadEditorPages(DynamicEditorPages pages, object item, bool saved)
  274. {
  275. Editor.UnloadPages(saved);
  276. }
  277. protected void UpdateButton(Button button, BitmapImage? image, string text)
  278. {
  279. var stackPnl = new StackPanel();
  280. stackPnl.Orientation = Orientation.Horizontal;
  281. //stackPnl.Margin = new Thickness(2);
  282. if (image != null)
  283. {
  284. var img = new Image();
  285. img.Source = image;
  286. img.Margin = new Thickness(2);
  287. stackPnl.Children.Add(img);
  288. }
  289. if (!string.IsNullOrEmpty(text))
  290. {
  291. var lbl = new Label();
  292. lbl.Content = text;
  293. lbl.VerticalAlignment = VerticalAlignment.Stretch;
  294. lbl.VerticalContentAlignment = VerticalAlignment.Center;
  295. lbl.Margin = new Thickness(2, 0, 5, 0);
  296. stackPnl.Children.Add(lbl);
  297. }
  298. button.Content = stackPnl;
  299. }
  300. private Dictionary<string, object?> EditorValueChanged(object sender, string name, object value)
  301. {
  302. if (OnEditorValueChanged != null)
  303. return OnEditorValueChanged(sender, name, value);
  304. return DynamicGridUtils.UpdateEditorValue(_items, name, value);
  305. }
  306. private void Editor_OnEditorCreated(object sender, double height, double width)
  307. {
  308. var screen = WpfScreen.GetScreenFrom(new Point(Left, Top));
  309. double spareheight = 90;
  310. double sparewidth = 25;
  311. var desiredheight = height + spareheight;
  312. var desiredwidth = width + sparewidth;
  313. if (Pages != null)
  314. foreach (var page in Pages)
  315. {
  316. if (desiredheight < page.MinimumSize().Height)
  317. desiredheight = page.MinimumSize().Height;
  318. if (desiredwidth < page.MinimumSize().Width)
  319. desiredwidth = page.MinimumSize().Width;
  320. }
  321. var maxheight = screen.WorkingArea.Height - 0;
  322. Height = desiredheight > maxheight ? maxheight : desiredheight;
  323. var maxwidth = screen.WorkingArea.Width - 0;
  324. Width = desiredwidth > maxwidth ? maxwidth : desiredwidth;
  325. Left = screen.DeviceBounds.Left + (screen.DeviceBounds.Width - Width) / 2.0F;
  326. Top = screen.DeviceBounds.Top + (screen.DeviceBounds.Height - Height) / 2.0F;
  327. Editor.VerticalAlignment = VerticalAlignment.Stretch;
  328. Editor.HorizontalAlignment = HorizontalAlignment.Stretch;
  329. var scaption = _items[0].GetType().GetCaption();
  330. Title = "Edit " + scaption.SplitCamelCase();
  331. if (Editor.IsCustomLayout)
  332. Title = Title + "*";
  333. OKButton.IsEnabled = !ReadOnly;
  334. }
  335. private DynamicGridColumns Editor_OnCustomiseColumns(object sender, DynamicGridColumns? source)
  336. {
  337. var columns = new DynamicGridColumns();
  338. if (_items != null && _items.Any())
  339. columns.ExtractColumns(_items.First().GetType(), "");
  340. if (OnCustomiseColumns != null)
  341. columns = OnCustomiseColumns.Invoke(this, columns);
  342. return columns;
  343. }
  344. private void Btn_Click(object sender, RoutedEventArgs e)
  345. {
  346. var button = (Button)sender;
  347. var deb = (DynamicEditorButton)button.Tag;
  348. deb.Click();
  349. }
  350. private void OKButton_Click(object sender, RoutedEventArgs e)
  351. {
  352. var errors = OnValidateData?.Invoke(this, Items);
  353. if (errors != null && errors.Any())
  354. {
  355. MessageBox.Show(
  356. string.Format("The following errors have been found with your data!\nPlease correct them and try again.\n\n- {0}",
  357. string.Join("\n- ", errors)), "Validation Error");
  358. return;
  359. }
  360. // Don't Commit the changes here, because we want to refer back to thos changes when we save the item
  361. // to trigger specific processes in the database
  362. DialogResult = true;
  363. //Close();
  364. }
  365. private void CancelButton_Click(object sender, RoutedEventArgs e)
  366. {
  367. // However, if we cancel the edits, then we can safely revert the items back to their original (loaded) state
  368. foreach (var item in _items)
  369. item.CancelChanges();
  370. DialogResult = false;
  371. //Close();
  372. }
  373. public bool TryFindEditor(string columnname, [NotNullWhen(true)] out IDynamicEditorControl? editor)
  374. {
  375. return Editor.TryFindEditor(columnname, out editor);
  376. }
  377. public IDynamicEditorControl? FindEditor(string columnname)
  378. {
  379. return Editor.FindEditor(columnname);
  380. }
  381. private void Window_Closing(object sender, CancelEventArgs e)
  382. {
  383. if (DialogResult == true)
  384. OnSaveItem?.Invoke(this, e);
  385. }
  386. #region Win32 API Stuff
  387. // Define the Win32 API methods we are going to use
  388. [DllImport("user32.dll")]
  389. private static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);
  390. [DllImport("user32.dll")]
  391. private static extern bool InsertMenu(IntPtr hMenu, int wPosition, int wFlags, int wIDNewItem, string lpNewItem);
  392. /// Define our Constants we will use
  393. public const int WM_SYSCOMMAND = 0x112;
  394. public const int MF_SEPARATOR = 0x800;
  395. public const int MF_BYPOSITION = 0x400;
  396. public const int MF_STRING = 0x0;
  397. public const int _EditLayoutID = 1000;
  398. public const int _ResetLayoutID = 1001;
  399. public IntPtr Handle => new WindowInteropHelper(this).Handle;
  400. private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
  401. {
  402. // Check if a System Command has been executed
  403. if (msg == WM_SYSCOMMAND)
  404. // Execute the appropriate code for the System Menu item that was clicked
  405. switch (wParam.ToInt32())
  406. {
  407. case _EditLayoutID:
  408. Editor.EditLayout();
  409. handled = true;
  410. break;
  411. case _ResetLayoutID:
  412. if (MessageBox.Show(
  413. "WARNING: This will delete any customisations you have made!\n\nAre you sure you wish to reset this layout?",
  414. "Confirm Layout Reset", MessageBoxButton.OKCancel) == MessageBoxResult.OK)
  415. Editor.ResetLayout();
  416. handled = true;
  417. break;
  418. }
  419. return IntPtr.Zero;
  420. }
  421. private void ConfigureSystemMenu(object sender, RoutedEventArgs e)
  422. {
  423. /// Get the Handle for the Forms System Menu
  424. var systemMenuHandle = GetSystemMenu(Handle, false);
  425. /// Create our new System Menu items just before the Close menu item
  426. InsertMenu(systemMenuHandle, 5, MF_BYPOSITION | MF_SEPARATOR, 0, string.Empty); // <-- Add a menu seperator
  427. InsertMenu(systemMenuHandle, 6, MF_BYPOSITION, _EditLayoutID, "Edit Layout");
  428. InsertMenu(systemMenuHandle, 7, MF_BYPOSITION, _ResetLayoutID, "Reset Layout");
  429. // Attach our WndProc handler to this Window
  430. var source = HwndSource.FromHwnd(Handle);
  431. source.AddHook(WndProc);
  432. }
  433. #endregion
  434. //private void Wiki_Click(object sender, RoutedEventArgs e)
  435. //{
  436. // System.Diagnostics.Process.Start("https://prs-software.com.au/wiki/index.php/" + CurrentPanelSlug());
  437. //}
  438. //private string CurrentPanelSlug()
  439. //{
  440. // if ((Items != null) && Items.Any())
  441. // return Items.First().GetType().EntityName().Split('.').Last();
  442. // return "";
  443. //}
  444. }
  445. }