DynamicEditorForm.xaml.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557
  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. Pages = pages;
  227. if (Pages == null || Pages.Count == 0)
  228. Editor.Margin = new Thickness(5, 5, 5, 0);
  229. if (buttons != null)
  230. foreach (var button in buttons)
  231. {
  232. var btn = new Button();
  233. UpdateButton(btn, button.Image, button.Name);
  234. btn.Tag = button;
  235. btn.Margin = new Thickness(5, 5, 0, 5);
  236. btn.Padding = new Thickness(5, 0, 5, 0);
  237. btn.Click += Btn_Click;
  238. Buttons.Children.Add(btn);
  239. button.Button = btn;
  240. button.Form = this;
  241. }
  242. }
  243. public DynamicEditorPages? Pages { get; }
  244. public BaseObject[] Items
  245. {
  246. get => _items;
  247. set
  248. {
  249. _items = value;
  250. UtilityViewModel.Slug = Items != null ? Items.Any() ? Items.First().GetType().EntityName().Split('.').Last() : "" : "";
  251. Editor.Load(_items.First().GetType().EntityName(), Pages);
  252. }
  253. }
  254. public bool ReadOnly { get; set; }
  255. public OnValidateData? OnValidateData;
  256. public event OnCustomiseColumns? OnCustomiseColumns;
  257. public event OnDefineFilter? OnDefineFilter;
  258. public event DefineEditorEventHandler? OnDefineEditor;
  259. public event OnFormCustomiseEditor? OnFormCustomiseEditor;
  260. public event OnReconfigureEditors? ReconfigureEditors;
  261. //public delegate void EditorValueChangedHandler(object sender, String name, object value, List<String> changes);
  262. //public event EditorValueChangedHandler OnEditorValueChanged;
  263. public event EditorValueChangedHandler? OnEditorValueChanged;
  264. //public event DefineFilter OnDefineFilter;
  265. public event OnDefineLookup? OnDefineLookups;
  266. public event OnLookupsDefined? OnLookupsDefined;
  267. public event GetDocumentEvent? OnGetDocument;
  268. public event FindDocumentEvent? OnFindDocument;
  269. public event SaveDocumentEvent? OnSaveDocument;
  270. public event OnSelectPage? OnSelectPage;
  271. public event DynamicGridSaveEvent? OnSaveItem;
  272. public void UnloadEditorPages(DynamicEditorPages pages, object item, bool saved)
  273. {
  274. Editor.UnloadPages(saved);
  275. }
  276. protected void UpdateButton(Button button, BitmapImage? image, string text)
  277. {
  278. var stackPnl = new StackPanel();
  279. stackPnl.Orientation = Orientation.Horizontal;
  280. //stackPnl.Margin = new Thickness(2);
  281. if (image != null)
  282. {
  283. var img = new Image();
  284. img.Source = image;
  285. img.Margin = new Thickness(2);
  286. stackPnl.Children.Add(img);
  287. }
  288. if (!string.IsNullOrEmpty(text))
  289. {
  290. var lbl = new Label();
  291. lbl.Content = text;
  292. lbl.VerticalAlignment = VerticalAlignment.Stretch;
  293. lbl.VerticalContentAlignment = VerticalAlignment.Center;
  294. lbl.Margin = new Thickness(2, 0, 5, 0);
  295. stackPnl.Children.Add(lbl);
  296. }
  297. button.Content = stackPnl;
  298. }
  299. private Dictionary<string, object?> EditorValueChanged(object sender, string name, object value)
  300. {
  301. if (OnEditorValueChanged != null)
  302. return OnEditorValueChanged(sender, name, value);
  303. return DynamicGridUtils.UpdateEditorValue(_items, name, value);
  304. }
  305. private void Editor_OnEditorCreated(object sender, double height, double width)
  306. {
  307. var screen = WpfScreen.GetScreenFrom(new Point(Left, Top));
  308. double spareheight = 90;
  309. double sparewidth = 25;
  310. var desiredheight = height + spareheight;
  311. var desiredwidth = width + sparewidth;
  312. if (Pages != null)
  313. foreach (var page in Pages)
  314. {
  315. if (desiredheight < page.MinimumSize().Height)
  316. desiredheight = page.MinimumSize().Height;
  317. if (desiredwidth < page.MinimumSize().Width)
  318. desiredwidth = page.MinimumSize().Width;
  319. }
  320. var maxheight = screen.WorkingArea.Height - 0;
  321. Height = desiredheight > maxheight ? maxheight : desiredheight;
  322. var maxwidth = screen.WorkingArea.Width - 0;
  323. Width = desiredwidth > maxwidth ? maxwidth : desiredwidth;
  324. Left = screen.DeviceBounds.Left + (screen.DeviceBounds.Width - Width) / 2.0F;
  325. Top = screen.DeviceBounds.Top + (screen.DeviceBounds.Height - Height) / 2.0F;
  326. Editor.VerticalAlignment = VerticalAlignment.Stretch;
  327. Editor.HorizontalAlignment = HorizontalAlignment.Stretch;
  328. var scaption = _items[0].GetType().GetCaption();
  329. Title = "Edit " + scaption.SplitCamelCase();
  330. if (Editor.IsCustomLayout)
  331. Title = Title + "*";
  332. OKButton.IsEnabled = !ReadOnly;
  333. }
  334. private DynamicGridColumns Editor_OnCustomiseColumns(object sender, DynamicGridColumns? source)
  335. {
  336. var columns = new DynamicGridColumns();
  337. if (_items != null && _items.Any())
  338. columns.ExtractColumns(_items.First().GetType(), "");
  339. if (OnCustomiseColumns != null)
  340. columns = OnCustomiseColumns.Invoke(this, columns);
  341. return columns;
  342. }
  343. private void Btn_Click(object sender, RoutedEventArgs e)
  344. {
  345. var button = (Button)sender;
  346. var deb = (DynamicEditorButton)button.Tag;
  347. deb.Click();
  348. }
  349. private void OKButton_Click(object sender, RoutedEventArgs e)
  350. {
  351. var errors = OnValidateData?.Invoke(this, Items);
  352. if (errors != null && errors.Any())
  353. {
  354. MessageBox.Show(
  355. string.Format("The following errors have been found with your data!\nPlease correct them and try again.\n\n- {0}",
  356. string.Join("\n- ", errors)), "Validation Error");
  357. return;
  358. }
  359. // Don't Commit the changes here, because we want to refer back to thos changes when we save the item
  360. // to trigger specific processes in the database
  361. DialogResult = true;
  362. //Close();
  363. }
  364. private void CancelButton_Click(object sender, RoutedEventArgs e)
  365. {
  366. // However, if we cancel the edits, then we can safely revert the items back to their original (loaded) state
  367. foreach (var item in _items)
  368. item.CancelChanges();
  369. DialogResult = false;
  370. //Close();
  371. }
  372. public bool TryFindEditor(string columnname, [NotNullWhen(true)] out IDynamicEditorControl? editor)
  373. {
  374. return Editor.TryFindEditor(columnname, out editor);
  375. }
  376. public IDynamicEditorControl? FindEditor(string columnname)
  377. {
  378. return Editor.FindEditor(columnname);
  379. }
  380. private void Window_Closing(object sender, CancelEventArgs e)
  381. {
  382. if (DialogResult == true)
  383. OnSaveItem?.Invoke(this, e);
  384. }
  385. #region Win32 API Stuff
  386. // Define the Win32 API methods we are going to use
  387. [DllImport("user32.dll")]
  388. private static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);
  389. [DllImport("user32.dll")]
  390. private static extern bool InsertMenu(IntPtr hMenu, int wPosition, int wFlags, int wIDNewItem, string lpNewItem);
  391. /// Define our Constants we will use
  392. public const int WM_SYSCOMMAND = 0x112;
  393. public const int MF_SEPARATOR = 0x800;
  394. public const int MF_BYPOSITION = 0x400;
  395. public const int MF_STRING = 0x0;
  396. public const int _EditLayoutID = 1000;
  397. public const int _ResetLayoutID = 1001;
  398. public IntPtr Handle => new WindowInteropHelper(this).Handle;
  399. private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
  400. {
  401. // Check if a System Command has been executed
  402. if (msg == WM_SYSCOMMAND)
  403. // Execute the appropriate code for the System Menu item that was clicked
  404. switch (wParam.ToInt32())
  405. {
  406. case _EditLayoutID:
  407. Editor.EditLayout();
  408. handled = true;
  409. break;
  410. case _ResetLayoutID:
  411. if (MessageBox.Show(
  412. "WARNING: This will delete any customisations you have made!\n\nAre you sure you wish to reset this layout?",
  413. "Confirm Layout Reset", MessageBoxButton.OKCancel) == MessageBoxResult.OK)
  414. Editor.ResetLayout();
  415. handled = true;
  416. break;
  417. }
  418. return IntPtr.Zero;
  419. }
  420. private void ConfigureSystemMenu(object sender, RoutedEventArgs e)
  421. {
  422. /// Get the Handle for the Forms System Menu
  423. var systemMenuHandle = GetSystemMenu(Handle, false);
  424. /// Create our new System Menu items just before the Close menu item
  425. InsertMenu(systemMenuHandle, 5, MF_BYPOSITION | MF_SEPARATOR, 0, string.Empty); // <-- Add a menu seperator
  426. InsertMenu(systemMenuHandle, 6, MF_BYPOSITION, _EditLayoutID, "Edit Layout");
  427. InsertMenu(systemMenuHandle, 7, MF_BYPOSITION, _ResetLayoutID, "Reset Layout");
  428. // Attach our WndProc handler to this Window
  429. var source = HwndSource.FromHwnd(Handle);
  430. source.AddHook(WndProc);
  431. }
  432. #endregion
  433. //private void Wiki_Click(object sender, RoutedEventArgs e)
  434. //{
  435. // System.Diagnostics.Process.Start("https://prs-software.com.au/wiki/index.php/" + CurrentPanelSlug());
  436. //}
  437. //private string CurrentPanelSlug()
  438. //{
  439. // if ((Items != null) && Items.Any())
  440. // return Items.First().GetType().EntityName().Split('.').Last();
  441. // return "";
  442. //}
  443. }
  444. }