using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Interop; using System.Windows.Media; using System.Windows.Media.Imaging; using InABox.Core; using InABox.DynamicGrid.Properties; using InABox.Wpf; using InABox.WPF; using Syncfusion.Windows.Shared; using Syncfusion.Windows.Tools.Controls; namespace InABox.DynamicGrid { public delegate BaseEditor? DefineEditorEventHandler(object item, DynamicGridColumn column); public delegate void DynamicGridSaveEvent(object sender, CancelEventArgs args); public interface IDynamicEditorForm { } public class UtilityItem { public string Name { get; set; } public ImageSource Icon { get; set; } public string Text { get; set; } public SizeMode Mode { get; set; } public ICommand Command { get; set; } } public class UtilityViewModel { /// /// Constructor of the UtilityViewModel class. /// public UtilityViewModel() { var utilities = new ObservableCollection(); utilities.Add(new UtilityItem { Name = "Help", Icon = Resources.help.AsBitmapImage(), Text = "", Mode = SizeMode.Normal, Command = HelpCommand }); Utilities = utilities; } /// /// Collection containing the complete details of the items to be bound in the title bar. /// public ObservableCollection Utilities { get; } /// /// Commmand for the Help button. /// public DelegateCommand HelpCommand => new(HelpCommandAction); public static string Slug { get; set; } /// /// Action that is performed when clicking the help button. /// private void HelpCommandAction(object param) { Process.Start("https://prs-software.com.au/wiki/index.php/" + Slug); } } /// /// Interaction logic for DynamicEditor.xaml /// public partial class DynamicEditorForm : ThemableChromelessWindow, IDynamicEditorForm { public delegate Document? FindDocumentEvent(string FileName); public delegate Document? GetDocumentEvent(Guid id); public delegate void SaveDocumentEvent(Document document); private BaseObject[] _items; public DynamicEditorGrid Editor; public DynamicEditorForm(Type type, DynamicEditorPages? pages = null, DynamicEditorButtons? buttons = null, Func? PageDataHandler = null, bool PreloadPages = false) { ReadOnly = false; InitializeComponent(); //this.Loaded += new RoutedEventHandler(ConfigureSystemMenu); var grid = Content as Grid; Editor = grid.Children.OfType().FirstOrDefault(); Editor.UnderlyingType = type; Editor.OnCustomiseColumns += Editor_OnCustomiseColumns; Editor.OnDefineFilter += (sender, t) => OnDefineFilter?.Invoke(sender, t); Editor.OnEditorCreated += Editor_OnEditorCreated; Editor.OnLoadPage += page => { page.Load(Items.First(), PageDataHandler); }; Editor.OnSelectPage += (tab, items) => { OnSelectPage?.Invoke(tab, items); }; Editor.PreloadPages = PreloadPages; Editor.OnUnloadPage += (page, saved) => { if (!saved) page.BeforeSave(Items.First()); else page.AfterSave(Items.First()); }; //Editor.OnGetPropertyInfo += (o, c) => { return CoreUtils.GetProperty(_item.GetType(), c); }; Editor.ReconfigureEditors += g => { ReconfigureEditors?.Invoke(g); }; Editor.OnGetEditor += c => { if (_items != null && _items.Any()) { var property = DatabaseSchema.Property(type, c.ColumnName); if (property == null) return new NullEditor(); if (property.Editor is NullEditor) return property.Editor; BaseEditor editor; if (property is CustomProperty) { editor = property.Editor.CloneEditor(); } else { editor = OnDefineEditor?.Invoke(_items[0], c) ?? c.Editor.CloneEditor(); var propEditor = property.Editor; editor.Page = propEditor.Page; editor.Caption = propEditor.Caption; } //defaultEditor.EditorSequence //EditorUtils.GetPropertyEditor(type, property, defaultEditor); /*BaseEditor editor = new NullEditor(); var caption = ""; var page = ""; try { var comps = c.ColumnName.Split('.'); for (var i = 0; i < comps.Length; i++) { var column = string.Join(".", comps.Take(i + 1)); var prop = CoreUtils.GetProperty(type, column); if (column.Equals(c.ColumnName)) { if (OnDefineEditor != null) editor = OnDefineEditor(_items[0], c); else editor = c.Editor != null ? c.Editor : new NullEditor(); } else { var pedit = prop.GetEditor(); if (pedit is NullEditor) return pedit; } editor = editor == null ? new NullEditor() : editor.Clone() as BaseEditor; var capattr = prop.GetCustomAttribute(); var subcap = capattr != null ? capattr.Text : comps[i]; var path = capattr != null ? capattr.IncludePath : true; if (!string.IsNullOrWhiteSpace(subcap)) caption = string.IsNullOrWhiteSpace(caption) || path == false ? subcap : string.Format("{0} {1}", caption, subcap); if (string.IsNullOrWhiteSpace(page)) { var pageattr = prop.GetCustomAttribute(); if (pageattr != null) page = pageattr.Page; } } editor.Caption = caption; editor.Page = page; } catch (Exception e) { var dmprop = DatabaseSchema.Property(_items[0].GetType(), c.ColumnName); if (dmprop is CustomProperty) { editor = dmprop.Editor.Clone() as BaseEditor; editor.Caption = dmprop.Caption; editor.Page = string.IsNullOrWhiteSpace(dmprop.Page) ? "Custom Fields" : dmprop.Page; } }*/ if (ReadOnly && editor.Editable.Equals(Editable.Enabled)) editor.Editable = Editable.Disabled; return editor; } return new NullEditor(); }; Editor.OnGridCustomiseEditor += (sender, column, editor) => OnFormCustomiseEditor?.Invoke(this, Items, column, editor); Editor.OnGetSequence += c => CoreUtils.GetPropertySequence(_items.First().GetType(), c.ColumnName); Editor.OnGetPropertyValue += (o, c) => { if (!_items.Any()) return null; object? result; try { result = CoreUtils.GetPropertyValue(_items.First(), c); } catch { result = _items.First().UserProperties.ContainsKey(c) ? _items.First().UserProperties[c] : null; } if (result == null) return null; foreach (var _item in _items) { object? curvalue; try { curvalue = CoreUtils.GetPropertyValue(_item, c); } catch { curvalue = _item.UserProperties.ContainsKey(c) ? _item.UserProperties[c] : null; } if (curvalue == null) return null; if (!curvalue.Equals(result)) return null; } return result; }; Editor.OnSetPropertyValue += (o, c, v) => { foreach (var _item in _items) if (_item.UserProperties.ContainsKey(c)) _item.UserProperties[c] = v; else CoreUtils.SetPropertyValue(_item, c, v); }; Editor.OnEditorValueChanged += EditorValueChanged; Editor.OnDefineLookups += sender => { OnDefineLookups?.Invoke(sender); }; Editor.OnLookupsDefined += sender => { OnLookupsDefined?.Invoke(sender); }; Editor.OnGetDocument += id => { return OnGetDocument?.Invoke(id); }; Editor.OnSaveDocument += doc => { OnSaveDocument?.Invoke(doc); }; Editor.OnFindDocument += file => { return OnFindDocument?.Invoke(file); }; Pages = pages; if (Pages == null || Pages.Count == 0) Editor.Margin = new Thickness(5, 5, 5, 0); if (buttons != null) foreach (var button in buttons) { var btn = new Button(); UpdateButton(btn, button.Image, button.Name); btn.Tag = button; btn.Margin = new Thickness(5, 5, 0, 5); btn.Padding = new Thickness(5, 0, 5, 0); btn.Click += Btn_Click; Buttons.Children.Add(btn); button.Button = btn; button.Form = this; } } public DynamicEditorPages? Pages { get; } public BaseObject[] Items { get => _items; set { _items = value; UtilityViewModel.Slug = Items != null ? Items.Any() ? Items.First().GetType().EntityName().Split('.').Last() : "" : ""; Editor.Load(_items.First().GetType().EntityName(), Pages); } } public bool ReadOnly { get; set; } public OnValidateData? OnValidateData; public event OnCustomiseColumns? OnCustomiseColumns; public event OnDefineFilter? OnDefineFilter; public event DefineEditorEventHandler? OnDefineEditor; public event OnFormCustomiseEditor? OnFormCustomiseEditor; public event OnReconfigureEditors? ReconfigureEditors; //public delegate void EditorValueChangedHandler(object sender, String name, object value, List changes); //public event EditorValueChangedHandler OnEditorValueChanged; public event EditorValueChangedHandler? OnEditorValueChanged; //public event DefineFilter OnDefineFilter; public event OnDefineLookup? OnDefineLookups; public event OnLookupsDefined? OnLookupsDefined; public event GetDocumentEvent? OnGetDocument; public event FindDocumentEvent? OnFindDocument; public event SaveDocumentEvent? OnSaveDocument; public event OnSelectPage? OnSelectPage; public event DynamicGridSaveEvent? OnSaveItem; public void UnloadEditorPages(DynamicEditorPages pages, object item, bool saved) { Editor.UnloadPages(saved); } protected void UpdateButton(Button button, BitmapImage? image, string text) { var stackPnl = new StackPanel(); stackPnl.Orientation = Orientation.Horizontal; //stackPnl.Margin = new Thickness(2); if (image != null) { var img = new Image(); img.Source = image; img.Margin = new Thickness(2); stackPnl.Children.Add(img); } if (!string.IsNullOrEmpty(text)) { var lbl = new Label(); lbl.Content = text; lbl.VerticalAlignment = VerticalAlignment.Stretch; lbl.VerticalContentAlignment = VerticalAlignment.Center; lbl.Margin = new Thickness(2, 0, 5, 0); stackPnl.Children.Add(lbl); } button.Content = stackPnl; } private Dictionary EditorValueChanged(object sender, string name, object value) { if (OnEditorValueChanged != null) return OnEditorValueChanged(sender, name, value); return DynamicGridUtils.UpdateEditorValue(_items, name, value); } private void Editor_OnEditorCreated(object sender, double height, double width) { var screen = WpfScreen.GetScreenFrom(new Point(Left, Top)); double spareheight = 90; double sparewidth = 25; var desiredheight = height + spareheight; var desiredwidth = width + sparewidth; if (Pages != null) foreach (var page in Pages) { if (desiredheight < page.MinimumSize().Height) desiredheight = page.MinimumSize().Height; if (desiredwidth < page.MinimumSize().Width) desiredwidth = page.MinimumSize().Width; } var maxheight = screen.WorkingArea.Height - 0; Height = desiredheight > maxheight ? maxheight : desiredheight; var maxwidth = screen.WorkingArea.Width - 0; Width = desiredwidth > maxwidth ? maxwidth : desiredwidth; Left = screen.DeviceBounds.Left + (screen.DeviceBounds.Width - Width) / 2.0F; Top = screen.DeviceBounds.Top + (screen.DeviceBounds.Height - Height) / 2.0F; Editor.VerticalAlignment = VerticalAlignment.Stretch; Editor.HorizontalAlignment = HorizontalAlignment.Stretch; var scaption = _items[0].GetType().GetCaption(); Title = "Edit " + scaption.SplitCamelCase(); if (Editor.IsCustomLayout) Title = Title + "*"; OKButton.IsEnabled = !ReadOnly; } private DynamicGridColumns Editor_OnCustomiseColumns(object sender, DynamicGridColumns? source) { var columns = new DynamicGridColumns(); if (_items != null && _items.Any()) columns.ExtractColumns(_items.First().GetType(), ""); if (OnCustomiseColumns != null) columns = OnCustomiseColumns.Invoke(this, columns); return columns; } private void Btn_Click(object sender, RoutedEventArgs e) { var button = (Button)sender; var deb = (DynamicEditorButton)button.Tag; deb.Click(); } private void OKButton_Click(object sender, RoutedEventArgs e) { var errors = OnValidateData?.Invoke(this, Items); if (errors != null && errors.Any()) { MessageBox.Show( string.Format("The following errors have been found with your data!\nPlease correct them and try again.\n\n- {0}", string.Join("\n- ", errors)), "Validation Error"); return; } // Don't Commit the changes here, because we want to refer back to thos changes when we save the item // to trigger specific processes in the database DialogResult = true; //Close(); } private void CancelButton_Click(object sender, RoutedEventArgs e) { // However, if we cancel the edits, then we can safely revert the items back to their original (loaded) state foreach (var item in _items) item.CancelChanges(); DialogResult = false; //Close(); } public bool TryFindEditor(string columnname, [NotNullWhen(true)] out IDynamicEditorControl? editor) { return Editor.TryFindEditor(columnname, out editor); } public IDynamicEditorControl? FindEditor(string columnname) { return Editor.FindEditor(columnname); } private void Window_Closing(object sender, CancelEventArgs e) { if (DialogResult == true) OnSaveItem?.Invoke(this, e); } #region Win32 API Stuff // Define the Win32 API methods we are going to use [DllImport("user32.dll")] private static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert); [DllImport("user32.dll")] private static extern bool InsertMenu(IntPtr hMenu, int wPosition, int wFlags, int wIDNewItem, string lpNewItem); /// Define our Constants we will use public const int WM_SYSCOMMAND = 0x112; public const int MF_SEPARATOR = 0x800; public const int MF_BYPOSITION = 0x400; public const int MF_STRING = 0x0; public const int _EditLayoutID = 1000; public const int _ResetLayoutID = 1001; public IntPtr Handle => new WindowInteropHelper(this).Handle; private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { // Check if a System Command has been executed if (msg == WM_SYSCOMMAND) // Execute the appropriate code for the System Menu item that was clicked switch (wParam.ToInt32()) { case _EditLayoutID: Editor.EditLayout(); handled = true; break; case _ResetLayoutID: if (MessageBox.Show( "WARNING: This will delete any customisations you have made!\n\nAre you sure you wish to reset this layout?", "Confirm Layout Reset", MessageBoxButton.OKCancel) == MessageBoxResult.OK) Editor.ResetLayout(); handled = true; break; } return IntPtr.Zero; } private void ConfigureSystemMenu(object sender, RoutedEventArgs e) { /// Get the Handle for the Forms System Menu var systemMenuHandle = GetSystemMenu(Handle, false); /// Create our new System Menu items just before the Close menu item InsertMenu(systemMenuHandle, 5, MF_BYPOSITION | MF_SEPARATOR, 0, string.Empty); // <-- Add a menu seperator InsertMenu(systemMenuHandle, 6, MF_BYPOSITION, _EditLayoutID, "Edit Layout"); InsertMenu(systemMenuHandle, 7, MF_BYPOSITION, _ResetLayoutID, "Reset Layout"); // Attach our WndProc handler to this Window var source = HwndSource.FromHwnd(Handle); source.AddHook(WndProc); } #endregion //private void Wiki_Click(object sender, RoutedEventArgs e) //{ // System.Diagnostics.Process.Start("https://prs-software.com.au/wiki/index.php/" + CurrentPanelSlug()); //} //private string CurrentPanelSlug() //{ // if ((Items != null) && Items.Any()) // return Items.First().GetType().EntityName().Split('.').Last(); // return ""; //} } }