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 RoslynPad.Editor; namespace InABox.DynamicGrid { public delegate void OnUpdateOtherEditorHandler(string columnname, object value); public delegate Dictionary EditorValueChangedHandler(object 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 Document? FindDocumentEvent(string FileName); public delegate Document? GetDocumentEvent(Guid id); public delegate object? GetPropertyValueHandler(object sender, string name); public delegate void SaveDocumentEvent(Document document); public delegate void SetPropertyValueHandler(object sender, string name, object value); public delegate object?[] 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; } public DynamicEditorPages Pages { get; private set; } = new(); 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; 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 event EditorValueChangedHandler? OnEditorValueChanged; public event OnAfterEditorValueChanged? OnAfterEditorValueChanged; public event OnReconfigureEditors? OnReconfigureEditors; public event OnDefineFilter? OnDefineFilter; public event OnDefineLookup? OnDefineLookups; public event GetDocumentEvent? OnGetDocument; public event FindDocumentEvent? OnFindDocument; public event SaveDocumentEvent? OnSaveDocument; 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) columns[col.ColumnName.Replace(prefix, "")] = col.ColumnName; } public IFilter? DefineFilter(Type type) => OnDefineFilter?.Invoke(type); public void LoadLookups(ILookupEditorControl editor) { OnDefineLookups?.Invoke(editor); } public Document? FindDocument(string filename) => OnFindDocument?.Invoke(filename); public Document? GetDocument(Guid id) => OnGetDocument?.Invoke(id); public void SaveDocument(Document document) => OnSaveDocument?.Invoke(document); object?[] IDynamicEditorHost.GetItems() => GetItems?.Invoke() ?? Array.Empty(); public BaseEditor? GetEditor(DynamicGridColumn column) => OnGetEditor?.Invoke(column); #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 Editors { get; set; } public PageType PageType => PageType.Editor; public int PageOrder { get; set; } public string Header { get; set; } private double GeneralHeight = 30; public DynamicEditPage(string header) { Header = header; Editors = new List(); InitialiseContent(); } public void AddEditor(string columnName, BaseEditor editor) { BaseDynamicEditorControl? element = DynamicEditorControlFactory.CreateControl(editor, EditorGrid); if (element != null) { element.EditorDefinition = editor; element.IsEnabled = editor.Editable == Editable.Enabled; 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; Editors.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 void BeforeSave(object item) { } public string Caption() => Header; public bool TryFindEditor(string columnname, [NotNullWhen(true)] out IDynamicEditorControl? editor) { editor = Editors.FirstOrDefault(x => x.ColumnName.Equals(columnname)); editor ??= Editors.FirstOrDefault(x => columnname.StartsWith(x.ColumnName + '.')); return editor is not null; } public IEnumerable FindEditors(DynamicGridColumn column) { return Editors.Where(x => string.Equals(x.ColumnName, column.ColumnName)); } #region Configure Editors private void Lookup_OnUpdateOtherEditor(string columnname, object value) { var editor = Editors.FirstOrDefault(x => x.ColumnName.Equals(columnname)); if (editor != null) CoreUtils.SetPropertyValue(editor, "Value", value); } private void ConfigureEditors() { foreach (var Editor in Editors) { var editor = Editor.EditorDefinition; var column = Editor.ColumnName; Editor.Configure(); if (!Editors.Any(x => x.ColumnName.Equals(Editor.ColumnName))) Editors.Add(Editor); 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; } foreach (var key in values.Keys) { var changedcolumns = EditorGrid.OnEditorValueChanged?.Invoke(EditorGrid, key, values[key]); ExtractChanged(changedcolumns); } var afterchanged = EditorGrid.OnAfterEditorValueChanged?.Invoke(EditorGrid, sender.ColumnName); ExtractChanged(afterchanged); if (changededitors.Any()) LoadEditorValues(changededitors); EditorGrid.ReconfigureEditors(); } private void LoadEditorValues(Dictionary? changededitors = null) { var columnnames = changededitors != null ? changededitors.Keys.ToArray() : Editors.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) { MessageBox.Show($"Unable to set editor value for {columnname} -> {curvalue}: {CoreUtils.FormatException(e)}"); } editor.Changed = false; } editor.Loaded = bLoaded; editor.OnEditorValueChanged += EditorValueChanged; } } public void Load(object item, Func? PageDataHandler) { ConfigureEditors(); LoadEditorValues(); foreach (var editor in Editors) { 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; } } } Editors.FirstOrDefault()?.SetFocus(); Ready = true; } 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; } Pages.Add(page); } return page; } public void SetLayoutType() where T : DynamicEditorGridLayout { LayoutType = typeof(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) { OnGridCustomiseEditor?.Invoke(this, column, editor); } if (editor != null && editor.Editable != Editable.Hidden) { 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) { 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 != Editable.Hidden) { 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) { Pages = pages; _columns = new DynamicGridColumns(); OnCustomiseColumns?.Invoke(this, _columns); CreateLayout(); Reload(); } #endregion } }