DynamicEditorForm.xaml.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Collections.ObjectModel;
  4. using System.ComponentModel;
  5. using System.Diagnostics;
  6. using System.Linq;
  7. using System.Reflection;
  8. using System.Runtime.InteropServices;
  9. using System.Windows;
  10. using System.Windows.Controls;
  11. using System.Windows.Input;
  12. using System.Windows.Interop;
  13. using System.Windows.Media;
  14. using System.Windows.Media.Imaging;
  15. using InABox.Core;
  16. using InABox.DynamicGrid.Properties;
  17. using InABox.Wpf;
  18. using InABox.WPF;
  19. using Syncfusion.Windows.Shared;
  20. using Syncfusion.Windows.Tools.Controls;
  21. namespace InABox.DynamicGrid
  22. {
  23. public delegate BaseEditor? DefineEditorEventHandler(object item, DynamicGridColumn column);
  24. public delegate void DynamicGridSaveEvent(object sender, CancelEventArgs args);
  25. public interface IDynamicEditorForm
  26. {
  27. }
  28. public class UtilityItem
  29. {
  30. public string Name { get; set; }
  31. public ImageSource Icon { get; set; }
  32. public string Text { get; set; }
  33. public SizeMode Mode { get; set; }
  34. public ICommand Command { get; set; }
  35. }
  36. public class UtilityViewModel
  37. {
  38. /// <summary>
  39. /// Constructor of the UtilityViewModel class.
  40. /// </summary>
  41. public UtilityViewModel()
  42. {
  43. var utilities = new ObservableCollection<UtilityItem>();
  44. utilities.Add(new UtilityItem
  45. { Name = "Help", Icon = Resources.help.AsBitmapImage(), Text = "", Mode = SizeMode.Normal, Command = HelpCommand });
  46. Utilities = utilities;
  47. }
  48. /// <summary>
  49. /// Collection containing the complete details of the items to be bound in the title bar.
  50. /// </summary>
  51. public ObservableCollection<UtilityItem> Utilities { get; }
  52. /// <summary>
  53. /// Commmand for the Help button.
  54. /// </summary>
  55. public DelegateCommand HelpCommand => new(HelpCommandAction);
  56. public static string Slug { get; set; }
  57. /// <summary>
  58. /// Action that is performed when clicking the help button.
  59. /// </summary>
  60. private void HelpCommandAction(object param)
  61. {
  62. Process.Start("https://prs-software.com.au/wiki/index.php/" + Slug);
  63. }
  64. }
  65. /// <summary>
  66. /// Interaction logic for DynamicEditor.xaml
  67. /// </summary>
  68. public partial class DynamicEditorForm : ThemableChromelessWindow, IDynamicEditorForm
  69. {
  70. public delegate Document? FindDocumentEvent(string FileName);
  71. public delegate Document? GetDocumentEvent(Guid id);
  72. public delegate void SaveDocumentEvent(Document document);
  73. private BaseObject[] _items;
  74. private Func<Type, CoreTable> _pagedatahandler = null;
  75. private bool _preloadpages = false;
  76. public DynamicEditorGrid Editor;
  77. public OnValidateData OnValidateData;
  78. public DynamicEditorForm(Type type, DynamicEditorPages pages = null, DynamicEditorButtons buttons = null,
  79. Func<Type, CoreTable>? PageDataHandler = null, bool PreloadPages = false)
  80. {
  81. ReadOnly = false;
  82. InitializeComponent();
  83. //this.Loaded += new RoutedEventHandler(ConfigureSystemMenu);
  84. var grid = Content as Grid;
  85. Editor = grid.Children.OfType<DynamicEditorGrid>().FirstOrDefault();
  86. Editor.UnderlyingType = type;
  87. Editor.OnCustomiseColumns += Editor_OnCustomiseColumns;
  88. Editor.OnDefineFilter += (sender, t) => OnDefineFilter(sender, t);
  89. Editor.OnEditorCreated += Editor_OnEditorCreated;
  90. Editor.OnLoadPage += page => { page.Load(Items.First(), PageDataHandler); };
  91. Editor.OnSelectPage += (tab, items) => { OnSelectPage?.Invoke(tab, items); };
  92. Editor.PreloadPages = PreloadPages;
  93. Editor.OnUnloadPage += (page, saved) =>
  94. {
  95. if (!saved)
  96. page.BeforeSave(Items.First());
  97. else
  98. page.AfterSave(Items.First());
  99. };
  100. //Editor.OnGetPropertyInfo += (o, c) => { return CoreUtils.GetProperty(_item.GetType(), c); };
  101. Editor.ReconfigureEditors += g => { ReconfigureEditors?.Invoke(g); };
  102. Editor.OnGetEditor += c =>
  103. {
  104. if (_items != null && _items.Any())
  105. {
  106. var property = DatabaseSchema.Property(type, c.ColumnName);
  107. if (property == null) return new NullEditor();
  108. if (property.Editor is NullEditor)
  109. return property.Editor;
  110. BaseEditor editor;
  111. if (property is CustomProperty)
  112. {
  113. editor = property.Editor.CloneEditor();
  114. }
  115. else
  116. {
  117. editor = OnDefineEditor?.Invoke(_items[0], c) ?? c.Editor.CloneEditor();
  118. var propEditor = property.Editor;
  119. editor.Page = propEditor.Page;
  120. editor.Caption = propEditor.Caption;
  121. }
  122. //defaultEditor.EditorSequence
  123. //EditorUtils.GetPropertyEditor(type, property, defaultEditor);
  124. /*BaseEditor editor = new NullEditor();
  125. var caption = "";
  126. var page = "";
  127. try
  128. {
  129. var comps = c.ColumnName.Split('.');
  130. for (var i = 0; i < comps.Length; i++)
  131. {
  132. var column = string.Join(".", comps.Take(i + 1));
  133. var prop = CoreUtils.GetProperty(type, column);
  134. if (column.Equals(c.ColumnName))
  135. {
  136. if (OnDefineEditor != null)
  137. editor = OnDefineEditor(_items[0], c);
  138. else
  139. editor = c.Editor != null ? c.Editor : new NullEditor();
  140. }
  141. else
  142. {
  143. var pedit = prop.GetEditor();
  144. if (pedit is NullEditor)
  145. return pedit;
  146. }
  147. editor = editor == null ? new NullEditor() : editor.Clone() as BaseEditor;
  148. var capattr = prop.GetCustomAttribute<Caption>();
  149. var subcap = capattr != null ? capattr.Text : comps[i];
  150. var path = capattr != null ? capattr.IncludePath : true;
  151. if (!string.IsNullOrWhiteSpace(subcap))
  152. caption = string.IsNullOrWhiteSpace(caption) || path == false ? subcap : string.Format("{0} {1}", caption, subcap);
  153. if (string.IsNullOrWhiteSpace(page))
  154. {
  155. var pageattr = prop.GetCustomAttribute<EditorSequence>();
  156. if (pageattr != null)
  157. page = pageattr.Page;
  158. }
  159. }
  160. editor.Caption = caption;
  161. editor.Page = page;
  162. }
  163. catch (Exception e)
  164. {
  165. var dmprop = DatabaseSchema.Property(_items[0].GetType(), c.ColumnName);
  166. if (dmprop is CustomProperty)
  167. {
  168. editor = dmprop.Editor.Clone() as BaseEditor;
  169. editor.Caption = dmprop.Caption;
  170. editor.Page = string.IsNullOrWhiteSpace(dmprop.Page) ? "Custom Fields" : dmprop.Page;
  171. }
  172. }*/
  173. if (ReadOnly && editor.Editable.Equals(Editable.Enabled))
  174. editor.Editable = Editable.Disabled;
  175. return editor;
  176. }
  177. return new NullEditor();
  178. };
  179. Editor.OnGridCustomiseEditor += (sender, column, editor) => OnFormCustomiseEditor?.Invoke(this, Items, column, editor);
  180. Editor.OnGetSequence += c => CoreUtils.GetPropertySequence(_items.First().GetType(), c.ColumnName);
  181. Editor.OnGetPropertyValue += (o, c) =>
  182. {
  183. if (!_items.Any())
  184. return null;
  185. object result = null;
  186. try
  187. {
  188. result = CoreUtils.GetPropertyValue(_items.First(), c);
  189. }
  190. catch
  191. {
  192. result = _items.First().UserProperties.ContainsKey(c) ? _items.First().UserProperties[c] : null;
  193. }
  194. if (result == null)
  195. return null;
  196. foreach (var _item in _items)
  197. {
  198. object curvalue = null;
  199. try
  200. {
  201. curvalue = CoreUtils.GetPropertyValue(_item, c);
  202. }
  203. catch
  204. {
  205. curvalue = _item.UserProperties.ContainsKey(c) ? _item.UserProperties[c] : null;
  206. }
  207. if (curvalue == null)
  208. return null;
  209. if (!curvalue.Equals(result))
  210. return null;
  211. }
  212. return result;
  213. };
  214. Editor.OnSetPropertyValue += (o, c, v) =>
  215. {
  216. foreach (var _item in _items)
  217. if (_item.UserProperties.ContainsKey(c))
  218. _item.UserProperties[c] = v;
  219. else
  220. CoreUtils.SetPropertyValue(_item, c, v);
  221. };
  222. Editor.OnEditorValueChanged += EditorValueChanged;
  223. Editor.OnDefineLookups += sender => { OnDefineLookups?.Invoke(sender); };
  224. Editor.OnLookupsDefined += sender => { OnLookupsDefined?.Invoke(sender); };
  225. Editor.OnGetDocument += id => { return OnGetDocument?.Invoke(id); };
  226. Editor.OnSaveDocument += doc => { OnSaveDocument?.Invoke(doc); };
  227. Editor.OnFindDocument += file => { return OnFindDocument?.Invoke(file); };
  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 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 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 != null ? OnValidateData.Invoke(this, Items) : null;
  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 IDynamicEditorControl FindEditor(string columnname)
  373. {
  374. return Editor.FindEditor(columnname);
  375. }
  376. public event DynamicGridSaveEvent OnSaveItem;
  377. private void Window_Closing(object sender, CancelEventArgs e)
  378. {
  379. if (DialogResult == true)
  380. OnSaveItem?.Invoke(this, e);
  381. }
  382. #region Win32 API Stuff
  383. // Define the Win32 API methods we are going to use
  384. [DllImport("user32.dll")]
  385. private static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);
  386. [DllImport("user32.dll")]
  387. private static extern bool InsertMenu(IntPtr hMenu, int wPosition, int wFlags, int wIDNewItem, string lpNewItem);
  388. /// Define our Constants we will use
  389. public const int WM_SYSCOMMAND = 0x112;
  390. public const int MF_SEPARATOR = 0x800;
  391. public const int MF_BYPOSITION = 0x400;
  392. public const int MF_STRING = 0x0;
  393. public const int _EditLayoutID = 1000;
  394. public const int _ResetLayoutID = 1001;
  395. public IntPtr Handle => new WindowInteropHelper(this).Handle;
  396. private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
  397. {
  398. // Check if a System Command has been executed
  399. if (msg == WM_SYSCOMMAND)
  400. // Execute the appropriate code for the System Menu item that was clicked
  401. switch (wParam.ToInt32())
  402. {
  403. case _EditLayoutID:
  404. Editor.EditLayout();
  405. handled = true;
  406. break;
  407. case _ResetLayoutID:
  408. if (MessageBox.Show(
  409. "WARNING: This will delete any customisations you have made!\n\nAre you sure you wish to reset this layout?",
  410. "Confirm Layout Reset", MessageBoxButton.OKCancel) == MessageBoxResult.OK)
  411. Editor.ResetLayout();
  412. handled = true;
  413. break;
  414. }
  415. return IntPtr.Zero;
  416. }
  417. private void ConfigureSystemMenu(object sender, RoutedEventArgs e)
  418. {
  419. /// Get the Handle for the Forms System Menu
  420. var systemMenuHandle = GetSystemMenu(Handle, false);
  421. /// Create our new System Menu items just before the Close menu item
  422. InsertMenu(systemMenuHandle, 5, MF_BYPOSITION | MF_SEPARATOR, 0, string.Empty); // <-- Add a menu seperator
  423. InsertMenu(systemMenuHandle, 6, MF_BYPOSITION, _EditLayoutID, "Edit Layout");
  424. InsertMenu(systemMenuHandle, 7, MF_BYPOSITION, _ResetLayoutID, "Reset Layout");
  425. // Attach our WndProc handler to this Window
  426. var source = HwndSource.FromHwnd(Handle);
  427. source.AddHook(WndProc);
  428. }
  429. #endregion
  430. //private void Wiki_Click(object sender, RoutedEventArgs e)
  431. //{
  432. // System.Diagnostics.Process.Start("https://prs-software.com.au/wiki/index.php/" + CurrentPanelSlug());
  433. //}
  434. //private string CurrentPanelSlug()
  435. //{
  436. // if ((Items != null) && Items.Any())
  437. // return Items.First().GetType().EntityName().Split('.').Last();
  438. // return "";
  439. //}
  440. }
  441. }