Pārlūkot izejas kodu

Tree View STuff

Kenric Nugteren 1 gadu atpakaļ
vecāks
revīzija
a22ae26f9d

+ 6 - 0
InABox.Core/Column.cs

@@ -359,6 +359,12 @@ namespace InABox.Core
             return Default(types);
         }
 
+        public Columns<T> Add(IEnumerable<Column<T>> columns)
+        {
+            this.columns.AddRange(columns);
+            return this;
+        }
+
         public Columns<T> Add(params string[] columnnames)
         {
             foreach (var name in columnnames)

+ 16 - 4
InABox.Core/CoreTreeNodes.cs

@@ -58,6 +58,17 @@ namespace InABox.Core
             }
         }
 
+        private CoreRow _row;
+        public CoreRow Row
+        {
+            get => _row;
+            set
+            {
+                _row = value;
+                RaisedOnPropertyChanged("Row");
+            }
+        }
+
         public event PropertyChangedEventHandler? PropertyChanged;
 
         public void RaisedOnPropertyChanged(string propertyName)
@@ -65,13 +76,14 @@ namespace InABox.Core
             PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
         }
 
-        public CoreTreeNode(CoreTreeNodes owner)
+        public CoreTreeNode(CoreTreeNodes owner, CoreRow row)
         {
             _owner = owner;
             _description = "";
+            _row = row;
         }
 
-        public CoreTreeNode(CoreTreeNodes owner, Guid id, Guid parent) : this(owner)
+        public CoreTreeNode(CoreTreeNodes owner, Guid id, Guid parent, CoreRow row) : this(owner, row)
         {
             _id = id;
             _parent = parent;
@@ -118,9 +130,9 @@ namespace InABox.Core
             _nodes = new List<CoreTreeNode>();
         }
 
-        public CoreTreeNode Add(Guid id, Guid parent)
+        public CoreTreeNode Add(Guid id, Guid parent, CoreRow row)
         {
-            var node = new CoreTreeNode(this, id, parent);
+            var node = new CoreTreeNode(this, id, parent, row);
             _nodes.Add(node);
             return node;
         }

+ 0 - 269
inabox.wpf/DynamicGrid/BaseDynamicGrid.cs

@@ -1,269 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Linq;
-using System.Linq.Expressions;
-using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
-using FastReport.Editor;
-using InABox.Core;
-using Syncfusion.Data;
-using static InABox.DynamicGrid.IDynamicGrid;
-using Selection = InABox.Core.Selection;
-
-namespace InABox.DynamicGrid
-{
-    public abstract class BaseDynamicGrid : ContentControl
-    {
-        public static readonly DependencyProperty UseWaitCursorProperty =
-            DependencyProperty.Register(nameof(UseWaitCursor), typeof(bool), typeof(BaseDynamicGrid));
-
-        protected enum ClipAction
-        {
-            Cut,
-            Copy
-        }
-
-        public BaseDynamicGrid()
-        {
-            UseWaitCursor = true;
-            Options = new FluentList<DynamicGridOption>();
-        }
-
-        public bool UseWaitCursor
-        {
-            get => (bool)GetValue(UseWaitCursorProperty);
-            set => SetValue(UseWaitCursorProperty, value);
-        }
-        
-        public static Brush SelectionBackground { get; set; }
-
-        public static Brush SelectionForeground { get; set; }
-
-        public static Brush FilterBackground { get; set; }
-        
-        
-        public FluentList<DynamicGridOption> Options { get; }
-        
-        public abstract void Reconfigure();
-        
-        public event IDynamicGrid.ReconfigureEvent? OnReconfigure;
-
-        protected void OnReconfigureEvent(FluentList<DynamicGridOption> options)
-        {
-            OnReconfigure?.Invoke(options);
-        }
-        
-        public virtual event OnCustomiseColumns? OnCustomiseColumns;
-
-        protected void DoCustomiseColumnsEvent(object sender, DynamicGridColumns columns) =>
-            OnCustomiseColumns?.Invoke(sender, columns);
-
-        static BaseDynamicGrid()
-        {
-            SelectionForeground = new SolidColorBrush(Colors.Black);
-            SelectionBackground = new SolidColorBrush(Colors.Silver);
-            FilterBackground = new SolidColorBrush(Color.FromArgb(0xFF, 0xE9, 0xF7, 0xC9));
-        }
-    }
-
-    public abstract class BaseDynamicGrid<T> : BaseDynamicGrid, IDynamicGrid where T : BaseObject, new()
-    {
-        public delegate void ValidateEvent(object sender, T[] items, List<string> errors);
-        
-        public event OnPrintData? OnPrintData;
-        
-        public event BeforeRefreshEventHandler? BeforeRefresh;
-        protected void NotifyBeforeRefresh(BeforeRefreshEventArgs args) => BeforeRefresh?.Invoke(this, args);
-        protected abstract bool OnBeforeRefresh();
-        
-        public event AfterRefreshEventHandler? AfterRefresh;
-        protected void NotifyAfterRefresh(AfterRefreshEventArgs args) => AfterRefresh?.Invoke(this, args);
-        protected abstract void OnAfterRefresh();
-        
-        public abstract event EntitySaveEvent? OnAfterSave;
-        public abstract event EntitySaveEvent? OnBeforeSave;
-
-        public event OnDefineFilter? OnDefineFilter;
-        public abstract event OnFilterRecord? OnFilterRecord;
-        public event OnCreateItem? OnCreateItem;
-        public abstract event OnCustomiseEditor<T>? OnCustomiseEditor;
-
-        public abstract event OnDoubleClick? OnDoubleClick;
-
-        public OnGetDynamicGridRowStyle? OnGetRowStyle { get; set; }
-        
-        public ValidateEvent? OnValidate { get; set; }
-        
-        protected DynamicGridRowStyleSelector<T> RowStyleSelector;
-
-        
-        
-        private bool _hasLoadedOptions = false;
-        
-        public BaseDynamicGrid()
-        {
-            
-            Options.OnChanged += (sender, args) =>
-            {
-                _hasLoadedOptions = true;
-                OptionsChanged();
-            };
-            
-            RowStyleSelector = GetRowStyleSelector();
-            RowStyleSelector.GetStyle += (row, style) => GetRowStyle(row, style);
-            
-            HiddenColumns = new HiddenColumnsList();
-        }
-
-        /// <summary>
-        /// Initialise things like custom buttons; called once during construction.
-        /// </summary>
-        protected abstract void Init();
-
-        protected abstract void DoReconfigure(FluentList<DynamicGridOption> options);
-
-        /// <summary>
-        /// Configure custom buttons and options.
-        /// </summary>
-        public void Reconfigure(FluentList<DynamicGridOption> options)
-        {
-            options.BeginUpdate().Clear();
-            DoReconfigure(options);
-            OnReconfigureEvent(options);
-            options.EndUpdate();
-            if (!_hasLoadedOptions)
-            {
-                _hasLoadedOptions = true;
-                OptionsChanged();
-            }
-        }
-        
-        public override void Reconfigure()
-        {
-            Reconfigure(Options);
-        }
-        
-        public void Reconfigure(ReconfigureEvent onReconfigure)
-        {
-            OnReconfigure += onReconfigure;
-            Reconfigure();
-        }
-        public bool HasOption(DynamicGridOption option, IEnumerable<DynamicGridOption>? options = null) => (options ?? Options).Contains(option);
-        bool IDynamicGrid.HasOption(InABox.DynamicGrid.DynamicGridOption option) => HasOption(option, null);
-
-        public class HiddenColumnsList
-        {
-            private List<string> Columns { get; set; } = new();
-
-            public IEnumerable<string> ColumnNames => Columns;
-
-            public void Add(Expression<Func<T, object?>> column) => Add(CoreUtils.GetFullPropertyName(column, "."));
-
-            public void Add(IColumn column) => Add(column.Property);
-
-            public void Add(string column)
-            {
-                if (!Contains(column))
-                    Columns.Add(column);
-            }
-
-            public bool Contains(string column) => Columns.Contains(column);
-        }
-
-        public HiddenColumnsList HiddenColumns { get; }
-
-        public void InitialiseEditorForm(IDynamicEditorForm editor, object[] items, Func<Type, CoreTable>? pageDataHandler = null, bool preloadPages = false)
-        {
-            InitialiseEditorForm(editor, items.Cast<T>().ToArray(), pageDataHandler, preloadPages);
-        }
-        public virtual bool EditItems(object[] items, Func<Type, CoreTable?>? PageDataHandler = null, bool PreloadPages = false)
-        {
-            var values = items.Cast<T>().ToArray();
-            return EditItems(values, PageDataHandler, PreloadPages);
-        }
-
-        //public abstract bool DirectEdit(CoreTable data);
-
-        public DynamicGridColumns MasterColumns { get; protected set; }
-        public DynamicGridColumns VisibleColumns { get; protected set; }
-        public CoreTable Data { get; set; }
-
-        public virtual void ConfigureColumns(DynamicGridColumns columns)
-        {
-            DoCustomiseColumnsEvent(this,columns);
-        }
-
-        public abstract CoreRow[] SelectedRows { get; set; }
-
-        public abstract void AddVisualFilter(string column, string value, FilterType filtertype = FilterType.Contains);
-
-        public abstract Button AddButton(string? caption, BitmapImage? image, string? tooltip, DynamicGridButtonClickEvent action,
-            DynamicGridButtonPosition position = DynamicGridButtonPosition.Left);
-
-        public Button AddButton(string? caption, BitmapImage? image, DynamicGridButtonClickEvent action, DynamicGridButtonPosition position = DynamicGridButtonPosition.Left)
-        {
-            var result = AddButton(caption, image, null, action, position);
-            return result;
-        }
-
-        public abstract void UpdateButton(Button button, BitmapImage? image, string? text, string? tooltip = null);
-
-        public virtual double RowHeight { get; set; }
-        public virtual double HeaderHeight { get; set; }
-        public new virtual double FontSize { get; set; }
-
-        public void AddHiddenColumn(string column) => HiddenColumns.Add(column);
-
-        public abstract int DesiredWidth();
-
-        public abstract void Refresh(bool reloadcolumns, bool reloaddata);
-
-        public abstract void UpdateRow<TType>(CoreRow row, string column, TType value, bool refresh = true);
-        public abstract void UpdateRow<TRow, TType>(CoreRow row, Expression<Func<TRow, TType>> column, TType value, bool refresh = true);
-        
-        protected abstract DynamicGridRowStyleSelector<T> GetRowStyleSelector();
-
-        protected virtual DynamicGridStyle GetRowStyle(CoreRow row, DynamicGridStyle style)
-        {
-            return OnGetRowStyle != null ? OnGetRowStyle(row, style) : style;
-        }
-        
-        public abstract void InitialiseEditorForm(IDynamicEditorForm editor, T[] items, Func<Type, CoreTable>? pageDataHandler = null, bool preloadPages = false);
-        public abstract bool EditItems(T[] items, Func<Type, CoreTable?>? PageDataHandler = null, bool PreloadPages = false);
-
-        public Filter<T>? DefineFilter()
-        {
-            if (OnDefineFilter is null)
-                return null;
-            var result = OnDefineFilter.Invoke(typeof(T)) as Filter<T>;
-            return result;
-        }
-
-        protected virtual T CreateItem()
-        {
-            var result = new T();
-            OnCreateItem?.Invoke(this, result);
-            return result;
-        }
-
-        protected virtual void DoPrint(object sender)
-        {
-            OnPrintData?.Invoke(sender);
-        }
-
-        protected abstract void OptionsChanged();
-        
-        public IEnumerable<TType> ExtractValues<TType>(Expression<Func<T, TType>> column, Selection selection)
-        {
-            var result = selection == Selection.None
-                ? Enumerable.Empty<TType>()
-                : selection == Selection.Selected
-                    ? SelectedRows.Select(r => r.Get(column))
-                    : Data.ExtractValues(column);
-            return result;
-        }
-    }
-}

+ 3 - 3
inabox.wpf/DynamicGrid/DynamicEnclosedListGrid.cs

@@ -132,12 +132,12 @@ namespace InABox.DynamicGrid
             ColumnsComponent.LoadColumnsMenu(menu);
         }
 
-        protected override TMany LoadItem(CoreRow row)
+        public override TMany LoadItem(CoreRow row)
         {
             return Items[row.Index];
         }
 
-        protected override TMany[] LoadItems(CoreRow[] rows)
+        public override TMany[] LoadItems(CoreRow[] rows)
         {
             var result = new List<TMany>();
             foreach (var row in rows)
@@ -156,7 +156,7 @@ namespace InABox.DynamicGrid
             }
         }
 
-        protected override void DeleteItems(params CoreRow[] rows)
+        public override void DeleteItems(params CoreRow[] rows)
         {
             foreach (var row in rows.OrderByDescending(x => x.Index))
                 Items.RemoveAt(row.Index);

+ 1779 - 2881
inabox.wpf/DynamicGrid/DynamicGrid.cs

@@ -32,6 +32,7 @@ using Syncfusion.UI.Xaml.Grid;
 using Syncfusion.UI.Xaml.Grid.Cells;
 using Syncfusion.UI.Xaml.Grid.Helpers;
 using Syncfusion.Windows.Shared;
+using static InABox.DynamicGrid.IDynamicGrid;
 using Brush = System.Windows.Media.Brush;
 using Color = System.Drawing.Color;
 using Columns = InABox.Core.Columns;
@@ -49,3455 +50,2352 @@ using String = System.String;
 using VerticalAlignment = System.Windows.VerticalAlignment;
 using VirtualizingCellsControl = Syncfusion.UI.Xaml.Grid.VirtualizingCellsControl;
 
-namespace InABox.DynamicGrid
+namespace InABox.DynamicGrid;
+
+public class DynamicGridRowStyle : DynamicGridStyle<VirtualizingCellsControl>
 {
-    
-    
+    public DynamicGridRowStyle() : base(null)
+    {
+    }
 
-    public class DynamicGridRowStyle : DynamicGridStyle<VirtualizingCellsControl>
+    public DynamicGridRowStyle(IDynamicGridStyle source) : base(source)
     {
-        public DynamicGridRowStyle() : base(null)
-        {
-        }
+    }
 
-        public DynamicGridRowStyle(IDynamicGridStyle source) : base(source)
-        {
-        }
+    public override DependencyProperty FontSizeProperty => Control.FontSizeProperty;
 
-        public override DependencyProperty FontSizeProperty => Control.FontSizeProperty;
+    public override DependencyProperty FontStyleProperty => Control.FontStyleProperty;
 
-        public override DependencyProperty FontStyleProperty => Control.FontStyleProperty;
+    public override DependencyProperty FontWeightProperty => Control.FontWeightProperty;
 
-        public override DependencyProperty FontWeightProperty => Control.FontWeightProperty;
+    public override DependencyProperty BackgroundProperty => Control.BackgroundProperty;
 
-        public override DependencyProperty BackgroundProperty => Control.BackgroundProperty;
+    public override DependencyProperty ForegroundProperty => Control.ForegroundProperty;
+}
 
-        public override DependencyProperty ForegroundProperty => Control.ForegroundProperty;
+public class DynamicGridCellStyle : DynamicGridStyle<Control>
+{
+    public DynamicGridCellStyle() : base(null)
+    {
     }
 
-    public class DynamicGridCellStyle : DynamicGridStyle<Control>
+    public DynamicGridCellStyle(IDynamicGridStyle source) : base(source)
     {
-        public DynamicGridCellStyle() : base(null)
-        {
-        }
+    }
 
-        public DynamicGridCellStyle(IDynamicGridStyle source) : base(source)
-        {
-        }
+    public override DependencyProperty FontSizeProperty => Control.FontSizeProperty;
 
-        public override DependencyProperty FontSizeProperty => Control.FontSizeProperty;
+    public override DependencyProperty FontStyleProperty => Control.FontStyleProperty;
 
-        public override DependencyProperty FontStyleProperty => Control.FontStyleProperty;
+    public override DependencyProperty FontWeightProperty => Control.FontWeightProperty;
 
-        public override DependencyProperty FontWeightProperty => Control.FontWeightProperty;
+    public override DependencyProperty BackgroundProperty => Control.BackgroundProperty;
 
-        public override DependencyProperty BackgroundProperty => Control.BackgroundProperty;
+    public override DependencyProperty ForegroundProperty => Control.ForegroundProperty;
+}
 
-        public override DependencyProperty ForegroundProperty => Control.ForegroundProperty;
+public class GridSelectionControllerExt : GridSelectionController
+{
+    public GridSelectionControllerExt(SfDataGrid datagrid)
+        : base(datagrid)
+    {
     }
 
-    public class GridSelectionControllerExt : GridSelectionController
+    protected override void ProcessSelectedItemChanged(SelectionPropertyChangedHandlerArgs handle)
     {
-        public GridSelectionControllerExt(SfDataGrid datagrid)
-            : base(datagrid)
-        {
-        }
-
-        protected override void ProcessSelectedItemChanged(SelectionPropertyChangedHandlerArgs handle)
+        base.ProcessSelectedItemChanged(handle);
+        if (handle.NewValue != null)
         {
-            base.ProcessSelectedItemChanged(handle);
-            if (handle.NewValue != null)
-            {
-                //this.DataGrid.ScrollInView(this.CurrentCellManager.CurrentRowColumnIndex);
-                //int rowIndex = this.CurrentCellManager.CurrentRowColumnIndex.RowIndex;
-                var columnIndex = CurrentCellManager.CurrentRowColumnIndex.ColumnIndex;
-                var scrollRowIndex = DataGrid.GetVisualContainer().ScrollRows.LastBodyVisibleLineIndex;
-                DataGrid.ScrollInView(new RowColumnIndex(scrollRowIndex, columnIndex));
-            }
+            //this.DataGrid.ScrollInView(this.CurrentCellManager.CurrentRowColumnIndex);
+            //int rowIndex = this.CurrentCellManager.CurrentRowColumnIndex.RowIndex;
+            var columnIndex = CurrentCellManager.CurrentRowColumnIndex.ColumnIndex;
+            var scrollRowIndex = DataGrid.GetVisualContainer().ScrollRows.LastBodyVisibleLineIndex;
+            DataGrid.ScrollInView(new RowColumnIndex(scrollRowIndex, columnIndex));
         }
     }
+}
 
-    public class DynamicGridSummaryStyleSelector : StyleSelector
-    {
-        private readonly IDynamicGrid _grid;
-
-        public DynamicGridSummaryStyleSelector(IDynamicGrid grid)
-        {
-            _grid = grid;
-        }
+public class DynamicGridSummaryStyleSelector : StyleSelector
+{
+    private readonly IDynamicGrid _grid;
 
-        public override Style SelectStyle(object item, DependencyObject container)
-        {
-            var vcol = ((GridTableSummaryCell)container).ColumnBase.ColumnIndex;
-            var col = vcol > -1 && vcol < _grid.VisibleColumns.Count ? _grid.VisibleColumns[vcol] : null;
-
-            var style = new Style(typeof(GridTableSummaryCell));
-            style.Setters.Add(new Setter(Control.BackgroundProperty, new SolidColorBrush(Colors.Gainsboro)));
-            style.Setters.Add(new Setter(Control.ForegroundProperty, new SolidColorBrush(Colors.Black)));
-            style.Setters.Add(new Setter(Control.HorizontalContentAlignmentProperty,
-                col != null ? col.HorizontalAlignment(typeof(double)) : HorizontalAlignment.Right));
-            style.Setters.Add(new Setter(Control.BorderBrushProperty, new SolidColorBrush(Colors.Gray)));
-            style.Setters.Add(new Setter(Control.BorderThicknessProperty, new Thickness(0, 0, 0.75, 0)));
-            style.Setters.Add(new Setter(Control.FontSizeProperty, 12D));
-            style.Setters.Add(new Setter(Control.FontWeightProperty, FontWeights.DemiBold));
-            return style;
-        }
+    public DynamicGridSummaryStyleSelector(IDynamicGrid grid)
+    {
+        _grid = grid;
     }
 
-    // Used to render boolean columns (the default "false" value shows what appears to be an intermediate state, which is ugly
-    // This should show nothing for false, and a tick in a box for true
-    public class BoolToImageConverter : UtilityConverter<bool,ImageSource>
+    public override Style SelectStyle(object item, DependencyObject container)
     {
+        var vcol = ((GridTableSummaryCell)container).ColumnBase.ColumnIndex;
+        var col = vcol > -1 && vcol < _grid.VisibleColumns.Count ? _grid.VisibleColumns[vcol] : null;
+
+        var style = new Style(typeof(GridTableSummaryCell));
+        style.Setters.Add(new Setter(Control.BackgroundProperty, new SolidColorBrush(Colors.Gainsboro)));
+        style.Setters.Add(new Setter(Control.ForegroundProperty, new SolidColorBrush(Colors.Black)));
+        style.Setters.Add(new Setter(Control.HorizontalContentAlignmentProperty,
+            col != null ? col.HorizontalAlignment(typeof(double)) : HorizontalAlignment.Right));
+        style.Setters.Add(new Setter(Control.BorderBrushProperty, new SolidColorBrush(Colors.Gray)));
+        style.Setters.Add(new Setter(Control.BorderThicknessProperty, new Thickness(0, 0, 0.75, 0)));
+        style.Setters.Add(new Setter(Control.FontSizeProperty, 12D));
+        style.Setters.Add(new Setter(Control.FontWeightProperty, FontWeights.DemiBold));
+        return style;
+    }
+}
 
-        public ImageSource TrueValue { get; set; }
-        public ImageSource FalseValue { get; set; }
+// Used to render boolean columns (the default "false" value shows what appears to be an intermediate state, which is ugly
+// This should show nothing for false, and a tick in a box for true
+public class BoolToImageConverter : UtilityConverter<bool,ImageSource>
+{
 
-        public BoolToImageConverter()
-        {
-            TrueValue = Wpf.Resources.Bullet_Tick.AsBitmapImage();
-        }
-        
-        public override ImageSource Convert(bool value)
-        {
-            return value ? TrueValue : FalseValue;
-        }
+    public ImageSource TrueValue { get; set; }
+    public ImageSource FalseValue { get; set; }
 
-        public override bool Deconvert(ImageSource value)
-        {
-            return ImageUtils.IsEqual(value as BitmapImage,TrueValue as BitmapImage);
-        }
+    public BoolToImageConverter()
+    {
+        TrueValue = Wpf.Resources.Bullet_Tick.AsBitmapImage();
     }
-
-    public class StringToColorImageConverter : IValueConverter
+    
+    public override ImageSource Convert(bool value)
     {
-        private readonly int _height = 50;
-        private readonly int _width = 25;
-        private readonly Dictionary<string, BitmapImage> cache = new();
-
-        public StringToColorImageConverter(int width, int height)
-        {
-            _width = width;
-            _height = height;
-        }
+        return value ? TrueValue : FalseValue;
+    }
 
-        public object? Convert(object value, Type targetType, object parameter, CultureInfo culture)
-        {
-            var str = value?.ToString();
-            if (str is null)
-                return null;
+    public override bool Deconvert(ImageSource value)
+    {
+        return ImageUtils.IsEqual(value as BitmapImage,TrueValue as BitmapImage);
+    }
+}
 
-            var colorcode = str.TrimStart('#');
+public class StringToColorImageConverter : IValueConverter
+{
+    private readonly int _height = 50;
+    private readonly int _width = 25;
+    private readonly Dictionary<string, BitmapImage> cache = new();
 
-            if (!cache.ContainsKey(colorcode))
-            {
-                var col = ImageUtils.StringToColor(colorcode);
-                var bmp = ImageUtils.BitmapFromColor(col, _width, _height, Color.Black);
-                cache[colorcode] = bmp.AsBitmapImage();
-            }
+    public StringToColorImageConverter(int width, int height)
+    {
+        _width = width;
+        _height = height;
+    }
 
-            var result = cache[colorcode];
-            return result;
-        }
+    public object? Convert(object value, Type targetType, object parameter, CultureInfo culture)
+    {
+        var str = value?.ToString();
+        if (str is null)
+            return null;
 
+        var colorcode = str.TrimStart('#');
 
-        public object? ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        if (!cache.ContainsKey(colorcode))
         {
-            return null;
+            var col = ImageUtils.StringToColor(colorcode);
+            var bmp = ImageUtils.BitmapFromColor(col, _width, _height, Color.Black);
+            cache[colorcode] = bmp.AsBitmapImage();
         }
+
+        var result = cache[colorcode];
+        return result;
     }
 
-    public class StringArrayConverter : IValueConverter
+
+    public object? ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
     {
-        object? IValueConverter.Convert(object value, Type targetType, object parameter, CultureInfo culture)
-        {
-            if (value is string[] strArray)
-            {
-                return string.Join("\n", strArray);
-            }
-            Logger.Send(LogType.Error, "", $"Attempt to convert an object which is not a string array: {value}.");
-            return null;
-        }
+        return null;
+    }
+}
 
-        object IValueConverter.ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+public class StringArrayConverter : IValueConverter
+{
+    object? IValueConverter.Convert(object value, Type targetType, object parameter, CultureInfo culture)
+    {
+        if (value is string[] strArray)
         {
-            return value;
+            return string.Join("\n", strArray);
         }
+        Logger.Send(LogType.Error, "", $"Attempt to convert an object which is not a string array: {value}.");
+        return null;
     }
 
-    [Serializable]
-    class DynamicGridDragFormat
+    object IValueConverter.ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
     {
-        private string entity;
+        return value;
+    }
+}
 
-        public DataTable Table { get; set; }
+[Serializable]
+class DynamicGridDragFormat
+{
+    private string entity;
 
-        public Type Entity { get => CoreUtils.GetEntity(entity); set => entity = value.EntityName(); }
+    public DataTable Table { get; set; }
 
-        public DynamicGridDragFormat(DataTable table, Type entity)
-        {
-            Table = table;
-            Entity = entity;
-        }
+    public Type Entity { get => CoreUtils.GetEntity(entity); set => entity = value.EntityName(); }
+
+    public DynamicGridDragFormat(DataTable table, Type entity)
+    {
+        Table = table;
+        Entity = entity;
     }
-    
-    public abstract class DynamicGrid<T> : BaseDynamicGrid<T> where T : BaseObject, new()
+}
+
+public abstract class DynamicGrid<T> : ContentControl, IDynamicGridUIComponentParent<T>, IDynamicGrid<T>
+    where T : BaseObject, new()
+{
+    public static readonly DependencyProperty UseWaitCursorProperty =
+        DependencyProperty.Register(nameof(UseWaitCursor), typeof(bool), typeof(DynamicGrid<>));
+
+    protected enum ClipAction
     {
-        private readonly Dictionary<string, string> _filterpredicates = new();
+        Cut,
+        Copy
+    }
 
-        private UIElement? _header;
-        private readonly Button Add;
+    private IDynamicGridUIComponent<T> UIComponent { get; set; }
 
-        private bool bChanged;
+    private UIElement? _header;
+    private readonly Button Add;
 
-        public bool bRefreshing;
-        private readonly Label ClipboardSpacer;
+    public bool bRefreshing;
+    bool IDynamicGridUIComponentParent<T>.IsRefreshing => bRefreshing;
 
-        private readonly ContextMenu ColumnsMenu;
-        private readonly Button Copy;
+    private readonly Label ClipboardSpacer;
 
-        private readonly Label Count;
+    private readonly Button Copy;
 
-        private readonly Button Cut;
-        private readonly SfDataGrid DataGrid;
-        private readonly Border Disabler;
+    private readonly Label Count;
 
-        private readonly Button Delete;
-        private readonly DockPanel Docker;
-        private readonly DynamicRowMovementColumn? down;
-        private readonly Button Edit;
-        private readonly Label EditSpacer;
-        private readonly Button Export;
-        private readonly Label ExportSpacer;
-        private readonly Button DuplicateBtn;
-        private readonly Button SwitchViewBtn;
+    private readonly Button Cut;
+    private readonly Border Disabler;
 
-        private readonly GridRowSizingOptions gridRowResizingOptions = new() { CanIncludeHiddenColumns = false, AutoFitMode = AutoFitMode.Default };
-        private readonly Button Help;
-        private readonly Button Import;
+    private readonly Button Delete;
+    private readonly DockPanel Docker;
+    private readonly DynamicRowMovementColumn? down;
+    private readonly Button Edit;
+    private readonly Label EditSpacer;
+    private readonly Button Export;
+    private readonly Label ExportSpacer;
+    private readonly Button DuplicateBtn;
+    private readonly Button SwitchViewBtn;
 
-        private class DirectEditingObject
-        {
-            public T Object { get; set; }
+    private readonly Button Help;
+    private readonly Button Import;
 
-            public CoreRow Row { get; set; }
 
-            public DataRow? DataRow { get; set; }
+    private readonly Grid Layout;
+    private readonly Label Loading;
 
-            public DirectEditingObject(T obj, CoreRow row, DataRow? dataRow)
-            {
-                Object = obj;
-                Row = row;
-                DataRow = dataRow;
-            }
-        }
+    private DoubleAnimation LoadingFader = new DoubleAnimation(1d, 0.2d, new Duration(TimeSpan.FromSeconds(2))) { AutoReverse = true };
 
-        private DirectEditingObject? _editingObject;
+    //private readonly Button MultiEdit;
+    private readonly Button Paste;
 
+    private readonly Button Print;
+    private readonly Label PrintSpacer;
 
-        private readonly Grid Layout;
-        private readonly Label Loading;
+    private readonly StackPanel LeftButtonStack;
+    private readonly StackPanel RightButtonStack;
 
-        private DoubleAnimation LoadingFader = new DoubleAnimation(1d, 0.2d, new Duration(TimeSpan.FromSeconds(2))) { AutoReverse = true };
+    private readonly DynamicRowMovementColumn? up;
 
-        protected Dictionary<string, CoreTable> Lookups = new();
-        //private readonly Button MultiEdit;
-        private readonly Button Paste;
+    protected DynamicGridRowStyleSelector<T> RowStyleSelector;
+
+    public bool UseWaitCursor
+    {
+        get => (bool)GetValue(UseWaitCursorProperty);
+        set => SetValue(UseWaitCursorProperty, value);
+    }
 
-        private readonly Button Print;
-        private readonly Label PrintSpacer;
+    #region Events
 
-        private readonly StackPanel LeftButtonStack;
-        private readonly StackPanel RightButtonStack;
+    public event IDynamicGrid.ReconfigureEvent? OnReconfigure;
 
-        private readonly DynamicRowMovementColumn? up;
+    public OnGetDynamicGridRowStyle? OnGetRowStyle { get; set; }
+    
+    public ValidateEvent<T>? OnValidate { get; set; }
+    
 
-        /// <summary>
-        /// <see langword="null"/> when <see cref="DataGrid.ItemsSource"/> is <see langword="null"/>, generally while the grid is refreshing its columns.
-        /// </summary>
-        private DataTable? DataGridItems => (DataGrid.ItemsSource as DataTable);
+    public event OnPrintData? OnPrintData;
+    
+    public event BeforeRefreshEventHandler? BeforeRefresh;
+    public event AfterRefreshEventHandler? AfterRefresh;
 
-        #region Events
+    public event OnDefineFilter? OnDefineFilter;
+    public event OnCreateItem? OnCreateItem;
 
-        /// <summary>
-        /// Called when an item is selected in the grid. It is not called if <see cref="IsReady"/> is not <see langword="true"/>.
-        /// </summary>
-        /// <remarks>
-        /// It is unnecessary to use this if within a grid. Instead, override <see cref="SelectItems(CoreRow[]?)"/>.
-        /// </remarks>
-        public event SelectItemHandler? OnSelectItem;
+    /// <summary>
+    /// Called when an item is selected in the grid. It is not called if <see cref="IsReady"/> is not <see langword="true"/>.
+    /// </summary>
+    /// <remarks>
+    /// It is unnecessary to use this if within a grid. Instead, override <see cref="SelectItems(CoreRow[]?)"/>.
+    /// </remarks>
+    public event SelectItemHandler? OnSelectItem;
 
-        public event OnCellDoubleClick? OnCellDoubleClick;
+    public event OnCellDoubleClick? OnCellDoubleClick;
 
-        public event EventHandler? OnChanged;
+    public event EventHandler? OnChanged;
 
-        public delegate void BeforeSelectionEvent(CancelEventArgs cancel);
-        public event BeforeSelectionEvent? OnBeforeSelection;
+    public delegate void BeforeSelectionEvent(CancelEventArgs cancel);
+    public event BeforeSelectionEvent? OnBeforeSelection;
 
-        public void DoChanged() => OnChanged?.Invoke(this, EventArgs.Empty);
+    public void DoChanged() => OnChanged?.Invoke(this, EventArgs.Empty);
 
-        public event EditorValueChangedHandler? OnEditorValueChanged;
+    public event EditorValueChangedHandler? OnEditorValueChanged;
 
-        public override event OnCustomiseEditor<T>? OnCustomiseEditor;
+    public event OnCustomiseEditor<T>? OnCustomiseEditor;
 
-        public override event OnFilterRecord? OnFilterRecord;
+    public event OnFilterRecord? OnFilterRecord;
 
-        public override event OnDoubleClick? OnDoubleClick;
+    public event OnDoubleClick? OnDoubleClick;
 
-        public override event EntitySaveEvent? OnBeforeSave;
-        public override event EntitySaveEvent? OnAfterSave;
+    public event EntitySaveEvent? OnBeforeSave;
+    public event EntitySaveEvent? OnAfterSave;
 
-        public delegate void EditorLoaded(IDynamicEditorForm editor, T[] items);
-        public event EditorLoaded OnEditorLoaded;
+    public delegate void EditorLoaded(IDynamicEditorForm editor, T[] items);
+    public event EditorLoaded OnEditorLoaded;
 
-        public event OnLoadEditorButtons<T> OnLoadEditorButtons;
+    public event OnLoadEditorButtons<T> OnLoadEditorButtons;
+    
+    public virtual event OnCustomiseColumns? OnCustomiseColumns;
 
-        #endregion
+    protected void DoCustomiseColumnsEvent(object sender, DynamicGridColumns columns) =>
+        OnCustomiseColumns?.Invoke(sender, columns);
 
-        private DynamicGridCellStyleConverter<System.Windows.Media.Brush?> CellBackgroundConverter;
-        private DynamicGridCellStyleConverter<System.Windows.Media.Brush?> CellForegroundConverter;
-        private DynamicGridCellStyleConverter<double?> CellFontSizeConverter;
-        private DynamicGridCellStyleConverter<System.Windows.FontStyle?> CellFontStyleConverter;
-        private DynamicGridCellStyleConverter<System.Windows.FontWeight?> CellFontWeightConverter;
+    #endregion
 
-        protected virtual Brush? GetCellBackground(CoreRow row, String columnname) => null;
-        protected virtual Brush? GetCellForeground(CoreRow row, String columnname) => null;
-        protected virtual double? GetCellFontSize(CoreRow row, String columnname) => null;
-        protected virtual FontStyle? GetCellFontStyle(CoreRow row, String columnname) => null;
-        protected virtual FontWeight? GetCellFontWeight(CoreRow row, String columnname) => null;
+    protected DynamicGridSettings Settings { get; set; }
 
-        protected DynamicGridSettings Settings { get; set; }
+    public DynamicGrid() : base()
+    {
+        UseWaitCursor = true;
 
-        public DynamicGrid() : base()
+        Options = new FluentList<DynamicGridOption>();
+        Options.OnChanged += (sender, args) =>
         {
-            IsReady = false;
+            _hasLoadedOptions = true;
+            OptionsChanged();
+        };
 
-            Data = new CoreTable();
+        ActionColumns = new DynamicActionColumns();
+        
+        RowStyleSelector = GetRowStyleSelector();
+        RowStyleSelector.GetStyle += (row, style) => GetRowStyle(row, style);
+        
+        IsReady = false;
 
-            ColumnsMenu = new ContextMenu();
-            ColumnsMenu.Opened += ColumnsMenu_ContextMenuOpening;
+        Data = new CoreTable();
+        MasterColumns = new DynamicGridColumns();
+        MasterColumns.ExtractColumns(typeof(T));
 
-            MasterColumns = new DynamicGridColumns();
-            MasterColumns.ExtractColumns(typeof(T));
+        HiddenColumns = new HiddenColumnsList();
+        foreach (var column in LookupFactory.RequiredColumns<T>().ColumnNames())
+        {
+            AddHiddenColumn(column);
+        }
 
-            foreach (var column in LookupFactory.RequiredColumns<T>().ColumnNames())
-            {
-                AddHiddenColumn(column);
-            }
+        if (IsSequenced)
+        {
+            up = new DynamicRowMovementColumn(DynamicRowMovement.Up, SwapRows);
+            ActionColumns.Add(up);
+            down = new DynamicRowMovementColumn(DynamicRowMovement.Down, SwapRows);
+            ActionColumns.Add(down);
+            HiddenColumns.Add(x => (x as ISequenceable)!.Sequence);
+        }
 
-            ActionColumns = new DynamicActionColumns();
+        VisibleColumns = new DynamicGridColumns();
 
-            if (IsSequenced)
+        UIComponent = new DynamicGridGridUIComponent<T>();
+        UIComponent.Parent = this;
+        
+        Loading = new Label();
+        Loading.Content = "Loading...";
+        Loading.Foreground = new SolidColorBrush(Colors.White);
+        Loading.VerticalContentAlignment = VerticalAlignment.Center;
+        Loading.HorizontalContentAlignment = HorizontalAlignment.Center;
+        Loading.Visibility = Visibility.Collapsed;
+        Loading.SetValue(Panel.ZIndexProperty, 999);
+        Loading.SetValue(Grid.RowProperty, 1);
+        Loading.FontSize = 14.0F;
+        LoadingFader.Completed += (sender, args) =>
+        {
+            if (Loading.Visibility == Visibility.Visible)
             {
-                up = new DynamicRowMovementColumn(DynamicRowMovement.Up, SwapRows);
-                ActionColumns.Add(up);
-                down = new DynamicRowMovementColumn(DynamicRowMovement.Down, SwapRows);
-                ActionColumns.Add(down);
-                HiddenColumns.Add(x => (x as ISequenceable)!.Sequence);
-            }
+                //Logger.Send(LogType.Information, this.GetType().EntityName().Split(".").Last(), "Loading Fader Restarting");
+                Loading.BeginAnimation(Label.OpacityProperty, LoadingFader);
+            }
+        };
 
-            CellBackgroundConverter = new DynamicGridCellStyleConverter<System.Windows.Media.Brush?>(this, GetCellBackground);
-            CellForegroundConverter = new DynamicGridCellStyleConverter<System.Windows.Media.Brush?>(this, GetCellForeground);
-            CellFontSizeConverter = new DynamicGridCellStyleConverter<double?>(this, GetCellFontSize);
-            CellFontStyleConverter = new DynamicGridCellStyleConverter<System.Windows.FontStyle?>(this, GetCellFontStyle);
-            CellFontWeightConverter = new DynamicGridCellStyleConverter<System.Windows.FontWeight?>(this, GetCellFontWeight);
-
-            VisibleColumns = new DynamicGridColumns();
-            
-            DataGrid = new SfDataGrid();
-            DataGrid.VerticalAlignment = VerticalAlignment.Stretch;
-            DataGrid.HorizontalAlignment = HorizontalAlignment.Stretch;
-            DataGrid.HeaderContextMenu = ColumnsMenu;
-            DataGrid.CellTapped += DataGrid_CellTapped;
-            DataGrid.CellDoubleTapped += DataGrid_CellDoubleTapped;
-            DataGrid.SelectionChanging += DataGrid_SelectionChanging;
-            DataGrid.SelectionChanged += DataGrid_SelectionChanged;
-            DataGrid.SelectionMode = GridSelectionMode.Extended;
-            DataGrid.SelectionUnit = GridSelectionUnit.Row;
-            DataGrid.CanMaintainScrollPosition = true;
-            DataGrid.SummaryCalculationUnit = SummaryCalculationUnit.AllRows;
-            DataGrid.LiveDataUpdateMode = LiveDataUpdateMode.AllowSummaryUpdate;
-
-            DataGrid.NavigationMode = NavigationMode.Row;
-            DataGrid.AllowEditing = false;
-            DataGrid.EditTrigger = EditTrigger.OnTap;
-
-            DataGrid.CurrentCellBeginEdit += DataGrid_CurrentCellBeginEdit;
-            DataGrid.CurrentCellEndEdit += DataGrid_CurrentCellEndEdit;
-            DataGrid.CurrentCellValueChanged += DataGrid_CurrentCellValueChanged;
-            DataGrid.CurrentCellDropDownSelectionChanged += DataGrid_CurrentCellDropDownSelectionChanged;
-            DataGrid.CurrentCellRequestNavigate += DataGrid_CurrentCellRequestNavigate;
-            DataGrid.PreviewKeyUp += DataGrid_PreviewKeyUp;
-            DataGrid.CurrentCellActivated += DataGrid_CurrentCellActivated;
-
-            DataGrid.BorderBrush = new SolidColorBrush(Colors.Gray);
-            DataGrid.BorderThickness = new Thickness(0.75F);
-            DataGrid.Background = new SolidColorBrush(Colors.DimGray);
-            DataGrid.AutoGenerateColumns = false;
-            DataGrid.ColumnSizer = GridLengthUnitType.AutoLastColumnFill;
-            DataGrid.SelectionForegroundBrush = BaseDynamicGrid.SelectionForeground;
-            DataGrid.RowSelectionBrush = BaseDynamicGrid.SelectionBackground;
-            
-            DataGrid.AllowDraggingRows = false;
-            DataGrid.Drop += DataGrid_Drop;
-            DataGrid.DragOver += DataGrid_DragOver;
-            DataGrid.RowDragDropTemplate = TemplateGenerator.CreateDataTemplate(() =>
-            {
-                var border = new Border();
-                border.Width = 100;
-                border.Height = 100;
-                border.BorderBrush = new SolidColorBrush(Colors.Firebrick);
-                border.Background = new SolidColorBrush(Colors.Red);
-                border.CornerRadius = new CornerRadius(5);
-                return border;
-            });
-            
-            DataGrid.CurrentCellBorderThickness = new Thickness(0);
-            DataGrid.AllowFiltering = false;
-            DataGrid.EnableDataVirtualization = true;
-            DataGrid.RowHeight = 30;
-            DataGrid.QueryRowHeight += DataGrid_QueryRowHeight;
-            DataGrid.HeaderRowHeight = 30;
-
-            DataGrid.MouseLeftButtonUp += DataGrid_MouseLeftButtonUp;
-            DataGrid.MouseRightButtonUp += DataGrid_MouseRightButtonUp;
-            DataGrid.KeyUp += DataGrid_KeyUp;
-            DataGrid.PreviewGotKeyboardFocus += DataGrid_PreviewGotKeyboardFocus;
-            //DataGrid.SelectionController = new GridSelectionControllerExt(DataGrid);
-            
-            DataGrid.SetValue(ScrollViewer.VerticalScrollBarVisibilityProperty, ScrollBarVisibility.Visible);
-            
-            DataGrid.FilterChanged += DataGrid_FilterChanged;
-
-            DataGrid.FilterItemsPopulating += DataGrid_FilterItemsPopulating;
-
-            var fltstyle = new Style(typeof(GridFilterControl));
-            fltstyle.Setters.Add(new Setter(GridFilterControl.FilterModeProperty, FilterMode.Both));
-            fltstyle.Setters.Add(new Setter(GridFilterControl.SortOptionVisibilityProperty, Visibility.Collapsed));
-            DataGrid.FilterPopupStyle = fltstyle;
-
-            DataGrid.RowStyleSelector = RowStyleSelector;
-            DataGrid.TableSummaryCellStyleSelector = new DynamicGridSummaryStyleSelector(this);
-
-            //DataGrid.MouseMove += DataGrid_MouseMove;
-            DataGrid.CellToolTipOpening += DataGrid_CellToolTipOpening;
-
-            //var headstyle = new Style(typeof(GridHeaderCellControl));
-            //headstyle.Setters.Add(new Setter(GridHeaderCellControl.BackgroundProperty, new SolidColorBrush(Colors.WhiteSmoke)));
-            //headstyle.Setters.Add(new Setter(GridHeaderCellControl.ForegroundProperty, new SolidColorBrush(Colors.Green)));
-            //headstyle.Setters.Add(new Setter(GridHeaderCellControl.FontSizeProperty, 12.0F));
-            //DataGrid.HeaderStyle = headstyle;
-
-            DataGrid.SizeChanged += DataGrid_SizeChanged;
-            DataGrid.SetValue(Grid.RowProperty, 1);
-
-            Loading = new Label();
-            Loading.Content = "Loading...";
-            Loading.Foreground = new SolidColorBrush(Colors.White);
-            Loading.VerticalContentAlignment = VerticalAlignment.Center;
-            Loading.HorizontalContentAlignment = HorizontalAlignment.Center;
-            Loading.Visibility = Visibility.Collapsed;
-            Loading.SetValue(Panel.ZIndexProperty, 999);
-            Loading.SetValue(Grid.RowProperty, 1);
-            Loading.FontSize = 14.0F;
-            LoadingFader.Completed += (sender, args) =>
-            {
-                if (Loading.Visibility == Visibility.Visible)
-                {
-                    //Logger.Send(LogType.Information, this.GetType().EntityName().Split(".").Last(), "Loading Fader Restarting");
-                    Loading.BeginAnimation(Label.OpacityProperty, LoadingFader);
-                }
-            };
+        Help = CreateButton(Wpf.Resources.help.AsBitmapImage(Color.White));
+        Help.Margin = new Thickness(0, 2, 2, 0);
+        Help.SetValue(DockPanel.DockProperty, Dock.Right);
+        Help.Click += (o, e) => ShowHelp(typeof(T).Name.Split('.').Last().SplitCamelCase().Replace(" ", "_"));
 
-            Help = CreateButton(Wpf.Resources.help.AsBitmapImage(Color.White));
-            Help.Margin = new Thickness(0, 2, 2, 0);
-            Help.SetValue(DockPanel.DockProperty, Dock.Right);
-            Help.Click += (o, e) => ShowHelp(typeof(T).Name.Split('.').Last().SplitCamelCase().Replace(" ", "_"));
+        Add = CreateButton(Wpf.Resources.add.AsBitmapImage(Color.White));
+        Add.Margin = new Thickness(0, 2, 2, 0);
+        Add.Click += Add_Click;
 
-            Add = CreateButton(Wpf.Resources.add.AsBitmapImage(Color.White));
-            Add.Margin = new Thickness(0, 2, 2, 0);
-            Add.Click += Add_Click;
-
-            Edit = CreateButton(Wpf.Resources.pencil.AsBitmapImage(Color.White));
-            Edit.Margin = new Thickness(0, 2, 2, 0);
-            Edit.Click += Edit_Click;
-
-            SwitchViewBtn = CreateButton(Wpf.Resources.alter.AsBitmapImage());
-            SwitchViewBtn.Margin = new Thickness(0, 2, 2, 0);
-            SwitchViewBtn.Click += SwitchView_Click;
-
-            EditSpacer = new Label { Width = 5 };
-
-            Print = CreateButton(Wpf.Resources.print.AsBitmapImage(Color.White));
-            Print.Margin = new Thickness(0, 2, 2, 0);
-            Print.Click += (o, e) => DoPrint(o);
-
-            PrintSpacer = new Label { Width = 5 };
-
-            Cut = CreateButton(Wpf.Resources.cut.AsBitmapImage(Color.White));
-            Cut.Margin = new Thickness(0, 2, 2, 0);
-            Cut.Click += Cut_Click;
-
-            Copy = CreateButton(Wpf.Resources.copy.AsBitmapImage(Color.White));
-            Copy.Margin = new Thickness(0, 2, 2, 0);
-            Copy.Click += Copy_Click;
-
-            Paste = CreateButton(Wpf.Resources.paste.AsBitmapImage(Color.White));
-            Paste.Margin = new Thickness(0, 2, 2, 0);
-            Paste.Click += Paste_Click;
-
-            ClipboardSpacer = new Label { Width = 5 };
-
-            Export = CreateButton(Wpf.Resources.doc_xls.AsBitmapImage(Color.White), "Export");
-            Export.Margin = new Thickness(0, 2, 2, 0);
-            Export.Click += Export_Click;
-
-            Import = CreateButton(Wpf.Resources.doc_xls.AsBitmapImage(Color.White), "Import");
-            Import.Margin = new Thickness(0, 2, 2, 0);
-            Import.Click += Import_Click;
-
-            ExportSpacer = new Label { Width = 5 };
-
-            LeftButtonStack = new StackPanel();
-            LeftButtonStack.Orientation = Orientation.Horizontal;
-            LeftButtonStack.SetValue(DockPanel.DockProperty, Dock.Left);
-
-            LeftButtonStack.Children.Add(Help);
-            LeftButtonStack.Children.Add(Add);
-            LeftButtonStack.Children.Add(Edit);
-            LeftButtonStack.Children.Add(SwitchViewBtn);
-            //Stack.Children.Add(MultiEdit);
-            LeftButtonStack.Children.Add(EditSpacer);
-
-            LeftButtonStack.Children.Add(Print);
-            LeftButtonStack.Children.Add(PrintSpacer);
-
-            LeftButtonStack.Children.Add(Cut);
-            LeftButtonStack.Children.Add(Copy);
-            LeftButtonStack.Children.Add(Paste);
-            LeftButtonStack.Children.Add(ClipboardSpacer);
-
-            LeftButtonStack.Children.Add(Export);
-            LeftButtonStack.Children.Add(Import);
-            LeftButtonStack.Children.Add(ExportSpacer);
-
-            RightButtonStack = new StackPanel();
-            RightButtonStack.Orientation = Orientation.Horizontal;
-            RightButtonStack.SetValue(DockPanel.DockProperty, Dock.Right);
-
-            Delete = CreateButton(Wpf.Resources.delete.AsBitmapImage(Color.White));
-            Delete.Margin = new Thickness(2, 2, 0, 0);
-            Delete.SetValue(DockPanel.DockProperty, Dock.Right);
-            Delete.Click += Delete_Click;
-
-            DuplicateBtn = AddButton("Duplicate", Wpf.Resources.paste.AsBitmapImage(Color.White), DoDuplicate);
-
-            Count = new Label();
-            Count.Height = 30;
-            Count.Margin = new Thickness(0, 2, 0, 0);
-            Count.VerticalContentAlignment = VerticalAlignment.Center;
-            Count.HorizontalContentAlignment = HorizontalAlignment.Center;
-            Count.SetValue(DockPanel.DockProperty, Dock.Left);
-
-            Docker = new DockPanel();
-
-            Docker.SetValue(Grid.RowProperty, 2);
-            Docker.SetValue(Grid.ColumnProperty, 0);
-            Docker.Children.Add(LeftButtonStack);
-            Docker.Children.Add(Delete);
-            Docker.Children.Add(RightButtonStack);
-            Docker.Children.Add(Count);
+        Edit = CreateButton(Wpf.Resources.pencil.AsBitmapImage(Color.White));
+        Edit.Margin = new Thickness(0, 2, 2, 0);
+        Edit.Click += Edit_Click;
 
+        SwitchViewBtn = CreateButton(Wpf.Resources.alter.AsBitmapImage());
+        SwitchViewBtn.Margin = new Thickness(0, 2, 2, 0);
+        SwitchViewBtn.Click += SwitchView_Click;
 
-            Layout = new Grid();
-            Layout.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
-            Layout.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) });
-            Layout.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
-            Layout.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) });
-            Layout.Children.Add(DataGrid);
-            Layout.Children.Add(Loading);
-            Layout.Children.Add(Docker);
-            
-            Disabler = new Border()
-            {
-                BorderBrush = new SolidColorBrush(Colors.Transparent),
-                Background = new SolidColorBrush(Colors.DimGray) { Opacity = 0.2 },
-                Visibility = Visibility.Collapsed,
-            };
-            Disabler.SetValue(Canvas.ZIndexProperty, 99);
-            Disabler.SetValue(Grid.RowSpanProperty, 3);
+        EditSpacer = new Label { Width = 5 };
 
-            Layout.Children.Add(Disabler);
-            
-            //Scroll.ApplyTemplate();
+        Print = CreateButton(Wpf.Resources.print.AsBitmapImage(Color.White));
+        Print.Margin = new Thickness(0, 2, 2, 0);
+        Print.Click += (o, e) => DoPrint(o);
 
-            Content = Layout;
+        PrintSpacer = new Label { Width = 5 };
 
-            IsEnabledChanged += (sender, args) =>
-            {
-                Disabler.Visibility = Equals(args.NewValue, true)
-                    ? Visibility.Collapsed
-                    : Visibility.Visible;
-            };
-
-            Settings = LoadSettings();
-            
-            Init();
-            Reconfigure();
-        }
+        Cut = CreateButton(Wpf.Resources.cut.AsBitmapImage(Color.White));
+        Cut.Margin = new Thickness(0, 2, 2, 0);
+        Cut.Click += Cut_Click;
 
-        protected virtual void BeforeSelection(CancelEventArgs cancel)
-        {
-            OnBeforeSelection?.Invoke(cancel);
-        }
+        Copy = CreateButton(Wpf.Resources.copy.AsBitmapImage(Color.White));
+        Copy.Margin = new Thickness(0, 2, 2, 0);
+        Copy.Click += Copy_Click;
 
-        private void DataGrid_SelectionChanging(object? sender, Syncfusion.UI.Xaml.Grid.GridSelectionChangingEventArgs e)
-        {
-            var cancel = new CancelEventArgs();
-            BeforeSelection(cancel);
-            if (cancel.Cancel)
-            {
-                e.Cancel = true;
-            }
-        }
+        Paste = CreateButton(Wpf.Resources.paste.AsBitmapImage(Color.White));
+        Paste.Margin = new Thickness(0, 2, 2, 0);
+        Paste.Click += Paste_Click;
 
-        private void DataGrid_SelectionChanged(object? sender, GridSelectionChangedEventArgs e)
-        {
-            if(IsReady && !bRefreshing)
-            {
-                StartTimer();
-            }
-        }
+        ClipboardSpacer = new Label { Width = 5 };
 
-        public bool IsReady { get; private set; }
+        Export = CreateButton(Wpf.Resources.doc_xls.AsBitmapImage(Color.White), "Export");
+        Export.Margin = new Thickness(0, 2, 2, 0);
+        Export.Click += Export_Click;
 
-        public UIElement? Header
-        {
-            get => _header;
-            set
-            {
-                if (_header is not null && Layout.Children.Contains(_header))
-                    Layout.Children.Remove(_header);
-                _header = value;
-                if (_header is not null)
-                {
-                    _header.SetValue(Grid.RowProperty, 0);
-                    _header.SetValue(Grid.ColumnProperty, 0);
-                    _header.SetValue(Grid.ColumnSpanProperty, 2);
-                    Layout.Children.Add(_header);
-                }
-            }
-        }
+        Import = CreateButton(Wpf.Resources.doc_xls.AsBitmapImage(Color.White), "Import");
+        Import.Margin = new Thickness(0, 2, 2, 0);
+        Import.Click += Import_Click;
 
-        /// <summary>
-        /// Represents the data in the grid. This is <see langword="null"/> until <see cref="Refresh(bool, bool)"/> is called.
-        /// </summary>
-        public CoreTable? MasterData { get; set; }
+        ExportSpacer = new Label { Width = 5 };
 
-        public DynamicActionColumns ActionColumns { get; }
+        LeftButtonStack = new StackPanel();
+        LeftButtonStack.Orientation = Orientation.Horizontal;
+        LeftButtonStack.SetValue(DockPanel.DockProperty, Dock.Left);
 
-        private bool IsSequenced => typeof(T).GetInterfaces().Any(x => x.Equals(typeof(ISequenceable)));
+        LeftButtonStack.Children.Add(Help);
+        LeftButtonStack.Children.Add(Add);
+        LeftButtonStack.Children.Add(Edit);
+        LeftButtonStack.Children.Add(SwitchViewBtn);
+        //Stack.Children.Add(MultiEdit);
+        LeftButtonStack.Children.Add(EditSpacer);
 
-        public override double RowHeight
-        {
-            get => DataGrid.RowHeight;
-            set => DataGrid.RowHeight = value;
-        }
+        LeftButtonStack.Children.Add(Print);
+        LeftButtonStack.Children.Add(PrintSpacer);
 
-        public override double HeaderHeight
-        {
-            get => DataGrid.HeaderRowHeight;
-            set => DataGrid.HeaderRowHeight = value;
-        }
+        LeftButtonStack.Children.Add(Cut);
+        LeftButtonStack.Children.Add(Copy);
+        LeftButtonStack.Children.Add(Paste);
+        LeftButtonStack.Children.Add(ClipboardSpacer);
 
-        protected override void OptionsChanged()
-        {
-            var reloadColumns = false;
+        LeftButtonStack.Children.Add(Export);
+        LeftButtonStack.Children.Add(Import);
+        LeftButtonStack.Children.Add(ExportSpacer);
 
-            ColumnsMenu.Visibility = HasOption(DynamicGridOption.SelectColumns) ? Visibility.Visible : Visibility.Hidden;
-            Help.Visibility = HasOption(DynamicGridOption.ShowHelp) ? Visibility.Visible : Visibility.Collapsed;
+        RightButtonStack = new StackPanel();
+        RightButtonStack.Orientation = Orientation.Horizontal;
+        RightButtonStack.SetValue(DockPanel.DockProperty, Dock.Right);
 
-            Add.Visibility = HasOption(DynamicGridOption.AddRows) ? Visibility.Visible : Visibility.Collapsed;
-            Edit.Visibility = HasOption(DynamicGridOption.EditRows) ? Visibility.Visible : Visibility.Collapsed;
+        Delete = CreateButton(Wpf.Resources.delete.AsBitmapImage(Color.White));
+        Delete.Margin = new Thickness(2, 2, 0, 0);
+        Delete.SetValue(DockPanel.DockProperty, Dock.Right);
+        Delete.Click += Delete_Click;
 
-            EditSpacer.Visibility = HasOption(DynamicGridOption.AddRows) || HasOption(DynamicGridOption.EditRows)
-                ? Visibility.Visible
-                : Visibility.Collapsed;
+        DuplicateBtn = AddButton("Duplicate", Wpf.Resources.paste.AsBitmapImage(Color.White), DoDuplicate);
 
-            Print.Visibility = HasOption(DynamicGridOption.Print) ? Visibility.Visible : Visibility.Collapsed;
-            PrintSpacer.Visibility = HasOption(DynamicGridOption.Print) ? Visibility.Visible : Visibility.Collapsed;
+        Count = new Label();
+        Count.Height = 30;
+        Count.Margin = new Thickness(0, 2, 0, 0);
+        Count.VerticalContentAlignment = VerticalAlignment.Center;
+        Count.HorizontalContentAlignment = HorizontalAlignment.Center;
+        Count.SetValue(DockPanel.DockProperty, Dock.Left);
 
-            Cut.Visibility = IsSequenced && HasOption(DynamicGridOption.EditRows) ? Visibility.Visible : Visibility.Collapsed;
-            Copy.Visibility = IsSequenced && HasOption(DynamicGridOption.EditRows) ? Visibility.Visible : Visibility.Collapsed;
-            Paste.Visibility = IsSequenced && HasOption(DynamicGridOption.EditRows) ? Visibility.Visible : Visibility.Collapsed;
-            ClipboardSpacer.Visibility = IsSequenced && HasOption(DynamicGridOption.EditRows) ? Visibility.Visible : Visibility.Collapsed;
+        Docker = new DockPanel();
 
-            Export.Visibility = HasOption(DynamicGridOption.ExportData) ? Visibility.Visible : Visibility.Collapsed;
-            Import.Visibility = HasOption(DynamicGridOption.ImportData) ? Visibility.Visible : Visibility.Collapsed;
-            ExportSpacer.Visibility = HasOption(DynamicGridOption.ExportData) || HasOption(DynamicGridOption.ImportData)
-                ? Visibility.Visible
-                : Visibility.Collapsed;
+        Docker.SetValue(Grid.RowProperty, 2);
+        Docker.SetValue(Grid.ColumnProperty, 0);
+        Docker.Children.Add(LeftButtonStack);
+        Docker.Children.Add(Delete);
+        Docker.Children.Add(RightButtonStack);
+        Docker.Children.Add(Count);
 
-            SwitchViewBtn.Visibility = HasOption(DynamicGridOption.DirectEdit) ? Visibility.Visible : Visibility.Collapsed;
-            var allowEditing = IsDirectEditMode();
 
-            if (DataGrid.AllowEditing != allowEditing)
-            {
-                DataGrid.NavigationMode = allowEditing ? NavigationMode.Cell : NavigationMode.Row;
-                DataGrid.AllowEditing = allowEditing;
-                reloadColumns = true;
-            }
+        Layout = new Grid();
+        Layout.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
+        Layout.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) });
+        Layout.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
+        Layout.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) });
 
-            Count.Visibility = HasOption(DynamicGridOption.RecordCount) ? Visibility.Visible : Visibility.Collapsed;
+        var control = UIComponent.Control;
+        control.SetValue(Grid.RowProperty, 1);
 
-            Delete.Visibility = HasOption(DynamicGridOption.DeleteRows) ? Visibility.Visible : Visibility.Collapsed;
-            DataGrid.AllowFiltering = HasOption(DynamicGridOption.FilterRows);
-            DataGrid.FilterRowPosition = HasOption(DynamicGridOption.FilterRows) ? FilterRowPosition.FixedTop : FilterRowPosition.None;
+        Layout.Children.Add(control);
+        Layout.Children.Add(Loading);
+        Layout.Children.Add(Docker);
+        
+        Disabler = new Border()
+        {
+            BorderBrush = new SolidColorBrush(Colors.Transparent),
+            Background = new SolidColorBrush(Colors.DimGray) { Opacity = 0.2 },
+            Visibility = Visibility.Collapsed,
+        };
+        Disabler.SetValue(Canvas.ZIndexProperty, 99);
+        Disabler.SetValue(Grid.RowSpanProperty, 3);
 
-            if (HasOption(DynamicGridOption.DragSource))
-            {
-                if (!DataGrid.AllowDraggingRows)
-                {
-                    DataGrid.AllowDraggingRows = true;
-                    DataGrid.RowDragDropController.DragStart += RowDragDropController_DragStart;
-                }
-            }
-            else
-            {
-                if (DataGrid.AllowDraggingRows)
-                {
-                    DataGrid.AllowDraggingRows = false;
-                    DataGrid.RowDragDropController.DragStart -= RowDragDropController_DragStart;
-                }
-            }
-            
-            DataGrid.AllowDrop = HasOption(DynamicGridOption.DragTarget);
-            
-            DataGrid.SelectionMode = HasOption(DynamicGridOption.MultiSelect) ? GridSelectionMode.Extended : GridSelectionMode.Single;
-            if (up != null)
-                up.Position = HasOption(DynamicGridOption.EditRows) ? DynamicActionColumnPosition.Start : DynamicActionColumnPosition.Hidden;
-            if (down != null)
-                down.Position = HasOption(DynamicGridOption.EditRows) ? DynamicActionColumnPosition.Start : DynamicActionColumnPosition.Hidden;
-
-            if (DuplicateBtn != null)
-                DuplicateBtn.Visibility = Visibility.Collapsed;
-
-            if(reloadColumns && DataGrid.Columns.Count > 0)
-            {
-                Refresh(true, false);
-            }
-        }
+        Layout.Children.Add(Disabler);
+        
+        //Scroll.ApplyTemplate();
 
-        protected virtual DynamicGridSettings LoadSettings()
-        {
-            return new DynamicGridSettings();
-        }
-        protected virtual void SaveSettings(DynamicGridSettings settings)
-        {
-        }
+        Content = Layout;
 
-        public bool IsDirectEditMode(IEnumerable<DynamicGridOption>? options = null)
+        IsEnabledChanged += (sender, args) =>
         {
-            return HasOption(DynamicGridOption.DirectEdit, options)
-                && (Settings.ViewMode == DynamicGridSettings.DynamicGridViewMode.DirectEdit
-                    || Settings.ViewMode == DynamicGridSettings.DynamicGridViewMode.Default);
-        }
+            Disabler.Visibility = Equals(args.NewValue, true)
+                ? Visibility.Collapsed
+                : Visibility.Visible;
+        };
 
-        private void SwitchView_Click(object sender, RoutedEventArgs e)
-        {
-            Settings.ViewMode = Settings.ViewMode switch
-            {
-                DynamicGridSettings.DynamicGridViewMode.Default => DynamicGridSettings.DynamicGridViewMode.Normal,
-                DynamicGridSettings.DynamicGridViewMode.Normal => DynamicGridSettings.DynamicGridViewMode.DirectEdit,
-                DynamicGridSettings.DynamicGridViewMode.DirectEdit or _ => DynamicGridSettings.DynamicGridViewMode.Normal
-            };
-            SaveSettings(Settings);
-            Reconfigure();
-        }
+        Settings = LoadSettings();
+        
+        Init();
+        Reconfigure();
+    }
 
-        protected override DynamicGridRowStyleSelector<T> GetRowStyleSelector()
-        {
-            return new DynamicGridRowStyleSelector<T, DynamicGridRowStyle>();
-        }
+    #region IDynamicGridUIComponentParent
 
-        protected override DynamicGridStyle GetRowStyle(CoreRow row, DynamicGridStyle style)
-        {
-            var result = base.GetRowStyle(row, style);
-            if (ClipBuffer != null)
-                if (ClipBuffer.Item2.Contains(row))
-                {
-                    var bgbrush = style.Background as SolidColorBrush;
-                    var bgcolor = bgbrush != null ? bgbrush.Color : Colors.Transparent;
+    bool IDynamicGridUIComponentParent<T>.CanSort()
+    {
+        return !IsSequenced;
+    }
 
-                    result = new DynamicGridRowStyle(style);
+    T IDynamicGridUIComponentParent<T>.LoadItem(CoreRow row) => LoadItem(row);
 
-                    result.Background = ClipBuffer.Item1 == ClipAction.Cut
-                        ? new SolidColorBrush(bgcolor.MixColors(0.5, Colors.Orchid))
-                        : new SolidColorBrush(bgcolor.MixColors(0.5, Colors.LightGreen));
 
-                    result.Foreground = new SolidColorBrush(Colors.Gray);
-                    result.FontStyle = FontStyles.Italic;
-                }
+    DynamicGridRowStyleSelector<T> IDynamicGridUIComponentParent<T>.RowStyleSelector => RowStyleSelector;
 
-            return result;
-        }
+    void IDynamicGridUIComponentParent<T>.BeforeSelection(CancelEventArgs cancel)
+    {
+        BeforeSelection(cancel);
+    }
 
-        private void DataGrid_CurrentCellActivated(object? sender, CurrentCellActivatedEventArgs e)            
-        {
-            // if (!IsDirectEditMode())
-            //     return;
-            // if ((DataGrid.SelectionController.CurrentCellManager.CurrentCell?.IsEditing != true) &&
-            //     e.ActivationTrigger == ActivationTrigger.Keyboard)
-            // {
-            //     var result = DataGrid.SelectionController.CurrentCellManager.BeginEdit();
-            // }
-        }
+    void IDynamicGridUIComponentParent<T>.SelectItems(CoreRow[] rows)
+    {
+        SelectItems(rows);
+    }
+    void IDynamicGridUIComponentParent<T>.EntityChanged(T obj, CoreRow row, string changedColumn, Dictionary<string, object?> changes)
+        => EntityChanged(obj, row, changedColumn, changes);
 
-        private void DataGrid_PreviewKeyUp(object sender, KeyEventArgs e)
-        {
-            if (e.Key == Key.OemPeriod)
-            {
-                var editor = e.OriginalSource as TimeSpanEdit;
-                if (editor != null && editor.SelectionStart < 2) editor.SelectionStart = 3;
-            }
-            else if (e.Key == Key.Tab)
-            {
-                if (IsDirectEditMode())
-                {
-                    DataGrid.SelectionController.CurrentCellManager.EndEdit();
-                    DataGrid.MoveFocus(new TraversalRequest(FocusNavigationDirection.Right));
-                    DataGrid.SelectionController.CurrentCellManager.BeginEdit();
-                    e.Handled = true;
-                }
-            }
-            //throw new NotImplementedException();
-        }
 
-        private void DataGrid_CurrentCellRequestNavigate(object? sender, CurrentCellRequestNavigateEventArgs e)
+    void IDynamicGridUIComponentParent<T>.UpdateData(T obj, CoreRow row, string changedColumn, Dictionary<CoreColumn, object?> updates)
+    {
+        var result = new Dictionary<string, object?>();
+
+        foreach (var (col, value) in updates)
         {
-            //throw new NotImplementedException();
+            UpdateRow(row, col.ColumnName, value, refresh: false);
+            DynamicGridUtils.UpdateEditorValue(new BaseObject[] { obj }, col.ColumnName, value, result);
         }
 
-        private void DataGrid_FilterChanged(object? o, GridFilterEventArgs e)
+        EntityChanged(obj, row, changedColumn, result);
+    }
+
+    void IDynamicGridUIComponentParent<T>.HandleKey(KeyEventArgs e)
+    {
+        if (IsSequenced)
         {
-            var col = DataGrid.Columns.IndexOf(e.Column);
-            if (ColumnList[col] is DynamicActionColumn column)
+            if (e.Key == Key.X && (Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control)
             {
-                if (e.FilterPredicates != null)
-                {
-                    var filter = e.FilterPredicates.Select(x => x.FilterValue.ToString()!).ToArray();
-                    bool include = e.FilterPredicates.Any(x => x.FilterType == FilterType.Equals);
-                    column.SelectedFilters = include ? filter : (column.Filters ?? Enumerable.Empty<string>()).Except(filter).ToArray();
-                }
-                else
-                    column.SelectedFilters = Array.Empty<string>();
-                DataGrid.ClearFilter(e.Column);
-                //e.FilterPredicates?.Clear();
-                //e.FilterPredicates?.Add(new FilterPredicate() { PredicateType = PredicateType.Or, FilterBehavior = Syncfusion.Data.FilterBehavior.StringTyped, FilterMode = ColumnFilter.DisplayText, FilterType = Syncfusion.Data.FilterType.NotEquals, FilterValue = "" });
-                //e.FilterPredicates?.Add(new FilterPredicate() { PredicateType = PredicateType.Or, FilterBehavior = Syncfusion.Data.FilterBehavior.StringTyped, FilterMode = ColumnFilter.DisplayText, FilterType = Syncfusion.Data.FilterType.Equals, FilterValue = "" });
-                Refresh(false, false);
-                e.Handled = true;
+                CutToClipBuffer();
             }
-
-            if (e.FilterPredicates == null)
+            else if (e.Key == Key.C && (Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control)
             {
-                if (_filterpredicates.ContainsKey(e.Column.MappingName))
-                    _filterpredicates.Remove(e.Column.MappingName);
+                CopyToClipBuffer();
             }
-            else
+            else if (e.Key == Key.V && (Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control)
             {
-                _filterpredicates[e.Column.MappingName] = Serialization.Serialize(e.FilterPredicates, true);
+                PasteFromClipBuffer();
+            }
+            else if (e.Key == Key.Escape)
+            {
+                ResetClipBuffer();
+                InvalidateGrid();
             }
-
-            UpdateRecordCount();
         }
+    }
 
-        private void DataGrid_FilterItemsPopulating(object? sender, GridFilterItemsPopulatingEventArgs e)
+    void IDynamicGridUIComponentParent<T>.DoubleClickCell(CoreRow? row, DynamicColumnBase? column)
+    {
+        if (OnCellDoubleClick is not null && column is DynamicGridColumn col)
         {
-            var col = DataGrid.Columns.IndexOf(e.Column);
-            if (ColumnList[col] is DynamicActionColumn column && column.Filters is not null)
-                e.ItemsSource = column.Filters.Select(x => new FilterElement
-                { DisplayText = x, ActualValue = x, IsSelected = column.SelectedFilters is null || column.SelectedFilters.Contains(x) });
+            var args = new DynamicGridCellClickEventArgs(row, col);
+            OnCellDoubleClick?.Invoke(this, args);
+            if (args.Handled)
+                return;
         }
 
-        // Please always use this function where possible!
-        /// <summary>
-        /// Get the <see cref="CoreRow"/> associated with a <paramref name="rowIndex"/> given from the Syncfusion DataGrid.
-        /// </summary>
-        /// <remarks>
-        /// This is mandatory to use whenever we want the data associated with an index which Syncfusion has given us,
-        /// because filtering and sorting will cause normal indexing operations to fail.
-        /// </remarks>
-        /// <param name="rowIndex"></param>
-        /// <returns></returns>
-        private CoreRow? GetRowFromIndex(int rowIndex)
+        if (row is not null)
+            DoDoubleClick(this);
+    }
+
+    void IDynamicGridUIComponentParent<T>.ExecuteActionColumn(DynamicActionColumn column, CoreRow[]? rows)
+    {
+        var bRefresh = false;
+        if(rows is null)
         {
-            // Syncfusion has given us the row index, so it also will give us the correct row, after sorting.
-            // Hence, here we use the syncfusion DataGrid.GetRecordAtRowIndex, which *should* always return a DataRowView.
-            var row = DataGrid.GetRecordAtRowIndex(rowIndex);
-            if (row is not DataRowView dataRowView || DataGridItems is not DataTable table)
-                return null;
-
-            var rowIdx = table.Rows.IndexOf(dataRowView.Row);
-            if (rowIdx < 0)
-                return null;
-            return Data.Rows[rowIdx];
+            bRefresh = column.Action?.Invoke(null) ?? false;
         }
-
-        private void DataGrid_CellToolTipOpening(object? sender, GridCellToolTipOpeningEventArgs e)
+        else
         {
-            if (ColumnList[e.RowColumnIndex.ColumnIndex] is not DynamicActionColumn col)
-                return;
-            var toolTip = col.ToolTip;
-            if (toolTip is null)
-                return;
-
-            var row = GetRowFromIndex(e.RowColumnIndex.RowIndex);
-
-            e.ToolTip.Template = TemplateGenerator.CreateControlTemplate(
-                typeof(ToolTip),
-                () => toolTip.Invoke(col, row)
-            );
+            foreach (var row in rows)
+                if (column.Action?.Invoke(row) == true)
+                    bRefresh = true;
         }
+        if (bRefresh)
+            Dispatcher.BeginInvoke(() => { Refresh(true, true); });
+    }
 
-        protected virtual void LoadColumnsMenu(ContextMenu menu)
+    void IDynamicGridUIComponentParent<T>.OpenColumnMenu(DynamicColumnBase column)
+    {
+        if(column is DynamicMenuColumn menuColumn)
         {
+            menuColumn.Action?.Invoke(SelectedRows.FirstOrDefault());
         }
-
-        private DirectEditingObject EnsureEditingObject(CoreRow row)
+        else if(column is DynamicActionColumn actionColumn)
         {
-            _editingObject ??= new(LoadItem(row), row, DataGridItems?.Rows[row.Index]);
-            return _editingObject;
+            var menu = actionColumn?.ContextMenu?.Invoke(SelectedRows);
+            if (menu != null && menu.Items.Count > 0)
+            {
+                menu.IsOpen = true;
+            }
         }
+    }
 
-        private void DataGrid_CurrentCellBeginEdit(object? sender, CurrentCellBeginEditEventArgs e)
-        {
-            var table = DataGridItems;
-            var row = GetRowFromIndex(e.RowColumnIndex.RowIndex);
-            if (table is null || row is null)
-                return;
+    void IDynamicGridUIComponentParent<T>.UpdateRecordCount(int count)
+    {
+        Count.Content = string.Format("{0} Records", count);
+    }
 
-            EnsureEditingObject(row);
+    void IDynamicGridUIComponentParent<T>.LoadColumnsMenu(ContextMenu menu)
+    {
+        menu.AddItem("Select Columns", null, SelectColumnsClick);
+        LoadColumnsMenu(menu);
+    }
 
-            var column = DataGrid.Columns[e.RowColumnIndex.ColumnIndex] as GridComboBoxColumn;
-            if (column != null && column.ItemsSource == null)
-            {
-                var colname = column.MappingName;
-                var colno = table.Columns.IndexOf(colname);
-                var property = Data.Columns[colno].ColumnName;
-                var prop = CoreUtils.GetProperty(typeof(T), property);
-                var editor = prop.GetEditor();
-                if (editor is ILookupEditor lookupEditor)
-                {
-                    if (!Lookups.ContainsKey(property))
-                        Lookups[property] = lookupEditor.Values(typeof(T), property);
-                    var combo = column;
-                    combo.ItemsSource = Lookups[property].ToDictionary(Lookups[property].Columns[0].ColumnName, "Display");
-                    combo.SelectedValuePath = "Key";
-                    combo.DisplayMemberPath = "Value";
-                }
-            }
 
-            bChanged = false;
-        }
+    void IDynamicGridUIComponentParent<T>.DragOver(object sender, DragEventArgs e)
+    {
+        HandleDragOver(sender, e);
+    }
+
+    void IDynamicGridUIComponentParent<T>.Drop(object sender, DragEventArgs e)
+    {
+        if (!HasOption(DynamicGridOption.DragTarget))
+            return;
 
-        private void DataGrid_CurrentCellValueChanged(object? sender, CurrentCellValueChangedEventArgs e)
+        if(DynamicGridUtils.TryGetDropData(e, out var entityType, out var table))
         {
-            var row = GetRowFromIndex(e.RowColumnIndex.RowIndex);
-            // Are we sure that this function is ever useful? It seems that since the data in the grid hasn't been updated by this point, this function is essentially useless (the data is updated in EndEdit). Probably need to check the GridCheckBoxColumn
+            OnDragEnd(entityType, table, e);
+        }
+        else
+        {
+            HandleDragDrop(sender, e);
+        }
+    }
 
-            if (row is null) return;
+    void IDynamicGridUIComponentParent<T>.DragStart(object? sender, CoreRow[] rows)
+    {
+        Logger.Send(LogType.Information, "", "RowDragDropController_DragStart");
+        
+        if (!HasOption(DynamicGridOption.DragSource))
+            return;
+        
+        OnRowsDragStart(rows);
+    }
 
-            if (e.Column is GridCheckBoxColumn)
-            {
-                EnsureEditingObject(row);
-            }
+    #endregion
 
-            if (_editingObject is not null)
-                UpdateData(_editingObject.Row.Index, e.RowColumnIndex.ColumnIndex);
-            if (e.Column is GridCheckBoxColumn)
-                _editingObject = null;
-            if (_editingObject is not null)
-                bChanged = true;
-        }
+    protected virtual DynamicGridRowStyleSelector<T> GetRowStyleSelector()
+    {
+        return new DynamicGridRowStyleSelector<T, DynamicGridRowStyle>();
+    }
 
-        private void DataGrid_CurrentCellDropDownSelectionChanged(object? sender,
-            CurrentCellDropDownSelectionChangedEventArgs e)
-        {
-            var row = GetRowFromIndex(e.RowColumnIndex.RowIndex);
-            if (row is null)
-                return;
-            EnsureEditingObject(row);
-            if ((_editingObject is not null) && (e.SelectedItem is Tuple<object?, string> tuple))
+    protected DynamicGridStyle GetRowStyle(CoreRow row, DynamicGridStyle style)
+    {
+        DynamicGridStyle? result = null;
+
+        if (ClipBuffer != null)
+            if (ClipBuffer.Item2.Contains(row))
             {
-                var mappedname = DataGrid.Columns[e.RowColumnIndex.ColumnIndex].MappingName;
+                var bgbrush = style.Background as SolidColorBrush;
+                var bgcolor = bgbrush != null ? bgbrush.Color : Colors.Transparent;
 
-                var colno = DataGridItems.Columns.IndexOf(mappedname);
-                var corecol = Data.Columns[colno].ColumnName;
+                result = new DynamicGridRowStyle(style);
 
-                var updates = new Dictionary<CoreColumn, object?>();
+                result.Background = ClipBuffer.Item1 == ClipAction.Cut
+                    ? new SolidColorBrush(bgcolor.MixColors(0.5, Colors.Orchid))
+                    : new SolidColorBrush(bgcolor.MixColors(0.5, Colors.LightGreen));
 
-                var prefix = String.Join(".", corecol.Split(".").Reverse().Skip(1).Reverse());
-                var field = corecol.Split(".").Last();
+                result.Foreground = new SolidColorBrush(Colors.Gray);
+                result.FontStyle = FontStyles.Italic;
+            }
+        result ??= OnGetRowStyle != null ? OnGetRowStyle(row, style) : style;
 
-                var prop = CoreUtils.GetProperty(typeof(T), corecol);
-                if (prop.GetEditor() is ILookupEditor editor)
-                {
-                    var data = editor.Values(typeof(T), corecol);
-                    var lookuprow = data.Rows.FirstOrDefault(r => Equals(r[field], tuple.Item1))
-                        ?? data.NewRow(true);
+        return result;
+    }
 
-                    foreach (CoreColumn lookupcol in data.Columns)
-                    {
-                        var columnname = String.IsNullOrWhiteSpace(prefix)
-                            ? lookupcol.ColumnName
-                            : String.Join(".", prefix, lookupcol.ColumnName);
-                        var updatecol = Data.Columns.FirstOrDefault(x => String.Equals(x.ColumnName, columnname));
-                        if (updatecol != null)
-                            updates[updatecol] = lookuprow[lookupcol.ColumnName];
-                    }
-                    var changes = UpdateData(updates);
-                    DoEntityChanged(this, new DynamicColumnEntityChangedEventArgs(corecol, changes));
-                    bChanged = true;
-                    
-                }
-            }
-        }
-
-        protected void UpdateCell(CoreRow coreRow, DataRow? dataRow, string colname, object? value)
-        {
-            var datacolname = colname.Replace(".", "_");
-            var table = DataGridItems;
-            if (table is null) return;
-
-            var colno = table.Columns.IndexOf(datacolname);
-            if (colno < 0)
-            {
-                // This column is not in the list. Because of linked properties, a single lookup change can cause a cascade of updates,
-                // many of which are likely unnecessary; hence, we shall ignore any which are unnecessary.
-                return;
-            }
-            var corecol = Data.Columns[colno].ColumnName;
-            coreRow[corecol] = value;
-            if(dataRow is not null)
-            {
-                dataRow[datacolname] = value ?? DBNull.Value;
-            }
-        }
-
-        protected void UpdateCell(int row, string colname, object? value)
-        {
-            var table = DataGridItems;
-            if (table is null) return;
+    protected virtual void BeforeSelection(CancelEventArgs cancel)
+    {
+        OnBeforeSelection?.Invoke(cancel);
+    }
 
-            UpdateCell(Data.Rows[row], table.Rows[row], colname, value);
-        }
+    public bool IsReady { get; protected set; }
 
-        private void DataGrid_CurrentCellEndEdit(object? sender, CurrentCellEndEditEventArgs e)
+    public UIElement? Header
+    {
+        get => _header;
+        set
         {
-            if (_editingObject is not null && bChanged)
+            if (_header is not null && Layout.Children.Contains(_header))
+                Layout.Children.Remove(_header);
+            _header = value;
+            if (_header is not null)
             {
-                UpdateData(_editingObject.Row.Index, e.RowColumnIndex.ColumnIndex);
+                _header.SetValue(Grid.RowProperty, 0);
+                _header.SetValue(Grid.ColumnProperty, 0);
+                _header.SetValue(Grid.ColumnSpanProperty, 2);
+                Layout.Children.Add(_header);
             }
-            if (bChanged)
-                DoChanged();
-            bChanged = false;
-            _editingObject = null;
-
-            // Commented out on 19/02/2024 by Kenric. I don't see this being necessary, though I could be wrong. Nevertheless, it was causing a bug when
-            // editing the filter row. It seems that this causes Syncfusion to commit the filter predicates internally, which means that after leaving a 
-            // filter row cell, the filter remained even once it was cleared, meaning a refresh was necessary to get the data back.
-            // I've tested on Bills to see if editing works with this empty, and it seems so.
-            //DataGridItems?.AcceptChanges();
         }
+    }
 
-        private void UpdateRow(CoreRow row, DataRow? dataRow, Dictionary<string, object?> changes)
-        {
-            foreach (var key in changes.Keys)
-                UpdateCell(row, dataRow, key, changes[key]);
+    /// <summary>
+    /// Represents the data in the grid. This is <see langword="null"/> until <see cref="Refresh(bool, bool)"/> is called.
+    /// </summary>
+    public CoreTable? MasterData { get; set; }
 
-            if (dataRow is not null)
-            {
-                for (var i = 0; i < ActionColumns.Count; i++)
-                    dataRow[$"ActionColumn{i}"] = ActionColumns[i].Data(row);
-            }
-        }
+    public DynamicGridColumns MasterColumns { get; protected set; }
+    public DynamicGridColumns VisibleColumns { get; protected set; }
+    public DynamicActionColumns ActionColumns { get; protected set; }
+    public CoreTable Data { get; set; }
 
-        private Dictionary<string, object?> UpdateData(Dictionary<CoreColumn, object?> updates)
-        {
-            var result = new Dictionary<string, object?>();
+    public class HiddenColumnsList
+    {
+        private List<string> Columns { get; set; } = new();
 
-            if (_editingObject is null)
-                return result;
+        public IEnumerable<string> ColumnNames => Columns;
 
-            var coreRow = _editingObject.Row;
-            var row = _editingObject.DataRow;
-            
-            foreach (var (col, value) in updates)
-            {
-                UpdateRow(coreRow, col.ColumnName, value, refresh: false);
-                DynamicGridUtils.UpdateEditorValue(new BaseObject[] { _editingObject.Object }, col.ColumnName, value, result);
-            }
-            
-            SaveItem(_editingObject.Object);
-            
-            foreach (var key in result.Keys)
-                UpdateCell(coreRow, row, key, result[key]);
-            
-            // foreach (var c in Data.Columns.Where(x => !string.Equals(column.ColumnName, x.ColumnName)))
-            // {
-            //     var scol = c.ColumnName.Replace('.', '_');
-            //     row[scol] = corerow[c.ColumnName] ?? DBNull.Value;
-            // }
-
-            if(row is not null)
-            {
-                for (var i = 0; i < ActionColumns.Count; i++)
-                    row[string.Format("ActionColumn{0}", i)] = ActionColumns[i].Data(coreRow);
-            }
-            
-            return result;
+        public void Add(Expression<Func<T, object?>> column) => Add(CoreUtils.GetFullPropertyName(column, "."));
 
-        }
-        
-        private void UpdateData(int rowIndex, int columnIndex)
-        {
-            var table = DataGridItems;
-            if (table is null)
-                return;
-            
-            var gridcol = ColumnList[columnIndex] as DynamicGridColumn;
+        public void Add(IColumn column) => Add(column.Property);
 
-            if (gridcol != null)
-            {
-                var datacol = Data.Columns.FirstOrDefault(x => x.ColumnName.Equals(gridcol.ColumnName));
-                if (datacol != null)
-                {
-                    var datacolindex = Data.Columns.IndexOf(datacol);
-
-                    var value = table.Rows[rowIndex][datacolindex];
-                    if (value is DBNull)
-                        value = CoreUtils.GetDefault(datacol.DataType);
-                    var changes = UpdateData(new Dictionary<CoreColumn, object?>() { { datacol, value } });
-                    DoEntityChanged(gridcol, new DynamicColumnEntityChangedEventArgs(datacol.ColumnName,changes));
-                }
-            }
-        }
-        
-        private void DataGrid_QueryRowHeight(object? sender, QueryRowHeightEventArgs e)
+        public void Add(string column)
         {
-            if (e.RowIndex > 0)
-            {
-                e.Height = DataGrid.RowHeight;
-                if (DataGrid.GridColumnSizer.GetAutoRowHeight(e.RowIndex, gridRowResizingOptions, out var autoHeight))
-                    if (autoHeight > DataGrid.RowHeight)
-                        e.Height = autoHeight;
-                e.Handled = true;
-            }
+            if (!Contains(column))
+                Columns.Add(column);
         }
 
-        private void DataGrid_SizeChanged(object sender, SizeChangedEventArgs e)
-        {
-            if (IsReady && !bRefreshing) ResizeColumns(DataGrid, e.NewSize.Width - 2, e.NewSize.Height - 2);
-        }
+        public bool Contains(string column) => Columns.Contains(column);
+    }
 
-        #region Row Selections
+    public void AddHiddenColumn(string column) => HiddenColumns.Add(column);
 
-        protected CoreRow[] GetVisibleRows()
-        {
-            var items = DataGrid.ItemsSource;
+    public HiddenColumnsList HiddenColumns { get; }
 
-            var result = new List<CoreRow>();
+    private static bool IsSequenced => typeof(T).GetInterfaces().Any(x => x.Equals(typeof(ISequenceable)));
 
-            var table = DataGridItems;
-            if (table is null) return Array.Empty<CoreRow>();
+    public double RowHeight
+    {
+        get => UIComponent.RowHeight;
+        set => UIComponent.RowHeight = value;
+    }
 
-            var rows = DataGrid.View.Records.Select(x => (x.Data as DataRowView)!).ToList();
-            foreach (var row in rows)
-            {
-                var iRow = table.Rows.IndexOf(row.Row);
-                result.Add(Data.Rows[iRow]);
-            }
+    public double HeaderHeight
+    {
+        get => UIComponent.HeaderRowHeight;
+        set => UIComponent.HeaderRowHeight = value;
+    }
 
-            //foreach (var item in DataGrid.SelectedItems)
-            //{
-            //    if (item is CoreRow)
-            //    {
-            //        //result.Add(item as CoreRow);
-            //    }
-            //    else
-            //    {
-            //        var datarow = item as System.Data.DataRowView;
-            //        int row = datarow.Row.Table.Rows.IndexOf(datarow.Row);
-            //        result.Add(Data.Rows[row]);
-            //    }
-            //}
-            return result.ToArray();
-        }
+    #region Options
 
-        private CoreRow[] GetSelectedRows()
-        {
-            //Logger.Send(LogType.Information, ClientFactory.UserID, String.Format("{0}: GetSelectedRows({1})", this.GetType().EntityName(), DataGrid.SelectedItems.Count));
-            var result = new List<CoreRow>();
-            foreach (var item in DataGrid.SelectedItems)
-            {
-                if (item is CoreRow)
-                {
-                    //result.Add(item as CoreRow);
-                }
-                else if (item is DataRowView datarow)
-                {
-                    var row = datarow.Row.Table.Rows.IndexOf(datarow.Row);
-                    result.Add(Data.Rows[row]);
-                }
-            }
+    /// <summary>
+    /// Initialise things like custom buttons; called once during construction.
+    /// </summary>
+    protected abstract void Init();
 
-            return result.ToArray();
-        }
-
-        private void SetSelectedRows(CoreRow[] rows)
-        {           
-            DataGrid.SelectedItems.Clear();
+    protected abstract void DoReconfigure(FluentList<DynamicGridOption> options);
 
-            var table = DataGridItems;
-            if(table is null) return;
+    private bool _hasLoadedOptions = false;
 
-            var dataRows = rows.Where(x => x.Index > -1).Select(row =>
-            {
-                return table.Rows[row.Index];
-            });
-            if (!HasOption(DynamicGridOption.MultiSelect))
-            {
-                dataRows = dataRows.Take(1);
-            }
+    protected void OptionsChanged()
+    {
+        var reloadColumns = false;
 
-            foreach (var row in dataRows)
-            {
-                var record = DataGrid.View?.Records.FirstOrDefault(x => (x.Data as DataRowView)!.Row == row);
-                if(record?.Data is DataRowView rowView)
-                {
-                    DataGrid.SelectedItems.Add(rowView);
-                }
-            }
-        }
+        Help.Visibility = HasOption(DynamicGridOption.ShowHelp) ? Visibility.Visible : Visibility.Collapsed;
 
-        public override CoreRow[] SelectedRows
-        {
-            get => GetSelectedRows();
-            set => SetSelectedRows(value);
-        }
+        Add.Visibility = HasOption(DynamicGridOption.AddRows) ? Visibility.Visible : Visibility.Collapsed;
+        Edit.Visibility = HasOption(DynamicGridOption.EditRows) ? Visibility.Visible : Visibility.Collapsed;
 
-        /// <summary>
-        /// Call the <see cref="OnSelectItem"/> event, and do any updating which needs to occur when items are selected.
-        /// </summary>
-        /// <param name="rows"></param>
-        protected virtual void SelectItems(CoreRow[]? rows)
-        {
-            if (IsReady)
-                OnSelectItem?.Invoke(this, new DynamicGridSelectionEventArgs(rows));
+        EditSpacer.Visibility = HasOption(DynamicGridOption.AddRows) || HasOption(DynamicGridOption.EditRows)
+            ? Visibility.Visible
+            : Visibility.Collapsed;
 
-            DuplicateBtn.Visibility =
-                typeof(T).IsAssignableTo(typeof(IDuplicatable)) && rows != null && rows.Length >= 1 ? Visibility.Visible : Visibility.Collapsed;
-        }
+        Print.Visibility = HasOption(DynamicGridOption.Print) ? Visibility.Visible : Visibility.Collapsed;
+        PrintSpacer.Visibility = HasOption(DynamicGridOption.Print) ? Visibility.Visible : Visibility.Collapsed;
 
-        private bool bFilterVisible;
-        private bool bSwallowKey;
+        Cut.Visibility = IsSequenced && HasOption(DynamicGridOption.EditRows) ? Visibility.Visible : Visibility.Collapsed;
+        Copy.Visibility = IsSequenced && HasOption(DynamicGridOption.EditRows) ? Visibility.Visible : Visibility.Collapsed;
+        Paste.Visibility = IsSequenced && HasOption(DynamicGridOption.EditRows) ? Visibility.Visible : Visibility.Collapsed;
+        ClipboardSpacer.Visibility = IsSequenced && HasOption(DynamicGridOption.EditRows) ? Visibility.Visible : Visibility.Collapsed;
 
-        private void DataGrid_PreviewGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
-        {
-            var bOld = bFilterVisible;
-            if (e.NewFocus is GridFilterControl)
-                bFilterVisible = true;
-            else if (e.NewFocus is ScrollViewer || e.NewFocus is SfDataGrid)
-                bFilterVisible = false;
-            if (bOld && !bFilterVisible)
-            {
-                //Logger.Send(LogType.Information, "", String.Format("{0}: PreviewGotKeyboardFocus -> {1}", this.GetType().EntityName(), e.NewFocus.GetType().EntityName()));
-                SelectItems(SelectedRows);
-                bSwallowKey = true;
-            }
-        }
+        Export.Visibility = HasOption(DynamicGridOption.ExportData) ? Visibility.Visible : Visibility.Collapsed;
+        Import.Visibility = HasOption(DynamicGridOption.ImportData) ? Visibility.Visible : Visibility.Collapsed;
+        ExportSpacer.Visibility = HasOption(DynamicGridOption.ExportData) || HasOption(DynamicGridOption.ImportData)
+            ? Visibility.Visible
+            : Visibility.Collapsed;
 
-        private void DataGrid_KeyUp(object sender, KeyEventArgs e)
-        {
-            if (sender != DataGrid) return;
+        SwitchViewBtn.Visibility = HasOption(DynamicGridOption.DirectEdit) ? Visibility.Visible : Visibility.Collapsed;
 
-            bSwallowKey = false;
+        Count.Visibility = HasOption(DynamicGridOption.RecordCount) ? Visibility.Visible : Visibility.Collapsed;
 
-            if (IsSequenced)
-            {
-                if (e.Key == Key.X && (Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control)
-                {
-                    CutToClipBuffer();
-                }
-                else if (e.Key == Key.C && (Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control)
-                {
-                    CopyToClipBuffer();
-                }
-                else if (e.Key == Key.V && (Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control)
-                {
-                    PasteFromClipBuffer();
-                }
-                else if (e.Key == Key.Escape)
-                {
-                    ResetClipBuffer();
-                    InvalidateGrid();
-                }
-            }
-        }
+        Delete.Visibility = HasOption(DynamicGridOption.DeleteRows) ? Visibility.Visible : Visibility.Collapsed;
 
-        private DispatcherTimer? clicktimer;
+        if (up != null)
+            up.Position = HasOption(DynamicGridOption.EditRows) ? DynamicActionColumnPosition.Start : DynamicActionColumnPosition.Hidden;
+        if (down != null)
+            down.Position = HasOption(DynamicGridOption.EditRows) ? DynamicActionColumnPosition.Start : DynamicActionColumnPosition.Hidden;
 
-        private void StartTimer()
-        {
-            if (clicktimer is null)
-            {
-                clicktimer = new DispatcherTimer();
-                clicktimer.Interval = TimeSpan.FromMilliseconds(200);
-                clicktimer.Tick += (o, e) =>
-                {
-                    clicktimer.IsEnabled = false;
-                    SelectItems(SelectedRows);
-                };
-            }
+        if (DuplicateBtn != null)
+            DuplicateBtn.Visibility = Visibility.Collapsed;
 
-            clicktimer.IsEnabled = true;
-        }
+        reloadColumns = reloadColumns || UIComponent.OptionsChanged();
 
-        private void StopTimer()
+        if(reloadColumns)
         {
-            if (clicktimer is not null)
-                clicktimer.IsEnabled = false;
+            Refresh(true, false);
         }
+    }
 
-        private void DataGrid_MouseRightButtonUp(object sender, MouseButtonEventArgs e)
-        {
-            if (!IsEnabled)
-                return;
+    public bool IsDirectEditMode(IEnumerable<DynamicGridOption>? options = null)
+    {
+        return HasOption(DynamicGridOption.DirectEdit, options)
+            && (Settings.ViewMode == DynamicGridSettings.DynamicGridViewMode.DirectEdit
+                || Settings.ViewMode == DynamicGridSettings.DynamicGridViewMode.Default);
+    }
 
-            var visualContainer = DataGrid.GetVisualContainer();
-            var rowcolumnindex = visualContainer.PointToCellRowColumnIndex(e.GetPosition(visualContainer));
-            var columnindex = DataGrid.ResolveToGridVisibleColumnIndex(rowcolumnindex.ColumnIndex);
-            if ((columnindex < 0) || (columnindex >= ColumnList.Count))
-                return;
-            var column = ColumnList[columnindex] as DynamicActionColumn;
+    private void SwitchView_Click(object sender, RoutedEventArgs e)
+    {
+        Settings.ViewMode = Settings.ViewMode switch
+        {
+            DynamicGridSettings.DynamicGridViewMode.Default => DynamicGridSettings.DynamicGridViewMode.Normal,
+            DynamicGridSettings.DynamicGridViewMode.Normal => DynamicGridSettings.DynamicGridViewMode.DirectEdit,
+            DynamicGridSettings.DynamicGridViewMode.DirectEdit or _ => DynamicGridSettings.DynamicGridViewMode.Normal
+        };
+        SaveSettings(Settings);
+        Reconfigure();
+    }
+    public FluentList<DynamicGridOption> Options { get; }
+    
 
-            var menu = column?.ContextMenu?.Invoke(SelectedRows);
-            if (menu != null && menu.Items.Count > 0)
-            {
-                menu.IsOpen = true;
-            }
-        }
+    protected void OnReconfigureEvent(FluentList<DynamicGridOption> options)
+    {
+        OnReconfigure?.Invoke(options);
+    }
 
-        private void DataGrid_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
+    /// <summary>
+    /// Configure custom buttons and options.
+    /// </summary>
+    public void Reconfigure(FluentList<DynamicGridOption> options)
+    {
+        options.BeginUpdate().Clear();
+        DoReconfigure(options);
+        OnReconfigureEvent(options);
+        options.EndUpdate();
+        if (!_hasLoadedOptions)
         {
-            if (!IsEnabled)
-                return;
+            _hasLoadedOptions = true;
+            OptionsChanged();
+        }
+    }
+    
+    public void Reconfigure()
+    {
+        Reconfigure(Options);
+    }
+    
+    public void Reconfigure(ReconfigureEvent onReconfigure)
+    {
+        OnReconfigure += onReconfigure;
+        Reconfigure();
+    }
+    public bool HasOption(DynamicGridOption option, IEnumerable<DynamicGridOption>? options = null) => (options ?? Options).Contains(option);
+    bool IDynamicGrid.HasOption(InABox.DynamicGrid.DynamicGridOption option) => HasOption(option, null);
 
-            // Header Click Here!
-            if (DataGrid.SelectedIndex == -1)
-            {
-                var visualContainer = DataGrid.GetVisualContainer();
-                var rowcolumnindex = visualContainer.PointToCellRowColumnIndex(e.GetPosition(visualContainer));
-                var columnindex = DataGrid.ResolveToGridVisibleColumnIndex(rowcolumnindex.ColumnIndex);
+    #endregion
 
-                if (columnindex > -1 && columnindex < ColumnList.Count)
-                {
-                    var bRefresh = false;
-                    var dac = ColumnList[columnindex] as DynamicActionColumn;
-                    if (dac != null)
-                        if (dac.Action?.Invoke(null) == true)
-                            bRefresh = true;
-                    if (bRefresh)
-                        Dispatcher.Invoke(() => { Refresh(false, true); });
-                }
-            }
-            else if (!bFilterVisible)
-            {
-                StartTimer();
-            }
+    protected virtual DynamicGridSettings LoadSettings()
+    {
+        return new DynamicGridSettings();
+    }
+    protected virtual void SaveSettings(DynamicGridSettings settings)
+    {
+    }
 
-            bSwallowKey = false;
-        }
 
+    protected virtual void LoadColumnsMenu(ContextMenu menu)
+    {
+    }
 
-        private void DataGrid_CellTapped(object? sender, GridCellTappedEventArgs e)
-        {
-            if (!IsEnabled)
-                return;
+    protected void UpdateCell(int row, string colname, object? value)
+    {
+        var coreRow = Data.Rows[row];
+        coreRow[colname] = value;
+        UIComponent.UpdateCell(coreRow, colname, value);
+    }
 
-            var dac = ColumnList[e.RowColumnIndex.ColumnIndex] as DynamicActionColumn;
-            if (dac != null)
-            {
-                var bRefresh = false;
-                if(e.ChangedButton == MouseButton.Left || (e.ChangedButton == MouseButton.Right && dac is DynamicMenuColumn))
-                {
-                    foreach (var row in SelectedRows)
-                        if (dac.Action?.Invoke(row) == true)
-                            bRefresh = true;
-                }
-                if (bRefresh)
-                    Task.Run(() => { Dispatcher.Invoke(() => { Refresh(true, true); }); });
-            }
-            else
-            {
-                StartTimer();
-            }
-        }
+    private void EntityChanged(T obj, CoreRow row, string changedColumn, Dictionary<string, object?> changes)
+    {
+        OnAfterEditorValueChanged(null, new T[] { obj }, new AfterEditorValueChangedArgs(changedColumn, changes), changes);
 
+        SaveItem(obj);
 
-        protected virtual void DoDoubleClick(object sender)
+        foreach (var (key, value) in changes)
         {
-            if (IsDirectEditMode())
-                return;
-
-            //SelectItems(SelectedRows);
-            var args = new HandledEventArgs(false);
-            OnDoubleClick?.Invoke(sender, args);
-            if (args.Handled)
-                return;
-            if (HasOption(DynamicGridOption.EditRows))
-                DoEdit();
+            row[key] = value;
         }
 
-        private void DataGrid_CellDoubleTapped(object? sender, GridCellDoubleTappedEventArgs e)
-        {
-            StopTimer();
-
-            if (OnCellDoubleClick is not null && ColumnList[e.RowColumnIndex.ColumnIndex] is DynamicGridColumn column)
-            {
-                var row = GetRowFromIndex(e.RowColumnIndex.RowIndex);
-                var args = new DynamicGridCellClickEventArgs(row, column);
-                OnCellDoubleClick?.Invoke(this, args);
-                if (args.Handled)
-                    return;
-            }
+        UIComponent.UpdateRow(row);
+    }
 
-            if (e.Record != null)
-                DoDoubleClick(this);
-        }
+    #region Row Selections
 
-        #endregion
+    protected CoreRow[] GetVisibleRows()
+    {
+        return UIComponent.GetVisibleRows();
+    }
 
-        #region Column Handling
 
-        private readonly List<DynamicColumnBase> ColumnList = new();
+    public CoreRow[] SelectedRows
+    {
+        get => UIComponent.SelectedRows;
+        set => UIComponent.SelectedRows = value;
+    }
 
-        protected virtual DynamicGridColumns LoadColumns()
-        {
-            var result = new DynamicGridColumns();
+    /// <summary>
+    /// Call the <see cref="OnSelectItem"/> event, and do any updating which needs to occur when items are selected.
+    /// </summary>
+    /// <param name="rows"></param>
+    protected virtual void SelectItems(CoreRow[]? rows)
+    {
+        if (IsReady)
+            OnSelectItem?.Invoke(this, new DynamicGridSelectionEventArgs(rows));
 
-            var cols = IsDirectEditMode()
-                ? new Columns<T>().Default(ColumnType.IncludeForeignKeys, ColumnType.ExcludeID)
-                : new Columns<T>().Default(ColumnType.IncludeLinked, ColumnType.ExcludeID);
-            result.AddRange(MasterColumns.Where(x => cols.Items.Any(c => c.Property.Equals(x.ColumnName)))
-                .OrderBy(x => CoreUtils.GetPropertySequence(typeof(T), x.ColumnName)));
-            return result;
-        }
+        DuplicateBtn.Visibility =
+            typeof(T).IsAssignableTo(typeof(IDuplicatable)) && rows != null && rows.Length >= 1 ? Visibility.Visible : Visibility.Collapsed;
+    }
 
+    protected virtual void DoDoubleClick(object sender)
+    {
+        if (IsDirectEditMode())
+            return;
+
+        //SelectItems(SelectedRows);
+        var args = new HandledEventArgs(false);
+        OnDoubleClick?.Invoke(sender, args);
+        if (args.Handled)
+            return;
+        if (HasOption(DynamicGridOption.EditRows))
+            DoEdit();
+    }
 
-        /// <summary>
-        /// Provide a set of columns which is the default for this grid.
-        /// </summary>
-        public virtual DynamicGridColumns GenerateColumns()
-        {
-            var columns = new DynamicGridColumns();
+    #endregion
 
-            var cols = IsDirectEditMode()
-                ? new Columns<T>().Default(ColumnType.IncludeForeignKeys, ColumnType.ExcludeID)
-                : new Columns<T>().Default(ColumnType.IncludeLinked, ColumnType.ExcludeID);
+    #region Column Handling
 
-            if (cols != null)
-            {
-                foreach (var col in cols.Items)
-                {
-                    var mc = MasterColumns.FirstOrDefault(x => x.ColumnName.Equals(col.Property));
-                    if (mc != null && mc.Editor is not NullEditor && mc.Editor.Visible != Visible.Hidden)
-                        columns.Add(mc);
-                }
-            }
-            return columns;
-        }
+    protected virtual DynamicGridColumns LoadColumns()
+    {
+        var result = new DynamicGridColumns();
+
+        var cols = IsDirectEditMode()
+            ? new Columns<T>().Default(ColumnType.IncludeForeignKeys, ColumnType.ExcludeID)
+            : new Columns<T>().Default(ColumnType.IncludeLinked, ColumnType.ExcludeID);
+        result.AddRange(MasterColumns.Where(x => cols.Items.Any(c => c.Property.Equals(x.ColumnName)))
+            .OrderBy(x => CoreUtils.GetPropertySequence(typeof(T), x.ColumnName)));
+        return result;
+    }
 
 
-        private bool SwapRows(int row1, int row2)
-        {
+    /// <summary>
+    /// Provide a set of columns which is the default for this grid.
+    /// </summary>
+    public virtual DynamicGridColumns GenerateColumns()
+    {
+        var columns = new DynamicGridColumns();
 
-            CoreRow[] rows = Data.Rows.Where(x => x.Index.Equals(row1) || x.Index.Equals(row2)).ToArray();
-            var items = LoadItems(rows);
-            var first = (items.First() as ISequenceable)!;
-            var last = (items.Last() as ISequenceable)!;
-            var iBuf1 = first.Sequence;
-            var iBuf2 = last.Sequence;
-            first.Sequence = iBuf2;
-            last.Sequence = iBuf1;
-            SaveItems(items);
-            return true;
-        }
+        var cols = IsDirectEditMode()
+            ? new Columns<T>().Default(ColumnType.IncludeForeignKeys, ColumnType.ExcludeID)
+            : new Columns<T>().Default(ColumnType.IncludeLinked, ColumnType.ExcludeID);
 
-        protected virtual void SaveColumns(DynamicGridColumns columns)
+        if (cols != null)
         {
-        }
-
-        public override int DesiredWidth()
-        {
-            var result = 0;
-            for (var i = 0; i < ColumnList.Count; i++)
+            foreach (var col in cols.Items)
             {
-                var col = ColumnList[i];
-                if (col is DynamicActionColumn)
-                {
-                    result += (int)RowHeight;
-                }
-                else if (col is DynamicGridColumn)
-                {
-                    var dgc = (DynamicGridColumn)col;
-                    result += dgc.Width > 0 ? dgc.Width : 300;
-                }
+                var mc = MasterColumns.FirstOrDefault(x => x.ColumnName.Equals(col.Property));
+                if (mc != null && mc.Editor is not NullEditor && mc.Editor.Visible != Visible.Hidden)
+                    columns.Add(mc);
             }
-
-            return result;
         }
+        return columns;
+    }
 
-        private void ResizeColumns(SfDataGrid grid, double width, double height)
-        {
-            if (Data == null || width <= 0)
-                return;
 
-            Dispatcher.BeginInvoke(() =>
-            {
-                //var vc = DataGrid.GetVisualContainer();
-                //vc.RowHeightManager.Reset();
-                //vc.InvalidateMeasureInfo();
-                
-                var fAvailWidth = width - (SystemParameters.VerticalScrollBarWidth);
-                
-                //if (Data.Rows.Count * (DataGrid.RowHeight + 1) + DataGrid.HeaderRowHeight > height + 0.5F)
-                //if (height < DataGrid.AutoScroller.VScrollBar.Maximum)
-                //    fAvailWidth -= (SystemParameters.VerticalScrollBarWidth + 0.75);
+    private bool SwapRows(int row1, int row2)
+    {
 
+        CoreRow[] rows = Data.Rows.Where(x => x.Index.Equals(row1) || x.Index.Equals(row2)).ToArray();
+        var items = LoadItems(rows);
+        var first = (items.First() as ISequenceable)!;
+        var last = (items.Last() as ISequenceable)!;
+        var iBuf1 = first.Sequence;
+        var iBuf2 = last.Sequence;
+        first.Sequence = iBuf2;
+        last.Sequence = iBuf1;
+        SaveItems(items);
+        return true;
+    }
 
-                double fCurWidth = 0.0F;
-                var NumAutoCols = 0;
+    protected virtual void SaveColumns(DynamicGridColumns columns)
+    {
+    }
 
+    public int DesiredWidth()
+    {
+        return UIComponent.DesiredWidth();
+    }
 
-                var colWidths = new Dictionary<int, double>();
-                for (var i = 0; i < ColumnList.Count; i++)
-                {
-                    var col = ColumnList[i];
-                    if (col is DynamicImageColumn dic)
-                    {
-                        colWidths[i] = RowHeight;
-                        fCurWidth += colWidths[i];
-                    }
-                    else if (col is DynamicTextColumn dxc)
-                    {
-                        colWidths[i] = dxc.Width;
-                        if (dxc.Width != 0)
-                            fCurWidth += Math.Max(0.0F, dxc.Width);
-                        else
-                            NumAutoCols++;
-                    }
-                    else if (col is DynamicTemplateColumn dtc)
-                    {
-                        colWidths[i] = dtc.Width;
-                        if (dtc.Width != 0)
-                            fCurWidth += Math.Max(0.0F, dtc.Width);
-                        else
-                            NumAutoCols++;
-                    }
-                    else if (col is DynamicGridColumn dgc)
-                    {
-                        colWidths[i] = dgc.Width;
-                        if (dgc.Width != 0)
-                            fCurWidth += Math.Max(0.0F, dgc.Width);
-                        else
-                            NumAutoCols++;
-                    }
-                }
+    public virtual void ConfigureColumns(DynamicGridColumns columns)
+    {
+        DoCustomiseColumnsEvent(this,columns);
+    }
+    
+    private void ReloadColumns()
+    {
+        ConfigureColumns(MasterColumns /*, false */);
 
-                if (NumAutoCols > 0)
-                {
-                    var fAutoWidth = (fAvailWidth - fCurWidth) / NumAutoCols;
-                    if (fAutoWidth < 100)
-                        fAutoWidth = 100;
-                    for (var i = 0; i < ColumnList.Count; i++)
-                        if (colWidths[i] == 0)
-                            colWidths[i] = fAutoWidth;
-                }
-                
-                foreach (var index in colWidths.Keys)
-                    DataGrid.Columns[index].Width = Math.Max(0.0F, colWidths[index]);
-            });
-            
-            //
-            // var vc = DataGrid.GetVisualContainer();
-            // vc.RowHeightManager.Reset();
-            // vc.InvalidateMeasureInfo();
-            // if (vc.ScrollOwner != null)
-            //     vc.ScrollOwner.HorizontalScrollBarVisibility = vc.ExtentWidth <= fAvailWidth ? ScrollBarVisibility.Hidden : ScrollBarVisibility.Visible;
+        VisibleColumns = LoadColumns();
 
-        }
+        ConfigureColumns(VisibleColumns /*, true */);
 
-        private void LoadActionColumns(DynamicActionColumnPosition position)
-        {
-            for (var i = 0; i < ActionColumns.Count; i++)
-            {
-                var column = ActionColumns[i];
-                if (column.Position == position)
-                {
-                    //String sColName = String.Format("ActionColumn{0}{1}", i, position == DynamicActionColumnPosition.Start ? "L" : "R");
-                    var sColName = string.Format("ActionColumn{0}", i);
-                    gridRowResizingOptions.ExcludeColumns.Add(sColName);
+        UIComponent.RefreshColumns(VisibleColumns, ActionColumns);
+    }
 
-                    if (column is DynamicImageColumn imgcol)
-                    {
-                        var newcol = new GridImageColumn();
-                        newcol.MappingName = sColName;
-                        //newcol.Stretch = Stretch.Uniform;
-                        newcol.Width = column.Width == 0 ? DataGrid.RowHeight : column.Width;
-                        newcol.Padding = new Thickness(4);
-                        newcol.ImageHeight = DataGrid.RowHeight - 8;
-                        newcol.ImageWidth = DataGrid.RowHeight - 8;
-                        newcol.ColumnSizer = GridLengthUnitType.None;
-                        newcol.HeaderText = column.HeaderText;
-                        newcol.AllowSorting = false;
-
-                        ApplyFilterStyle(newcol, true, true);
-
-                        newcol.ShowToolTip = column.ToolTip != null;
-                        newcol.ShowHeaderToolTip = column.ToolTip != null;
-
-                        var style = new Style();
-                        style.Setters.Add(new Setter(BackgroundProperty, new SolidColorBrush(Colors.Gainsboro)));
-                        style.Setters.Add(new Setter(IsEnabledProperty, false));
-                        newcol.FilterRowCellStyle = style;
-
-                        var headstyle = new Style(typeof(GridHeaderCellControl));
-                        headstyle.Setters.Add(new Setter(BackgroundProperty, new SolidColorBrush(Colors.Gainsboro)));
-                        headstyle.Setters.Add(new Setter(ForegroundProperty, new SolidColorBrush(Colors.Black)));
-                        headstyle.Setters.Add(new Setter(FontSizeProperty, 12D));
-                        if (!string.IsNullOrWhiteSpace(column.HeaderText))
-                        {
-                            //headstyle.Setters.Add(new Setter(LayoutTransformProperty, new RotateTransform(270.0F)));
-                            headstyle.Setters.Add(new Setter(BorderThicknessProperty, new Thickness(0.0, 0.0, 0, 0)));
-                            headstyle.Setters.Add(new Setter(MarginProperty, new Thickness(0, 0, 0.75, 0.75)));
-                            if (imgcol.VerticalHeader)
-                                headstyle.Setters.Add(new Setter(TemplateProperty,
-                                    Application.Current.Resources["VerticalColumnHeader"] as ControlTemplate));
-                        }
-                        else
-                        {
-                            var image = imgcol.Image?.Invoke(null);
-                            if (image != null)
-                            {
-                                var template = new ControlTemplate(typeof(GridHeaderCellControl));
-                                var border = new FrameworkElementFactory(typeof(Border));
-                                border.SetValue(Border.BackgroundProperty, new SolidColorBrush(Colors.Gainsboro));
-                                border.SetValue(Border.PaddingProperty, new Thickness(4));
-                                border.SetValue(MarginProperty, new Thickness(0, 0, 1, 1));
-                                var img = new FrameworkElementFactory(typeof(Image));
-                                img.SetValue(Image.SourceProperty, image);
-                                border.AppendChild(img);
-                                template.VisualTree = border;
-                                headstyle.Setters.Add(new Setter(TemplateProperty, template));
-                            }
-                        }
+    #endregion
 
-                        newcol.HeaderStyle = headstyle;
+    #region Refresh / Reload
 
-                        DataGrid.Columns.Add(newcol);
-                        ColumnList.Add(column);
-                    }
-                    else if (column is DynamicTextColumn txtCol)
-                    {
+    protected abstract void Reload(Filters<T> criteria, Columns<T> columns, ref SortOrder<T>? sort, Action<CoreTable?, Exception?> action);
 
-                        var newcol = new GridTextColumn();
-                        gridRowResizingOptions.ExcludeColumns.Add(sColName);
-                        newcol.TextWrapping = TextWrapping.NoWrap;
-
-                        newcol.TextAlignment = txtCol.Alignment == Alignment.NotSet
-                            ? TextAlignment.Left
-                            : txtCol.Alignment == Alignment.BottomLeft || txtCol.Alignment == Alignment.MiddleLeft ||
-                              txtCol.Alignment == Alignment.TopLeft
-                                ? TextAlignment.Left
-                                : txtCol.Alignment == Alignment.BottomCenter || txtCol.Alignment == Alignment.MiddleCenter ||
-                                  txtCol.Alignment == Alignment.TopCenter
-                                    ? TextAlignment.Center
-                                    : TextAlignment.Right;
-
-                        newcol.AllowEditing = false;
-                        newcol.UpdateTrigger = UpdateSourceTrigger.PropertyChanged;
-                        newcol.MappingName = sColName;
-                        newcol.Width = column.Width;
-                        newcol.ColumnSizer = GridLengthUnitType.None;
-                        newcol.HeaderText = column.HeaderText;
-                        newcol.AllowFiltering = column.Filters != null && column.Filters.Any();
-                        newcol.AllowSorting = false;
-                        newcol.FilterRowOptionsVisibility = Visibility.Collapsed;
-                        newcol.ShowHeaderToolTip = column.ToolTip != null;
-
-                        var style = new Style();
-                        style.Setters.Add(new Setter(BackgroundProperty, new SolidColorBrush(Colors.Gainsboro)));
-                        style.Setters.Add(new Setter(IsEnabledProperty, false));
-                        newcol.FilterRowCellStyle = style;
-
-                        var headstyle = new Style(typeof(GridHeaderCellControl));
-                        headstyle.Setters.Add(new Setter(BackgroundProperty, new SolidColorBrush(Colors.Gainsboro)));
-                        headstyle.Setters.Add(new Setter(ForegroundProperty, new SolidColorBrush(Colors.Black)));
-                        headstyle.Setters.Add(new Setter(FontSizeProperty, 12D));
-                        headstyle.Setters.Add(new Setter(MarginProperty, new Thickness(0, -0.75, 0, 0.75)));
-                        headstyle.Setters.Add(new Setter(BorderThicknessProperty, new Thickness(0.75)));
-                        if (txtCol.VerticalHeader)
-                        {
-                            headstyle.Setters.Add(new Setter(HorizontalContentAlignmentProperty, HorizontalAlignment.Left));
-                            headstyle.Setters.Add(new Setter(TemplateProperty,
-                                Application.Current.Resources["VerticalColumnHeader"] as ControlTemplate));
-                        }
+    public Filter<T>? DefineFilter()
+    {
+        if (OnDefineFilter is null)
+            return null;
+        var result = OnDefineFilter.Invoke(typeof(T)) as Filter<T>;
+        return result;
+    }
 
-                        newcol.HeaderStyle = headstyle;
+    protected virtual bool FilterRecord(CoreRow row)
+    {
+        var bOK = ActionColumns.All(x =>
+        {
+            return x.FilterRecord is null || x.SelectedFilters is null || !x.SelectedFilters.Any()
+                || x.FilterRecord.Invoke(row, x.SelectedFilters);
+        });
+        if (bOK && OnFilterRecord is not null)
+            bOK = OnFilterRecord(row);
+        return bOK;
+    }
 
-                        DataGrid.Columns.Add(newcol);
-                        ColumnList.Add(column);
-                    }
-                    else if (column is DynamicTemplateColumn tmplCol)
-                    {
-                        var newcol = new GridTemplateColumn();
-                        newcol.CellTemplateSelector = new TemplateColumnSelector() { DataTemplate = tmplCol.Template };
-                        newcol.AllowEditing = false;
-                        newcol.UpdateTrigger = UpdateSourceTrigger.PropertyChanged;
-                        
-                        newcol.Width = tmplCol.Width;
-                        newcol.ColumnSizer = GridLengthUnitType.None;
-                        newcol.HeaderText = column.HeaderText;
-                        newcol.AllowFiltering = false;
-                        newcol.AllowSorting = false;
-                        newcol.FilterRowOptionsVisibility = Visibility.Collapsed;
-                        newcol.ShowToolTip = false;
-                        newcol.ShowHeaderToolTip = false;
-
-                        var style = new Style();
-                        style.Setters.Add(new Setter(BackgroundProperty, new SolidColorBrush(Colors.Gainsboro)));
-                        style.Setters.Add(new Setter(IsEnabledProperty, false));
-                        newcol.FilterRowCellStyle = style;
-
-                        var headstyle = new Style(typeof(GridHeaderCellControl));
-                        headstyle.Setters.Add(new Setter(BackgroundProperty, new SolidColorBrush(Colors.Gainsboro)));
-                        headstyle.Setters.Add(new Setter(ForegroundProperty, new SolidColorBrush(Colors.Black)));
-                        headstyle.Setters.Add(new Setter(FontSizeProperty, 12D));
-                        headstyle.Setters.Add(new Setter(MarginProperty, new Thickness(0, -0.75, 0, 0.75)));
-                        headstyle.Setters.Add(new Setter(BorderThicknessProperty, new Thickness(0.75)));
-                        newcol.HeaderStyle = headstyle;
-
-                        DataGrid.Columns.Add(newcol);
-                        ColumnList.Add(column);
-                    }
-                }
-            }
-        }
-        
-        private class TemplateColumnSelector : DataTemplateSelector
-        {
-            public Func<FrameworkElement> DataTemplate { get; init; }
-            
-            public override DataTemplate SelectTemplate(object item, DependencyObject container) 
-                => TemplateGenerator.CreateDataTemplate(DataTemplate);
-        }
+    public IEnumerable<TType> ExtractValues<TType>(Expression<Func<T, TType>> column, Selection selection)
+    {
+        var result = selection == Selection.None
+            ? Enumerable.Empty<TType>()
+            : selection == Selection.Selected
+                ? SelectedRows.Select(r => r.Get(column))
+                : Data.ExtractValues(column);
+        return result;
+    }
 
-        private bool CanSort()
-        {
-            return !IsSequenced;
-        }
-        
-        private void ReloadColumns()
-        {
+    public void Refresh(bool reloadcolumns, bool reloaddata)
+    {
+        if (bRefreshing)
+            return;
 
-            ConfigureColumns(MasterColumns /*, false */);
+        if (!DoBeforeRefresh())
+            return;
 
-            VisibleColumns = LoadColumns();
+        UIComponent.BeforeRefresh();
 
-            ConfigureColumns(VisibleColumns /*, true */);
+        var cursor = UseWaitCursor ? new WaitCursor() : null;
 
-            DataGrid.Columns.Suspend();
+        Loading.Visibility = Visibility.Visible;
+        Loading.BeginAnimation(Label.OpacityProperty, LoadingFader);
 
-            ColumnList.Clear();
-            DataGrid.Columns.Clear();
-            DataGrid.TableSummaryRows.Clear();
-            var Summaries = new ObservableCollection<ISummaryColumn>();
+        bRefreshing = true;
 
-            gridRowResizingOptions.ExcludeColumns = new List<string>();
+        if (reloadcolumns)
+            ReloadColumns();
 
-            LoadActionColumns(DynamicActionColumnPosition.Start);
+        if (reloaddata)
+        {
+            _lookupcache.Clear();
 
-            foreach (var column in VisibleColumns)
-            {
-                IProperty? prop;
-                try
-                {
-                    prop = DatabaseSchema.Property(typeof(T), column.ColumnName);
-                }
-                catch (Exception e)
-                {
-                    Logger.Send(LogType.Error, ClientFactory.UserID,
-                        string.Format("Error constructing Column [{0}] : {1}\n{2}", column.ColumnName, e.Message, e.StackTrace));
-                    prop = null;
-                }
+            var criteria = new Filters<T>();
+            var filter = DefineFilter();
+            if (filter != null)
+                criteria.Add(filter);
 
-                if (prop != null)
-                {
+            var columns = DataColumns();
 
-                    IDynamicGridEditorColumn? newcol = null;
-                    if (prop.Editor is IntegerEditor)
-                        newcol = new DynamicGridIntegerColumn<T>(column);
-                    else if (prop.Editor is CurrencyEditor)
-                        newcol = new DynamicGridCurrencyColumn<T>(column);
-                    else if (prop.Editor is DoubleEditor)
-                        newcol = new DynamicGridDoubleColumn<T>(column);
-                    else if (prop.Editor is DateTimeEditor)
-                        newcol = new DynamicGridDateTimeColumn<T>(column);
-                    else if (prop.Editor is DateEditor)
-                        newcol = new DynamicGridDateColumn<T>(column);
-                    else if (prop.Editor is TimeOfDayEditor)
-                        newcol = new DynamicGridTimeOfDayColumn<T>(column);
-                    else if (prop.Editor is TimestampEditor)
-                        newcol = new DynamicGridTimeStampColumn<T>(column);
-                    else if (prop.Editor is DurationEditor)
-                        newcol = new DynamicGridDurationColumn<T>(column);
-                    else if (prop.Editor is CheckBoxEditor)
-                        newcol = new DynamicGridCheckBoxColumn<T>(column);
-                    else if (prop.Editor is ColorEditor)
-                        newcol = new DynamicGridColorColumn<T>(column, column.Width, (int)DataGrid.RowHeight);
-                    else if (prop.Editor is PopupEditor)
-                        newcol = new DynamicGridPopupColumn<T>(column);
-                    else if (prop.Editor is CodePopupEditor)
-                        newcol = new DynamicGridCodePopupColumn<T>(column);
-                    else if (prop.Editor is EnumLookupEditor)
-                        newcol = new DynamicGridEnumLookupColumn<T>(column);
-                    else if (prop.Editor is ComboLookupEditor)
-                        newcol = new DynamicGridComboLookupColumn<T>(column);
-                    else if (prop.Editor is LookupEditor)
-                        newcol = new DynamicGridLookupColumn<T>(column);
-                    else if (prop.Editor is MemoEditor)
-                        newcol = new DynamicGridMemoColumn<T>(column);
-                    else if (prop.Editor is CodeEditor)
-                        newcol = new DynamicGridCodeColumn<T>(column);                    
-                    else if (prop.Editor is UniqueCodeEditor)
-                        newcol = new DynamicGridUniqueCodeColumn<T>(column);
-                    else if (prop.Editor is TextBoxEditor)
-                        newcol = new DynamicGridTextBoxColumn<T>(column);
-
-                    if (newcol != null)
+            var sort = LookupFactory.DefineSort<T>();
+            if (sort == null && IsSequenced)
+                sort = new SortOrder<T>("Sequence");
+
+            Reload(
+                criteria
+                , columns
+                , ref sort
+                , (table, exception) =>
+                {
+                    if (exception != null)
                     {
-                        newcol.GetEntity = () => _editingObject.Object;
-                        newcol.EntityChanged += DoEntityChanged;
-                        if (!newcol.VariableHeight)
-                            gridRowResizingOptions.ExcludeColumns.Add(newcol.MappingName);
-
-                        newcol.Column.AllowEditing = newcol.Editable && IsDirectEditMode();
-
-                        var summary = newcol.Summary();
-                        if (summary != null)
-                            Summaries.Add(summary);
-
-                        
-                        ApplyFilterStyle(newcol.Column, newcol.Filtered, false);
-                        
-                        var headstyle = new Style(typeof(GridHeaderCellControl));
-                        headstyle.Setters.Add(new Setter(BackgroundProperty, new SolidColorBrush(Colors.Gainsboro)));
-                        headstyle.Setters.Add(new Setter(ForegroundProperty, new SolidColorBrush(Colors.Black)));
-                        headstyle.Setters.Add(new Setter(FontSizeProperty, 12D));
-                        newcol.Column.HeaderStyle = headstyle;
-
-                        var cellstyle = new Style();
-                        if (IsDirectEditMode())
+                        Dispatcher.Invoke(() =>
                         {
-                            if (prop.Editor is null || !prop.Editor.Editable.IsDirectEditable())
-                            {
-                                cellstyle.Setters.Add(new Setter(BackgroundProperty,
-                                    new SolidColorBrush(Colors.WhiteSmoke)));
-                                newcol.Column.AllowEditing = false;
-                            }
-                            else
-                            {
-                                cellstyle.Setters.Add(new Setter(BackgroundProperty,
-                                    new SolidColorBrush(Colors.LightYellow)));
-                                newcol.Column.AllowEditing = true;
-                            }
-
-                            cellstyle.Setters.Add(new Setter(ForegroundProperty, new SolidColorBrush(Colors.Black)));
-                            newcol.Column.CellStyle = cellstyle;
-                        }
-                        else
-                        {
-                            cellstyle.Setters.Add(new Setter(BackgroundProperty,
-                                new Binding()
-                                {
-                                    Path = new PropertyPath("."), Converter = CellBackgroundConverter,
-                                    ConverterParameter = column.ColumnName
-                                }));
-                            cellstyle.Setters.Add(new Setter(ForegroundProperty,
-                                new Binding()
-                                    { Converter = CellForegroundConverter, ConverterParameter = column.ColumnName }));
-                            cellstyle.Setters.Add(new Setter(FontSizeProperty,
-                                new Binding()
-                                    { Converter = CellFontSizeConverter, ConverterParameter = column.ColumnName }));
-                            cellstyle.Setters.Add(new Setter(FontStyleProperty,
-                                new Binding()
-                                    { Converter = CellFontStyleConverter, ConverterParameter = column.ColumnName }));
-                            cellstyle.Setters.Add(new Setter(FontWeightProperty,
-                                new Binding()
-                                    { Converter = CellFontWeightConverter, ConverterParameter = column.ColumnName }));
-                            newcol.Column.CellStyle = cellstyle;
-                        }
-                        
-                        DataGrid.Columns.Add(newcol.Column);
-                        ColumnList.Add(column);
-                        foreach (var extra in newcol.ExtraColumns)
-                            AddHiddenColumn(extra);
-                        
+                            MessageWindow.ShowError("Sorry! We couldn't load the data.", exception);
+                        });
                     }
-                    
-                }
-            }
-
-            LoadActionColumns(DynamicActionColumnPosition.End);
-
-            if (Summaries.Any())
-            {
-                DataGrid.CellRenderers.Remove("TableSummary");
-                DataGrid.CellRenderers.Add("TableSummary", new DynamicGridAggregateRenderer());
-
-                DataGrid.TableSummaryRows.Add(new GridTableSummaryRow
-                {
-                    ShowSummaryInRow = false,
-                    Position = TableSummaryRowPosition.Bottom,
-                    SummaryColumns = Summaries
-                });
-            }
-
-            DataGrid.Columns.Resume();
-            DataGrid.RefreshColumns();
-
-            foreach (var key in _filterpredicates.Keys.ToArray())
-                if (DataGrid.Columns.Any(x => string.Equals(x.MappingName, key)))
-                {
-                    var predicates = Serialization.Deserialize<List<FilterPredicate>>(_filterpredicates[key]);
-                    foreach (var predicate in predicates)
+                    else if (table is not null)
                     {
-                        DataGrid.Columns[key].FilterPredicates.Add(predicate);
-                        DataGrid.Columns[key].FilteredFrom = FilteredFrom.FilterRow;
+                        MasterData = table;
+                        Dispatcher.Invoke(() =>
+                        {
+                            ProcessData(reloadcolumns, reloaddata);
+                            DoAfterRefresh();
+                            bRefreshing = false;
+                            IsReady = true;
+                        });
                     }
                 }
-                else
-                {
-                    _filterpredicates.Remove(key);
-                }
-
-            ResizeColumns(DataGrid, DataGrid.ActualWidth - 2, DataGrid.ActualHeight - 2);
+            );
         }
-
-        private void DoEntityChanged(object column, DynamicColumnEntityChangedEventArgs args)
+        else
         {
-            if(_editingObject is null)
-            {
-                return;
-            }
+            ProcessData(reloadcolumns, reloaddata);
+            DoAfterRefresh();
+            bRefreshing = false;
+            IsReady = true;
 
-            OnAfterEditorValueChanged(null, new T[] { _editingObject.Object }, new AfterEditorValueChangedArgs(args.ColumnName, args.Changes), args.Changes);
-            SaveItem(_editingObject.Object);
-            UpdateRow(_editingObject.Row, _editingObject.DataRow, args.Changes);
-            //DataGrid.SelectionController.CurrentCellManager.EndEdit();
+            Loading.BeginAnimation(Label.OpacityProperty, null);
+            Loading.Visibility = Visibility.Collapsed;
         }
 
-
-        private void ApplyFilterStyle(GridColumn column, bool filtering, bool isactioncolumn)
+        if (cursor != null)
         {
-            var filterstyle = new Style();
-            if (filtering)
-            {
-                filterstyle.Setters.Add(new Setter(BackgroundProperty, BaseDynamicGrid.FilterBackground));
-                column.ImmediateUpdateColumnFilter = true;
-                column.ColumnFilter = ColumnFilter.Value;
-                column.FilterRowCondition = FilterRowCondition.Contains;
-                column.FilterRowOptionsVisibility = Visibility.Collapsed;
-                column.AllowBlankFilters = true;
-                column.AllowSorting = isactioncolumn
-                    ? false
-                    : CanSort();
-            }
-            else
-            {
-                filterstyle.Setters.Add(new Setter(BackgroundProperty, new SolidColorBrush(Colors.Gainsboro)));
-                filterstyle.Setters.Add(new Setter(IsEnabledProperty, false));
-                column.ColumnFilter = ColumnFilter.Value;
-                column.AllowFiltering = false;
-                column.AllowSorting = false;
-                column.FilterRowEditorType = "TextBox";
-                column.FilterRowOptionsVisibility = Visibility.Collapsed;
-            }
-
-            column.FilterRowCellStyle = filterstyle;
+            cursor.Dispose();
+            cursor = null;
         }
+    }
+    
+    protected void NotifyBeforeRefresh(BeforeRefreshEventArgs args) => BeforeRefresh?.Invoke(this, args);
+    
+    protected void NotifyAfterRefresh(AfterRefreshEventArgs args) => AfterRefresh?.Invoke(this, args);
 
-        #endregion
-
-        #region Refresh / Reload
-
-        protected abstract void Reload(Filters<T> criteria, Columns<T> columns, ref SortOrder<T>? sort, Action<CoreTable?, Exception?> action);
+    protected bool OnBeforeRefresh()
+    {
+        return true;
+    }
 
-        protected virtual bool FilterRecord(CoreRow row)
+    private bool DoBeforeRefresh()
+    {
+        var result = OnBeforeRefresh();
+        if (result)
         {
-            var bOK = ActionColumns.All(x =>
-            {
-                return x.FilterRecord is null || x.SelectedFilters is null || !x.SelectedFilters.Any()
-                    || x.FilterRecord.Invoke(row, x.SelectedFilters);
-            });
-            if (bOK && OnFilterRecord is not null)
-                bOK = OnFilterRecord(row);
-            return bOK;
+            var args = new BeforeRefreshEventArgs() { Cancel = false };
+            NotifyBeforeRefresh(args);
+            result = args.Cancel == false;
         }
 
+        return result;
+    }
 
-        public override void Refresh(bool reloadcolumns, bool reloaddata)
-        {
-
-            if (bRefreshing)
-                return;
+    protected void OnAfterRefresh()
+    {
+    }
 
-            if (!DoBeforeRefresh())
-                return;
+    protected void DoAfterRefresh()
+    {
+        OnAfterRefresh();
+        NotifyAfterRefresh(new AfterRefreshEventArgs());
+    }
 
-            DataGrid.SelectionForegroundBrush = BaseDynamicGrid.SelectionForeground;
-            DataGrid.RowSelectionBrush = BaseDynamicGrid.SelectionBackground;
+    public Columns<T> DataColumns()
+    {
+        var columns = new Columns<T>();
+        foreach (var column in VisibleColumns)
+            columns.Add(column.ColumnName);
+        foreach (var column in HiddenColumns.ColumnNames)
+            columns.Add(new Column<T>(column));
+        return columns;
+    }
 
-            var cursor = UseWaitCursor ? new WaitCursor() : null;
 
-            Loading.Visibility = Visibility.Visible;
-            Loading.BeginAnimation(Label.OpacityProperty, LoadingFader);
+    private void ProcessData(bool reloadcolumns, bool reloaddata)
+    {
+        Data.Columns.Clear();
+        Data.Setters.Clear();
+        if (MasterData != null)
+            foreach (var column in MasterData.Columns)
+                Data.Columns.Add(column);
 
-            bRefreshing = true;
+        LoadData();
+    }
 
-            // Yo, please don't remove this.
-            // The issue was when we were dynamically adding ActionColumns, and if we had to remove and then re-add them, we were getting massive performance hits
-            // for no reason. I think perhaps the image columns were trying to refer to data that didn't exist anymore when calling DataGrid.Columns.Refresh(),
-            // and thus some mega problems (perhaps even exceptions within Syncfusion) were occurring, and this seems to fix it.
-            // I don't pretend to know why it works; this is probably the strangest problem I've ever come across.
-            if (reloadcolumns)
-                DataGrid.ItemsSource = null;
+    protected readonly Dictionary<CoreRow, CoreRow> _recordmap = new();
 
-            if (reloadcolumns)
-                ReloadColumns();
+    public void UpdateRow<TRow, TType>(CoreRow row, Expression<Func<TRow, TType>> column, TType value, bool refresh = true)
+    {
+        row.Set(column, value);
+        _recordmap[row].Set(column, value);
+        if (refresh)
+            InvalidateRow(row);
+    }
 
-            if (reloaddata)
-            {
-                _lookupcache.Clear();
+    public void UpdateRow<TType>(CoreRow row, string column, TType value, bool refresh = true)
+    {
+        row.Set(column, value);
+        _recordmap[row].Set(column, value);
+        if (refresh)
+            InvalidateRow(row);
+    }
 
-                var criteria = new Filters<T>();
-                var filter = DefineFilter();
-                if (filter != null)
-                    criteria.Add(filter);
 
-                var columns = DataColumns();
+    public void AddRow(CoreRow row)
+    {
+        if (MasterData is null) return;
 
-                var sort = LookupFactory.DefineSort<T>();
-                if (sort == null && IsSequenced)
-                    sort = new SortOrder<T>("Sequence");
+        var masterrow = MasterData.NewRow();
+        MasterData.LoadRow(masterrow, row);
+        Refresh(false, false);
+    }
 
-                Reload(
-                    criteria
-                    , columns
-                    , ref sort
-                    , (table, exception) =>
-                    {
-                        if (exception != null)
-                        {
-                            Dispatcher.Invoke(() =>
-                            {
-                                MessageWindow.ShowError("Sorry! We couldn't load the data.", exception);
-                            });
-                        }
-                        else if (table is not null)
-                        {
-                            MasterData = table;
-                            Dispatcher.Invoke(() =>
-                            {
-                                ProcessData(reloadcolumns, reloaddata);
-                                DoAfterRefresh();
-                                bRefreshing = false;
-                                IsReady = true;
-                            });
-                        }
-                    }
-                );
-            }
-            else
-            {
-                ProcessData(reloadcolumns, reloaddata);
-                DoAfterRefresh();
-                bRefreshing = false;
-                IsReady = true;
+    public void AddRow(T data)
+    {
+        if (MasterData is null) return;
 
-                Loading.BeginAnimation(Label.OpacityProperty, null);
-                Loading.Visibility = Visibility.Collapsed;
-            }
+        var masterrow = MasterData.NewRow();
+        MasterData.LoadRow(masterrow, data);
+        MasterData.Rows.Add(masterrow);
+        Refresh(false, false);
+    }
 
-            if (cursor != null)
-            {
-                cursor.Dispose();
-                cursor = null;
-            }
-        }
+    public void DeleteRow(CoreRow row)
+    {
+        if (MasterData is null) return;
 
-        protected override bool OnBeforeRefresh()
-        {
-            return true;
-        }
+        var masterrow = _recordmap[row];
+        MasterData.Rows.Remove(masterrow);
+        Refresh(false, false);
+    }
 
-        private bool DoBeforeRefresh()
-        {
-            var result = OnBeforeRefresh();
-            if (result)
+    private void FilterRows(CoreTable from, CoreTable into, Dictionary<CoreRow, CoreRow>? recordMap = null, Func<CoreRow, bool>? filter = null)
+    {
+        into.Rows.Clear();
+        recordMap?.Clear();
+        foreach (var row in from.Rows.ToArray())
+            if (FilterRecord(row) && filter?.Invoke(row) != false)
             {
-                var args = new BeforeRefreshEventArgs() { Cancel = false };
-                NotifyBeforeRefresh(args);
-                result = args.Cancel == false;
-            }
-
-            return result;
-        }
-
-        protected override void OnAfterRefresh()
-        {
-        }
-
-        protected void DoAfterRefresh()
-        {
-            OnAfterRefresh();
-            NotifyAfterRefresh(new AfterRefreshEventArgs());
-        }
-
-        public Columns<T> DataColumns()
-        {
-            var columns = new Columns<T>();
-            foreach (var column in VisibleColumns)
-                columns.Add(column.ColumnName);
-            foreach (var column in HiddenColumns.ColumnNames)
-                columns.Add(new Column<T>(column));
-            return columns;
-        }
-
-
-        private void ProcessData(bool reloadcolumns, bool reloaddata)
-        {
-            Data.Columns.Clear();
-            Data.Setters.Clear();
-            if (MasterData != null)
-                foreach (var column in MasterData.Columns)
-                    Data.Columns.Add(column);
-
-            LoadData();
-        }
-
-        protected readonly Dictionary<CoreRow, CoreRow> _recordmap = new();
-
-        public override void UpdateRow<TRow, TType>(CoreRow row, Expression<Func<TRow, TType>> column, TType value, bool refresh = true)
-        {
-            row.Set(column, value);
-            _recordmap[row].Set(column, value);
-            if (refresh)
-                InvalidateRow(row);
-        }
-
-        public override void UpdateRow<TType>(CoreRow row, string column, TType value, bool refresh = true)
-        {
-            row.Set(column, value);
-            _recordmap[row].Set(column, value);
-            if (refresh)
-                InvalidateRow(row);
-        }
-
-
-        public void AddRow(CoreRow row)
-        {
-            if (MasterData is null) return;
-
-            var masterrow = MasterData.NewRow();
-            MasterData.LoadRow(masterrow, row);
-            Refresh(false, false);
-        }
-
-        public void AddRow(T data)
-        {
-            if (MasterData is null) return;
-
-            var masterrow = MasterData.NewRow();
-            MasterData.LoadRow(masterrow, data);
-            MasterData.Rows.Add(masterrow);
-            Refresh(false, false);
-        }
-
-        public void DeleteRow(CoreRow row)
-        {
-            if (MasterData is null) return;
-
-            var masterrow = _recordmap[row];
-            MasterData.Rows.Remove(masterrow);
-            Refresh(false, false);
-        }
-
-        private void FilterRows(CoreTable from, CoreTable into, Dictionary<CoreRow, CoreRow>? recordMap = null, Func<CoreRow, bool>? filter = null)
-        {
-            into.Rows.Clear();
-            recordMap?.Clear();
-            foreach (var row in from.Rows.ToArray())
-                if (FilterRecord(row) && filter?.Invoke(row) != false)
+                var newrow = into.NewRow();
+                for (var i = 0; i < into.Columns.Count; i++)
                 {
-                    var newrow = into.NewRow();
-                    for (var i = 0; i < into.Columns.Count; i++)
-                    {
-                        var value = i < row.Values.Count ? row.Values[i] : null;
-                        if (into.Columns[i].DataType.IsNumeric())
-                            value = into.Columns[i].DataType.IsDefault(value) ? null : value;
-                        //else if (Data.Columns[i].DataType == typeof(String[]))
-                        //    value = String.Join("\n", value as String[]);
-                        newrow.Values.Add(value);
-                    }
-
-                    //newrow.Values.AddRange(row.Values);
-                    //if ((OnFilterRecord == null) || (OnFilterRecord(row)))
-                    into.Rows.Add(newrow);
-                    recordMap?.TryAdd(newrow, row);
+                    var value = i < row.Values.Count ? row.Values[i] : null;
+                    if (into.Columns[i].DataType.IsNumeric())
+                        value = into.Columns[i].DataType.IsDefault(value) ? null : value;
+                    //else if (Data.Columns[i].DataType == typeof(String[]))
+                    //    value = String.Join("\n", value as String[]);
+                    newrow.Values.Add(value);
                 }
-        }
-
-        private void LoadData()
-        {
-            ResetClipBuffer();
-            if (MasterData is null)
-                return;
-            FilterRows(MasterData, Data, _recordmap);
-
-            InvalidateGrid();
 
-            //ScrollBar.Value = _CurrentRow <= 0 ? 0 : _CurrentRow;
-            SelectedRows = Array.Empty<CoreRow>();
-        }
-
-        //IncrementalList<T> _data = null;
-
-        public void InvalidateRow(CoreRow row)
-        {
-            var table = DataGridItems;
-            if(table is null)
-            {
-                return;
+                //newrow.Values.AddRange(row.Values);
+                //if ((OnFilterRecord == null) || (OnFilterRecord(row)))
+                into.Rows.Add(newrow);
+                recordMap?.TryAdd(newrow, row);
             }
+    }
 
-            var rowdata = new List<object?>(row.Values);
-            foreach (var ac in ActionColumns)
-                rowdata.Add(ac.Data(row));
+    private void LoadData()
+    {
+        ResetClipBuffer();
+        if (MasterData is null)
+            return;
+        FilterRows(MasterData, Data, _recordmap);
 
-            var datarow = DataGridItems.Rows[row.Index];
-            for (var i = 0; i < rowdata.Count; i++)
-                datarow[i] = rowdata[i] ?? DBNull.Value;
-            //datarow.ItemArray = rowdata.ToArray(); 
-        }
+        InvalidateGrid();
 
-        private void InvalidateGrid()
-        {
-            var defaults = new List<object?>();
-            var result = new DataTable();
+        //ScrollBar.Value = _CurrentRow <= 0 ? 0 : _CurrentRow;
+        SelectedRows = Array.Empty<CoreRow>();
+    }
 
-            foreach (var column in Data.Columns)
-            {
-                var colname = column.ColumnName.Replace('.', '_');
-                if (!result.Columns.Contains(colname))
-                {
-                    result.Columns.Add(colname, column.DataType);
-                    if (!IsDirectEditMode())
-                        defaults.Add(column.DataType.GetDefault());
-                }
-            }
+    //IncrementalList<T> _data = null;
 
-            for (var i = 0; i < ActionColumns.Count; i++)
-                result.Columns.Add(string.Format("ActionColumn{0}", i),
-                    ActionColumns[i] is DynamicImageColumn
-                        ? typeof(BitmapImage)
-                        : typeof(String)
-                );
+    public void InvalidateRow(CoreRow row)
+    {
+        UIComponent.InvalidateRow(row);
+    }
 
-            foreach (var row in Data.Rows)
-            {
-                var newrow = result.NewRow();
-                CoreRowToDataRow(newrow, row, defaults);
-                result.Rows.Add(newrow);
-            }
+    private void InvalidateGrid()
+    {
+        if (RowStyleSelector != null)
+            RowStyleSelector.Data = Data;
 
-            if (RowStyleSelector != null)
-                RowStyleSelector.Data = Data;
+        UIComponent.RefreshData(Data);
 
-            //int rowIndex = DataGrid.SelectionController.CurrentCellManager.CurrentRowColumnIndex.RowIndex;
-            //int columnIndex = DataGrid.SelectionController.CurrentCellManager.CurrentRowColumnIndex.ColumnIndex;
-            //int scrollRowIndex = DataGrid.GetVisualContainer().ScrollRows.LastBodyVisibleLineIndex;
-            DataGrid.ItemsSource = result;
-            //this.DataGrid.ScrollInView(new Syncfusion.UI.Xaml.ScrollAxis.RowColumnIndex(scrollRowIndex, columnIndex));
-            ResizeColumns(DataGrid, DataGrid.ActualWidth - 1, DataGrid.ActualHeight);
+        Loading.BeginAnimation(Label.OpacityProperty, null);
+        Loading.Visibility = Visibility.Collapsed;
+    }
 
-            UpdateRecordCount();
+    public void AddVisualFilter(string column, string value, FilterType filtertype = FilterType.Contains)
+    {
+        UIComponent.AddVisualFilter(column, value, filtertype);
+    }
 
-            Loading.BeginAnimation(Label.OpacityProperty, null);
-            Loading.Visibility = Visibility.Collapsed;
-        }
+    #endregion
 
-        private void UpdateRecordCount()
-        {
-            var count = DataGrid.View != null ? DataGrid.View.Records.Count : Data.Rows.Count;
-            Count.Content = string.Format("{0} Records", count);
-            //Count.Visibility = _HasOption(DynamicGridOptions.RecordCount) && (count > 0) ? Visibility.Visible : Visibility.Collapsed;
-        }
+    #region Item Manipulation
 
-        public IList<CoreRow> FilteredRows()
-        {
-            var result = new List<CoreRow>();
-            var table = DataGridItems;
-            if(table is null)
-            {
-                return Array.Empty<CoreRow>();
-            }
-            var rows = DataGrid.View.Records.Select(x => (x.Data as DataRowView)!).ToList();
-            foreach (var row in rows)
-            {
-                var iRow = table.Rows.IndexOf(row.Row);
-                result.Add(Data.Rows[iRow]);
-            }
+    #region Load/Save/Delete
 
-            return result;
+    public virtual T[] LoadItems(CoreRow[] rows)
+    {
+        var result = new List<T>();
+        foreach (var row in rows)
+        {
+            var index = Data.Rows.IndexOf(row);
+            result.Add(LoadItem(row));
         }
 
-        // Doesn't appear to be used - removed 19/12/2022
-        /*private object?[] CreateRowValues(CoreRow row, List<object> defaults)
-        {
-            var rowdata = new List<object?>(row.Values);
-            foreach (var ac in ActionColumns)
-                rowdata.Add(ac.Image.Invoke(row));
-            var result = ProcessRow(rowdata, defaults);
-            return result.ToArray();
-        }*/
+        return result.ToArray();
+    }
 
-        private void CoreRowToDataRow(DataRow newrow, CoreRow row, List<object?> defaults)
-        {
-            var rowdata = new List<object?>(row.Values);
-            foreach (var ac in ActionColumns)
-                rowdata.Add(ac.Data(row));
+    public abstract T LoadItem(CoreRow row);
 
-            try
-            {
-                var data = ProcessRow(rowdata, defaults).ToArray();
-                newrow.ItemArray = data;
-            }
-            catch (Exception)
-            {
-                throw;
-            }
-        }
+    public abstract void SaveItem(T item);
 
-        private static IEnumerable<object?> ProcessRow(List<object?> values, List<object?> defaults)
-        {
-            if (defaults == null || !defaults.Any())
-                return values;
-            var result = new List<object?>();
-            for (var i = 0; i < values.Count; i++)
-            {
-                var value = values[i];
-                var defaultvalue = i < defaults.Count ? defaults[i] : null;
-                result.Add(value == null || (value.Equals(defaultvalue) && !value.GetType().IsEnum) ? null : value);
-            }
+    public virtual void SaveItems(T[] items)
+    {
+        foreach (var item in items)
+            SaveItem(item);
+    }
 
-            return result;
-        }
+    protected virtual bool CanDeleteItems(params CoreRow[] rows)
+    {
+        return true;
+    }
 
-        //private void LoadMoreItems(uint count, int from)
-        //{
-        //    var rows = Data.Rows.Skip(from).AsQueryable().Take(50);
-        //    _data.LoadItems(rows.Select(x => x.ToObject<T>()));
-        //    //var list = _orders.Skip(baseIndex).Take(50).ToList();
-        //    //IncrementalItemsSource.LoadItems(list);
-        //}
+    public abstract void DeleteItems(params CoreRow[] rows);
 
-        public override void AddVisualFilter(string column, string value, FilterType filtertype = FilterType.Contains)
-        {
-            if (string.IsNullOrWhiteSpace(value))
-                return;
-            var col = DataGrid.Columns.FirstOrDefault((x=>String.Equals(x.MappingName?.ToUpper(),column?.Replace(".", "_").ToUpper())));
-            if (col != null)
-            {
-                col.FilterPredicates.Add(new FilterPredicate { FilterType = filtertype, FilterValue = value });
-                col.FilteredFrom = FilteredFrom.FilterRow;
+    protected virtual void DoDelete()
+    {
+        var rows = SelectedRows.ToArray();
 
-            }
+        if (rows.Any())
+            if (CanDeleteItems(rows))
+                if (MessageBox.Show("Are you sure you wish to delete the selected records?", "Confirm Delete", MessageBoxButton.YesNo) ==
+                    MessageBoxResult.Yes)
+                {
+                    DeleteItems(rows);
+                    SelectedRows = Array.Empty<CoreRow>();
+                    OnChanged?.Invoke(this, EventArgs.Empty);
+                    Refresh(false, true);
+                    SelectItems(null);
+                }
+    }
 
-        }
+    private void Delete_Click(object sender, RoutedEventArgs e)
+    {
+        DoDelete();
+    }
 
-        #endregion
+    #endregion
 
-        #region Item Manipulation
+    #region Edit
 
-        #region Load/Save/Delete
+    protected virtual void DoEdit()
+    {
+        if (!SelectedRows.Any())
+            return;
 
-        protected virtual T[] LoadItems(CoreRow[] rows)
+        var sel = SelectedRows.ToArray();
+        if (AddEditClick(SelectedRows))
         {
-            var result = new List<T>();
-            foreach (var row in rows)
-            {
-                var index = Data.Rows.IndexOf(row);
-                result.Add(LoadItem(row));
-            }
-
-            return result.ToArray();
+            InvalidateGrid();
+            SelectedRows = sel;
+            SelectItems(SelectedRows);
         }
+    }
 
-        protected abstract T LoadItem(CoreRow row);
+    private void Edit_Click(object sender, RoutedEventArgs e)
+    {
+        DoEdit();
+    }
 
-        public abstract void SaveItem(T item);
+    protected virtual void DoAdd(bool OpenEditorOnDirectEdit = false)
+    {
+        //CoreRow row = (SelectedRow > -1) && (SelectedRow < Data.Rows.Count) ?  Data.Rows[this.SelectedRow] : null;
 
-        public virtual void SaveItems(T[] items)
+        if (IsDirectEditMode() && !OpenEditorOnDirectEdit)
         {
-            foreach (var item in items)
-                SaveItem(item);
-        }
+            var item = CreateItem();
+            SaveItem(item);
 
-        protected virtual bool CanDeleteItems(params CoreRow[] rows)
-        {
-            return true;
-        }
+            var datarow = Data.NewRow();
+            ObjectToRow(item, datarow);
+            Data.Rows.Add(datarow);
 
-        protected abstract void DeleteItems(params CoreRow[] rows);
+            var masterrow = MasterData.NewRow();
+            ObjectToRow(item, masterrow);
+            MasterData.Rows.Add(masterrow);
 
-        protected virtual void DoDelete()
-        {
-            var rows = SelectedRows.ToArray();
+            _recordmap[datarow] = masterrow;
 
-            if (rows.Any())
-                if (CanDeleteItems(rows))
-                    if (MessageBox.Show("Are you sure you wish to delete the selected records?", "Confirm Delete", MessageBoxButton.YesNo) ==
-                        MessageBoxResult.Yes)
-                    {
-                        DeleteItems(rows);
-                        SelectedRows = Array.Empty<CoreRow>();
-                        OnChanged?.Invoke(this, EventArgs.Empty);
-                        Refresh(false, true);
-                        SelectItems(null);
-                    }
+            InvalidateGrid();
+            SelectedRows = new[] { datarow };
+            OnChanged?.Invoke(this, EventArgs.Empty);
         }
-
-        private void Delete_Click(object sender, RoutedEventArgs e)
+        else if (AddEditClick(null))
         {
-            DoDelete();
+            Refresh(false, true);
         }
+    }
 
-        #endregion
-
-        #region Edit
+    private void Add_Click(object sender, RoutedEventArgs e)
+    {
+        if (CanCreateItems())
+            DoAdd();
+    }
 
-        protected virtual void DoEdit()
-        {
-            if (!SelectedRows.Any())
-                return;
+    public virtual DynamicEditorPages LoadEditorPages(T item)
+    {
+        DynamicEditorPages pages = new DynamicEditorPages();
+        DynamicGridUtils.LoadOneToManyPages(typeof(T), pages);
+        DynamicGridUtils.LoadEnclosedListPages(typeof(T), pages);
+        DynamicGridUtils.LoadManyToManyPages(typeof(T), pages);
+        DynamicGridUtils.LoadCustomEditorPages(typeof(T), pages);
+        foreach (var page in pages)
+            page.Ready = false;
+        return pages;
+    }
 
-            var sel = SelectedRows.ToArray();
-            if (AddEditClick(SelectedRows))
+    public virtual void LoadEditorButtons(T item, DynamicEditorButtons buttons)
+    {
+        buttons.Clear();
+        buttons.Add(
+            "",
+            Wpf.Resources.help.AsBitmapImage(),
+            item,
+            (f, i) =>
             {
-                InvalidateGrid();
-                SelectedRows = sel;
-                SelectItems(SelectedRows);
+                Process.Start(new ProcessStartInfo("https://prsdigital.com.au/wiki/index.php/" + typeof(T).Name.SplitCamelCase().Replace(" ", "_"))
+                { UseShellExecute = true });
             }
-        }
-
-        private void Edit_Click(object sender, RoutedEventArgs e)
-        {
-            DoEdit();
-        }
-
-        protected virtual void DoAdd(bool OpenEditorOnDirectEdit = false)
-        {
-            //CoreRow row = (SelectedRow > -1) && (SelectedRow < Data.Rows.Count) ?  Data.Rows[this.SelectedRow] : null;
-
-            if (IsDirectEditMode() && !OpenEditorOnDirectEdit)
-            {
-                var item = CreateItem();
-                SaveItem(item);
-
-                var datarow = Data.NewRow();
-                ObjectToRow(item, datarow);
-                Data.Rows.Add(datarow);
+        );
 
-                var masterrow = MasterData.NewRow();
-                ObjectToRow(item, masterrow);
-                MasterData.Rows.Add(masterrow);
-
-                _recordmap[datarow] = masterrow;
+        OnLoadEditorButtons?.Invoke(item, buttons);
+    }
 
-                InvalidateGrid();
-                SelectedRows = new[] { datarow };
-                OnChanged?.Invoke(this, EventArgs.Empty);
-            }
-            else if (AddEditClick(null))
-            {
-                Refresh(false, true);
-            }
-        }
+    protected virtual void BeforeLoad(IDynamicEditorForm form, T[] items)
+    {
+        form.BeforeLoad();
+    }
 
-        private void Add_Click(object sender, RoutedEventArgs e)
-        {
-            if (CanCreateItems())
-                DoAdd();
-        }
+    void IDynamicGrid.InitialiseEditorForm(IDynamicEditorForm editor, object[] items, Func<Type, CoreTable>? pageDataHandler, bool preloadPages)
+    {
+        InitialiseEditorForm(editor, items.Cast<T>().ToArray(), pageDataHandler, preloadPages);
+    }
+    public virtual bool EditItems(object[] items, Func<Type, CoreTable?>? PageDataHandler = null, bool PreloadPages = false)
+    {
+        var values = items.Cast<T>().ToArray();
+        return EditItems(values, PageDataHandler, PreloadPages);
+    }
 
-        public virtual DynamicEditorPages LoadEditorPages(T item)
-        {
-            DynamicEditorPages pages = new DynamicEditorPages();
-            DynamicGridUtils.LoadOneToManyPages(typeof(T), pages);
-            DynamicGridUtils.LoadEnclosedListPages(typeof(T), pages);
-            DynamicGridUtils.LoadManyToManyPages(typeof(T), pages);
-            DynamicGridUtils.LoadCustomEditorPages(typeof(T), pages);
-            foreach (var page in pages)
-                page.Ready = false;
-            return pages;
-        }
+    public void InitialiseEditorForm(IDynamicEditorForm editor, T[] items, Func<Type, CoreTable?>? pageDataHandler = null, bool preloadPages = false)
+    {
+        var pages = items.Length == 1 ? LoadEditorPages(items.First()) : new DynamicEditorPages();
 
-        public virtual void LoadEditorButtons(T item, DynamicEditorButtons buttons)
-        {
-            buttons.Clear();
-            buttons.Add(
-                "",
-                Wpf.Resources.help.AsBitmapImage(),
-                item,
-                (f, i) =>
-                {
-                    Process.Start(new ProcessStartInfo("https://prsdigital.com.au/wiki/index.php/" + typeof(T).Name.SplitCamelCase().Replace(" ", "_"))
-                    { UseShellExecute = true });
-                }
-            );
+        var buttons = new DynamicEditorButtons();
+        if (items.Length == 1)
+            LoadEditorButtons(items.First(), buttons);
 
-            OnLoadEditorButtons?.Invoke(item, buttons);
-        }
+        editor.Setup(items.Any() ? items.First().GetType() : typeof(T), pages, buttons, pageDataHandler, preloadPages);
 
-        protected virtual void BeforeLoad(IDynamicEditorForm form, T[] items)
+        editor.OnCustomiseColumns = (sender, columns) =>
         {
-            form.BeforeLoad();
-        }
+            columns.Clear();
+            columns.AddRange(MasterColumns);
+            ConfigureColumns(columns);
+        };
 
-        public override void InitialiseEditorForm(IDynamicEditorForm editor, T[] items, Func<Type, CoreTable?>? pageDataHandler = null, bool preloadPages = false)
+        editor.OnDefineEditor = (o, c) =>
         {
-            var pages = items.Length == 1 ? LoadEditorPages(items.First()) : new DynamicEditorPages();
+            var result = GetEditor(o, c);
 
-            var buttons = new DynamicEditorButtons();
-            if (items.Length == 1)
-                LoadEditorButtons(items.First(), buttons);
+            if (result != null)
+                result = result.CloneEditor();
 
-            editor.Setup(items.Any() ? items.First().GetType() : typeof(T), pages, buttons, pageDataHandler, preloadPages);
+            return result;
+        };
 
-            editor.OnCustomiseColumns = (sender, columns) =>
-            {
-                columns.Clear();
-                columns.AddRange(MasterColumns);
-                ConfigureColumns(columns);
-            };
+        editor.OnFormCustomiseEditor += DoCustomiseEditor;
 
-            editor.OnDefineEditor = (o, c) =>
-            {
-                var result = GetEditor(o, c);
+        editor.OnDefineFilter = (type, column) => { return DefineLookupFilter(type, column, items); };
 
-                if (result != null)
-                    result = result.CloneEditor();
+        //editor.OnDefineFilter += (o, e) => { return DefineFilter(items, e); };
+        editor.OnDefineLookups = editor => DefineLookups(editor, items);
 
-                return result;
-            };
+        editor.OnEditorValueChanged += (s, n, v) => EditorValueChanged(editor, items, n, v);
 
-            editor.OnFormCustomiseEditor += DoCustomiseEditor;
+        editor.OnAfterEditorValueChanged += (g, args) => AfterEditorValueChanged(g, items, args);
 
-            editor.OnDefineFilter = (type, column) => { return DefineLookupFilter(type, column, items); };
+        editor.OnReconfigureEditors = g => DoReconfigureEditors(g, items);
 
-            //editor.OnDefineFilter += (o, e) => { return DefineFilter(items, e); };
-            editor.OnDefineLookups = editor => DefineLookups(editor, items);
+        editor.OnValidateData += (o, i) => ValidateData(items);
 
-            editor.OnEditorValueChanged += (s, n, v) => EditorValueChanged(editor, items, n, v);
+        editor.OnSelectPage += SelectPage;
 
-            editor.OnAfterEditorValueChanged += (g, args) => AfterEditorValueChanged(g, items, args);
+        editor.OnSaveItem = (o, e) =>
+        {
+            try
+            {
+                using var Wait = new WaitCursor();
+                DoBeforeSave(editor, items);
 
-            editor.OnReconfigureEditors = g => DoReconfigureEditors(g, items);
 
-            editor.OnValidateData += (o, i) => ValidateData(items);
+                if (items.Length == 1)
+                    editor.UnloadEditorPages(false);
+                foreach (var item in items)
+                    SaveItem(item);
+                if (items.Length == 1)
+                    editor.UnloadEditorPages(true);
 
-            editor.OnSelectPage += SelectPage;
+                DoAfterSave(editor, items);
 
-            editor.OnSaveItem = (o, e) =>
+            }
+            catch (Exception err)
             {
-                try
-                {
-                    using var Wait = new WaitCursor();
-                    DoBeforeSave(editor, items);
-
+                MessageBox.Show(err.Message);
+                e.Cancel = true;
+            }
+        };
 
-                    if (items.Length == 1)
-                        editor.UnloadEditorPages(false);
-                    foreach (var item in items)
-                        SaveItem(item);
-                    if (items.Length == 1)
-                        editor.UnloadEditorPages(true);
+        BeforeLoad(editor, items);
+        editor.Items = items;
+        AfterLoad(editor, items);
+    }
 
-                    DoAfterSave(editor, items);
+    private void DoCustomiseEditor(IDynamicEditorForm sender, object[] items, DynamicGridColumn column, BaseEditor editor)
+    {
+        CustomiseEditor((T[])items, column, editor);
+        OnCustomiseEditor?.Invoke(sender, (T[])items, column, editor);
+    }
 
-                }
-                catch (Exception err)
-                {
-                    MessageBox.Show(err.Message);
-                    e.Cancel = true;
-                }
-            };
+    protected virtual void CustomiseEditor(T[] items, DynamicGridColumn column, BaseEditor editor)
+    {
+    }
 
-            BeforeLoad(editor, items);
-            editor.Items = items;
-            AfterLoad(editor, items);
-        }
+    protected virtual void DoAfterSave(IDynamicEditorForm editor, T[] items)
+    {
+        OnAfterSave?.Invoke(editor, items);
+    }
 
-        private void DoCustomiseEditor(IDynamicEditorForm sender, object[] items, DynamicGridColumn column, BaseEditor editor)
-        {
-            CustomiseEditor((T[])items, column, editor);
-            OnCustomiseEditor?.Invoke(sender, (T[])items, column, editor);
-        }
+    protected virtual void DoBeforeSave(IDynamicEditorForm editor, T[] items)
+    {
+        OnBeforeSave?.Invoke(editor, items);
+    }
 
-        protected virtual void CustomiseEditor(T[] items, DynamicGridColumn column, BaseEditor editor)
-        {
-        }
 
-        protected virtual void DoAfterSave(IDynamicEditorForm editor, T[] items)
+    public bool EditItems(T[] items, Func<Type, CoreTable?>? PageDataHandler = null, bool PreloadPages = false)
+    {
+        DynamicEditorForm editor;
+        using (var cursor = new WaitCursor())
         {
-            OnAfterSave?.Invoke(editor, items);
-        }
+            editor = new DynamicEditorForm();
+            editor.SetValue(Panel.ZIndexProperty, 999);
 
-        protected virtual void DoBeforeSave(IDynamicEditorForm editor, T[] items)
-        {
-            OnBeforeSave?.Invoke(editor, items);
+            InitialiseEditorForm(editor, items, PageDataHandler, PreloadPages);
+            OnEditorLoaded?.Invoke(editor, items);
         }
 
+        return editor.ShowDialog() == true;
+    }
 
-        public override bool EditItems(T[] items, Func<Type, CoreTable?>? PageDataHandler = null, bool PreloadPages = false)
-        {
-            DynamicEditorForm editor;
-            using (var cursor = new WaitCursor())
-            {
-                editor = new DynamicEditorForm();
-                editor.SetValue(Panel.ZIndexProperty, 999);
-
-                InitialiseEditorForm(editor, items, PageDataHandler, PreloadPages);
-                OnEditorLoaded?.Invoke(editor, items);
-            }
-
-            return editor.ShowDialog() == true;
-        }
+    private Dictionary<String, object?> AfterEditorValueChanged(DynamicEditorGrid grid, T[] items, AfterEditorValueChangedArgs args)
+    {
+        var changes = new Dictionary<string, object?>();
+        OnAfterEditorValueChanged(grid, items, args, changes);
+        return changes;
+    }
 
-        private Dictionary<String, object?> AfterEditorValueChanged(DynamicEditorGrid grid, T[] items, AfterEditorValueChangedArgs args)
+    protected virtual void OnAfterEditorValueChanged(DynamicEditorGrid? grid, T[] items, AfterEditorValueChangedArgs args, Dictionary<String, object?> changes)
+    {
+    }
+    
+    protected virtual void DoReconfigureEditors(DynamicEditorGrid grid, T[] items)
+    {
+        /*if (items.First() is IDimensioned dimensioned)
         {
-            var changes = new Dictionary<string, object?>();
-            OnAfterEditorValueChanged(grid, items, args, changes);
-            return changes;
-        }
+            UpdateEditor(grid, x => x.Dimensions.Quantity, dimensioned.Dimensions.GetUnit().HasQuantity);
+            UpdateEditor(grid, x => x.Dimensions.Length, dimensioned.Dimensions.GetUnit().HasLength);
+            UpdateEditor(grid, x => x.Dimensions.Width, dimensioned.Dimensions.GetUnit().HasWidth);
+            UpdateEditor(grid, x => x.Dimensions.Height, dimensioned.Dimensions.GetUnit().HasHeight);
+            UpdateEditor(grid, x => x.Dimensions.Weight, dimensioned.Dimensions.GetUnit().HasWeight);
+        }*/
+    }
 
-        protected virtual void OnAfterEditorValueChanged(DynamicEditorGrid? grid, T[] items, AfterEditorValueChangedArgs args, Dictionary<String, object?> changes)
-        {
-        }
-        
-        protected virtual void DoReconfigureEditors(DynamicEditorGrid grid, T[] items)
-        {
-            /*if (items.First() is IDimensioned dimensioned)
-            {
-                UpdateEditor(grid, x => x.Dimensions.Quantity, dimensioned.Dimensions.GetUnit().HasQuantity);
-                UpdateEditor(grid, x => x.Dimensions.Length, dimensioned.Dimensions.GetUnit().HasLength);
-                UpdateEditor(grid, x => x.Dimensions.Width, dimensioned.Dimensions.GetUnit().HasWidth);
-                UpdateEditor(grid, x => x.Dimensions.Height, dimensioned.Dimensions.GetUnit().HasHeight);
-                UpdateEditor(grid, x => x.Dimensions.Weight, dimensioned.Dimensions.GetUnit().HasWeight);
-            }*/
-        }
+    private string[]? ValidateData(T[] items)
+    {
+        var errors = new List<string>();
+        DoValidate(items, errors);
+        OnValidate?.Invoke(this, items, errors);
+        return errors.Any() ? errors.ToArray() : null;
+    }
 
-        private string[]? ValidateData(T[] items)
-        {
-            var errors = new List<string>();
-            DoValidate(items, errors);
-            OnValidate?.Invoke(this, items, errors);
-            return errors.Any() ? errors.ToArray() : null;
-        }
+    protected virtual void DoValidate(T[] items, List<string> errors)
+    {
+    }
 
-        protected virtual void DoValidate(T[] items, List<string> errors)
-        {
-        }
 
 
+    protected virtual void AfterLoad(IDynamicEditorForm editor, T[] items)
+    {
+        editor.AfterLoad();
+    }
 
-        protected virtual void AfterLoad(IDynamicEditorForm editor, T[] items)
-        {
-            editor.AfterLoad();
-        }
+    protected virtual void SelectPage(object sender, BaseObject[]? items)
+    {
+    }
 
-        protected virtual void SelectPage(object sender, BaseObject[]? items)
+    protected virtual Dictionary<string, object?> EditorValueChanged(IDynamicEditorForm editor, T[] items, string name, object value)
+    {
+        var result = DynamicGridUtils.UpdateEditorValue(items, name, value);
+        if (OnEditorValueChanged != null)
         {
+            var newchanges = OnEditorValueChanged(editor, name, value);
+            foreach (var key in newchanges.Keys)
+                result[key] = newchanges[key];
         }
 
-        protected virtual Dictionary<string, object?> EditorValueChanged(IDynamicEditorForm editor, T[] items, string name, object value)
-        {
-            var result = DynamicGridUtils.UpdateEditorValue(items, name, value);
-            if (OnEditorValueChanged != null)
-            {
-                var newchanges = OnEditorValueChanged(editor, name, value);
-                foreach (var key in newchanges.Keys)
-                    result[key] = newchanges[key];
-            }
+        return result;
+    }
 
-            return result;
-        }
 
 
+    private readonly Dictionary<Tuple<Type, Type>, Dictionary<object, object>> _lookupcache = new();
 
-        private readonly Dictionary<Tuple<Type, Type>, Dictionary<object, object>> _lookupcache = new();
+    protected virtual void DefineLookups(ILookupEditorControl sender, T[] items, bool async = true)
+    {
+        if (sender.EditorDefinition is not ILookupEditor editor)
+            return;
 
-        protected virtual void DefineLookups(ILookupEditorControl sender, T[] items, bool async = true)
+        var colname = sender.ColumnName;
+        if (async)
         {
-            if (sender.EditorDefinition is not ILookupEditor editor)
-                return;
-
-            var colname = sender.ColumnName;
-            if (async)
+            Task.Run(() =>
             {
-                Task.Run(() =>
+                try
                 {
-                    try
-                    {
-                        var values = editor.Values(typeof(T), colname, items);
-                        Dispatcher.Invoke(
-                            () =>
+                    var values = editor.Values(typeof(T), colname, items);
+                    Dispatcher.Invoke(
+                        () =>
+                        {
+                            try
                             {
-                                try
-                                {
-                                    //Logger.Send(LogType.Information, typeof(T).Name, "Dispatching Results" + colname);
-                                    sender.LoadLookups(values);
-                                }
-                                catch (Exception e2)
-                                {
-                                    Logger.Send(LogType.Information, typeof(T).Name,
-                                        "Exception (2) in LoadLookups: " + e2.Message + "\n" + e2.StackTrace);
-                                }
+                                //Logger.Send(LogType.Information, typeof(T).Name, "Dispatching Results" + colname);
+                                sender.LoadLookups(values);
                             }
-                        );
-                    }
-                    catch (Exception e)
-                    {
-                        Logger.Send(LogType.Information, typeof(T).Name,
-                            "Exception (1) in LoadLookups: " + e.Message + "\n" + e.StackTrace);
-                    }
-                });
-            }
-            else
-            {
-                var values = editor.Values(typeof(T), colname, items);
-                sender.LoadLookups(values);
-            }
+                            catch (Exception e2)
+                            {
+                                Logger.Send(LogType.Information, typeof(T).Name,
+                                    "Exception (2) in LoadLookups: " + e2.Message + "\n" + e2.StackTrace);
+                            }
+                        }
+                    );
+                }
+                catch (Exception e)
+                {
+                    Logger.Send(LogType.Information, typeof(T).Name,
+                        "Exception (1) in LoadLookups: " + e.Message + "\n" + e.StackTrace);
+                }
+            });
         }
-
-        /// <summary>
-        /// Retrieves an editor to display for the given column of <paramref name="item"/>.
-        /// </summary>
-        /// <param name="item">The object being edited.</param>
-        /// <param name="column">The column of the editor.</param>
-        /// <returns>A new editor, or <see langword="null"/> if no editor defined and no sensible default exists.</returns>
-        protected virtual BaseEditor? GetEditor(object item, DynamicGridColumn column)
+        else
         {
-            return column.Editor ?? CoreUtils.GetProperty(item.GetType(), column.ColumnName).GetEditor();
+            var values = editor.Values(typeof(T), colname, items);
+            sender.LoadLookups(values);
         }
+    }
 
-        protected IFilter? DefineLookupFilter(Type type, string column, T[] items)
-        {
-            return LookupFactory.DefineLookupFilter(typeof(T), type, column, items);
-        }
+    /// <summary>
+    /// Retrieves an editor to display for the given column of <paramref name="item"/>.
+    /// </summary>
+    /// <param name="item">The object being edited.</param>
+    /// <param name="column">The column of the editor.</param>
+    /// <returns>A new editor, or <see langword="null"/> if no editor defined and no sensible default exists.</returns>
+    protected virtual BaseEditor? GetEditor(object item, DynamicGridColumn column)
+    {
+        return column.Editor ?? CoreUtils.GetProperty(item.GetType(), column.ColumnName).GetEditor();
+    }
 
-        protected virtual void SetEditorValue(object item, string name, object value)
-        {
-            try
-            {
-                CoreUtils.SetPropertyValue(item, name, value);
-            }
-            catch (Exception e)
-            {
-                Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
-            }
-        }
+    protected IFilter? DefineLookupFilter(Type type, string column, T[] items)
+    {
+        return LookupFactory.DefineLookupFilter(typeof(T), type, column, items);
+    }
 
-        protected virtual object? GetEditorValue(object item, string name)
+    protected virtual void SetEditorValue(object item, string name, object value)
+    {
+        try
         {
-            return CoreUtils.GetPropertyValue(item, name);
+            CoreUtils.SetPropertyValue(item, name, value);
         }
-
-        protected virtual bool CanCreateItems()
+        catch (Exception e)
         {
-            return true;
+            Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
         }
+    }
 
-        private bool AddEditClick(CoreRow[]? rows)
-        {
-            if (!IsEnabled || bRefreshing)
-                return false;
+    protected virtual object? GetEditorValue(object item, string name)
+    {
+        return CoreUtils.GetPropertyValue(item, name);
+    }
 
-            if (rows == null || !rows.Any())
-            {
+    protected virtual bool CanCreateItems()
+    {
+        return true;
+    }
 
-                if (!CanCreateItems())
-                    return false;
+    private bool AddEditClick(CoreRow[]? rows)
+    {
+        if (!IsEnabled || bRefreshing)
+            return false;
 
-                var item = CreateItem();
+        if (rows == null || !rows.Any())
+        {
 
-                // Yea, and this won't work, because we're actually usually showing the description of a linked item,
-                // not the id of the link, and we need to set the ID to have it work properly :-(
+            if (!CanCreateItems())
+                return false;
 
-                //foreach (String key in VisualFilters.Keys)
-                //    CoreUtils.SetPropertyValue(item, key, VisualFilters[key]);
+            var item = CreateItem();
 
-                if (EditItems(new[] { item }))
-                {
-                    //_CurrentRow = Data.Rows.Count;
-                    var row = Data.NewRow();
-                    ObjectToRow(item, row);
-                    Data.Rows.Add(row);
-                    InvalidateGrid();
-                    SelectedRows = new[] { row };
-                    OnChanged?.Invoke(this, EventArgs.Empty);
-                    return true;
-                }
+            // Yea, and this won't work, because we're actually usually showing the description of a linked item,
+            // not the id of the link, and we need to set the ID to have it work properly :-(
 
-                return false;
-            }
+            //foreach (String key in VisualFilters.Keys)
+            //    CoreUtils.SetPropertyValue(item, key, VisualFilters[key]);
 
-            var items = Array.Empty<T>();
-            using (new WaitCursor())
+            if (EditItems(new[] { item }))
             {
-                Stopwatch sw = new Stopwatch();
-                sw.Start();
-                items = LoadItems(rows);
-                //Logger.Send(LogType.Information, "DG:LoadItems", String.Format("Loaded Items: {0}ms", sw.ElapsedMilliseconds));
-                sw.Stop();
+                //_CurrentRow = Data.Rows.Count;
+                var row = Data.NewRow();
+                ObjectToRow(item, row);
+                Data.Rows.Add(row);
+                InvalidateGrid();
+                SelectedRows = new[] { row };
+                DoChanged();
+                return true;
             }
 
-            if (items.Any())
+            return false;
+        }
+
+        var items = Array.Empty<T>();
+        using (new WaitCursor())
+        {
+            Stopwatch sw = new Stopwatch();
+            sw.Start();
+            items = LoadItems(rows);
+            //Logger.Send(LogType.Information, "DG:LoadItems", String.Format("Loaded Items: {0}ms", sw.ElapsedMilliseconds));
+            sw.Stop();
+        }
+
+        if (items.Any())
+        {
+            var sel = SelectedRows;
+            if (EditItems(items))
             {
-                var sel = SelectedRows;
-                if (EditItems(items))
+                for (var i = 0; i < items.Length; i++)
                 {
-                    for (var i = 0; i < items.Length; i++)
-                    {
-                        ObjectToRow(items[i], rows[i]);
-                        ObjectToRow(items[i], _recordmap[rows[i]]);
-                    }
-                    InvalidateGrid();
-                    SelectedRows = sel;
-                    OnChanged?.Invoke(this, EventArgs.Empty);
-                    return true;
+                    ObjectToRow(items[i], rows[i]);
+                    ObjectToRow(items[i], _recordmap[rows[i]]);
                 }
-
-                return false;
+                InvalidateGrid();
+                SelectedRows = sel;
+                OnChanged?.Invoke(this, EventArgs.Empty);
+                return true;
             }
 
             return false;
         }
 
-        #endregion
+        return false;
+    }
+
+    #endregion
+
+    #region Duplicate
+
+    protected virtual IEnumerable<T> LoadDuplicatorItems(CoreRow[] rows)
+    {
+        return LoadItems(rows);
+    }
+
+    private bool DoDuplicate(Button button, CoreRow[] rows)
+    {
+        if (!rows.Any())
+        {
+            MessageBox.Show("Please select at least one record to duplicate!");
+            return false;
+        }
 
-        #region Duplicate
+        /*var ids = ExtractValues(x => x.ID, Selection.Selected).ToArray();
+        if (!ids.Any())
+        {
+            MessageBox.Show("Please select at least one record to duplicate!");
+            return false;
+        }*/
 
-        protected virtual IEnumerable<T> LoadDuplicatorItems(CoreRow[] rows)
+        var duplicator = (new T() as IDuplicatable)?.GetDuplicator();
+        if (duplicator is null)
         {
-            return LoadItems(rows);
+            MessageBox.Show($"Cannot duplicate {typeof(T)}");
+            return false;
         }
 
-        private bool DoDuplicate(Button button, CoreRow[] rows)
+        duplicator.Duplicate(LoadDuplicatorItems(rows));// new Filter<T>(x => x.ID).InList(ids));
+        return true;
+    }
+
+    #endregion
+
+    public virtual T CreateItem()
+    {
+        var result = new T();
+        OnCreateItem?.Invoke(this, result);
+        return result;
+    }
+
+    protected virtual void DoPrint(object sender)
+    {
+        OnPrintData?.Invoke(sender);
+    }
+    protected virtual void ShowHelp(string slug)
+    {
+        Process.Start(new ProcessStartInfo("https://prsdigital.com.au/wiki/index.php/" + slug) { UseShellExecute = true });
+    }
+
+    protected void ReloadForms<TTargetType, TTargetForm, TSourceForm>(IDynamicEditorForm editor, TTargetType item,
+        Expression<Func<TSourceForm, object?>> sourcekey, Guid sourceid)
+        where TTargetType : Entity, new()
+        where TTargetForm : Entity, IRemotable, IPersistent, IDigitalFormInstance, new()
+        where TSourceForm : Entity, IRemotable, IPersistent, IDigitalForm<TTargetType>, new()
+    {
+        var type = typeof(IDynamicOneToManyGrid<,>).MakeGenericType(typeof(TTargetType), typeof(TTargetForm));
+        var page =
+            editor.Pages?.FirstOrDefault(x => x.GetType().GetInterfaces().Contains(type)) as IDynamicOneToManyGrid<TTargetType, TTargetForm>;
+        if (page != null && item != null)
         {
-            if (!rows.Any())
+            if (!page.Ready)
+                page.Load(item, null);
+
+            CoreTable table;
+            if (sourceid == Guid.Empty)
+            {
+                table = new CoreTable();
+                table.LoadColumns(typeof(TSourceForm));
+            }
+            else
             {
-                MessageBox.Show("Please select at least one record to duplicate!");
-                return false;
+                table = new Client<TSourceForm>().Query(
+                    new Filter<TSourceForm>(sourcekey).IsEqualTo(sourceid).And(x => x.Form.AppliesTo)
+                        .IsEqualTo(typeof(TTargetType).EntityName().Split('.').Last())
+                );
             }
 
-            /*var ids = ExtractValues(x => x.ID, Selection.Selected).ToArray();
-            if (!ids.Any())
+            var newforms = new List<TTargetForm>();
+            foreach (var row in table.Rows)
             {
-                MessageBox.Show("Please select at least one record to duplicate!");
-                return false;
-            }*/
+                var sourceform = row.ToObject<TSourceForm>();
+                var targetform = new TTargetForm();
 
-            var duplicator = (new T() as IDuplicatable)?.GetDuplicator();
-            if (duplicator is null)
-            {
-                MessageBox.Show($"Cannot duplicate {typeof(T)}");
-                return false;
+                targetform.Form.ID = sourceform.Form.ID;
+                targetform.Form.Synchronise(sourceform.Form);
+                newforms.Add(targetform);
             }
 
-            duplicator.Duplicate(LoadDuplicatorItems(rows));// new Filter<T>(x => x.ID).InList(ids));
-            return true;
+            page.Items.Clear();
+            page.LoadItems(newforms.ToArray());
         }
+    }
 
-        #endregion
 
-        protected virtual void ShowHelp(string slug)
-        {
-            Process.Start(new ProcessStartInfo("https://prsdigital.com.au/wiki/index.php/" + slug) { UseShellExecute = true });
-        }
+    #region ClipBuffer
 
-        protected void ReloadForms<TTargetType, TTargetForm, TSourceForm>(IDynamicEditorForm editor, TTargetType item,
-            Expression<Func<TSourceForm, object?>> sourcekey, Guid sourceid)
-            where TTargetType : Entity, new()
-            where TTargetForm : Entity, IRemotable, IPersistent, IDigitalFormInstance, new()
-            where TSourceForm : Entity, IRemotable, IPersistent, IDigitalForm<TTargetType>, new()
-        {
-            var type = typeof(IDynamicOneToManyGrid<,>).MakeGenericType(typeof(TTargetType), typeof(TTargetForm));
-            var page =
-                editor.Pages?.FirstOrDefault(x => x.GetType().GetInterfaces().Contains(type)) as IDynamicOneToManyGrid<TTargetType, TTargetForm>;
-            if (page != null && item != null)
-            {
-                if (!page.Ready)
-                    page.Load(item, null);
+    private Tuple<ClipAction, CoreRow[]>? ClipBuffer;
 
-                CoreTable table;
-                if (sourceid == Guid.Empty)
-                {
-                    table = new CoreTable();
-                    table.LoadColumns(typeof(TSourceForm));
-                }
-                else
-                {
-                    table = new Client<TSourceForm>().Query(
-                        new Filter<TSourceForm>(sourcekey).IsEqualTo(sourceid).And(x => x.Form.AppliesTo)
-                            .IsEqualTo(typeof(TTargetType).EntityName().Split('.').Last())
-                    );
-                }
+    protected void ResetClipBuffer()
+    {
+        ClipBuffer = null;
+    }
 
-                var newforms = new List<TTargetForm>();
-                foreach (var row in table.Rows)
-                {
-                    var sourceform = row.ToObject<TSourceForm>();
-                    var targetform = new TTargetForm();
+    protected void SetClipBuffer(ClipAction action, CoreRow[] rows)
+    {
+        ClipBuffer = new Tuple<ClipAction, CoreRow[]>(action, rows);
+    }
 
-                    targetform.Form.ID = sourceform.Form.ID;
-                    targetform.Form.Synchronise(sourceform.Form);
-                    newforms.Add(targetform);
-                }
+    private void CutToClipBuffer()
+    {
+        SetClipBuffer(ClipAction.Cut, SelectedRows);
+        InvalidateGrid();
+    }
 
-                page.Items.Clear();
-                page.LoadItems(newforms.ToArray());
-            }
-        }
 
+    private void CopyToClipBuffer()
+    {
+        SetClipBuffer(ClipAction.Copy, SelectedRows);
+        InvalidateGrid();
+    }
 
-        #region ClipBuffer
+    private void PasteFromClipBuffer()
+    {
+        if (ClipBuffer == null)
+            return;
 
-        private Tuple<ClipAction, CoreRow[]>? ClipBuffer;
+        if (!IsSequenced)
+            return;
 
-        protected void ResetClipBuffer()
+        using (new WaitCursor())
         {
-            ClipBuffer = null;
-        }
+            var updates = ClipBuffer.Item2.Select(x => x.ToObject<T>()).ToList();
+            if (BeforePaste(updates, ClipBuffer.Item1))
+            {
+                var currow = SelectedRows.FirstOrDefault()
+                    ?? Data.Rows.LastOrDefault();
 
-        protected void SetClipBuffer(ClipAction action, CoreRow[] rows)
-        {
-            ClipBuffer = new Tuple<ClipAction, CoreRow[]>(action, rows);
-        }
+                var sequence = currow != null ? currow.Get<T, long>(c => ((ISequenceable)c).Sequence) : 0;
 
-        private void CutToClipBuffer()
-        {
-            SetClipBuffer(ClipAction.Cut, SelectedRows);
-            InvalidateGrid();
-        }
+                var postrows = Data.Rows.Where(r => !ClipBuffer.Item2.Contains(r) && r.Get<ISequenceable, long>(x => x.Sequence) >= sequence);
+                updates.AddRange(LoadItems(postrows.ToArray()));
 
+                foreach (var update in updates)
+                {
+                    sequence++;
+                    ((ISequenceable)update).Sequence = sequence;
+                }
+            }
 
-        private void CopyToClipBuffer()
-        {
-            SetClipBuffer(ClipAction.Copy, SelectedRows);
-            InvalidateGrid();
+            if (updates.Any())
+            {
+                SaveItems(updates.ToArray());
+                Refresh(false, true);
+            }
         }
+    }
 
-        private void PasteFromClipBuffer()
-        {
-            if (ClipBuffer == null)
-                return;
-
-            if (!IsSequenced)
-                return;
+    protected virtual bool BeforePaste(IEnumerable<T> items, ClipAction action)
+    {
+        return true;
+    }
+    private void Cut_Click(object sender, RoutedEventArgs e)
+    {
+        CutToClipBuffer();
+    }
 
-            using (new WaitCursor())
-            {
-                var updates = ClipBuffer.Item2.Select(x => x.ToObject<T>()).ToList();
-                if (BeforePaste(updates, ClipBuffer.Item1))
-                {
-                    var currow = SelectedRows.FirstOrDefault()
-                        ?? Data.Rows.LastOrDefault();
+    private void Copy_Click(object sender, RoutedEventArgs e)
+    {
+        CopyToClipBuffer();
+    }
 
-                    var sequence = currow != null ? currow.Get<T, long>(c => ((ISequenceable)c).Sequence) : 0;
+    private void Paste_Click(object sender, RoutedEventArgs e)
+    {
+        PasteFromClipBuffer();
+    }
 
-                    var postrows = Data.Rows.Where(r => !ClipBuffer.Item2.Contains(r) && r.Get<ISequenceable, long>(x => x.Sequence) >= sequence);
-                    updates.AddRange(LoadItems(postrows.ToArray()));
+    #endregion
 
-                    foreach (var update in updates)
-                    {
-                        sequence++;
-                        ((ISequenceable)update).Sequence = sequence;
-                    }
-                }
+    protected virtual void ObjectToRow(T obj, CoreRow row)
+    {
+        row.Table.LoadRow(row, obj);
+    }
 
-                if (updates.Any())
-                {
-                    SaveItems(updates.ToArray());
-                    Refresh(false, true);
-                }
-            }
-        }
+    #region Import / Export
 
-        protected virtual bool BeforePaste(IEnumerable<T> items, ClipAction action)
-        {
-            return true;
-        }
-        private void Cut_Click(object sender, RoutedEventArgs e)
-        {
-            CutToClipBuffer();
-        }
+    protected virtual CoreTable LoadImportKeys(String[] fields)
+    {
+        var result = new CoreTable();
+        result.LoadColumns(new Columns<T>(fields));
+        return result;
+    }
 
-        private void Copy_Click(object sender, RoutedEventArgs e)
-        {
-            CopyToClipBuffer();
-        }
+    protected virtual Guid GetImportID()
+    {
+        return Guid.Empty;
+    }
 
-        private void Paste_Click(object sender, RoutedEventArgs e)
-        {
-            PasteFromClipBuffer();
-        }
+    protected virtual bool CustomiseImportItem(T item)
+    {
+        if (IsSequenced)
+            ((ISequenceable)item).Sequence = CoreUtils.GenerateSequence();
+        return true;
+    }
 
-        #endregion
+    protected virtual string CustomiseImportFileName(string filename)
+    {
+        return filename;
+    }
 
-        protected virtual void ObjectToRow(T obj, CoreRow row)
-        {
-            row.Table.LoadRow(row, obj);
-        }
+    protected virtual void DoImport()
+    {
+        var list = new DynamicImportList(
+            typeof(T),
+            GetImportID()
+        );
+        list.OnImportItem += o => { return CustomiseImportItem((T)o); };
+        list.OnCustomiseImport += (o, args) => { args.FileName = CustomiseImportFileName(args.FileName); };
+        list.OnSave += (sender, entity) => SaveItem(entity as T);
+        list.OnLoad += (sender, type, fields, id) => LoadImportKeys(fields);
+        list.ShowDialog();
+        Refresh(false, true);
+    }
 
-        #region Import / Export
+    private void Import_Click(object sender, RoutedEventArgs e)
+    {
+        DoImport();
+    }
 
-        protected virtual CoreTable LoadImportKeys(String[] fields)
-        {
-            var result = new CoreTable();
-            result.LoadColumns(new Columns<T>(fields));
-            return result;
-        }
+    protected virtual void CustomiseExportColumns(List<string> columnnames)
+    {
+    }
 
-        protected virtual Guid GetImportID()
-        {
-            return Guid.Empty;
-        }
+    protected virtual string CustomiseExportFileName(string filename)
+    {
+        return filename;
+    }
 
-        protected virtual bool CustomiseImportItem(T item)
-        {
-            if (IsSequenced)
-                ((ISequenceable)item).Sequence = CoreUtils.GenerateSequence();
-            return true;
-        }
+    protected virtual void CustomiseExportFilters(Filters<T> filters, CoreRow[] visiblerows)
+    {
+    }
 
-        protected virtual string CustomiseImportFileName(string filename)
-        {
-            return filename;
-        }
+    protected virtual void ApplyExportFilter(CoreTable table, object data)
+    {
+    }
+    private static bool FilterByPredicate(CoreRow row, string column, FilterPredicate predicate)
+    {
+        var value = row[column];
+        var vStr = value?.ToString() ?? "";
+        var pValue = predicate.FilterValue;
+        var pStr = pValue?.ToString() ?? "";
+        return predicate.FilterType switch
+        {
+            FilterType.Contains => vStr.Contains(pStr),
+            FilterType.EndsWith => vStr.EndsWith(pStr),
+            FilterType.Equals => vStr.Equals(pStr),
+            FilterType.GreaterThan => vStr.CompareTo(pStr) > 0,
+            FilterType.GreaterThanOrEqual => vStr.CompareTo(pStr) >= 0,
+            FilterType.LessThan => vStr.CompareTo(pStr) < 0,
+            FilterType.LessThanOrEqual => vStr.CompareTo(pStr) <= 0,
+            FilterType.NotContains => !vStr.Contains(pStr),
+            FilterType.NotEndsWith => !vStr.EndsWith(pStr),
+            FilterType.NotEquals => !vStr.Equals(pStr),
+            FilterType.NotStartsWith => !vStr.StartsWith(pStr),
+            FilterType.StartsWith => vStr.StartsWith(pStr),
+            _ => true,
+        };
+    }
 
-        protected virtual void DoImport()
-        {
-            var list = new DynamicImportList(
-                typeof(T),
-                GetImportID()
-            );
-            list.OnImportItem += o => { return CustomiseImportItem((T)o); };
-            list.OnCustomiseImport += (o, args) => { args.FileName = CustomiseImportFileName(args.FileName); };
-            list.OnSave += (sender, entity) => SaveItem(entity as T);
-            list.OnLoad += (sender, type, fields, id) => LoadImportKeys(fields);
-            list.ShowDialog();
-            Refresh(false, true);
-        }
+    protected virtual void DoExport()
+    {
+        var columnnames = VisibleColumns.Select(x => x.ColumnName).ToList();
+        CustomiseExportColumns(columnnames);
+        var form = new DynamicExportForm(typeof(T), columnnames);
+        if (form.ShowDialog() != true)
+            return;
 
-        private void Import_Click(object sender, RoutedEventArgs e)
-        {
-            DoImport();
-        }
+        var filters = new Filters<T>();
+        filters.Add(DefineFilter());
 
-        protected virtual void CustomiseExportColumns(List<string> columnnames)
-        {
-        }
+        var predicates = UIComponent.GetFilterPredicates();
 
-        protected virtual string CustomiseExportFileName(string filename)
-        {
-            return filename;
-        }
+        var visiblerows = GetVisibleRows();
+        CustomiseExportFilters(filters, visiblerows);
 
-        protected virtual void CustomiseExportFilters(Filters<T> filters, CoreRow[] visiblerows)
-        {
-        }
+        var columns = new Columns<T>(form.Fields);
+        var otherColumns = form.GetChildFields()
+            .Select(x => new Tuple<Type, IColumns>(
+                x.Key,
+                (Activator.CreateInstance(typeof(Columns<>).MakeGenericType(x.Key), new object[] { x.Value }) as IColumns)!))
+            .Where(x => x.Item2.ColumnNames().Any()).ToList();
 
-        protected virtual void ApplyExportFilter(CoreTable table, object data)
+        var reloadColumns = new Columns<T>();
+        foreach (var column in columns.ColumnNames())
         {
+            reloadColumns.Add(column);
         }
-        private static bool FilterByPredicate(CoreRow row, string column, FilterPredicate predicate)
+        foreach (var column in HiddenColumns.ColumnNames)
         {
-            var value = row[column];
-            var vStr = value?.ToString() ?? "";
-            var pValue = predicate.FilterValue;
-            var pStr = pValue?.ToString() ?? "";
-            return predicate.FilterType switch
-            {
-                FilterType.Contains => vStr.Contains(pStr),
-                FilterType.EndsWith => vStr.EndsWith(pStr),
-                FilterType.Equals => vStr.Equals(pStr),
-                FilterType.GreaterThan => vStr.CompareTo(pStr) > 0,
-                FilterType.GreaterThanOrEqual => vStr.CompareTo(pStr) >= 0,
-                FilterType.LessThan => vStr.CompareTo(pStr) < 0,
-                FilterType.LessThanOrEqual => vStr.CompareTo(pStr) <= 0,
-                FilterType.NotContains => !vStr.Contains(pStr),
-                FilterType.NotEndsWith => !vStr.EndsWith(pStr),
-                FilterType.NotEquals => !vStr.Equals(pStr),
-                FilterType.NotStartsWith => !vStr.StartsWith(pStr),
-                FilterType.StartsWith => vStr.StartsWith(pStr),
-                _ => true,
-            };
+            reloadColumns.Add(column);
         }
-        private List<Tuple<string, FilterPredicate>> GetFilterPredicates()
+        foreach (var (column, _) in predicates)
         {
-            var list = new List<Tuple<string, FilterPredicate>>();
-            foreach (var column in DataGrid.Columns)
-            {
-                var colIndex = DataGrid.Columns.IndexOf(column);
-                var col = ColumnList[colIndex];
-                if (col is DynamicGridColumn gridColumn)
-                {
-                    foreach (var predicate in column.FilterPredicates)
-                    {
-                        list.Add(new(gridColumn.ColumnName, predicate));
-                    }
-                }
-            }
-            return list;
+            reloadColumns.Add(column);
         }
 
-        protected virtual void DoExport()
+        var sort = LookupFactory.DefineSort<T>();
+        Reload(filters, reloadColumns, ref sort, (data, err) => Dispatcher.Invoke(() =>
         {
-            var columnnames = VisibleColumns.Select(x => x.ColumnName).ToList();
-            CustomiseExportColumns(columnnames);
-            var form = new DynamicExportForm(typeof(T), columnnames);
-            if (form.ShowDialog() != true)
-                return;
-
-            var filters = new Filters<T>();
-            filters.Add(DefineFilter());
-
-            var predicates = GetFilterPredicates();
-
-            var visiblerows = GetVisibleRows();
-            CustomiseExportFilters(filters, visiblerows);
-
-            var columns = new Columns<T>(form.Fields);
-            var otherColumns = form.GetChildFields()
-                .Select(x => new Tuple<Type, IColumns>(
-                    x.Key,
-                    (Activator.CreateInstance(typeof(Columns<>).MakeGenericType(x.Key), new object[] { x.Value }) as IColumns)!))
-                .Where(x => x.Item2.ColumnNames().Any()).ToList();
-
-            var reloadColumns = new Columns<T>();
-            foreach (var column in columns.ColumnNames())
-            {
-                reloadColumns.Add(column);
-            }
-            foreach (var column in HiddenColumns.ColumnNames)
-            {
-                reloadColumns.Add(column);
-            }
-            foreach (var (column, _) in predicates)
+            if (data is not null)
             {
-                reloadColumns.Add(column);
-            }
+                var newData = new CoreTable();
+                foreach (var column in columns.Items)
+                    newData.Columns.Add(new CoreColumn { ColumnName = column.Property, DataType = column.Type });
 
-            var sort = LookupFactory.DefineSort<T>();
-            Reload(filters, reloadColumns, ref sort, (data, err) => Dispatcher.Invoke(() =>
-            {
-                if (data is not null)
+                FilterRows(data, newData, filter: (row) =>
                 {
-                    var newData = new CoreTable();
-                    foreach (var column in columns.Items)
-                        newData.Columns.Add(new CoreColumn { ColumnName = column.Property, DataType = column.Type });
-
-                    FilterRows(data, newData, filter: (row) =>
+                    foreach (var (column, predicate) in predicates)
                     {
-                        foreach (var (column, predicate) in predicates)
+                        if (!FilterByPredicate(row, column, predicate))
                         {
-                            if (!FilterByPredicate(row, column, predicate))
-                            {
-                                return false;
-                            }
+                            return false;
                         }
-                        return true;
-                    });
-
-                    var list = new List<Tuple<Type?, CoreTable>>() { new(typeof(T), newData) };
-                    list.AddRange(LoadExportTables(filters, otherColumns));
-                    DoExportTables(list);
-                }
-                else if (err is not null)
-                {
-                    Logger.Send(LogType.Error, "", $"Error in export: {CoreUtils.FormatException(err)}");
-                    MessageBox.Show(err.Message);
-                }
-            }));
-        }
-
-        private void Export_Click(object sender, RoutedEventArgs e)
-        {
-            DoExport();
-        }
+                    }
+                    return true;
+                });
 
-        /// <summary>
-        /// Loads the child tables for an export, based on the filter of the parent table.
-        /// </summary>
-        /// <remarks>
-        /// If not overriden, defaults to creating empty tables with no records.
-        /// </remarks>
-        /// <param name="filter">Filter for the parent table.</param>
-        /// <param name="tableColumns">A list of the child table types, with columns to load for each</param>
-        /// <returns>A list of tables, in the same order as they came in <paramref name="tableColumns"/></returns>
-        protected virtual IEnumerable<Tuple<Type?, CoreTable>> LoadExportTables(Filters<T> filter, IEnumerable<Tuple<Type, IColumns>> tableColumns)
-        {
-            return tableColumns.Select(x =>
+                var list = new List<Tuple<Type?, CoreTable>>() { new(typeof(T), newData) };
+                list.AddRange(LoadExportTables(filters, otherColumns));
+                DoExportTables(list);
+            }
+            else if (err is not null)
             {
-                var table = new CoreTable();
-                table.LoadColumns(x.Item2);
-                return new Tuple<Type?, CoreTable>(x.Item1, table);
-            });
-        }
-
-        private void DoExportTables(List<Tuple<Type?, CoreTable>> data)
-        {
-            var filename = CustomiseExportFileName(typeof(T).EntityName().Split('.').Last());
-            ExcelExporter.DoExport(data, filename);
-        }
+                Logger.Send(LogType.Error, "", $"Error in export: {CoreUtils.FormatException(err)}");
+                MessageBox.Show(err.Message);
+            }
+        }));
+    }
 
-        #endregion
+    private void Export_Click(object sender, RoutedEventArgs e)
+    {
+        DoExport();
+    }
 
-        public void ScrollIntoView(CoreRow row)
+    /// <summary>
+    /// Loads the child tables for an export, based on the filter of the parent table.
+    /// </summary>
+    /// <remarks>
+    /// If not overriden, defaults to creating empty tables with no records.
+    /// </remarks>
+    /// <param name="filter">Filter for the parent table.</param>
+    /// <param name="tableColumns">A list of the child table types, with columns to load for each</param>
+    /// <returns>A list of tables, in the same order as they came in <paramref name="tableColumns"/></returns>
+    protected virtual IEnumerable<Tuple<Type?, CoreTable>> LoadExportTables(Filters<T> filter, IEnumerable<Tuple<Type, IColumns>> tableColumns)
+    {
+        return tableColumns.Select(x =>
         {
-            DataGrid.ScrollInView(new RowColumnIndex(row.Index + 1, 0));
-        }
-
-        #endregion
+            var table = new CoreTable();
+            table.LoadColumns(x.Item2);
+            return new Tuple<Type?, CoreTable>(x.Item1, table);
+        });
+    }
 
-        #region Custom Buttons
+    private void DoExportTables(List<Tuple<Type?, CoreTable>> data)
+    {
+        var filename = CustomiseExportFileName(typeof(T).EntityName().Split('.').Last());
+        ExcelExporter.DoExport(data, filename);
+    }
 
-        private Button CreateButton(BitmapImage? image = null, string? text = null, string? tooltip = null)
-        {
-            var button = new Button();
-            button.SetValue(BorderBrushProperty, new SolidColorBrush(Colors.Gray));
-            button.SetValue(BorderThicknessProperty, new Thickness(0.75));
-            button.Height = 30;
-            UpdateButton(button, image, text, tooltip);
-            return button;
-        }
+    #endregion
 
-        public override void UpdateButton(Button button, BitmapImage? image, string? text, string? tooltip = null)
-        {
-            var stackPnl = new StackPanel();
-            stackPnl.Orientation = Orientation.Horizontal;
-            //stackPnl.Margin = new Thickness(2);
+    public void ScrollIntoView(CoreRow row)
+    {
+        UIComponent.ScrollIntoView(row);
+    }
 
-            if (image != null)
-            {
-                var img = new Image();
-                img.Source = image;
-                img.Margin = new Thickness(2);
-                img.ToolTip = tooltip;
-                stackPnl.Children.Add(img);
-            }
+    #endregion
 
-            if (!string.IsNullOrEmpty(text))
-            {
-                button.MaxWidth = double.MaxValue;
-                var lbl = new Label();
-                lbl.Content = text;
-                lbl.VerticalAlignment = VerticalAlignment.Stretch;
-                lbl.VerticalContentAlignment = VerticalAlignment.Center;
-                lbl.Margin = new Thickness(2, 0, 5, 0);
-                lbl.ToolTip = ToolTip;
-                stackPnl.Children.Add(lbl);
-            }
-            else
-                button.MaxWidth = 30;
+    #region Custom Buttons
 
-            button.Content = stackPnl;
-            button.ToolTip = tooltip;
-        }
+    private Button CreateButton(BitmapImage? image = null, string? text = null, string? tooltip = null)
+    {
+        var button = new Button();
+        button.SetValue(BorderBrushProperty, new SolidColorBrush(Colors.Gray));
+        button.SetValue(BorderThicknessProperty, new Thickness(0.75));
+        button.Height = 30;
+        UpdateButton(button, image, text, tooltip);
+        return button;
+    }
 
-        private bool bFirstButtonAdded = true;
+    public void UpdateButton(Button button, BitmapImage? image, string? text, string? tooltip = null)
+    {
+        var stackPnl = new StackPanel();
+        stackPnl.Orientation = Orientation.Horizontal;
+        //stackPnl.Margin = new Thickness(2);
 
-        private bool AnyButtonsVisible()
+        if (image != null)
         {
-            if (Add.Visibility != Visibility.Collapsed)
-                return true;
-            if (Edit.Visibility != Visibility.Collapsed)
-                return true;
-            /*if (MultiEdit.Visibility != Visibility.Collapsed)
-                return true;*/
-            if (Export.Visibility != Visibility.Collapsed)
-                return true;
-            return false;
+            var img = new Image();
+            img.Source = image;
+            img.Margin = new Thickness(2);
+            img.ToolTip = tooltip;
+            stackPnl.Children.Add(img);
         }
 
-
-        public override Button AddButton(string? caption, BitmapImage? image, string? tooltip, DynamicGridButtonClickEvent action, DynamicGridButtonPosition position = DynamicGridButtonPosition.Left)
+        if (!string.IsNullOrEmpty(text))
         {
-            var button = CreateButton(image, caption, tooltip);
-            button.Margin = position == DynamicGridButtonPosition.Right
-                ? new Thickness(2, 2, 0, 0)
-                : bFirstButtonAdded && AnyButtonsVisible()
-                    ? new Thickness(0, 2, 0, 0)
-                    : new Thickness(0, 2, 2, 0);
-            button.Padding = !String.IsNullOrWhiteSpace(caption) ? new Thickness(5, 1, 5, 1) : new Thickness(1);
-            button.Tag = action;
-            button.Click += Button_Click;
-            if (position == DynamicGridButtonPosition.Right)
-                RightButtonStack.Children.Add(button);
-            else
-                LeftButtonStack.Children.Add(button);
-            bFirstButtonAdded = false;
-            return button;
+            button.MaxWidth = double.MaxValue;
+            var lbl = new Label();
+            lbl.Content = text;
+            lbl.VerticalAlignment = VerticalAlignment.Stretch;
+            lbl.VerticalContentAlignment = VerticalAlignment.Center;
+            lbl.Margin = new Thickness(2, 0, 5, 0);
+            lbl.ToolTip = ToolTip;
+            stackPnl.Children.Add(lbl);
         }
+        else
+            button.MaxWidth = 30;
 
-        private void Button_Click(object sender, RoutedEventArgs e)
-        {
-            var button = (Button)sender;
-            var action = (DynamicGridButtonClickEvent)button.Tag;
+        button.Content = stackPnl;
+        button.ToolTip = tooltip;
+    }
 
-            //CoreRow row = (CurrentRow > -1) && (CurrentRow < Data.Rows.Count) ? Data.Rows[this.CurrentRow] : null;
-            if (action.Invoke(button, SelectedRows))
-                Refresh(false, true);
-        }
+    private bool bFirstButtonAdded = true;
 
-        #endregion
+    private bool AnyButtonsVisible()
+    {
+        if (Add.Visibility != Visibility.Collapsed)
+            return true;
+        if (Edit.Visibility != Visibility.Collapsed)
+            return true;
+        /*if (MultiEdit.Visibility != Visibility.Collapsed)
+            return true;*/
+        if (Export.Visibility != Visibility.Collapsed)
+            return true;
+        return false;
+    }
 
-        #region Header Actions
 
-        private void ColumnsMenu_ContextMenuOpening(object sender, RoutedEventArgs e)
-        {
-            if (sender is not ContextMenu menu) return;
-            menu.Items.Clear();
+    public Button AddButton(string? caption, BitmapImage? image, string? tooltip, DynamicGridButtonClickEvent action, DynamicGridButtonPosition position = DynamicGridButtonPosition.Left)
+    {
+        var button = CreateButton(image, caption, tooltip);
+        button.Margin = position == DynamicGridButtonPosition.Right
+            ? new Thickness(2, 2, 0, 0)
+            : bFirstButtonAdded && AnyButtonsVisible()
+                ? new Thickness(0, 2, 0, 0)
+                : new Thickness(0, 2, 2, 0);
+        button.Padding = !String.IsNullOrWhiteSpace(caption) ? new Thickness(5, 1, 5, 1) : new Thickness(1);
+        button.Tag = action;
+        button.Click += Button_Click;
+        if (position == DynamicGridButtonPosition.Right)
+            RightButtonStack.Children.Add(button);
+        else
+            LeftButtonStack.Children.Add(button);
+        bFirstButtonAdded = false;
+        return button;
+    }
 
-            menu.AddItem("Select Columns", null, SelectColumnsClick);
-            LoadColumnsMenu(menu);
-        }
+    public Button AddButton(string? caption, BitmapImage? image, DynamicGridButtonClickEvent action, DynamicGridButtonPosition position = DynamicGridButtonPosition.Left)
+    {
+        var result = AddButton(caption, image, null, action, position);
+        return result;
+    }
 
-        private void SelectColumnsClick()
-        {
-            var editor = new DynamicGridColumnsEditor(typeof(T));
-            editor.DirectEdit = IsDirectEditMode();
+    private void Button_Click(object sender, RoutedEventArgs e)
+    {
+        var button = (Button)sender;
+        var action = (DynamicGridButtonClickEvent)button.Tag;
 
-            editor.Columns.AddRange(VisibleColumns);
+        //CoreRow row = (CurrentRow > -1) && (CurrentRow < Data.Rows.Count) ? Data.Rows[this.CurrentRow] : null;
+        if (action.Invoke(button, SelectedRows))
+            Refresh(false, true);
+    }
 
-            if (editor.ShowDialog().Equals(true))
-            {
-                VisibleColumns.Clear();
-                VisibleColumns.AddRange(editor.Columns);
-                SaveColumns(VisibleColumns);
-                //OnSaveColumns?.Invoke(this, editor.Columns);
-                Refresh(true, true);
-            }
-        }
+    #endregion
 
-        #endregion
+    #region Header Actions
 
-        #region Drag + Drop
+    private void SelectColumnsClick()
+    {
+        var editor = new DynamicGridColumnsEditor(typeof(T));
+        editor.DirectEdit = IsDirectEditMode();
 
-        /// <summary>
-        /// Handle a number of rows from a different <see cref="DynamicGrid{T}"/> being dragged into this one.
-        /// </summary>
-        /// <param name="entity">The type of entity that that the rows of <paramref name="table"/> represent.</param>
-        /// <param name="table">The data being dragged.</param>
-        /// <param name="e"></param>
-        protected virtual void OnDragEnd(Type entity, CoreTable table, DragEventArgs e)
-        {
-            Logger.Send(LogType.Information,"","OnDragEnd");
-        }
+        editor.Columns.AddRange(VisibleColumns);
 
-        private void DataGrid_DragOver(object sender, DragEventArgs e)
+        if (editor.ShowDialog().Equals(true))
         {
-            HandleDragOver(sender, e);
+            VisibleColumns.Clear();
+            VisibleColumns.AddRange(editor.Columns);
+            SaveColumns(VisibleColumns);
+            //OnSaveColumns?.Invoke(this, editor.Columns);
+            Refresh(true, true);
         }
+    }
 
-        private void DataGrid_Drop(object sender, DragEventArgs e)
-        {
-            if (!HasOption(DynamicGridOption.DragTarget))
-                return;
-
-            if(DynamicGridUtils.TryGetDropData(e, out var entityType, out var table))
-            {
-                OnDragEnd(entityType, table, e);
-            }
-            else
-            {
-                HandleDragDrop(sender, e);
-            }
-        }
+    #endregion
 
-        /// <summary>
-        /// Handle all types of items being dragged onto this grid that aren't handled by <see cref="OnDragEnd(Type, CoreTable, DragEventArgs)"/>,
-        /// i.e., data which is not a <see cref="CoreTable"/> from another <see cref="DynamicGrid{T}"/>
-        /// </summary>
-        /// <remarks>
-        /// Can be used to handle files, for example.
-        /// </remarks>
-        /// <param name="sender"></param>
-        /// <param name="e"></param>
-        protected virtual void HandleDragDrop(object sender, DragEventArgs e)
-        {
-        }
+    #region Drag + Drop
 
-        protected virtual void HandleDragOver(object sender, DragEventArgs e)
-        {
-        }
+    /// <summary>
+    /// Handle a number of rows from a different <see cref="DynamicGrid{T}"/> being dragged into this one.
+    /// </summary>
+    /// <param name="entity">The type of entity that that the rows of <paramref name="table"/> represent.</param>
+    /// <param name="table">The data being dragged.</param>
+    /// <param name="e"></param>
+    protected virtual void OnDragEnd(Type entity, CoreTable table, DragEventArgs e)
+    {
+        Logger.Send(LogType.Information,"","OnDragEnd");
+    }
 
-        protected DragDropEffects DragTable(Type entity, CoreTable table)
-        {
-            Logger.Send(LogType.Information, "", "DragTable");
-            var data = new DataObject();
-            data.SetData(DynamicGridUtils.DragFormat, new DynamicGridDragFormat(table.ToDataTable(), entity));
-            var effect = DragDrop.DoDragDrop(this, data, DragDropEffects.All);
-            return effect;
-        }
+    /// <summary>
+    /// Handle all types of items being dragged onto this grid that aren't handled by <see cref="OnDragEnd(Type, CoreTable, DragEventArgs)"/>,
+    /// i.e., data which is not a <see cref="CoreTable"/> from another <see cref="DynamicGrid{T}"/>
+    /// </summary>
+    /// <remarks>
+    /// Can be used to handle files, for example.
+    /// </remarks>
+    /// <param name="sender"></param>
+    /// <param name="e"></param>
+    protected virtual void HandleDragDrop(object sender, DragEventArgs e)
+    {
+    }
 
-        protected virtual DragDropEffects OnRowsDragStart(CoreRow[] rows)
-        {
-            Logger.Send(LogType.Information, "", "OnRowsDragStart");
-            var table = new CoreTable();
-            table.LoadColumns(Data.Columns);
-            table.LoadRows(rows);
-            return DragTable(typeof(T), table);
-        }
+    protected virtual void HandleDragOver(object sender, DragEventArgs e)
+    {
+    }
 
-        private void RowDragDropController_DragStart(object? sender, GridRowDragStartEventArgs e)
-        {
-            Logger.Send(LogType.Information, "", "RowDragDropController_DragStart");
-            
-            if (!HasOption(DynamicGridOption.DragSource))
-                return;
-            
-            var rows = e.DraggingRecords.Select(record =>
-            {
-                var rowIndex = DataGrid.ResolveToRowIndex(record);
-                return GetRowFromIndex(rowIndex);
-            }).NotNull().ToArray();
-            OnRowsDragStart(rows);
-        }
+    protected DragDropEffects DragTable(Type entity, CoreTable table)
+    {
+        Logger.Send(LogType.Information, "", "DragTable");
+        var data = new DataObject();
+        data.SetData(DynamicGridUtils.DragFormat, new DynamicGridDragFormat(table.ToDataTable(), entity));
+        var effect = DragDrop.DoDragDrop(this, data, DragDropEffects.All);
+        return effect;
+    }
 
-        #endregion
+    protected virtual DragDropEffects OnRowsDragStart(CoreRow[] rows)
+    {
+        Logger.Send(LogType.Information, "", "OnRowsDragStart");
+        var table = new CoreTable();
+        table.LoadColumns(Data.Columns);
+        table.LoadRows(rows);
+        return DragTable(typeof(T), table);
     }
+
+    #endregion
 }

+ 2 - 0
inabox.wpf/DynamicGrid/DynamicGridCommon.cs

@@ -47,6 +47,8 @@ namespace InABox.DynamicGrid
 
     public delegate void OnFormCustomiseEditor(IDynamicEditorForm sender, object[] items, DynamicGridColumn column, BaseEditor editor);
 
+    public delegate void ValidateEvent<T>(object sender, T[] items, List<string> errors);
+
     /// <summary>
     /// 
     /// </summary>

+ 16 - 5
inabox.wpf/DynamicGrid/DynamicGridUtils.cs

@@ -14,6 +14,7 @@ using InABox.Core.Reports;
 using Syncfusion.Data.Extensions;
 using System.Diagnostics.CodeAnalysis;
 using System.Data;
+using System.Windows.Media;
 
 namespace InABox.DynamicGrid;
 
@@ -467,7 +468,7 @@ public static class DynamicGridUtils
         grids = grids.Where(x => x.IsSubclassOfRawGeneric(gridType)).ToArray();
         var entityGrids = grids.Where(x =>
         {
-            var baseGrid = x.GetSuperclassDefinition(typeof(BaseDynamicGrid<>));
+            var baseGrid = x.GetSuperclassDefinition(typeof(DynamicGrid<>));
             return baseGrid?.GenericTypeArguments[0] == entityType;
         }).ToList();
 
@@ -495,21 +496,21 @@ public static class DynamicGridUtils
         return gridType.MakeGenericType(entityType);
     }
 
-    public static Window CreateGridWindow(string title, BaseDynamicGrid dynamicGrid)
+    public static Window CreateGridWindow(string title, IDynamicGrid dynamicGrid)
     {
         dynamicGrid.Margin = new Thickness(5);
 
         var window = new ThemableWindow { Title = title, Content = dynamicGrid };
 
-        (dynamicGrid as IDynamicGrid)!.Refresh(true, true);
+        dynamicGrid.Refresh(true, true);
 
         return window;
     }
     public static Window CreateGridWindow(string title, Type entityType, Type? gridType = null)
     {
         gridType ??= typeof(DynamicGrid<>);
-        var grid = CreateDynamicGrid(gridType, entityType) as BaseDynamicGrid;
-        return CreateGridWindow(title, grid!);
+        var grid = CreateDynamicGrid(gridType, entityType) as FrameworkElement;
+        return CreateGridWindow(title, grid);
     }
     public static Window CreateGridWindow<TGrid, TEntity>(string title)
         where TEntity : BaseObject
@@ -608,6 +609,16 @@ public static class DynamicGridUtils
 
     #endregion
 
+    #region Style
+    
+    public static Brush SelectionBackground { get; set; } = new SolidColorBrush(Colors.Black);
+
+    public static Brush SelectionForeground { get; set; } = new SolidColorBrush(Colors.Silver);
+
+    public static Brush FilterBackground { get; set; } = new SolidColorBrush(System.Windows.Media.Color.FromArgb(0xFF, 0xE9, 0xF7, 0xC9));
+
+    #endregion
+
     public static void PopulateFormMenu<TEntityForm, TEntity, TEntityLink>(
         ItemsControl menu,
         Guid entityID,

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 792 - 391
inabox.wpf/DynamicGrid/DynamicTreeView.cs


+ 4 - 4
inabox.wpf/DynamicGrid/Editors/ButtonEditor/ListEditorControl.cs

@@ -49,11 +49,11 @@ public class ListEditorControl : DynamicEditorControl<IList, ListEditor>
                 return;
             var type = _data.GetType().GetGenericArguments().First();
             var gridtype = DynamicGridUtils.FindDynamicGrid(typeof(DynamicItemsListGrid<>), type);
-            BaseDynamicGrid? grid = Activator.CreateInstance(gridtype) as BaseDynamicGrid;
+            var grid = Activator.CreateInstance(gridtype);
             if (grid is not IDynamicItemsListGrid idg )
                 return;
             idg.Items = _data;
-            grid.OnReconfigure += options =>
+            idg.OnReconfigure += options =>
             {
                 options
                     .Add(DynamicGridOption.AddRows)
@@ -63,8 +63,8 @@ public class ListEditorControl : DynamicEditorControl<IList, ListEditor>
                 if (DirectEdit)
                     options.Add(DynamicGridOption.DirectEdit);
             };
-            grid.Reconfigure();
-            var window = DynamicGridUtils.CreateGridWindow("License Mappings", grid);
+            idg.Reconfigure();
+            var window = DynamicGridUtils.CreateGridWindow("License Mappings", idg);
             if (ListWidth > 0)
                 window.Width = ListWidth;
             if (ListHeight > 0)

+ 79 - 59
inabox.wpf/DynamicGrid/IDynamicGrid.cs

@@ -10,89 +10,109 @@ using System.Windows.Media.Imaging;
 using InABox.Core;
 using Syncfusion.Data;
 
-namespace InABox.DynamicGrid
+namespace InABox.DynamicGrid;
+
+
+public enum DynamicGridButtonPosition
 {
+    Left,
+    Right
+}
 
-    public enum DynamicGridButtonPosition
-    {
-        Left,
-        Right
-    }
-    
-    
-    public interface IDynamicGrid
-    {
-        Thickness Margin { get; set; }
 
-        DynamicGridColumns MasterColumns { get; }
-        DynamicGridColumns VisibleColumns { get; }
+public interface IDynamicGrid
+{
+    bool IsReady { get; }
+
+    Thickness Margin { get; set; }
+
+    DynamicGridColumns MasterColumns { get; }
+    DynamicGridColumns VisibleColumns { get; }
+    DynamicActionColumns ActionColumns { get; }
+
+    CoreTable Data { get; set; }
+    CoreRow[] SelectedRows { get; set; }
+
+    double RowHeight { get; set; }
+    double HeaderHeight { get; set; }
 
-        CoreTable Data { get; set; }
-        CoreRow[] SelectedRows { get; set; }
+    double FontSize { get; set; }
+    void Refresh(bool columns, bool data);
 
-        double RowHeight { get; set; }
-        double HeaderHeight { get; set; }
+    void DoChanged();
 
-        double FontSize { get; set; }
-        void Refresh(bool columns, bool data);
+    void InitialiseEditorForm(IDynamicEditorForm editor, object[] items, Func<Type, CoreTable>? pageDataHandler = null, bool preloadPages = false);
+    bool EditItems(object[] items, Func<Type, CoreTable?>? PageDataHandler = null, bool PreloadPages = false);
 
-        void InitialiseEditorForm(IDynamicEditorForm editor, object[] items, Func<Type, CoreTable>? pageDataHandler = null, bool preloadPages = false);
-        bool EditItems(object[] items, Func<Type, CoreTable?>? PageDataHandler = null, bool PreloadPages = false);
+    //bool DirectEdit(CoreTable data);
 
-        //bool DirectEdit(CoreTable data);
+    event OnFilterRecord OnFilterRecord;
+    event OnCreateItem OnCreateItem;
 
-        event OnFilterRecord OnFilterRecord;
-        event OnCreateItem OnCreateItem;
+    event OnDoubleClick? OnDoubleClick;
 
-        event OnDoubleClick? OnDoubleClick;
+    delegate void ReconfigureEvent(FluentList<DynamicGridOption> options);
+    event ReconfigureEvent? OnReconfigure;
 
-        delegate void ReconfigureEvent(FluentList<DynamicGridOption> options);
-        event ReconfigureEvent? OnReconfigure;
+    public abstract void Reconfigure();
 
-        public abstract void Reconfigure();
+    /// <summary>
+    /// Add <paramref name="onReconfigure"/> to <see cref="OnReconfigure"/>, and then call <see cref="Reconfigure"/>.
+    /// </summary>
+    /// <remarks>
+    /// Probably should only be called once per grid, otherwise there will be multiple event handlers bound.<br/>
+    /// If you want to reconfigure without specifiying
+    /// a new reconfigure event, use <see cref="Reconfigure"/>.
+    /// </remarks>
+    /// <param name="onReconfigure"></param>
+    public void Reconfigure(ReconfigureEvent onReconfigure);
 
-        /// <summary>
-        /// Add <paramref name="onReconfigure"/> to <see cref="OnReconfigure"/>, and then call <see cref="Reconfigure"/>.
-        /// </summary>
-        /// <remarks>
-        /// Probably should only be called once per grid, otherwise there will be multiple event handlers bound.<br/>
-        /// If you want to reconfigure without specifiying
-        /// a new reconfigure event, use <see cref="Reconfigure"/>.
-        /// </remarks>
-        /// <param name="onReconfigure"></param>
-        public void Reconfigure(ReconfigureEvent onReconfigure);
+    public bool HasOption(DynamicGridOption option);
 
-        public bool HasOption(DynamicGridOption option);
+    void AddVisualFilter(string column, string value, FilterType filtertype = FilterType.Contains);
 
-        void AddVisualFilter(string column, string value, FilterType filtertype = FilterType.Contains);
+    Button AddButton(string? caption, BitmapImage? image, string? tooltip, DynamicGridButtonClickEvent action, DynamicGridButtonPosition position = DynamicGridButtonPosition.Left);
 
-        Button AddButton(string? caption, BitmapImage? image, string? tooltip, DynamicGridButtonClickEvent action, DynamicGridButtonPosition position = DynamicGridButtonPosition.Left);
+    Button AddButton(string? caption, BitmapImage? image, DynamicGridButtonClickEvent action, DynamicGridButtonPosition position = DynamicGridButtonPosition.Left);
 
-        Button AddButton(string? caption, BitmapImage? image, DynamicGridButtonClickEvent action, DynamicGridButtonPosition position = DynamicGridButtonPosition.Left);
+    void UpdateButton(Button button, BitmapImage? image, string? text, string? tooltip = null);
 
-        void UpdateButton(Button button, BitmapImage? image, string? text, string? tooltip = null);
+    event OnDefineFilter OnDefineFilter;
 
-        event OnDefineFilter OnDefineFilter;
+    event OnPrintData OnPrintData;
+    
+    event OnCustomiseColumns OnCustomiseColumns;
+
+    event BeforeRefreshEventHandler BeforeRefresh;
+    
+    event AfterRefreshEventHandler AfterRefresh;
+    
+    event EntitySaveEvent? OnAfterSave;
+    event EntitySaveEvent? OnBeforeSave;
+
+    int DesiredWidth();
 
-        event OnPrintData OnPrintData;
-        
-        event OnCustomiseColumns OnCustomiseColumns;
+    void ConfigureColumns(DynamicGridColumns columns /*, bool dolookups */);
 
-        event BeforeRefreshEventHandler BeforeRefresh;
-        
-        event AfterRefreshEventHandler AfterRefresh;
-        
-        event EntitySaveEvent? OnAfterSave;
-        event EntitySaveEvent? OnBeforeSave;
+    void AddHiddenColumn(string column);
+
+    void UpdateRow<TType>(CoreRow row, string column, TType value, bool refresh = true);
+
+    void UpdateRow<T, TType>(CoreRow row, Expression<Func<T, TType>> column, TType value, bool refresh = true);
+}
+
+public interface IDynamicGrid<T> : IDynamicGrid
+    where T : BaseObject, new()
+{
+    T CreateItem();
 
-        int DesiredWidth();
+    T LoadItem(CoreRow row);
 
-        void ConfigureColumns(DynamicGridColumns columns /*, bool dolookups */);
+    void SaveItem(T item);
 
-        void AddHiddenColumn(string column);
+    void SaveItems(T[] items);
 
-        void UpdateRow<TType>(CoreRow row, string column, TType value, bool refresh = true);
+    void DeleteItems(CoreRow[] rows);
 
-        void UpdateRow<T, TType>(CoreRow row, Expression<Func<T, TType>> column, TType value, bool refresh = true);
-    }
+    bool EditItems(T[] items, Func<Type, CoreTable?>? PageDataHandler = null, bool PreloadPages = false)
 }

+ 1406 - 0
inabox.wpf/DynamicGrid/UIComponent/DynamicGridGridUIComponent.cs

@@ -0,0 +1,1406 @@
+using InABox.Clients;
+using InABox.Core;
+using InABox.WPF;
+using Syncfusion.Data;
+using Syncfusion.UI.Xaml.Grid;
+using Syncfusion.UI.Xaml.Grid.Helpers;
+using Syncfusion.UI.Xaml.ScrollAxis;
+using Syncfusion.Windows.Shared;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Data;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Threading;
+using DataRow = System.Data.DataRow;
+
+namespace InABox.DynamicGrid;
+
+public class DynamicGridGridUIComponent<T> : IDynamicGridUIComponent<T>
+    where T : BaseObject, new()
+{
+    private readonly Dictionary<string, string> _filterpredicates = new();
+
+
+    private Dictionary<string, CoreTable> Lookups = new();
+
+    private IDynamicGridUIComponentParent<T> _parent;
+    public IDynamicGridUIComponentParent<T> Parent
+    {
+        get => _parent;
+        set
+        {
+            _parent = value;
+
+            CellBackgroundConverter = new DynamicGridCellStyleConverter<System.Windows.Media.Brush?>(Parent, GetCellBackground);
+            CellForegroundConverter = new DynamicGridCellStyleConverter<System.Windows.Media.Brush?>(Parent, GetCellForeground);
+            CellFontSizeConverter = new DynamicGridCellStyleConverter<double?>(Parent, GetCellFontSize);
+            CellFontStyleConverter = new DynamicGridCellStyleConverter<System.Windows.FontStyle?>(Parent, GetCellFontStyle);
+            CellFontWeightConverter = new DynamicGridCellStyleConverter<System.Windows.FontWeight?>(Parent, GetCellFontWeight);
+
+            DataGrid.RowStyleSelector = Parent.RowStyleSelector;
+            DataGrid.TableSummaryCellStyleSelector = new DynamicGridSummaryStyleSelector(Parent);
+        }
+    }
+    FrameworkElement IDynamicGridUIComponent<T>.Control => DataGrid;
+
+
+    private readonly SfDataGrid DataGrid;
+
+    private readonly ContextMenu ColumnsMenu;
+
+    private readonly GridRowSizingOptions gridRowResizingOptions = new() { CanIncludeHiddenColumns = false, AutoFitMode = AutoFitMode.Default };
+
+    /// <summary>
+    /// <see langword="null"/> when <see cref="DataGrid.ItemsSource"/> is <see langword="null"/>, generally while the grid is refreshing its columns.
+    /// </summary>
+    private DataTable? DataGridItems => (DataGrid.ItemsSource as DataTable);
+
+    private DynamicGridCellStyleConverter<System.Windows.Media.Brush?> CellBackgroundConverter;
+    private DynamicGridCellStyleConverter<System.Windows.Media.Brush?> CellForegroundConverter;
+    private DynamicGridCellStyleConverter<double?> CellFontSizeConverter;
+    private DynamicGridCellStyleConverter<System.Windows.FontStyle?> CellFontStyleConverter;
+    private DynamicGridCellStyleConverter<System.Windows.FontWeight?> CellFontWeightConverter;
+
+    public DynamicGridGridUIComponent()
+    {
+        ColumnsMenu = new ContextMenu();
+        ColumnsMenu.Opened += ColumnsMenu_ContextMenuOpening;
+
+        DataGrid = new SfDataGrid();
+        DataGrid.VerticalAlignment = VerticalAlignment.Stretch;
+        DataGrid.HorizontalAlignment = HorizontalAlignment.Stretch;
+        DataGrid.HeaderContextMenu = ColumnsMenu;
+        DataGrid.CellTapped += DataGrid_CellTapped;
+        DataGrid.CellDoubleTapped += DataGrid_CellDoubleTapped;
+        DataGrid.SelectionChanging += DataGrid_SelectionChanging;
+        DataGrid.SelectionChanged += DataGrid_SelectionChanged;
+        DataGrid.SelectionMode = GridSelectionMode.Extended;
+        DataGrid.SelectionUnit = GridSelectionUnit.Row;
+        DataGrid.CanMaintainScrollPosition = true;
+        DataGrid.SummaryCalculationUnit = SummaryCalculationUnit.AllRows;
+        DataGrid.LiveDataUpdateMode = LiveDataUpdateMode.AllowSummaryUpdate;
+
+        DataGrid.NavigationMode = NavigationMode.Row;
+        DataGrid.AllowEditing = false;
+        DataGrid.EditTrigger = EditTrigger.OnTap;
+
+        DataGrid.CurrentCellBeginEdit += DataGrid_CurrentCellBeginEdit;
+        DataGrid.CurrentCellEndEdit += DataGrid_CurrentCellEndEdit;
+        DataGrid.CurrentCellValueChanged += DataGrid_CurrentCellValueChanged;
+        DataGrid.CurrentCellDropDownSelectionChanged += DataGrid_CurrentCellDropDownSelectionChanged;
+        DataGrid.PreviewKeyUp += DataGrid_PreviewKeyUp;
+
+        DataGrid.BorderBrush = new SolidColorBrush(Colors.Gray);
+        DataGrid.BorderThickness = new Thickness(0.75F);
+        DataGrid.Background = new SolidColorBrush(Colors.DimGray);
+        DataGrid.AutoGenerateColumns = false;
+        DataGrid.ColumnSizer = GridLengthUnitType.AutoLastColumnFill;
+        DataGrid.SelectionForegroundBrush = DynamicGrid<T>.SelectionForeground;
+        DataGrid.RowSelectionBrush = DynamicGrid<T>.SelectionBackground;
+        
+        DataGrid.AllowDraggingRows = false;
+        DataGrid.Drop += DataGrid_Drop;
+        DataGrid.DragOver += DataGrid_DragOver;
+        DataGrid.RowDragDropTemplate = TemplateGenerator.CreateDataTemplate(() =>
+        {
+            var border = new Border();
+            border.Width = 100;
+            border.Height = 100;
+            border.BorderBrush = new SolidColorBrush(Colors.Firebrick);
+            border.Background = new SolidColorBrush(Colors.Red);
+            border.CornerRadius = new CornerRadius(5);
+            return border;
+        });
+        
+        DataGrid.CurrentCellBorderThickness = new Thickness(0);
+        DataGrid.AllowFiltering = false;
+        DataGrid.EnableDataVirtualization = true;
+        DataGrid.RowHeight = 30;
+        DataGrid.QueryRowHeight += DataGrid_QueryRowHeight;
+        DataGrid.HeaderRowHeight = 30;
+
+        DataGrid.MouseLeftButtonUp += DataGrid_MouseLeftButtonUp;
+        DataGrid.MouseRightButtonUp += DataGrid_MouseRightButtonUp;
+        DataGrid.KeyUp += DataGrid_KeyUp;
+        DataGrid.PreviewGotKeyboardFocus += DataGrid_PreviewGotKeyboardFocus;
+        //DataGrid.SelectionController = new GridSelectionControllerExt(DataGrid);
+        
+        DataGrid.SetValue(ScrollViewer.VerticalScrollBarVisibilityProperty, ScrollBarVisibility.Visible);
+        
+        DataGrid.FilterChanged += DataGrid_FilterChanged;
+
+        DataGrid.FilterItemsPopulating += DataGrid_FilterItemsPopulating;
+
+        var fltstyle = new Style(typeof(GridFilterControl));
+        fltstyle.Setters.Add(new Setter(GridFilterControl.FilterModeProperty, FilterMode.Both));
+        fltstyle.Setters.Add(new Setter(GridFilterControl.SortOptionVisibilityProperty, Visibility.Collapsed));
+        DataGrid.FilterPopupStyle = fltstyle;
+
+        //DataGrid.MouseMove += DataGrid_MouseMove;
+        DataGrid.CellToolTipOpening += DataGrid_CellToolTipOpening;
+
+        //var headstyle = new Style(typeof(GridHeaderCellControl));
+        //headstyle.Setters.Add(new Setter(GridHeaderCellControl.BackgroundProperty, new SolidColorBrush(Colors.WhiteSmoke)));
+        //headstyle.Setters.Add(new Setter(GridHeaderCellControl.ForegroundProperty, new SolidColorBrush(Colors.Green)));
+        //headstyle.Setters.Add(new Setter(GridHeaderCellControl.FontSizeProperty, 12.0F));
+        //DataGrid.HeaderStyle = headstyle;
+
+        DataGrid.SizeChanged += DataGrid_SizeChanged;
+    }
+
+    private void ColumnsMenu_ContextMenuOpening(object sender, RoutedEventArgs e)
+    {
+        if (sender is not ContextMenu menu) return;
+        menu.Items.Clear();
+
+        Parent.LoadColumnsMenu(menu);
+    }
+
+    #region Selection
+
+    public CoreRow[] SelectedRows
+    {
+        get => GetSelectedRows();
+        set => SetSelectedRows(value);
+    }
+
+    private CoreRow[] GetSelectedRows()
+    {
+        //Logger.Send(LogType.Information, ClientFactory.UserID, String.Format("{0}: GetSelectedRows({1})", this.GetType().EntityName(), DataGrid.SelectedItems.Count));
+        var result = new List<CoreRow>();
+        foreach (var item in DataGrid.SelectedItems)
+        {
+            if (item is CoreRow)
+            {
+                //result.Add(item as CoreRow);
+            }
+            else if (item is DataRowView datarow)
+            {
+                var row = datarow.Row.Table.Rows.IndexOf(datarow.Row);
+                result.Add(Parent.Data.Rows[row]);
+            }
+        }
+
+        return result.ToArray();
+    }
+
+    private void SetSelectedRows(CoreRow[] rows)
+    {           
+        DataGrid.SelectedItems.Clear();
+
+        var table = DataGridItems;
+        if(table is null) return;
+
+        var dataRows = rows.Where(x => x.Index > -1).Select(row =>
+        {
+            return table.Rows[row.Index];
+        });
+        if (!Parent.HasOption(DynamicGridOption.MultiSelect))
+        {
+            dataRows = dataRows.Take(1);
+        }
+
+        foreach (var row in dataRows)
+        {
+            var record = DataGrid.View?.Records.FirstOrDefault(x => (x.Data as DataRowView)!.Row == row);
+            if(record?.Data is DataRowView rowView)
+            {
+                DataGrid.SelectedItems.Add(rowView);
+            }
+        }
+    }
+
+    private void DataGrid_SelectionChanging(object? sender, Syncfusion.UI.Xaml.Grid.GridSelectionChangingEventArgs e)
+    {
+        var cancel = new CancelEventArgs();
+        Parent.BeforeSelection(cancel);
+        if (cancel.Cancel)
+        {
+            e.Cancel = true;
+        }
+    }
+
+    private void DataGrid_SelectionChanged(object? sender, GridSelectionChangedEventArgs e)
+    {
+        if(Parent.IsReady && !Parent.IsRefreshing)
+        {
+            StartTimer();
+        }
+    }
+
+    private DispatcherTimer? clicktimer;
+
+    private void StartTimer()
+    {
+        if (clicktimer is null)
+        {
+            clicktimer = new DispatcherTimer();
+            clicktimer.Interval = TimeSpan.FromMilliseconds(200);
+            clicktimer.Tick += (o, e) =>
+            {
+                clicktimer.IsEnabled = false;
+                Parent.SelectItems(SelectedRows);
+            };
+        }
+
+        clicktimer.IsEnabled = true;
+    }
+
+    private void StopTimer()
+    {
+        if (clicktimer is not null)
+            clicktimer.IsEnabled = false;
+    }
+
+    public CoreRow[] GetVisibleRows()
+    {
+        var items = DataGrid.ItemsSource;
+
+        var result = new List<CoreRow>();
+
+        var table = DataGridItems;
+        if (table is null) return Array.Empty<CoreRow>();
+
+        var rows = DataGrid.View.Records.Select(x => (x.Data as DataRowView)!).ToList();
+        foreach (var row in rows)
+        {
+            var iRow = table.Rows.IndexOf(row.Row);
+            result.Add(Parent.Data.Rows[iRow]);
+        }
+
+        return result.ToArray();
+    }
+
+    #endregion
+
+    private bool bFilterVisible;
+
+    private void DataGrid_MouseRightButtonUp(object sender, MouseButtonEventArgs e)
+    {
+        if (!DataGrid.IsEnabled)
+            return;
+
+        var visualContainer = DataGrid.GetVisualContainer();
+        var rowcolumnindex = visualContainer.PointToCellRowColumnIndex(e.GetPosition(visualContainer));
+        var columnindex = DataGrid.ResolveToGridVisibleColumnIndex(rowcolumnindex.ColumnIndex);
+
+        var column = GetColumn(columnindex);
+        if(column is not null)
+        {
+            Parent.OpenColumnMenu(column);
+        }
+    }
+
+    private void DataGrid_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
+    {
+        if (!DataGrid.IsEnabled)
+            return;
+
+        // Header Click Here!
+        if (DataGrid.SelectedIndex == -1)
+        {
+            var visualContainer = DataGrid.GetVisualContainer();
+            var rowcolumnindex = visualContainer.PointToCellRowColumnIndex(e.GetPosition(visualContainer));
+            var columnindex = DataGrid.ResolveToGridVisibleColumnIndex(rowcolumnindex.ColumnIndex);
+
+            var column = GetColumn(columnindex);
+            if(column is DynamicActionColumn dac)
+            {
+                Parent.ExecuteActionColumn(dac, null);
+            }
+        }
+        else if (!bFilterVisible)
+        {
+            StartTimer();
+        }
+    }
+
+    private void DataGrid_CellTapped(object? sender, GridCellTappedEventArgs e)
+    {
+        if (!DataGrid.IsEnabled)
+            return;
+
+        if (GetColumn(e.RowColumnIndex.ColumnIndex) is DynamicActionColumn dac)
+        {
+            if(e.ChangedButton == MouseButton.Left || (e.ChangedButton == MouseButton.Right && dac is DynamicMenuColumn))
+            {
+                Parent.ExecuteActionColumn(dac, SelectedRows);
+            }
+        }
+        else
+        {
+            StartTimer();
+        }
+    }
+
+    private void DataGrid_KeyUp(object sender, KeyEventArgs e)
+    {
+        if (sender != DataGrid) return;
+
+        Parent.HandleKey(e);
+    }
+
+    private void DataGrid_PreviewGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
+    {
+        var bOld = bFilterVisible;
+        if (e.NewFocus is GridFilterControl)
+            bFilterVisible = true;
+        else if (e.NewFocus is ScrollViewer || e.NewFocus is SfDataGrid)
+            bFilterVisible = false;
+        if (bOld && !bFilterVisible)
+        {
+            Parent.SelectItems(SelectedRows);
+        }
+    }
+
+
+    // Please always use this function where possible!
+    /// <summary>
+    /// Get the <see cref="CoreRow"/> associated with a <paramref name="rowIndex"/> given from the Syncfusion DataGrid.
+    /// </summary>
+    /// <remarks>
+    /// This is mandatory to use whenever we want the data associated with an index which Syncfusion has given us,
+    /// because filtering and sorting will cause normal indexing operations to fail.
+    /// </remarks>
+    /// <param name="rowIndex"></param>
+    /// <returns></returns>
+    private CoreRow? GetRowFromIndex(int rowIndex)
+    {
+        // Syncfusion has given us the row index, so it also will give us the correct row, after sorting.
+        // Hence, here we use the syncfusion DataGrid.GetRecordAtRowIndex, which *should* always return a DataRowView.
+        var row = DataGrid.GetRecordAtRowIndex(rowIndex);
+        if (row is not DataRowView dataRowView || DataGridItems is not DataTable table)
+            return null;
+
+        var rowIdx = table.Rows.IndexOf(dataRowView.Row);
+        if (rowIdx < 0)
+            return null;
+        return Parent.Data.Rows[rowIdx];
+    }
+
+    private void DataGrid_CellDoubleTapped(object? sender, GridCellDoubleTappedEventArgs e)
+    {
+        StopTimer();
+        Parent.DoubleClickCell(GetRowFromIndex(e.RowColumnIndex.RowIndex), GetColumn(e.RowColumnIndex.ColumnIndex));
+    }
+
+    public double RowHeight
+    {
+        get => DataGrid.RowHeight;
+        set => DataGrid.RowHeight = value;
+    }
+
+    public double HeaderRowHeight
+    {
+        get => DataGrid.HeaderRowHeight;
+        set => DataGrid.HeaderRowHeight = value;
+    }
+    
+    private void DataGrid_QueryRowHeight(object? sender, QueryRowHeightEventArgs e)
+    {
+        if (e.RowIndex > 0)
+        {
+            e.Height = DataGrid.RowHeight;
+            if (DataGrid.GridColumnSizer.GetAutoRowHeight(e.RowIndex, gridRowResizingOptions, out var autoHeight))
+                if (autoHeight > DataGrid.RowHeight)
+                    e.Height = autoHeight;
+            e.Handled = true;
+        }
+    }
+
+    private void DataGrid_SizeChanged(object sender, SizeChangedEventArgs e)
+    {
+        if (Parent.IsReady && !Parent.IsRefreshing)
+            ResizeColumns(DataGrid, e.NewSize.Width - 2, e.NewSize.Height - 2);
+    }
+
+    public bool OptionsChanged()
+    {
+        ColumnsMenu.Visibility = Parent.HasOption(DynamicGridOption.SelectColumns) ? Visibility.Visible : Visibility.Hidden;
+
+        var allowEditing = Parent.IsDirectEditMode();
+        var reloadColumns = false;
+
+        if (DataGrid.AllowEditing != allowEditing)
+        {
+            DataGrid.NavigationMode = allowEditing ? NavigationMode.Cell : NavigationMode.Row;
+            DataGrid.AllowEditing = allowEditing;
+            reloadColumns = true;
+        }
+        DataGrid.AllowFiltering = Parent.HasOption(DynamicGridOption.FilterRows);
+        DataGrid.FilterRowPosition = Parent.HasOption(DynamicGridOption.FilterRows) ? FilterRowPosition.FixedTop : FilterRowPosition.None;
+
+        if (Parent.HasOption(DynamicGridOption.DragSource))
+        {
+            if (!DataGrid.AllowDraggingRows)
+            {
+                DataGrid.AllowDraggingRows = true;
+                DataGrid.RowDragDropController.DragStart += RowDragDropController_DragStart;
+            }
+        }
+        else
+        {
+            if (DataGrid.AllowDraggingRows)
+            {
+                DataGrid.AllowDraggingRows = false;
+                DataGrid.RowDragDropController.DragStart -= RowDragDropController_DragStart;
+            }
+        }
+        
+        DataGrid.AllowDrop = Parent.HasOption(DynamicGridOption.DragTarget);
+        
+        DataGrid.SelectionMode = Parent.HasOption(DynamicGridOption.MultiSelect) ? GridSelectionMode.Extended : GridSelectionMode.Single;
+
+        return reloadColumns && DataGrid.Columns.Count > 0;
+    }
+
+    private void DataGrid_FilterChanged(object? o, GridFilterEventArgs e)
+    {
+        var col = DataGrid.Columns.IndexOf(e.Column);
+        if (GetColumn(col) is DynamicActionColumn column)
+        {
+            if (e.FilterPredicates != null)
+            {
+                var filter = e.FilterPredicates.Select(x => x.FilterValue.ToString()!).ToArray();
+                bool include = e.FilterPredicates.Any(x => x.FilterType == FilterType.Equals);
+                column.SelectedFilters = include ? filter : (column.Filters ?? Enumerable.Empty<string>()).Except(filter).ToArray();
+            }
+            else
+                column.SelectedFilters = Array.Empty<string>();
+            DataGrid.ClearFilter(e.Column);
+            //e.FilterPredicates?.Clear();
+            //e.FilterPredicates?.Add(new FilterPredicate() { PredicateType = PredicateType.Or, FilterBehavior = Syncfusion.Data.FilterBehavior.StringTyped, FilterMode = ColumnFilter.DisplayText, FilterType = Syncfusion.Data.FilterType.NotEquals, FilterValue = "" });
+            //e.FilterPredicates?.Add(new FilterPredicate() { PredicateType = PredicateType.Or, FilterBehavior = Syncfusion.Data.FilterBehavior.StringTyped, FilterMode = ColumnFilter.DisplayText, FilterType = Syncfusion.Data.FilterType.Equals, FilterValue = "" });
+            Parent.Refresh(false, false);
+            e.Handled = true;
+        }
+
+        if (e.FilterPredicates == null)
+        {
+            if (_filterpredicates.ContainsKey(e.Column.MappingName))
+                _filterpredicates.Remove(e.Column.MappingName);
+        }
+        else
+        {
+            _filterpredicates[e.Column.MappingName] = Serialization.Serialize(e.FilterPredicates, true);
+        }
+
+        UpdateRecordCount();
+    }
+
+    private void UpdateRecordCount()
+    {
+        var count = DataGrid.View != null ? DataGrid.View.Records.Count : Parent.Data.Rows.Count;
+        Parent.UpdateRecordCount(count);
+    }
+
+    private void DataGrid_FilterItemsPopulating(object? sender, GridFilterItemsPopulatingEventArgs e)
+    {
+        var col = DataGrid.Columns.IndexOf(e.Column);
+        if (GetColumn(col) is DynamicActionColumn column && column.Filters is not null)
+            e.ItemsSource = column.Filters.Select(x => new FilterElement
+            { DisplayText = x, ActualValue = x, IsSelected = column.SelectedFilters is null || column.SelectedFilters.Contains(x) });
+    }
+
+    private void DataGrid_CellToolTipOpening(object? sender, GridCellToolTipOpeningEventArgs e)
+    {
+        if (GetColumn(e.RowColumnIndex.ColumnIndex) is not DynamicActionColumn col)
+            return;
+        var toolTip = col.ToolTip;
+        if (toolTip is null)
+            return;
+
+        var row = GetRowFromIndex(e.RowColumnIndex.RowIndex);
+
+        e.ToolTip.Template = TemplateGenerator.CreateControlTemplate(
+            typeof(ToolTip),
+            () => toolTip.Invoke(col, row)
+        );
+    }
+    public void AddVisualFilter(string column, string value, FilterType filtertype = FilterType.Contains)
+    {
+        if (string.IsNullOrWhiteSpace(value))
+            return;
+        var col = DataGrid.Columns.FirstOrDefault((x=>String.Equals(x.MappingName?.ToUpper(),column?.Replace(".", "_").ToUpper())));
+        if (col != null)
+        {
+            col.FilterPredicates.Add(new FilterPredicate { FilterType = filtertype, FilterValue = value });
+            col.FilteredFrom = FilteredFrom.FilterRow;
+
+        }
+    }
+    public List<Tuple<string, FilterPredicate>> GetFilterPredicates()
+    {
+        var list = new List<Tuple<string, FilterPredicate>>();
+        foreach (var column in DataGrid.Columns)
+        {
+            var colIndex = DataGrid.Columns.IndexOf(column);
+            var col = ColumnList[colIndex];
+            if (col is DynamicGridColumn gridColumn)
+            {
+                foreach (var predicate in column.FilterPredicates)
+                {
+                    list.Add(new(gridColumn.ColumnName, predicate));
+                }
+            }
+        }
+        return list;
+    }
+
+    public void ScrollIntoView(CoreRow row)
+    {
+        DataGrid.ScrollInView(new RowColumnIndex(row.Index + 1, 0));
+    }
+
+    protected virtual Brush? GetCellBackground(CoreRow row, String columnname) => null;
+    protected virtual Brush? GetCellForeground(CoreRow row, String columnname) => null;
+    protected virtual double? GetCellFontSize(CoreRow row, String columnname) => null;
+    protected virtual FontStyle? GetCellFontStyle(CoreRow row, String columnname) => null;
+    protected virtual FontWeight? GetCellFontWeight(CoreRow row, String columnname) => null;
+
+    #region Columns
+
+    private readonly List<DynamicColumnBase> ColumnList = new();
+
+    private List<DynamicActionColumn> ActionColumns = new();
+
+    private DynamicColumnBase? GetColumn(int index) =>
+        index >= 0 && index < ColumnList.Count ? ColumnList[index] : null;
+
+    public int DesiredWidth()
+    {
+        var result = 0;
+        for (var i = 0; i < ColumnList.Count; i++)
+        {
+            var col = ColumnList[i];
+            if (col is DynamicActionColumn)
+            {
+                result += (int)RowHeight;
+            }
+            else if (col is DynamicGridColumn)
+            {
+                var dgc = (DynamicGridColumn)col;
+                result += dgc.Width > 0 ? dgc.Width : 300;
+            }
+        }
+
+        return result;
+    }
+
+    public static Dictionary<int, double> CalculateColumnSizes(IList<DynamicColumnBase> columns, double width, double height, double rowHeight)
+    {
+        var fAvailWidth = width - (SystemParameters.VerticalScrollBarWidth);
+        
+        //if (Data.Rows.Count * (DataGrid.RowHeight + 1) + DataGrid.HeaderRowHeight > height + 0.5F)
+        //if (height < DataGrid.AutoScroller.VScrollBar.Maximum)
+        //    fAvailWidth -= (SystemParameters.VerticalScrollBarWidth + 0.75);
+
+
+        double fCurWidth = 0.0F;
+        var NumAutoCols = 0;
+
+
+        var colWidths = new Dictionary<int, double>();
+        for (var i = 0; i < columns.Count; i++)
+        {
+            var col = columns[i];
+            if (col is DynamicImageColumn dic)
+            {
+                colWidths[i] = rowHeight;
+                fCurWidth += colWidths[i];
+            }
+            else if (col is DynamicTextColumn dxc)
+            {
+                colWidths[i] = dxc.Width;
+                if (dxc.Width != 0)
+                    fCurWidth += Math.Max(0.0F, dxc.Width);
+                else
+                    NumAutoCols++;
+            }
+            else if (col is DynamicTemplateColumn dtc)
+            {
+                colWidths[i] = dtc.Width;
+                if (dtc.Width != 0)
+                    fCurWidth += Math.Max(0.0F, dtc.Width);
+                else
+                    NumAutoCols++;
+            }
+            else if (col is DynamicGridColumn dgc)
+            {
+                colWidths[i] = dgc.Width;
+                if (dgc.Width != 0)
+                    fCurWidth += Math.Max(0.0F, dgc.Width);
+                else
+                    NumAutoCols++;
+            }
+        }
+
+        if (NumAutoCols > 0)
+        {
+            var fAutoWidth = (fAvailWidth - fCurWidth) / NumAutoCols;
+            if (fAutoWidth < 100)
+                fAutoWidth = 100;
+            for (var i = 0; i < columns.Count; i++)
+                if (colWidths[i] == 0)
+                    colWidths[i] = fAutoWidth;
+        }
+
+        return colWidths;
+    }
+
+    private void ResizeColumns(SfDataGrid grid, double width, double height)
+    {
+        if (Parent.Data == null || width <= 0)
+            return;
+
+        grid.Dispatcher.BeginInvoke(() =>
+        {
+            foreach (var (index, size) in CalculateColumnSizes(ColumnList, width, height, RowHeight))
+                DataGrid.Columns[index].Width = Math.Max(0.0F, size);
+        });
+    }
+
+    private void LoadActionColumns(DynamicActionColumnPosition position)
+    {
+        for (var i = 0; i < ActionColumns.Count; i++)
+        {
+            var column = ActionColumns[i];
+            if (column.Position == position)
+            {
+                //String sColName = String.Format("ActionColumn{0}{1}", i, position == DynamicActionColumnPosition.Start ? "L" : "R");
+                var sColName = string.Format("ActionColumn{0}", i);
+                gridRowResizingOptions.ExcludeColumns.Add(sColName);
+
+                if (column is DynamicImageColumn imgcol)
+                {
+                    var newcol = new GridImageColumn();
+                    newcol.MappingName = sColName;
+                    //newcol.Stretch = Stretch.Uniform;
+                    newcol.Width = column.Width == 0 ? DataGrid.RowHeight : column.Width;
+                    newcol.Padding = new Thickness(4);
+                    newcol.ImageHeight = DataGrid.RowHeight - 8;
+                    newcol.ImageWidth = DataGrid.RowHeight - 8;
+                    newcol.ColumnSizer = GridLengthUnitType.None;
+                    newcol.HeaderText = column.HeaderText;
+                    newcol.AllowSorting = false;
+
+                    ApplyFilterStyle(newcol, true, true);
+
+                    newcol.ShowToolTip = column.ToolTip != null;
+                    newcol.ShowHeaderToolTip = column.ToolTip != null;
+
+                    var style = new Style();
+                    style.Setters.Add(new Setter(Control.BackgroundProperty, new SolidColorBrush(Colors.Gainsboro)));
+                    style.Setters.Add(new Setter(Control.IsEnabledProperty, false));
+                    newcol.FilterRowCellStyle = style;
+
+                    var headstyle = new Style(typeof(GridHeaderCellControl));
+                    headstyle.Setters.Add(new Setter(Control.BackgroundProperty, new SolidColorBrush(Colors.Gainsboro)));
+                    headstyle.Setters.Add(new Setter(Control.ForegroundProperty, new SolidColorBrush(Colors.Black)));
+                    headstyle.Setters.Add(new Setter(Control.FontSizeProperty, 12D));
+                    if (!string.IsNullOrWhiteSpace(column.HeaderText))
+                    {
+                        //headstyle.Setters.Add(new Setter(LayoutTransformProperty, new RotateTransform(270.0F)));
+                        headstyle.Setters.Add(new Setter(Control.BorderThicknessProperty, new Thickness(0.0, 0.0, 0, 0)));
+                        headstyle.Setters.Add(new Setter(Control.MarginProperty, new Thickness(0, 0, 0.75, 0.75)));
+                        if (imgcol.VerticalHeader)
+                            headstyle.Setters.Add(new Setter(Control.TemplateProperty,
+                                Application.Current.Resources["VerticalColumnHeader"] as ControlTemplate));
+                    }
+                    else
+                    {
+                        var image = imgcol.Image?.Invoke(null);
+                        if (image != null)
+                        {
+                            var template = new ControlTemplate(typeof(GridHeaderCellControl));
+                            var border = new FrameworkElementFactory(typeof(Border));
+                            border.SetValue(Border.BackgroundProperty, new SolidColorBrush(Colors.Gainsboro));
+                            border.SetValue(Border.PaddingProperty, new Thickness(4));
+                            border.SetValue(Control.MarginProperty, new Thickness(0, 0, 1, 1));
+                            var img = new FrameworkElementFactory(typeof(Image));
+                            img.SetValue(Image.SourceProperty, image);
+                            border.AppendChild(img);
+                            template.VisualTree = border;
+                            headstyle.Setters.Add(new Setter(Control.TemplateProperty, template));
+                        }
+                    }
+
+                    newcol.HeaderStyle = headstyle;
+
+                    DataGrid.Columns.Add(newcol);
+                    ColumnList.Add(column);
+                }
+                else if (column is DynamicTextColumn txtCol)
+                {
+
+                    var newcol = new GridTextColumn();
+                    gridRowResizingOptions.ExcludeColumns.Add(sColName);
+                    newcol.TextWrapping = TextWrapping.NoWrap;
+
+                    newcol.TextAlignment = txtCol.Alignment == Alignment.NotSet
+                        ? TextAlignment.Left
+                        : txtCol.Alignment == Alignment.BottomLeft || txtCol.Alignment == Alignment.MiddleLeft ||
+                          txtCol.Alignment == Alignment.TopLeft
+                            ? TextAlignment.Left
+                            : txtCol.Alignment == Alignment.BottomCenter || txtCol.Alignment == Alignment.MiddleCenter ||
+                              txtCol.Alignment == Alignment.TopCenter
+                                ? TextAlignment.Center
+                                : TextAlignment.Right;
+
+                    newcol.AllowEditing = false;
+                    newcol.UpdateTrigger = UpdateSourceTrigger.PropertyChanged;
+                    newcol.MappingName = sColName;
+                    newcol.Width = column.Width;
+                    newcol.ColumnSizer = GridLengthUnitType.None;
+                    newcol.HeaderText = column.HeaderText;
+                    newcol.AllowFiltering = column.Filters != null && column.Filters.Any();
+                    newcol.AllowSorting = false;
+                    newcol.FilterRowOptionsVisibility = Visibility.Collapsed;
+                    newcol.ShowHeaderToolTip = column.ToolTip != null;
+
+                    var style = new Style();
+                    style.Setters.Add(new Setter(Control.BackgroundProperty, new SolidColorBrush(Colors.Gainsboro)));
+                    style.Setters.Add(new Setter(Control.IsEnabledProperty, false));
+                    newcol.FilterRowCellStyle = style;
+
+                    var headstyle = new Style(typeof(GridHeaderCellControl));
+                    headstyle.Setters.Add(new Setter(Control.BackgroundProperty, new SolidColorBrush(Colors.Gainsboro)));
+                    headstyle.Setters.Add(new Setter(Control.ForegroundProperty, new SolidColorBrush(Colors.Black)));
+                    headstyle.Setters.Add(new Setter(Control.FontSizeProperty, 12D));
+                    headstyle.Setters.Add(new Setter(Control.MarginProperty, new Thickness(0, -0.75, 0, 0.75)));
+                    headstyle.Setters.Add(new Setter(Control.BorderThicknessProperty, new Thickness(0.75)));
+                    if (txtCol.VerticalHeader)
+                    {
+                        headstyle.Setters.Add(new Setter(Control.HorizontalContentAlignmentProperty, HorizontalAlignment.Left));
+                        headstyle.Setters.Add(new Setter(Control.TemplateProperty,
+                            Application.Current.Resources["VerticalColumnHeader"] as ControlTemplate));
+                    }
+
+                    newcol.HeaderStyle = headstyle;
+
+                    DataGrid.Columns.Add(newcol);
+                    ColumnList.Add(column);
+                }
+                else if (column is DynamicTemplateColumn tmplCol)
+                {
+                    var newcol = new GridTemplateColumn();
+                    newcol.CellTemplateSelector = new TemplateColumnSelector() { DataTemplate = tmplCol.Template };
+                    newcol.AllowEditing = false;
+                    newcol.UpdateTrigger = UpdateSourceTrigger.PropertyChanged;
+                    
+                    newcol.Width = tmplCol.Width;
+                    newcol.ColumnSizer = GridLengthUnitType.None;
+                    newcol.HeaderText = column.HeaderText;
+                    newcol.AllowFiltering = false;
+                    newcol.AllowSorting = false;
+                    newcol.FilterRowOptionsVisibility = Visibility.Collapsed;
+                    newcol.ShowToolTip = false;
+                    newcol.ShowHeaderToolTip = false;
+
+                    var style = new Style();
+                    style.Setters.Add(new Setter(Control.BackgroundProperty, new SolidColorBrush(Colors.Gainsboro)));
+                    style.Setters.Add(new Setter(Control.IsEnabledProperty, false));
+                    newcol.FilterRowCellStyle = style;
+
+                    var headstyle = new Style(typeof(GridHeaderCellControl));
+                    headstyle.Setters.Add(new Setter(Control.BackgroundProperty, new SolidColorBrush(Colors.Gainsboro)));
+                    headstyle.Setters.Add(new Setter(Control.ForegroundProperty, new SolidColorBrush(Colors.Black)));
+                    headstyle.Setters.Add(new Setter(Control.FontSizeProperty, 12D));
+                    headstyle.Setters.Add(new Setter(Control.MarginProperty, new Thickness(0, -0.75, 0, 0.75)));
+                    headstyle.Setters.Add(new Setter(Control.BorderThicknessProperty, new Thickness(0.75)));
+                    newcol.HeaderStyle = headstyle;
+
+                    DataGrid.Columns.Add(newcol);
+                    ColumnList.Add(column);
+                }
+            }
+        }
+    }
+
+    public class TemplateColumnSelector : DataTemplateSelector
+    {
+        public Func<FrameworkElement> DataTemplate { get; init; }
+        
+        public override DataTemplate SelectTemplate(object item, DependencyObject container) 
+            => TemplateGenerator.CreateDataTemplate(DataTemplate);
+    }
+
+    private void ApplyFilterStyle(GridColumn column, bool filtering, bool isactioncolumn)
+    {
+        var filterstyle = new Style();
+        if (filtering)
+        {
+            filterstyle.Setters.Add(new Setter(Control.BackgroundProperty, DynamicGrid<T>.FilterBackground));
+            column.ImmediateUpdateColumnFilter = true;
+            column.ColumnFilter = ColumnFilter.Value;
+            column.FilterRowCondition = FilterRowCondition.Contains;
+            column.FilterRowOptionsVisibility = Visibility.Collapsed;
+            column.AllowBlankFilters = true;
+            column.AllowSorting = isactioncolumn
+                ? false
+                : Parent.CanSort();
+        }
+        else
+        {
+            filterstyle.Setters.Add(new Setter(Control.BackgroundProperty, new SolidColorBrush(Colors.Gainsboro)));
+            filterstyle.Setters.Add(new Setter(Control.IsEnabledProperty, false));
+            column.ColumnFilter = ColumnFilter.Value;
+            column.AllowFiltering = false;
+            column.AllowSorting = false;
+            column.FilterRowEditorType = "TextBox";
+            column.FilterRowOptionsVisibility = Visibility.Collapsed;
+        }
+
+        column.FilterRowCellStyle = filterstyle;
+    }
+
+    public void RefreshColumns(DynamicGridColumns columns, DynamicActionColumns actionColumns)
+    {
+        // Yo, please don't remove this.
+        // The issue was when we were dynamically adding ActionColumns, and if we had to remove and then re-add them, we were getting massive performance hits
+        // for no reason. I think perhaps the image columns were trying to refer to data that didn't exist anymore when calling DataGrid.Columns.Refresh(),
+        // and thus some mega problems (perhaps even exceptions within Syncfusion) were occurring, and this seems to fix it.
+        // I don't pretend to know why it works; this is probably the strangest problem I've ever come across.
+        DataGrid.ItemsSource = null;
+
+        DataGrid.Columns.Suspend();
+
+        ColumnList.Clear();
+        DataGrid.Columns.Clear();
+        DataGrid.TableSummaryRows.Clear();
+        var Summaries = new ObservableCollection<ISummaryColumn>();
+
+        gridRowResizingOptions.ExcludeColumns = new List<string>();
+
+        ActionColumns = actionColumns.ToList();
+
+        LoadActionColumns(DynamicActionColumnPosition.Start);
+
+        foreach (var column in columns)
+        {
+            IProperty? prop;
+            try
+            {
+                prop = DatabaseSchema.Property(typeof(T), column.ColumnName);
+            }
+            catch (Exception e)
+            {
+                Logger.Send(LogType.Error, ClientFactory.UserID,
+                    string.Format("Error constructing Column [{0}] : {1}\n{2}", column.ColumnName, e.Message, e.StackTrace));
+                prop = null;
+            }
+
+            if (prop != null)
+            {
+
+                IDynamicGridEditorColumn? newcol = null;
+                if (prop.Editor is IntegerEditor)
+                    newcol = new DynamicGridIntegerColumn<T>(column);
+                else if (prop.Editor is CurrencyEditor)
+                    newcol = new DynamicGridCurrencyColumn<T>(column);
+                else if (prop.Editor is DoubleEditor)
+                    newcol = new DynamicGridDoubleColumn<T>(column);
+                else if (prop.Editor is DateTimeEditor)
+                    newcol = new DynamicGridDateTimeColumn<T>(column);
+                else if (prop.Editor is DateEditor)
+                    newcol = new DynamicGridDateColumn<T>(column);
+                else if (prop.Editor is TimeOfDayEditor)
+                    newcol = new DynamicGridTimeOfDayColumn<T>(column);
+                else if (prop.Editor is TimestampEditor)
+                    newcol = new DynamicGridTimeStampColumn<T>(column);
+                else if (prop.Editor is DurationEditor)
+                    newcol = new DynamicGridDurationColumn<T>(column);
+                else if (prop.Editor is CheckBoxEditor)
+                    newcol = new DynamicGridCheckBoxColumn<T>(column);
+                else if (prop.Editor is ColorEditor)
+                    newcol = new DynamicGridColorColumn<T>(column, column.Width, (int)DataGrid.RowHeight);
+                else if (prop.Editor is PopupEditor)
+                    newcol = new DynamicGridPopupColumn<T>(column);
+                else if (prop.Editor is CodePopupEditor)
+                    newcol = new DynamicGridCodePopupColumn<T>(column);
+                else if (prop.Editor is EnumLookupEditor)
+                    newcol = new DynamicGridEnumLookupColumn<T>(column);
+                else if (prop.Editor is ComboLookupEditor)
+                    newcol = new DynamicGridComboLookupColumn<T>(column);
+                else if (prop.Editor is LookupEditor)
+                    newcol = new DynamicGridLookupColumn<T>(column);
+                else if (prop.Editor is MemoEditor)
+                    newcol = new DynamicGridMemoColumn<T>(column);
+                else if (prop.Editor is CodeEditor)
+                    newcol = new DynamicGridCodeColumn<T>(column);                    
+                else if (prop.Editor is UniqueCodeEditor)
+                    newcol = new DynamicGridUniqueCodeColumn<T>(column);
+                else if (prop.Editor is TextBoxEditor)
+                    newcol = new DynamicGridTextBoxColumn<T>(column);
+
+                if (newcol != null)
+                {
+                    newcol.GetEntity = () => _editingObject.Object;
+                    newcol.EntityChanged += DoEntityChanged;
+                    if (!newcol.VariableHeight)
+                        gridRowResizingOptions.ExcludeColumns.Add(newcol.MappingName);
+
+                    newcol.Column.AllowEditing = newcol.Editable && Parent.IsDirectEditMode();
+
+                    var summary = newcol.Summary();
+                    if (summary != null)
+                        Summaries.Add(summary);
+
+                    
+                    ApplyFilterStyle(newcol.Column, newcol.Filtered, false);
+                    
+                    var headstyle = new Style(typeof(GridHeaderCellControl));
+                    headstyle.Setters.Add(new Setter(Control.BackgroundProperty, new SolidColorBrush(Colors.Gainsboro)));
+                    headstyle.Setters.Add(new Setter(Control.ForegroundProperty, new SolidColorBrush(Colors.Black)));
+                    headstyle.Setters.Add(new Setter(Control.FontSizeProperty, 12D));
+                    newcol.Column.HeaderStyle = headstyle;
+
+                    var cellstyle = new Style();
+                    if (Parent.IsDirectEditMode())
+                    {
+                        if (prop.Editor is null || !prop.Editor.Editable.IsDirectEditable())
+                        {
+                            cellstyle.Setters.Add(new Setter(Control.BackgroundProperty,
+                                new SolidColorBrush(Colors.WhiteSmoke)));
+                            newcol.Column.AllowEditing = false;
+                        }
+                        else
+                        {
+                            cellstyle.Setters.Add(new Setter(Control.BackgroundProperty,
+                                new SolidColorBrush(Colors.LightYellow)));
+                            newcol.Column.AllowEditing = true;
+                        }
+
+                        cellstyle.Setters.Add(new Setter(Control.ForegroundProperty, new SolidColorBrush(Colors.Black)));
+                        newcol.Column.CellStyle = cellstyle;
+                    }
+                    else
+                    {
+                        cellstyle.Setters.Add(new Setter(Control.BackgroundProperty,
+                            new Binding()
+                            {
+                                Path = new PropertyPath("."), Converter = CellBackgroundConverter,
+                                ConverterParameter = column.ColumnName
+                            }));
+                        cellstyle.Setters.Add(new Setter(Control.ForegroundProperty,
+                            new Binding()
+                                { Converter = CellForegroundConverter, ConverterParameter = column.ColumnName }));
+                        cellstyle.Setters.Add(new Setter(Control.FontSizeProperty,
+                            new Binding()
+                                { Converter = CellFontSizeConverter, ConverterParameter = column.ColumnName }));
+                        cellstyle.Setters.Add(new Setter(Control.FontStyleProperty,
+                            new Binding()
+                                { Converter = CellFontStyleConverter, ConverterParameter = column.ColumnName }));
+                        cellstyle.Setters.Add(new Setter(Control.FontWeightProperty,
+                            new Binding()
+                                { Converter = CellFontWeightConverter, ConverterParameter = column.ColumnName }));
+                        newcol.Column.CellStyle = cellstyle;
+                    }
+                    
+                    DataGrid.Columns.Add(newcol.Column);
+                    ColumnList.Add(column);
+                    foreach (var extra in newcol.ExtraColumns)
+                        Parent.AddHiddenColumn(extra);
+                    
+                }
+                
+            }
+        }
+
+        LoadActionColumns(DynamicActionColumnPosition.End);
+
+        if (Summaries.Any())
+        {
+            DataGrid.CellRenderers.Remove("TableSummary");
+            DataGrid.CellRenderers.Add("TableSummary", new DynamicGridAggregateRenderer());
+
+            DataGrid.TableSummaryRows.Add(new GridTableSummaryRow
+            {
+                ShowSummaryInRow = false,
+                Position = TableSummaryRowPosition.Bottom,
+                SummaryColumns = Summaries
+            });
+        }
+
+        DataGrid.Columns.Resume();
+        DataGrid.RefreshColumns();
+
+        foreach (var key in _filterpredicates.Keys.ToArray())
+            if (DataGrid.Columns.Any(x => string.Equals(x.MappingName, key)))
+            {
+                var predicates = Serialization.Deserialize<List<FilterPredicate>>(_filterpredicates[key]);
+                foreach (var predicate in predicates)
+                {
+                    DataGrid.Columns[key].FilterPredicates.Add(predicate);
+                    DataGrid.Columns[key].FilteredFrom = FilteredFrom.FilterRow;
+                }
+            }
+            else
+            {
+                _filterpredicates.Remove(key);
+            }
+
+        ResizeColumns(DataGrid, DataGrid.ActualWidth - 2, DataGrid.ActualHeight - 2);
+    }
+
+    #endregion
+
+    #region Data
+
+    public void BeforeRefresh()
+    {
+        DataGrid.SelectionForegroundBrush = DynamicGridUtils.SelectionForeground;
+        DataGrid.RowSelectionBrush = DynamicGridUtils.SelectionBackground;
+    }
+
+    public void RefreshData(CoreTable data)
+    {
+        var defaults = new List<object?>();
+        var result = new DataTable();
+
+        foreach (var column in data.Columns)
+        {
+            var colname = column.ColumnName.Replace('.', '_');
+            if (!result.Columns.Contains(colname))
+            {
+                result.Columns.Add(colname, column.DataType);
+                if (!Parent.IsDirectEditMode())
+                    defaults.Add(column.DataType.GetDefault());
+            }
+        }
+
+        for (var i = 0; i < ActionColumns.Count; i++)
+            result.Columns.Add(string.Format("ActionColumn{0}", i),
+                ActionColumns[i] is DynamicImageColumn
+                    ? typeof(BitmapImage)
+                    : typeof(String)
+            );
+
+        foreach (var row in data.Rows)
+        {
+            var newrow = result.NewRow();
+            CoreRowToDataRow(newrow, row, defaults);
+            result.Rows.Add(newrow);
+        }
+
+        //int rowIndex = DataGrid.SelectionController.CurrentCellManager.CurrentRowColumnIndex.RowIndex;
+        //int columnIndex = DataGrid.SelectionController.CurrentCellManager.CurrentRowColumnIndex.ColumnIndex;
+        //int scrollRowIndex = DataGrid.GetVisualContainer().ScrollRows.LastBodyVisibleLineIndex;
+        DataGrid.ItemsSource = result;
+        //this.DataGrid.ScrollInView(new Syncfusion.UI.Xaml.ScrollAxis.RowColumnIndex(scrollRowIndex, columnIndex));
+        ResizeColumns(DataGrid, DataGrid.ActualWidth - 1, DataGrid.ActualHeight);
+
+        UpdateRecordCount();
+    }
+
+    public void InvalidateRow(CoreRow row)
+    {
+        var table = DataGridItems;
+        if(table is null)
+        {
+            return;
+        }
+
+        var rowdata = new List<object?>(row.Values);
+        foreach (var ac in ActionColumns)
+            rowdata.Add(ac.Data(row));
+
+        var datarow = DataGridItems.Rows[row.Index];
+        for (var i = 0; i < rowdata.Count; i++)
+            datarow[i] = rowdata[i] ?? DBNull.Value;
+        //datarow.ItemArray = rowdata.ToArray(); 
+    }
+
+    private void CoreRowToDataRow(DataRow newrow, CoreRow row, List<object?> defaults)
+    {
+        var rowdata = new List<object?>(row.Values);
+        foreach (var ac in ActionColumns)
+            rowdata.Add(ac.Data(row));
+
+        try
+        {
+            var data = ProcessRow(rowdata, defaults).ToArray();
+            newrow.ItemArray = data;
+        }
+        catch (Exception)
+        {
+            throw;
+        }
+    }
+
+    private static IEnumerable<object?> ProcessRow(List<object?> values, List<object?> defaults)
+    {
+        if (defaults == null || !defaults.Any())
+            return values;
+        var result = new List<object?>();
+        for (var i = 0; i < values.Count; i++)
+        {
+            var value = values[i];
+            var defaultvalue = i < defaults.Count ? defaults[i] : null;
+            result.Add(value == null || (value.Equals(defaultvalue) && !value.GetType().IsEnum) ? null : value);
+        }
+
+        return result;
+    }
+
+    #endregion
+
+    #region Direct Edit
+
+    private bool bChanged;
+
+    private class DirectEditingObject
+    {
+        public T Object { get; set; }
+
+        public CoreRow Row { get; set; }
+
+        public DataRow? DataRow { get; set; }
+
+        public DirectEditingObject(T obj, CoreRow row, DataRow? dataRow)
+        {
+            Object = obj;
+            Row = row;
+            DataRow = dataRow;
+        }
+    }
+
+    private DirectEditingObject? _editingObject;
+
+    private DirectEditingObject EnsureEditingObject(CoreRow row)
+    {
+        _editingObject ??= new(Parent.LoadItem(row), row, DataGridItems?.Rows[row.Index]);
+        return _editingObject;
+    }
+
+    private DataRow? GetDataRow(CoreRow row)
+    {
+        return DataGridItems?.Rows[row.Index];
+    }
+
+    void IDynamicGridUIComponent<T>.UpdateCell(CoreRow row, string column, object? value)
+    {
+        var dataRow = GetDataRow(row);
+
+        var datacolname = column.Replace(".", "_");
+        if(dataRow is not null)
+        {
+            dataRow[datacolname] = value ?? DBNull.Value;
+        }
+    }
+
+    void IDynamicGridUIComponent<T>.UpdateRow(CoreRow row)
+    {
+        var dataRow = GetDataRow(row);
+        if(dataRow is not null)
+        {
+            for (var i = 0; i < ActionColumns.Count; i++)
+                dataRow[string.Format("ActionColumn{0}", i)] = ActionColumns[i].Data(row);
+        }
+    }
+
+    private void DoEntityChanged(IDynamicColumnBase column, DynamicColumnEntityChangedEventArgs args)
+    {
+        if (_editingObject is null) return;
+
+        Parent.EntityChanged(_editingObject.Object, _editingObject.Row, args.ColumnName, args.Changes);
+    }
+
+    private void UpdateData(string column, Dictionary<CoreColumn, object?> updates)
+    {
+        if (_editingObject is null)
+            return;
+
+        var coreRow = _editingObject.Row;
+
+        Parent.UpdateData(_editingObject.Object, coreRow, column, updates);
+    }
+
+    private void UpdateData(int rowIndex, int columnIndex)
+    {
+        var table = DataGridItems;
+        if (table is null)
+            return;
+        
+        if (GetColumn(columnIndex) is DynamicGridColumn gridcol)
+        {
+            var datacol = Parent.Data.Columns.FirstOrDefault(x => x.ColumnName.Equals(gridcol.ColumnName));
+            if (datacol != null)
+            {
+                var datacolindex = Parent.Data.Columns.IndexOf(datacol);
+
+                var value = table.Rows[rowIndex][datacolindex];
+                if (value is DBNull)
+                    value = CoreUtils.GetDefault(datacol.DataType);
+
+                UpdateData(datacol.ColumnName, new Dictionary<CoreColumn, object?>() { { datacol, value } });
+            }
+        }
+    }
+
+    private void DataGrid_CurrentCellBeginEdit(object? sender, CurrentCellBeginEditEventArgs e)
+    {
+        var table = DataGridItems;
+        var row = GetRowFromIndex(e.RowColumnIndex.RowIndex);
+        if (table is null || row is null)
+            return;
+
+        EnsureEditingObject(row);
+
+        var column = DataGrid.Columns[e.RowColumnIndex.ColumnIndex] as GridComboBoxColumn;
+        if (column != null && column.ItemsSource == null)
+        {
+            var colname = column.MappingName;
+            var colno = table.Columns.IndexOf(colname);
+            var property = Parent.Data.Columns[colno].ColumnName;
+            var prop = CoreUtils.GetProperty(typeof(T), property);
+            var editor = prop.GetEditor();
+            if (editor is ILookupEditor lookupEditor)
+            {
+                if (!Lookups.ContainsKey(property))
+                    Lookups[property] = lookupEditor.Values(typeof(T), property);
+                var combo = column;
+                combo.ItemsSource = Lookups[property].ToDictionary(Lookups[property].Columns[0].ColumnName, "Display");
+                combo.SelectedValuePath = "Key";
+                combo.DisplayMemberPath = "Value";
+            }
+        }
+
+        bChanged = false;
+    }
+
+    private void DataGrid_CurrentCellValueChanged(object? sender, CurrentCellValueChangedEventArgs e)
+    {
+        var row = GetRowFromIndex(e.RowColumnIndex.RowIndex);
+        // Are we sure that this function is ever useful? It seems that since the data in the grid hasn't been updated by this point, this function is essentially useless (the data is updated in EndEdit). Probably need to check the GridCheckBoxColumn
+
+        if (row is null) return;
+
+        if (e.Column is GridCheckBoxColumn)
+        {
+            EnsureEditingObject(row);
+        }
+
+        if (_editingObject is not null)
+            UpdateData(_editingObject.Row.Index, e.RowColumnIndex.ColumnIndex);
+        if (e.Column is GridCheckBoxColumn)
+            _editingObject = null;
+        if (_editingObject is not null)
+            bChanged = true;
+    }
+
+    private void DataGrid_CurrentCellDropDownSelectionChanged(object? sender,
+        CurrentCellDropDownSelectionChangedEventArgs e)
+    {
+        var row = GetRowFromIndex(e.RowColumnIndex.RowIndex);
+        if (row is null)
+            return;
+        EnsureEditingObject(row);
+        if ((_editingObject is not null) && (e.SelectedItem is Tuple<object?, string> tuple))
+        {
+            var mappedname = DataGrid.Columns[e.RowColumnIndex.ColumnIndex].MappingName;
+
+            var colno = DataGridItems.Columns.IndexOf(mappedname);
+            var corecol = Parent.Data.Columns[colno].ColumnName;
+
+            var updates = new Dictionary<CoreColumn, object?>();
+
+            var prefix = String.Join(".", corecol.Split(".").Reverse().Skip(1).Reverse());
+            var field = corecol.Split(".").Last();
+
+            var prop = CoreUtils.GetProperty(typeof(T), corecol);
+            if (prop.GetEditor() is ILookupEditor editor)
+            {
+                var data = editor.Values(typeof(T), corecol);
+                var lookuprow = data.Rows.FirstOrDefault(r => Equals(r[field], tuple.Item1))
+                    ?? data.NewRow(true);
+
+                foreach (CoreColumn lookupcol in data.Columns)
+                {
+                    var columnname = String.IsNullOrWhiteSpace(prefix)
+                        ? lookupcol.ColumnName
+                        : String.Join(".", prefix, lookupcol.ColumnName);
+                    var updatecol = Parent.Data.Columns.FirstOrDefault(x => String.Equals(x.ColumnName, columnname));
+                    if (updatecol != null)
+                        updates[updatecol] = lookuprow[lookupcol.ColumnName];
+                }
+                UpdateData(corecol, updates);
+                bChanged = true;
+                
+            }
+        }
+    }
+
+    private void DataGrid_CurrentCellEndEdit(object? sender, CurrentCellEndEditEventArgs e)
+    {
+        if (_editingObject is not null && bChanged)
+        {
+            UpdateData(_editingObject.Row.Index, e.RowColumnIndex.ColumnIndex);
+        }
+        if (bChanged)
+            Parent.DoChanged();
+        bChanged = false;
+        _editingObject = null;
+
+        // Commented out on 19/02/2024 by Kenric. I don't see this being necessary, though I could be wrong. Nevertheless, it was causing a bug when
+        // editing the filter row. It seems that this causes Syncfusion to commit the filter predicates internally, which means that after leaving a 
+        // filter row cell, the filter remained even once it was cleared, meaning a refresh was necessary to get the data back.
+        // I've tested on Bills to see if editing works with this empty, and it seems so.
+        //DataGridItems?.AcceptChanges();
+    }
+
+    private void DataGrid_PreviewKeyUp(object sender, KeyEventArgs e)
+    {
+        if (e.Key == Key.OemPeriod)
+        {
+            var editor = e.OriginalSource as TimeSpanEdit;
+            if (editor != null && editor.SelectionStart < 2) editor.SelectionStart = 3;
+        }
+        else if (e.Key == Key.Tab)
+        {
+            if (Parent.IsDirectEditMode())
+            {
+                DataGrid.SelectionController.CurrentCellManager.EndEdit();
+                DataGrid.MoveFocus(new TraversalRequest(FocusNavigationDirection.Right));
+                DataGrid.SelectionController.CurrentCellManager.BeginEdit();
+                e.Handled = true;
+            }
+        }
+    }
+
+    #endregion
+
+    #region Drag + Drop
+
+    private void DataGrid_DragOver(object sender, DragEventArgs e)
+    {
+        Parent.DragOver(sender, e);
+    }
+
+    private void DataGrid_Drop(object sender, DragEventArgs e)
+    {
+        Parent.Drop(sender, e);
+    }
+
+    private void RowDragDropController_DragStart(object? sender, GridRowDragStartEventArgs e)
+    {
+        var rows = e.DraggingRecords.Select(record =>
+        {
+            var rowIndex = DataGrid.ResolveToRowIndex(record);
+            return GetRowFromIndex(rowIndex);
+        }).NotNull().ToArray();
+        Parent.DragStart(sender, rows);
+    }
+
+    #endregion
+}

+ 295 - 0
inabox.wpf/DynamicGrid/UIComponent/DynamicGridTreeUIComponent.cs

@@ -0,0 +1,295 @@
+using InABox.Core;
+using InABox.Wpf;
+using InABox.WPF;
+using Syncfusion.Data;
+using Syncfusion.UI.Xaml.TreeGrid;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+
+namespace InABox.DynamicGrid;
+
+public class DynamicGridTreeUIComponent<T> : IDynamicGridUIComponent<T>
+    where T : BaseObject, new()
+{
+    private IDynamicGridUIComponentParent<T> _parent;
+    public IDynamicGridUIComponentParent<T> Parent
+    {
+        get => _parent;
+        set
+        {
+            _parent = value;
+        }
+    }
+
+    private Column<T> IDColumn;
+    private Column<T> ParentColumn;
+    private Column<T> DescriptionColumn;
+
+    private ContextMenu _menu;
+    private SfTreeGrid _tree;
+    private readonly ContextMenu ColumnsMenu;
+
+    private Dictionary<Guid, CoreRow>? _rowMap;
+
+    public event OnContextMenuOpening OnContextMenuOpening;
+
+    FrameworkElement IDynamicGridUIComponent<T>.Control => _tree;
+
+    private bool _shownumbers = false;
+    public bool ShowNumbers
+    {
+        get => _shownumbers;
+        set
+        {
+            _shownumbers = value;
+            _tree.Columns[1].Width = value ? 50 : 0;
+        } 
+    }
+
+    public DynamicGridTreeUIComponent(Expression<Func<T, Guid>> idColumn, Expression<Func<T, Guid>> parentIDColumn, Expression<Func<T, string>> descriptionColumn)
+    {
+        IDColumn = new Column<T>(CoreUtils.GetFullPropertyName(idColumn, "."));
+        ParentColumn = new Column<T>(CoreUtils.GetFullPropertyName(parentIDColumn, "."));
+        DescriptionColumn = new Column<T>(CoreUtils.GetFullPropertyName(descriptionColumn, "."));
+
+        ColumnsMenu = new ContextMenu();
+        ColumnsMenu.Opened += ColumnsMenu_ContextMenuOpening;
+
+        _tree = new SfTreeGrid();
+        _tree.ChildPropertyName = "Children";
+        //_tree.ParentPropertyName = "Parent";
+        _tree.AutoGenerateColumns = false;
+        _tree.AutoExpandMode = AutoExpandMode.AllNodesExpanded;
+        //_tree.NodeCollapsing += (o, e) => { e.Cancel = true; };
+        _tree.HeaderRowHeight = 0D;
+        _tree.HeaderContextMenu = ColumnsMenu;
+        _tree.SelectionChanged += (o,e) =>
+        {
+            var row = GetRow(_tree.SelectedItem as CoreTreeNode);
+            if(row is not null)
+            {
+                Parent.SelectItems(new[] { row });
+            }
+            else
+            {
+                Parent.SelectItems(Array.Empty<CoreRow>());
+            }
+        }
+        _tree.AllowSelectionOnExpanderClick = false;
+
+        _menu = new ContextMenu();
+        var additem = new MenuItem() { Header = "Add Child Folder" };
+        additem.Click += (o, e) => { DoAddItem((_tree.SelectedItem as CoreTreeNode)!.ID, true); };
+        _menu.Items.Add(additem);
+
+        _tree.ContextMenuOpening += _tree_ContextMenuOpening;
+        _tree.ContextMenu = _menu;
+        _tree.Background = new SolidColorBrush(Colors.DimGray);
+        
+        var cellStyle = new Style(typeof(TreeGridRowControl));
+        cellStyle.Setters.Add(new Setter(Control.BackgroundProperty, new SolidColorBrush(Colors.White)));
+        _tree.RowStyle = cellStyle;            
+        
+        _tree.SelectionBackground = new SolidColorBrush(System.Windows.Media.Color.FromArgb(0xFF, 0x11, 0x9E, 0xD9));
+        
+        _tree.Columns.Add(new TreeGridTextColumn()
+            {
+                MappingName = "Description"
+            }
+        );
+        
+        _tree.Columns.Add(new TreeGridTextColumn()
+            {
+                MappingName = "Number",
+                Width = _shownumbers ? 50 : 0,
+                TextAlignment = TextAlignment.Right
+            }
+        );
+        
+        _tree.ColumnSizer = TreeColumnSizer.Star;
+        _tree.RowHeight = 30D;
+        _tree.SetValue(Grid.RowProperty, 0);
+    }
+
+    private CoreRow? GetRow(CoreTreeNode? node)
+    {
+        return node is not null ? _rowMap?.GetValueOrDefault(node.ID) : null;
+    }
+
+    private void ColumnsMenu_ContextMenuOpening(object sender, RoutedEventArgs e)
+    {
+        if (sender is not ContextMenu menu) return;
+        menu.Items.Clear();
+
+        Parent.LoadColumnsMenu(menu);
+    }
+
+    public bool OptionsChanged()
+    {
+        ColumnsMenu.Visibility = Parent.HasOption(DynamicTreeOption.SelectColumns) ? Visibility.Visible : Visibility.Hidden;
+    }
+
+    #region Context Menu
+
+    private void _tree_ContextMenuOpening(object sender, ContextMenuEventArgs e)
+    {
+        _menu.Items.Clear();
+        if (OnContextMenuOpening is not null)
+        {
+            OnContextMenuOpening.Invoke((_tree.SelectedItem as CoreTreeNode)!, _menu);
+            if(_menu.Items.Count == 0)
+            {
+                e.Handled = true;
+            }
+        }
+        else
+        {
+            if (Parent.HasOption(DynamicGridOption.AddRows))
+            {
+                _menu.AddItem("Add Item", null, (_tree.SelectedItem as CoreTreeNode)!.ID, (id) => DoAddItem(id, true));
+            }
+        }
+    }
+
+    #endregion
+
+    #region CRUD
+
+    protected T DoCreateItem(Guid parent)
+    {
+        var result = Parent.CreateItem();
+        CoreUtils.SetPropertyValue(result, ParentColumn.Property, parent);
+        return result;
+    }
+    
+    protected void DoAddItem(Guid id, bool edit)
+    {
+        try
+        {
+            var item = DoCreateItem(id);
+            if (edit)
+            {
+                if (Parent.EditItems(new[] { item }))
+                {
+                    Parent.Refresh(false, true);
+                }
+            }
+            else
+            {
+                Parent.SaveItem(item);
+                Parent.Refresh(false, true);
+            }
+        }
+        catch (Exception e)
+        {
+            MessageWindow.ShowError("An error occurred while adding an item", e);
+        }
+    }
+
+    #endregion
+
+    #region Refresh
+
+    public CoreTreeNodes Nodes { get; set; }
+
+    public void BeforeRefresh()
+    {
+        throw new NotImplementedException();
+    }
+
+    public void RefreshColumns(DynamicGridColumns columns, DynamicActionColumns actionColumns)
+    {
+        throw new NotImplementedException();
+    }
+
+    public void RefreshData(CoreTable data)
+    {
+        var nodes = new CoreTreeNodes();
+        foreach (var row in data.Rows)
+        {
+            var _id = row.Get<Guid>(IDColumn.Property);
+            var _parent = row.Get<Guid>(ParentColumn.Property);
+            var _description = row.Get<string>(DescriptionColumn.Property);
+            nodes.Add(_id, _parent, row).Description = _description;
+        }
+        Nodes = nodes;
+        _tree.ItemsSource = nodes.Nodes;
+        CalculateRowHeight();
+    }
+
+    #endregion
+
+    public CoreRow[] SelectedRows
+    {
+        get
+        {
+            return _tree.SelectedItems.OfType<CoreTreeNode>()
+                .Select(x => GetRow(x)).NotNull().ToArray();
+        }
+        set
+        {
+            _tree.SelectedItems.Clear();
+            foreach (var row in value)
+            {
+                _tree.SelectedItems.Add(Nodes.Find(row.Get<Guid>(IDColumn.Property)));
+            }
+        }
+    }
+    public double RowHeight
+    {
+        get => _tree.RowHeight;
+        set => _tree.RowHeight = value;
+    }
+    public double HeaderRowHeight
+    {
+        get => _tree.HeaderRowHeight;
+        set => _tree.HeaderRowHeight = value;
+    }
+
+
+    public int DesiredWidth()
+    {
+        throw new NotImplementedException();
+    }
+
+    public void AddVisualFilter(string column, string value, FilterType filtertype = FilterType.Contains)
+    {
+    }
+    public List<Tuple<string, FilterPredicate>> GetFilterPredicates()
+    {
+        return new List<Tuple<string, FilterPredicate>>();
+    }
+
+    public CoreRow[] GetVisibleRows()
+    {
+        throw new NotImplementedException();
+    }
+
+    public void InvalidateRow(CoreRow row)
+    {
+        throw new NotImplementedException();
+    }
+
+
+    public void ScrollIntoView(CoreRow row)
+    {
+        throw new NotImplementedException();
+    }
+
+    public void UpdateCell(CoreRow row, string column, object? value)
+    {
+        throw new NotImplementedException();
+    }
+
+    public void UpdateRow(CoreRow row)
+    {
+        throw new NotImplementedException();
+    }
+}

+ 78 - 0
inabox.wpf/DynamicGrid/UIComponent/IDynamicGridUIComponent.cs

@@ -0,0 +1,78 @@
+using InABox.Core;
+using Syncfusion.Data;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+
+namespace InABox.DynamicGrid;
+
+public interface IDynamicGridUIComponentParent<T> : IDynamicGrid<T>
+{
+    bool IsRefreshing { get; }
+
+    bool CanSort();
+
+    DynamicGridRowStyleSelector<T> RowStyleSelector { get; }
+
+    void BeforeSelection(CancelEventArgs cancel);
+
+    void SelectItems(CoreRow[] rows);
+
+    void HandleKey(KeyEventArgs args);
+
+    void LoadColumnsMenu(ContextMenu menu);
+
+    void DoubleClickCell(CoreRow? row, DynamicColumnBase? column);
+    void ExecuteActionColumn(DynamicActionColumn column, CoreRow[]? rows);
+    void OpenColumnMenu(DynamicColumnBase column);
+
+    void UpdateData(T obj, CoreRow row, string changedColumn, Dictionary<CoreColumn, object?> updates);
+    void EntityChanged(T obj, CoreRow row, string changedColumn, Dictionary<string, object?> changes);
+
+    void UpdateRecordCount(int count);
+    public bool IsDirectEditMode(IEnumerable<DynamicGridOption>? options = null);
+
+    void DragOver(object sender, DragEventArgs e);
+    void Drop(object sender, DragEventArgs e);
+    void DragStart(object? sender, CoreRow[] rows);
+}
+
+public interface IDynamicGridUIComponent<T>
+    where T : BaseObject, new()
+{
+    IDynamicGridUIComponentParent<T> Parent { set; }
+
+    FrameworkElement Control { get; }
+
+    CoreRow[] SelectedRows { get; set; }
+
+    double RowHeight { get; set; }
+    double HeaderRowHeight { get; set; }
+    int DesiredWidth();
+
+    /// <summary>
+    /// Do any required updates when the options list is changed.
+    /// </summary>
+    /// <returns>Whether the columns need to be reloaded.</returns>
+    bool OptionsChanged();
+
+    void UpdateRow(CoreRow row);
+    void UpdateCell(CoreRow row, string column, object? value);
+
+    void AddVisualFilter(string column, string value, FilterType filtertype = FilterType.Contains);
+    List<Tuple<string, FilterPredicate>> GetFilterPredicates();
+
+    void BeforeRefresh();
+    void RefreshColumns(DynamicGridColumns columns, DynamicActionColumns actionColumns);
+    void RefreshData(CoreTable data);
+    void InvalidateRow(CoreRow row);
+
+    void ScrollIntoView(CoreRow row);
+    CoreRow[] GetVisibleRows();
+}

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels