using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using InABox.Clients; using InABox.Core; using InABox.Wpf; using InABox.WPF; using NPOI.HSSF.Record.Aggregates; namespace InABox.DynamicGrid; public delegate void OnUpdateOtherEditorHandler(string columnname, object value); public delegate Dictionary EditorValueChangedHandler(IDynamicEditorForm sender, string name, object value); /// /// Interaction logic for DynamicEditorGrid.xaml /// public partial class DynamicEditorGrid : UserControl, IDynamicEditorHost { public delegate void EditorCreatedHandler(object sender, double height, double width); public delegate object? GetPropertyValueHandler(object sender, string name); public delegate void SetPropertyValueHandler(object sender, string name, object value); public delegate BaseObject[] GetItemsEvent(); // Column Definitions as defined by calling model private DynamicGridColumns _columns = new(); private Type? LayoutType; private DynamicEditorGridLayout? Layout; private bool _tabStripVisible = true; public bool TabStripVisible { get { return _tabStripVisible; } set { _tabStripVisible = value; if (Layout != null) Layout.TabStripVisible = value; } } public DynamicEditorGrid() { InitializeComponent(); Loaded += DynamicEditorGrid_Loaded; } private DynamicEditorPages _pages = new(); public IEnumerable Pages => _pages; private void AddPage(IDynamicEditorPage page) { page.ReadOnly = ReadOnly; _pages.Add(page); } private void SetPages(DynamicEditorPages pages) { _pages = pages; foreach (var page in _pages) { page.ReadOnly = ReadOnly; } } public bool PreloadPages { get; set; } public Type UnderlyingType { get; set; } public OnLoadPage? OnLoadPage { get; set; } public event OnSelectPage? OnSelectPage; public event OnUnloadPage? OnUnloadPage; public DynamicGridColumns Columns => _columns; private bool _readOnly; public bool ReadOnly { get => _readOnly; set { _readOnly = value; foreach(var page in Pages) { page.ReadOnly = value; } } } public IEnumerable Editors { get { foreach (var page in Pages) { if (page is DynamicEditPage editPage) { foreach(var editor in editPage.Editors) { yield return editor; } } } } } public bool TryFindEditor(string columnname, [NotNullWhen(true)] out IDynamicEditorControl? editor) { foreach (var page in Pages) { if (page is DynamicEditPage editPage) { if (editPage.TryFindEditor(columnname, out editor)) return true; } } editor = null; return false; } public IDynamicEditorControl? FindEditor(string columnname) { TryFindEditor(columnname, out var editor); return editor; } public virtual void ReconfigureEditors() { OnReconfigureEditors?.Invoke(this); } public object? GetPropertyValue(string columnname) { return OnGetPropertyValue?.Invoke(this, columnname); } public event EditorCreatedHandler? OnEditorCreated; public event OnCustomiseColumns? OnCustomiseColumns; public event OnGetEditor? OnGetEditor; public event OnGridCustomiseEditor? OnGridCustomiseEditor; public event OnGetEditorSequence? OnGetSequence; public event GetPropertyValueHandler? OnGetPropertyValue; public event SetPropertyValueHandler? OnSetPropertyValue; public delegate Dictionary EditorGridValueChangedHandler(DynamicEditorGrid sender, string name, object? value); public event EditorGridValueChangedHandler? OnEditorValueChanged; public event OnAfterEditorValueChanged? OnAfterEditorValueChanged; public event OnReconfigureEditors? OnReconfigureEditors; public event OnDefineLookupFilter? OnDefineFilter; public event OnDefineLookup? OnDefineLookups; public event GetItemsEvent? GetItems; private void DynamicEditorGrid_Loaded(object sender, RoutedEventArgs e) { //Reload(); } public void Reload() { LoadPages(); ReconfigureEditors(); } #region Host Implementation IEnumerable IDynamicEditorHost.Columns => Columns; public void LoadColumns(string column, Dictionary columns) { columns.Clear(); var comps = column.Split('.').ToList(); comps.RemoveAt(comps.Count - 1); var prefix = string.Format("{0}.", string.Join(".", comps)); var cols = Columns.Where(x => !x.ColumnName.Equals(column) && x.ColumnName.StartsWith(prefix)); foreach (var col in cols) { var subColumn = col.ColumnName[prefix.Length..]; columns[subColumn] = col.ColumnName; } } public void LoadLookups(ILookupEditorControl editor) { OnDefineLookups?.Invoke(editor); } BaseObject[] IDynamicEditorHost.GetItems() => GetItems?.Invoke() ?? Array.Empty(); public BaseEditor? GetEditor(DynamicGridColumn column) => OnGetEditor?.Invoke(column); public Type GetEditorType() => UnderlyingType; #endregion #region Edit Page public class DynamicEditPage : ContentControl, IDynamicEditorPage { private Grid Grid; public DynamicEditorGrid EditorGrid { get; set; } = null!; // Set by DynamicEditorGrid public bool Ready { get; set; } private List EditorList { get; set; } public IEnumerable Editors => EditorList; public PageType PageType => PageType.Editor; public int PageOrder { get; set; } public string Header { get; set; } private double GeneralHeight = 30; private bool _readOnly; public bool ReadOnly { get => _readOnly; set { if(_readOnly != value) { _readOnly = value; foreach(var editor in EditorList) { editor.IsEnabled = !value && editor.EditorDefinition.Editable.IsEditable(); } } } } public DynamicEditPage(string header) { Header = header; EditorList = []; InitialiseContent(); } public void AddEditor(string columnName, BaseEditor editor) { BaseDynamicEditorControl? element = DynamicEditorControlFactory.CreateControl(editor, EditorGrid); if (element != null) { element.IsEnabled = !ReadOnly && editor.Editable.IsEditable(); if (!string.IsNullOrWhiteSpace(editor.ToolTip)) { element.ToolTip = new ToolTip() { Content = editor.ToolTip }; } var label = new Label(); label.Content = CoreUtils.Neatify(editor.Caption); // 2 label.Margin = new Thickness(0F, 0F, 0F, 0F); label.HorizontalAlignment = HorizontalAlignment.Stretch; label.VerticalAlignment = VerticalAlignment.Stretch; label.HorizontalContentAlignment = HorizontalAlignment.Left; label.VerticalContentAlignment = VerticalAlignment.Center; label.SetValue(Grid.RowProperty, Grid.RowDefinitions.Count); label.SetValue(Grid.ColumnProperty, 0); label.Visibility = string.IsNullOrWhiteSpace(editor.Caption) ? Visibility.Collapsed : Visibility.Visible; Grid.Children.Add(label); element.ColumnName = columnName; element.Color = editor is UniqueCodeEditor ? Color.FromArgb(0xFF, 0xF6, 0xC9, 0xE8) : Colors.LightYellow; EditorList.Add(element); element.Margin = new Thickness(5F, 2.5F, 5F, 2.5F); double iHeight = element.DesiredHeight(); if (iHeight == int.MaxValue) { Grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }); GeneralHeight += element.MinHeight + 5.0F; } else { Grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(iHeight + 5.0F) }); GeneralHeight += iHeight + 5.0F; } double iWidth = element.DesiredWidth(); if (iWidth == int.MaxValue) { element.HorizontalAlignment = HorizontalAlignment.Stretch; } else { element.HorizontalAlignment = HorizontalAlignment.Left; element.Width = iWidth; } element.SetValue(Grid.RowProperty, Grid.RowDefinitions.Count - 1); element.SetValue(Grid.ColumnProperty, 1); Grid.Children.Add(element); } } [MemberNotNull(nameof(Grid))] private void InitialiseContent() { Grid = new Grid { HorizontalAlignment = HorizontalAlignment.Stretch, VerticalAlignment = VerticalAlignment.Stretch, Margin = new Thickness(0, 2.5, 0, 2.5) }; Grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }); Grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }); var scroll = new ScrollViewer { HorizontalAlignment = HorizontalAlignment.Stretch, VerticalAlignment = VerticalAlignment.Stretch, VerticalScrollBarVisibility = ScrollBarVisibility.Auto, Padding = new Thickness(2), Content = Grid }; var border = new Border { BorderBrush = new SolidColorBrush(Colors.Gray), Background = new SolidColorBrush(Colors.White), BorderThickness = new Thickness(0.75), Child = scroll }; Content = border; } public void AfterSave(object item) { } public event EventHandler? OnChanged; public void BeforeSave(object item) { } public string Caption() => Header; public bool TryFindEditor(string columnname, [NotNullWhen(true)] out IDynamicEditorControl? editor) { editor = EditorList.FirstOrDefault(x => x.ColumnName.Equals(columnname)); editor ??= EditorList.FirstOrDefault(x => columnname.StartsWith(x.ColumnName + '.')); return editor is not null; } public IEnumerable FindEditors(DynamicGridColumn column) { return EditorList.Where(x => string.Equals(x.ColumnName, column.ColumnName)); } #region Configure Editors private void Lookup_OnUpdateOtherEditor(string columnname, object value) { var editor = EditorList.FirstOrDefault(x => x.ColumnName.Equals(columnname)); if (editor != null) CoreUtils.SetPropertyValue(editor, "Value", value); } private void ConfigureEditors() { foreach (var editor in EditorList) { editor.Configure(); editor.Loaded = true; } } #endregion private void EditorValueChanged(IDynamicEditorControl sender, Dictionary values) { //Logger.Send(LogType.Information, "", string.Format("DynamicEditorGrid.EditorValueChanged({0})", values.Keys.Count)); var changededitors = new Dictionary(); void ExtractChanged(Dictionary? columns) { if (columns != null) foreach (var (change, value) in columns) if (!changededitors.ContainsKey(change) && !change.Equals(sender.ColumnName)) changededitors[change] = value; } var name = sender.ColumnName; var resetAll = false; if(values.Remove(name, out var value)) { var changedcolumns = EditorGrid.OnEditorValueChanged?.Invoke(EditorGrid, name, value); resetAll = changedcolumns?.ContainsKey(name) ?? false; ExtractChanged(changedcolumns); } else { } foreach (var (key, otherValue) in values) { var changes = new Dictionary(); if (resetAll) { var prop = DatabaseSchema.Property(EditorGrid.UnderlyingType, key); if(prop is not null) { var def = CoreUtils.GetDefault(prop.PropertyType); var resetChanges = EditorGrid.OnEditorValueChanged?.Invoke(EditorGrid, key, def); if(resetChanges is not null) { changes = resetChanges; } } else { } } var changedOtherColumns = EditorGrid.OnEditorValueChanged?.Invoke(EditorGrid, key, otherValue); if(changedOtherColumns is not null) { foreach (var (k, v) in changedOtherColumns) { changes[k] = v; } } ExtractChanged(changes); } var afterchanged = EditorGrid.OnAfterEditorValueChanged?.Invoke(EditorGrid, new AfterEditorValueChangedArgs(sender.ColumnName, changededitors)); ExtractChanged(afterchanged); if (changededitors.Count != 0) LoadEditorValues(changededitors); EditorGrid.ReconfigureEditors(); } private void LoadEditorValues(Dictionary? changededitors = null) { var columnnames = changededitors != null ? changededitors.Keys.ToArray() : EditorList.Select(x => x.ColumnName).ToArray(); foreach (var columnname in columnnames) { if (!TryFindEditor(columnname, out var editor)) continue; var bLoaded = editor.Loaded; editor.Loaded = false; if (changededitors != null && changededitors.ContainsKey(columnname)) { editor.SetValue(columnname, changededitors[columnname]); } else { var curvalue = EditorGrid.GetPropertyValue(columnname); try { editor.SetValue(columnname, curvalue); } catch (Exception e) { MessageWindow.ShowError($"Unable to set editor value for {columnname} -> {curvalue}", e); } editor.Changed = false; } editor.Loaded = bLoaded; editor.OnEditorValueChanged += EditorValueChanged; } } public void Load(object item, Func? PageDataHandler) { ConfigureEditors(); LoadEditorValues(); foreach (var editor in EditorList) { foreach(var (column, editorValue) in editor.GetValues()) { var entityValue = EditorGrid.GetPropertyValue(column); if (!Equals(editorValue, entityValue)) { bool bLoaded = editor.Loaded; editor.Loaded = false; editor.SetValue(column, entityValue); editor.Loaded = bLoaded; } } } EditorList.FirstOrDefault()?.SetFocus(); Ready = true; } public void DoChanged() { OnChanged?.Invoke(this, EventArgs.Empty); } public Size MinimumSize() => new Size(800, GeneralHeight); public int Order() => PageOrder; } #endregion #region Loading + Editing Layout private decimal GetSequence(DynamicGridColumn column) { if (OnGetSequence != null) return OnGetSequence.Invoke(column); return 999; } private DynamicEditPage GetEditPage(string name) { var page = Pages.Where(x => x is DynamicEditPage page && page.Header == name).FirstOrDefault() as DynamicEditPage; if(page is null) { page = new DynamicEditPage(name) { // Setting this here because it's needed now to be able to create the layout. EditorGrid = this }; if (name == "General") { page.PageOrder = -1; } else { page.PageOrder = 0; } AddPage(page); } return page; } public void SetLayoutType() where T : DynamicEditorGridLayout { LayoutType = typeof(T); } public void SetLayoutType(Type t) { if (!t.IsSubclassOf(typeof(DynamicEditorGridLayout))) throw new Exception($"{t.Name} is not a {nameof(DynamicEditorGridLayout)}!"); LayoutType = t; } private void InitialiseLayout() { Layout = (Activator.CreateInstance(LayoutType ?? typeof(DefaultDynamicEditorGridLayout)) as DynamicEditorGridLayout)!; Layout.OnSelectPage += Layout_SelectPage; Layout.TabStripVisible = _tabStripVisible; Content = Layout; } private void CreateLayout() { if(Layout is null) { InitialiseLayout(); } foreach (var column in _columns.OrderBy(x => GetSequence(x))) { var iProp = DatabaseSchema.Property(UnderlyingType, column.ColumnName); var editor = OnGetEditor?.Invoke(column); if (editor != null && iProp?.ShouldShowEditor() != true) { editor.Visible = Visible.Hidden; editor.Editable = Editable.Hidden; } if(editor is not null) { foreach(var security in editor.Security) { if (!Security.IsAllowed(security.SecurityDescriptor)) { editor.Editable = editor.Editable.Combine(security.Editable); if (editor.Editable == Editable.Hidden) { break; } } } } if(editor is not null) { OnGridCustomiseEditor?.Invoke(this, column, editor); } if (editor != null && editor.Editable.EditorVisible()) { var page = string.IsNullOrWhiteSpace(editor.Page) ? iProp is StandardProperty ? "General" : "Custom Fields" : editor.Page; var editPage = GetEditPage(page); editPage.AddEditor(column.ColumnName, editor); } else if (iProp?.HasParentEditor() == true) { // Add the parent editor if it hasn't been added already. var parent = iProp.GetParentWithEditor(); if(parent is not null) { var parentEditor = parent.Editor; if(parentEditor is not null) { OnGridCustomiseEditor?.Invoke(this, new DynamicGridColumn { ColumnName = parent.Name }, parentEditor); } if(parentEditor is not null && parentEditor.Editable.EditorVisible()) { var page = string.IsNullOrWhiteSpace(parentEditor.Page) ? parent is StandardProperty ? "General" : "Custom Fields" : parentEditor.Page; var editPage = GetEditPage(page); if (!editPage.TryFindEditor(parent.Name, out var editorControl)) { editPage.AddEditor(parent.Name, parentEditor); } } } } } OnEditorCreated?.Invoke(this, 0, 800); } #endregion #region Pages private void Layout_SelectPage(IDynamicEditorPage page) { if (!page.Ready) using (new WaitCursor()) { OnLoadPage?.Invoke(page); } OnSelectPage?.Invoke(this, null); } public void UnloadPages(bool saved) { if(Pages is not null) foreach (var page in Pages) if (page.Ready) OnUnloadPage?.Invoke(page, saved); } private void LoadPages() { if (Pages != null && Layout is not null) using (new WaitCursor()) { foreach (var page in Pages) { page.Ready = false; page.EditorGrid = this; } Layout.LoadPages(Pages); if (PreloadPages) { foreach(var page in Pages) { OnLoadPage?.Invoke(page); } } } } public void Load(DynamicEditorPages pages) { SetPages(pages); _columns = new DynamicGridColumns(); OnCustomiseColumns?.Invoke(this, _columns); CreateLayout(); Reload(); } #endregion }