using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Forms.VisualStyles;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
using InABox.Clients;
using InABox.Core;
using InABox.WPF;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.Xaml.Behaviors;
using Syncfusion.Data;
using Syncfusion.DocIO.ReaderWriter.DataStreamParser.Escher;
using Syncfusion.UI.Xaml.Grid;
using Syncfusion.UI.Xaml.Grid.Cells;
using Syncfusion.UI.Xaml.Grid.Helpers;
using Syncfusion.Windows.Shared;
using Brush = System.Windows.Media.Brush;
using Color = System.Drawing.Color;
using Columns = InABox.Core.Columns;
using DataColumn = System.Data.DataColumn;
using DataRow = System.Data.DataRow;
using FilterElement = Syncfusion.UI.Xaml.Grid.FilterElement;
using FontStyle = System.Windows.FontStyle;
using GridCellToolTipOpeningEventArgs = Syncfusion.UI.Xaml.Grid.GridCellToolTipOpeningEventArgs;
using GridFilterEventArgs = Syncfusion.UI.Xaml.Grid.GridFilterEventArgs;
using GridSelectionMode = Syncfusion.UI.Xaml.Grid.GridSelectionMode;
using Image = System.Windows.Controls.Image;
using RowColumnIndex = Syncfusion.UI.Xaml.ScrollAxis.RowColumnIndex;
using SolidColorBrush = System.Windows.Media.SolidColorBrush;
using String = System.String;
using VerticalAlignment = System.Windows.VerticalAlignment;
using VirtualizingCellsControl = Syncfusion.UI.Xaml.Grid.VirtualizingCellsControl;

namespace InABox.DynamicGrid
{
    
    

    public class DynamicGridRowStyle : DynamicGridStyle<VirtualizingCellsControl>
    {
        public DynamicGridRowStyle() : base(null)
        {
        }

        public DynamicGridRowStyle(IDynamicGridStyle source) : base(source)
        {
        }

        public override DependencyProperty FontSizeProperty => Control.FontSizeProperty;

        public override DependencyProperty FontStyleProperty => Control.FontStyleProperty;

        public override DependencyProperty FontWeightProperty => Control.FontWeightProperty;

        public override DependencyProperty BackgroundProperty => Control.BackgroundProperty;

        public override DependencyProperty ForegroundProperty => Control.ForegroundProperty;
    }

    public class DynamicGridCellStyle : DynamicGridStyle<Control>
    {
        public DynamicGridCellStyle() : base(null)
        {
        }

        public DynamicGridCellStyle(IDynamicGridStyle source) : base(source)
        {
        }

        public override DependencyProperty FontSizeProperty => Control.FontSizeProperty;

        public override DependencyProperty FontStyleProperty => Control.FontStyleProperty;

        public override DependencyProperty FontWeightProperty => Control.FontWeightProperty;

        public override DependencyProperty BackgroundProperty => Control.BackgroundProperty;

        public override DependencyProperty ForegroundProperty => Control.ForegroundProperty;
    }

    public class GridSelectionControllerExt : GridSelectionController
    {
        public GridSelectionControllerExt(SfDataGrid datagrid)
            : base(datagrid)
        {
        }

        protected override void ProcessSelectedItemChanged(SelectionPropertyChangedHandlerArgs handle)
        {
            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));
            }
        }
    }

    public class DynamicGridSummaryStyleSelector : StyleSelector
    {
        private readonly IDynamicGrid _grid;

        public DynamicGridSummaryStyleSelector(IDynamicGrid grid)
        {
            _grid = 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;
        }
    }

    // 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 : IValueConverter
    {
        private static readonly BitmapImage tick = Wpf.Resources.Bullet_Tick.AsBitmapImage();

        public object? Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return value is bool boolean && boolean ? tick : null;
        }

        public object? ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return null;
        }
    }

    public class StringToColorImageConverter : IValueConverter
    {
        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;
        }

        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('#');

            if (!cache.ContainsKey(colorcode))
            {
                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 object? ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return null;
        }
    }

    public class StringArrayConverter : IValueConverter
    {
        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;
        }

        object IValueConverter.ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return value;
        }
    }

    [Serializable]
    class DynamicGridDragFormat
    {
        private string entity;

        public DataTable Table { get; set; }

        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()
    {
        private readonly Dictionary<string, string> _filterpredicates = new();

        private UIElement? _header;
        private readonly Button Add;

        private bool bChanged;

        public bool bRefreshing;
        private readonly Label ClipboardSpacer;

        private readonly ContextMenu ColumnsMenu;
        private readonly Button Copy;

        private readonly Label Count;

        private readonly Button Cut;
        private readonly SfDataGrid DataGrid;
        private readonly Border Disabler;

        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 GridRowSizingOptions gridRowResizingOptions = new() { CanIncludeHiddenColumns = false, AutoFitMode = AutoFitMode.SmartFit };
        private readonly Button Help;
        private readonly Button Import;


        private T? inplaceeditor;

        private readonly Grid Layout;
        private readonly Label Loading;

        private DoubleAnimation LoadingFader = new DoubleAnimation(1d, 0.2d, new Duration(TimeSpan.FromSeconds(2))) { AutoReverse = true };

        protected Dictionary<string, CoreTable> Lookups = new();
        //private readonly Button MultiEdit;
        private readonly Button Paste;

        private readonly Button Print;
        private readonly Label PrintSpacer;

        private readonly StackPanel LeftButtonStack;
        private readonly StackPanel RightButtonStack;

        private readonly DynamicRowMovementColumn? up;

        /// <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);

        #region Events

        public event SelectItemHandler? OnSelectItem;

        public event OnCellDoubleClick? OnCellDoubleClick;

        public event EventHandler? OnChanged;

        public delegate void BeforeSelectionEvent(CancelEventArgs cancel);
        public event BeforeSelectionEvent? OnBeforeSelection;

        public void DoChanged() => OnChanged?.Invoke(this, EventArgs.Empty);

        public event EditorValueChangedHandler? OnEditorValueChanged;

        public override event OnCustomiseEditor<T>? OnCustomiseEditor;

        public override event OnCustomiseColumns? OnCustomiseColumns;

        public override event OnFilterRecord? OnFilterRecord;

        public override event OnDoubleClick? OnDoubleClick;

        public override event EntitySaveEvent? OnBeforeSave;
        public override event EntitySaveEvent? OnAfterSave;

        public delegate void EditorLoaded(IDynamicEditorForm editor, T[] items);
        public event EditorLoaded OnEditorLoaded;

        #endregion

        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;

        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; }

        public DynamicGrid() : base()
        {
            IsReady = false;

            Data = new CoreTable();

            ColumnsMenu = new ContextMenu();
            var SelectColumns = new MenuItem { Header = "Select Columns" };
            SelectColumns.Click += SelectColumnsClick;
            ColumnsMenu.Items.Add(SelectColumns);
            LoadColumnsMenu(ColumnsMenu);

            MasterColumns = new DynamicGridColumns();
            MasterColumns.ExtractColumns(typeof(T));

            foreach (var column in LookupFactory.RequiredColumns<T>().ColumnNames())
            {
                AddHiddenColumn(column);
            }

            ActionColumns = new DynamicActionColumns();

            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);
            }

            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.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.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(" ", "_"));

            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);


            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);

            Layout.Children.Add(Disabler);
            
            //Scroll.ApplyTemplate();

            Content = Layout;

            IsEnabledChanged += (sender, args) =>
            {
                Disabler.Visibility = Equals(args.NewValue, true)
                    ? Visibility.Collapsed
                    : Visibility.Visible;
            };

            Settings = LoadSettings();
            
            Init();
            Reconfigure();
        }

        protected virtual void BeforeSelection(CancelEventArgs cancel)
        {
            OnBeforeSelection?.Invoke(cancel);
        }

        private void DataGrid_SelectionChanging(object? sender, Syncfusion.UI.Xaml.Grid.GridSelectionChangingEventArgs e)
        {
            var cancel = new CancelEventArgs();
            BeforeSelection(cancel);
            if (cancel.Cancel)
            {
                e.Cancel = true;
            }
        }

        public bool IsReady { get; private set; }

        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);
                }
            }
        }

        /// <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; }

        public DynamicActionColumns ActionColumns { get; }

        private bool IsSequenced => typeof(T).GetInterfaces().Any(x => x.Equals(typeof(ISequenceable)));

        public override double RowHeight
        {
            get => DataGrid.RowHeight;
            set => DataGrid.RowHeight = value;
        }

        public override double HeaderHeight
        {
            get => DataGrid.HeaderRowHeight;
            set => DataGrid.HeaderRowHeight = value;
        }

        protected override void OptionsChanged()
        {
            var reloadColumns = false;

            ColumnsMenu.Visibility = HasOption(DynamicGridOption.SelectColumns) ? Visibility.Visible : Visibility.Hidden;
            Help.Visibility = HasOption(DynamicGridOption.ShowHelp) ? Visibility.Visible : Visibility.Collapsed;

            Add.Visibility = HasOption(DynamicGridOption.AddRows) ? Visibility.Visible : Visibility.Collapsed;
            Edit.Visibility = HasOption(DynamicGridOption.EditRows) ? Visibility.Visible : Visibility.Collapsed;

            EditSpacer.Visibility = HasOption(DynamicGridOption.AddRows) || HasOption(DynamicGridOption.EditRows)
                ? Visibility.Visible
                : Visibility.Collapsed;

            Print.Visibility = HasOption(DynamicGridOption.Print) ? Visibility.Visible : Visibility.Collapsed;
            PrintSpacer.Visibility = HasOption(DynamicGridOption.Print) ? Visibility.Visible : Visibility.Collapsed;

            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;

            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;

            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;
            }

            Count.Visibility = HasOption(DynamicGridOption.RecordCount) ? Visibility.Visible : Visibility.Collapsed;

            Delete.Visibility = HasOption(DynamicGridOption.DeleteRows) ? Visibility.Visible : Visibility.Collapsed;
            DataGrid.AllowFiltering = HasOption(DynamicGridOption.FilterRows);
            DataGrid.FilterRowPosition = HasOption(DynamicGridOption.FilterRows) ? FilterRowPosition.FixedTop : FilterRowPosition.None;

            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);
            }
        }

        protected virtual DynamicGridSettings LoadSettings()
        {
            return new DynamicGridSettings();
        }
        protected virtual void SaveSettings(DynamicGridSettings settings)
        {
        }

        public bool IsDirectEditMode(IEnumerable<DynamicGridOption>? options = null)
        {
            return HasOption(DynamicGridOption.DirectEdit, options)
                && (Settings.ViewMode == DynamicGridSettings.DynamicGridViewMode.DirectEdit
                    || Settings.ViewMode == DynamicGridSettings.DynamicGridViewMode.Default);
        }

        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();
        }

        protected override DynamicGridRowStyleSelector<T> GetRowStyleSelector()
        {
            return new DynamicGridRowStyleSelector<T, DynamicGridRowStyle>();
        }

        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;

                    result = new DynamicGridRowStyle(style);

                    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;
                }

            return result;
        }

        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();
            // }
        }

        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)
        {
            //throw new NotImplementedException();
        }

        private void DataGrid_FilterChanged(object? o, GridFilterEventArgs e)
        {
            var col = DataGrid.Columns.IndexOf(e.Column);
            if (ColumnList[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 = "" });
                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 DataGrid_FilterItemsPopulating(object? sender, GridFilterItemsPopulatingEventArgs e)
        {
            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) });
        }

        private CoreRow? GetRowFromIndex(int rowIndex)
        {
            var row = rowIndex - (HasOption(DynamicGridOption.FilterRows) ? 2 : 1);
            if (row < 0 || DataGridItems is null)
                return null;
            row = DataGridItems.Rows.IndexOf((DataGrid.View.Records[row].Data as DataRowView)!.Row);
            if (row < 0)
                return null;
            return Data.Rows[row];
        }

        private void DataGrid_CellToolTipOpening(object? sender, GridCellToolTipOpeningEventArgs e)
        {
            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);
            if (row is null)
                return;
            e.ToolTip.Template = TemplateGenerator.CreateControlTemplate(
                typeof(ToolTip),
                () => toolTip.Invoke(col, row)
            );
        }

        //private void DataGrid_MouseMove(object sender, MouseEventArgs e)
        //{
        //    var visualcontainer = DataGrid.GetVisualContainer();
        //    var point = e.GetPosition(visualcontainer);
        //    var rowColumnIndex = visualcontainer.PointToCellRowColumnIndex(point);
        //    var recordIndex = DataGrid.ResolveToRecordIndex(rowColumnIndex.RowIndex);

        //    if (recordIndex < 0)
        //        return;
        //    if (!rowColumnIndex.IsEmpty)
        //    {
        //        if (DataGrid.View.TopLevelGroup != null)
        //        {
        //            // Get the current row record while grouping
        //            var record = DataGrid.View.TopLevelGroup.DisplayElements[recordIndex];
        //            if (record.GetType() == typeof(RecordEntry))
        //            {
        //                var data = (record as RecordEntry).Data as CoreRow;
        //            }
        //        }

        //        else
        //        {
        //            //For getting the record, need to resolve the corresponding record index from row index                      
        //            var record1 = DataGrid.View.Records[DataGrid.ResolveToRecordIndex(rowColumnIndex.RowIndex)].Data;
        //        }
        //        //Gets the column from ColumnsCollection by resolving the corresponding column index from  GridVisibleColumnIndex                       
        //        var gridColumn = DataGrid.Columns[DataGrid.ResolveToGridVisibleColumnIndex(rowColumnIndex.ColumnIndex)];
        //        if (gridColumn is GridImageColumn)
        //        {

        //        }
        //    }

        //}

        protected virtual void LoadColumnsMenu(ContextMenu menu)
        {
        }

        private void DataGrid_CurrentCellBeginEdit(object? sender, CurrentCellBeginEditEventArgs e)
        {
            var headerrows = HasOption(DynamicGridOption.FilterRows) ? 2 : 1;
            if (e.RowColumnIndex.RowIndex < headerrows || DataGridItems is null)
                return;

            inplaceeditor ??= LoadItem(Data.Rows[e.RowColumnIndex.RowIndex - headerrows]);

            var column = DataGrid.Columns[e.RowColumnIndex.ColumnIndex] as GridComboBoxColumn;
            if (column != null && column.ItemsSource == null)
            {
                var colname = column.MappingName;
                var colno = DataGridItems.Columns.IndexOf(colname);
                var property = Data.Columns[colno].ColumnName;
                var prop = CoreUtils.GetProperty(typeof(T), property);
                var editor = prop.GetEditor();
                if (editor is ILookupEditor)
                {
                    if (!Lookups.ContainsKey(property))
                        Lookups[property] = ((ILookupEditor)editor).Values(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)
        {
            // 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
            var headerrows = HasOption(DynamicGridOption.FilterRows) ? 2 : 1;
            if (e.RowColumnIndex.RowIndex < headerrows)
                return;
            if (e.Column is GridCheckBoxColumn)
                inplaceeditor = LoadItem(Data.Rows[e.RowColumnIndex.RowIndex - headerrows]);
            if (inplaceeditor is not null)
                UpdateData(DataGrid.Columns[e.RowColumnIndex.ColumnIndex].MappingName);
            if (e.Column is GridCheckBoxColumn)
                inplaceeditor = null;
            if (inplaceeditor is not null)
                bChanged = true;
        }

        private void DataGrid_CurrentCellDropDownSelectionChanged(object? sender,
            CurrentCellDropDownSelectionChangedEventArgs e)
        {
            var headerrows = HasOption(DynamicGridOption.FilterRows) ? 2 : 1;
            if (e.RowColumnIndex.RowIndex < headerrows)
                return;
            inplaceeditor ??= LoadItem(Data.Rows[e.RowColumnIndex.RowIndex - headerrows]);
            if ((inplaceeditor is not null) && (e.SelectedItem is Tuple<object?, string> tuple))
            {
                
                var iRow = SelectedRows.First().Index;
                
                var mappedname = DataGrid.Columns[e.RowColumnIndex.ColumnIndex].MappingName;

                var colno = DataGridItems.Columns.IndexOf(mappedname);
                var corecol = 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(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 = Data.Columns.FirstOrDefault(x => String.Equals(x.ColumnName, columnname));
                        if (updatecol != null)
                            updates[updatecol] = lookuprow[lookupcol.ColumnName];
                    }
                    UpdateData(updates);
                    bChanged = true;
                    
                }
            }
        }

        protected void UpdateCell(int row, 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;
            var corerow = Data.Rows[row];
            corerow[corecol] = value;

            var datarow = table.Rows[row];
            datarow[datacolname] = value ?? DBNull.Value;
        }

        private void DataGrid_CurrentCellEndEdit(object? sender, CurrentCellEndEditEventArgs e)
        {
            var headerrows = HasOption(DynamicGridOption.FilterRows) ? 2 : 1;
            if (e.RowColumnIndex.RowIndex < headerrows)
                return;
            if (inplaceeditor is not null && bChanged) 
                UpdateData(DataGrid.Columns[e.RowColumnIndex.ColumnIndex].MappingName);
            if (bChanged)
                DoChanged();
            bChanged = false;
            inplaceeditor = null;
            DataGridItems?.AcceptChanges();

        }

        private void UpdateRow(Dictionary<string, object?> changes)
        {
            if (!SelectedRows.Any())
                return;

            var iRow = SelectedRows.First().Index;
            var corerow = Data.Rows[iRow];
            var row = DataGridItems.Rows[iRow];
            
            foreach (var key in changes.Keys)
                UpdateCell(iRow, key, changes[key]);
            
            for (var i = 0; i < ActionColumns.Count; i++)
                row[$"ActionColumn{i}"] = ActionColumns[i].Data(corerow);
        }

        private void UpdateData(Dictionary<CoreColumn, object?> updates)
        {
            if (!SelectedRows.Any())
                return;

            var iRow = SelectedRows.First().Index;
            var corerow = Data.Rows[iRow];
            var row = DataGridItems.Rows[iRow];

            var changes = new Dictionary<string, object?>();
            foreach (var (col, value) in updates)
            {
                UpdateRow(corerow, col.ColumnName, value, refresh: false);
                DynamicGridUtils.UpdateEditorValue(new BaseObject[] { inplaceeditor }, col.ColumnName, value, changes);
            }
            
            SaveItem(inplaceeditor);
            
            foreach (var key in changes.Keys)
                UpdateCell(iRow, key, changes[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;
            // }

            for (var i = 0; i < ActionColumns.Count; i++)
                row[string.Format("ActionColumn{0}", i)] = ActionColumns[i].Data(corerow);
            
        }

        
        private void UpdateData(Dictionary<String, object?> changes)
        {
            if (!SelectedRows.Any())
                return;
            
            var iRow = SelectedRows.First().Index; //e.RowColumnIndex.RowIndex - (HasOption(DynamicGridOptions.FilterRows) ? 2 : 1);
            if (DataGridItems is null || iRow > DataGridItems.Rows.Count)
                return;

            var updates = new Dictionary<CoreColumn, object?>();
            foreach (var key in changes.Keys)
            {
                var colno = DataGridItems.Columns.IndexOf(key);
                var column = Data.Columns[colno];
                updates[column] = changes[key];
            }
            
            UpdateData(updates);
            
        }
        
        private void UpdateData(String mappedname)
        {
            if (!SelectedRows.Any())
                return;
            
            var iRow = SelectedRows.First().Index; //e.RowColumnIndex.RowIndex - (HasOption(DynamicGridOptions.FilterRows) ? 2 : 1);
            if (DataGridItems is null || iRow > DataGridItems.Rows.Count)
                return;

            var colno = DataGridItems.Columns.IndexOf(mappedname);
            var column = Data.Columns[colno];
            var value = DataGridItems.Rows[iRow][mappedname];
            if(value is DBNull)
            {
                value = CoreUtils.GetDefault(column.DataType);
            }
            
            UpdateData(new Dictionary<CoreColumn, object?>() { { column, 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 (IsReady && !bRefreshing) ResizeColumns(DataGrid, e.NewSize.Width - 2, e.NewSize.Height - 2);
        }




        #region Row Selections

        protected 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(Data.Rows[iRow]);
            }

            //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();
        }

        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
                {
                    var datarow = item as DataRowView;
                    if (datarow != null)
                    {
                        var row = datarow.Row.Table.Rows.IndexOf(datarow.Row);
                        result.Add(Data.Rows[row]);
                    }
                }

            return result.ToArray();
        }

        private void SetSelectedRows(CoreRow[] rows)
        {
            //Logger.Send(LogType.Information, ClientFactory.UserID, String.Format("{0}: SetSelectedRows({1})", this.GetType().EntityName(), rows.Length));
            //   CoreTableAdapter<T> adapter = (CoreTableAdapter<T>)DataGrid.ItemsSource;             
            DataGrid.SelectedItems.Clear();
            var bFirst = true;
            foreach (var row in rows.Where(x => x.Index > -1))
            {
                //DataTable table = (DataTable)DataGrid.ItemsSource;
                if (bFirst || HasOption(DynamicGridOption.MultiSelect))
                    DataGrid.SelectedItems.Add(DataGrid.GetRecordAtRowIndex(row.Index + (HasOption(DynamicGridOption.FilterRows) ? 2 : 1)));
                bFirst = false;
            }
        }

        public override CoreRow[] SelectedRows
        {
            get => GetSelectedRows();
            set => SetSelectedRows(value);
        }

        protected virtual void SelectItems(CoreRow[]? rows)
        {
            if (IsReady)
                OnSelectItem?.Invoke(this, new DynamicGridSelectionEventArgs(rows));

            DuplicateBtn.Visibility =
                typeof(T).IsAssignableTo(typeof(IDuplicatable)) && rows != null && rows.Length >= 1 ? Visibility.Visible : Visibility.Collapsed;
        }

        private bool bFilterVisible;
        private bool bSwallowKey;

        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;
            }
        }

        private void DataGrid_KeyUp(object sender, KeyEventArgs e)
        {
            if (!bFilterVisible && !bSwallowKey && DataGrid.SelectedIndex > -1)
                //Logger.Send(LogType.Information, "", String.Format("{0}: KeyUp -> {1}", this.GetType().EntityName(), SelectedRows.Length));          
                SelectItems(SelectedRows);
            bSwallowKey = false;

            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();
                }
            }
        }

        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;
                    SelectItems(SelectedRows);
                };
            }

            clicktimer.IsEnabled = true;
        }

        private void StopTimer()
        {
            if (clicktimer is not null)
                clicktimer.IsEnabled = false;
        }

        private void DataGrid_MouseRightButtonUp(object sender, MouseButtonEventArgs e)
        {
            if (!IsEnabled)
                return;

            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;
            var rowindex = rowcolumnindex.RowIndex - (HasOption(DynamicGridOption.FilterRows) ? 2 : 1);
            if (rowindex < 0 || rowindex >= Data.Rows.Count)
                return;
            var row = Data.Rows[rowindex];
            var menu = column?.ContextMenu?.Invoke(SelectedRows);
            if (menu != null && menu.Items.Count > 0)
                menu.IsOpen = true;
        }

        private void DataGrid_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            if (!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);

                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();
            }

            bSwallowKey = false;
        }


        private void DataGrid_CellTapped(object? sender, GridCellTappedEventArgs e)
        {
            if (!IsEnabled)
                return;

            var dac = ColumnList[e.RowColumnIndex.ColumnIndex] as DynamicActionColumn;
            if (dac != null)
            {
                var bRefresh = false;
                if(e.ChangedButton == MouseButton.Left)
                {
                    foreach (var row in SelectedRows)
                        if (dac.Action?.Invoke(row) == true)
                            bRefresh = true;
                }
                if (bRefresh)
                    Task.Run(() => { Dispatcher.Invoke(() => { Refresh(true, true); }); });
            }
            else
            {
                StartTimer();
            }
        }


        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();
        }

        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;
            }

            if (e.Record != null)
                DoDoubleClick(this);
        }

        #endregion

        #region Column Handling

        private readonly List<DynamicColumnBase> ColumnList = new();

        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)
        {

            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;
        }

        protected virtual void SaveColumns(DynamicGridColumns columns)
        {
        }

        public override 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;
        }

        private void ResizeColumns(SfDataGrid grid, double width, double height)
        {
            if (Data == null || width <= 0)
                return;

            Dispatcher.BeginInvoke(() =>
            {
                var fAvailWidth = width;

                //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 < ColumnList.Count; i++)
                {
                    var col = ColumnList[i];
                    if (col is DynamicActionColumn dac)
                    {
                        colWidths[i] = dac.Width == 0 ? RowHeight : dac.Width;
                        fCurWidth += colWidths[i];
                    }
                    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 < 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;

        }

        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;

                        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));
                            }
                        }

                        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 == 0 ? DataGrid.RowHeight : 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.ShowToolTip = 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));
                        }

                        newcol.HeaderStyle = headstyle;

                        DataGrid.Columns.Add(newcol);
                        ColumnList.Add(column);
                    }


                }
            }
        }

        private bool CanSort()
        {
            return !IsSequenced;
        }

        
        
        private void ReloadColumns()
        {

            ConfigureColumns(MasterColumns /*, false */);

            VisibleColumns = LoadColumns();

            ConfigureColumns(VisibleColumns /*, true */);

            DataGrid.Columns.Suspend();

            ColumnList.Clear();
            DataGrid.Columns.Clear();
            DataGrid.TableSummaryRows.Clear();
            var Summaries = new ObservableCollection<ISummaryColumn>();

            gridRowResizingOptions.ExcludeColumns = new List<string>();

            LoadActionColumns(DynamicActionColumnPosition.Start);

            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;
                }

                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 = () => inplaceeditor;
                        newcol.EntityChanged = (changes) =>
                        {
                            SaveItem(inplaceeditor);
                            UpdateRow(changes);
                            DataGrid.SelectionController.CurrentCellManager.EndEdit();
                        };
                            
                        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())
                        {
                            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);
                        
                    }
                    
                }
            }

            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);
        }

        

        private void ApplyFilterStyle(GridColumn column, bool filtering, bool isactioncolumn)
        {
            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;
        }

        #endregion

        #region Refresh / Reload

        protected abstract void Reload(Filters<T> criteria, Columns<T> columns, ref SortOrder<T>? sort, Action<CoreTable?, Exception?> action);

        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;
        }


        public override void Refresh(bool reloadcolumns, bool reloaddata)
        {

            if (bRefreshing)
                return;

            if (!DoBeforeRefresh())
                return;

            DataGrid.SelectionForegroundBrush = BaseDynamicGrid.SelectionForeground;
            DataGrid.RowSelectionBrush = BaseDynamicGrid.SelectionBackground;

            var cursor = UseWaitCursor ? new WaitCursor() : null;

            Loading.Visibility = Visibility.Visible;
            Loading.BeginAnimation(Label.OpacityProperty, LoadingFader);

            bRefreshing = true;

            // 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;

            if (reloadcolumns)
                ReloadColumns();

            if (reloaddata)
            {
                _lookupcache.Clear();

                var criteria = new Filters<T>();
                var filter = DefineFilter();
                if (filter != null)
                    criteria.Add(filter);

                var columns = DataColumns();

                var sort = LookupFactory.DefineSort<T>();
                if (sort == null && IsSequenced)
                    sort = new SortOrder<T>("Sequence");

                Reload(
                    criteria
                    , columns
                    , ref sort
                    , (table, exception) =>
                    {
                        if (exception != null)
                        {
                            Dispatcher.Invoke(() =>
                            {
                                MessageBox.Show(String.Format("Error: {0}", exception.Message));
                            });
                        }
                        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;

                Loading.BeginAnimation(Label.OpacityProperty, null);
                Loading.Visibility = Visibility.Collapsed;
            }

            if (cursor != null)
            {
                cursor.Dispose();
                cursor = null;
            }
            bRefreshing = false;
        }

        protected override bool OnBeforeRefresh()
        {
            return true;
        }

        private bool DoBeforeRefresh()
        {
            var result = OnBeforeRefresh();
            if (result)
            {
                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)
                columns.Add(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 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);
                }
        }

        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;
            }

            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 InvalidateGrid()
        {
            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 (!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);
            }

            if (RowStyleSelector != null)
                RowStyleSelector.Data = 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);

            UpdateRecordCount();

            Loading.BeginAnimation(Label.OpacityProperty, null);
            Loading.Visibility = Visibility.Collapsed;
        }

        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;
        }

        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]);
            }

            return result;
        }

        // 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();
        }*/

        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;
        }

        //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 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;

            }

        }

        #endregion

        #region Item Manipulation

        #region Load/Save/Delete

        protected 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));
            }

            return result.ToArray();
        }

        protected abstract T LoadItem(CoreRow row);

        public abstract void SaveItem(T item);

        public virtual void SaveItems(T[] items)
        {
            foreach (var item in items)
                SaveItem(item);
        }

        protected virtual bool CanDeleteItems(params CoreRow[] rows)
        {
            return true;
        }

        protected abstract void DeleteItems(params CoreRow[] rows);

        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

        #region Edit

        protected virtual void DoEdit()
        {
            if (!SelectedRows.Any())
                return;

            var sel = SelectedRows.ToArray();
            if (AddEditClick(SelectedRows))
            {
                InvalidateGrid();
                SelectedRows = sel;
                SelectItems(SelectedRows);
            }
        }

        private void Edit_Click(object sender, RoutedEventArgs e)
        {
            DoEdit();
        }

        /*private void MultiEdit_Click(object sender, RoutedEventArgs e)
        {
            using (new WaitCursor())
            {
                var criteria = new Filters<T>();
                var columns = new Columns<T>();
                columns.Add("ID");
                var iprops = DatabaseSchema.Properties(typeof(T)).Where(x => x.Editor is not NullEditor);
                foreach (var iprop in iprops)
                    columns.Add(iprop.Name);
                var sort = LookupFactory.DefineSort<T>();
                Reload(
                    criteria,
                    columns,
                    ref sort,
                    (table, exception) =>
                    {
                        if(table is not null)
                        {
                            Dispatcher.Invoke(() => { DirectEdit(table); });
                        }
                        else if(exception is not null)
                        {
                            Logger.Send(LogType.Error, "", $"Error in MultiEdit: {CoreUtils.FormatException(exception)}");
                            MessageBox.Show(exception.Message);
                        }
                    }
                );
            }
        }*/

        /*public override bool DirectEdit(CoreTable data)
        {
            var window = new DynamicEditWindow<T>();

            window.OnCreateItem += () => CreateItem();

            window.OnCustomiseColumns += (o, c) =>
            {
                ConfigureColumns(MasterColumns);
                if (OnCustomiseColumns != null)
                    return OnCustomiseColumns(this, MasterColumns);
                return MasterColumns;
            };

            window.OnGetEditor += c =>
            {
                var result = GetEditor(this, c)?.CloneEditor();
                if (result == null)
                    return null;
                OnCustomiseEditor?.Invoke(window, null, c, result);
                return result;
            };

            window.OnGetSequence += c =>
            {
                decimal result = 0.0M;
                var customprop = DatabaseSchema.Property(typeof(T), c.ColumnName);
                if (customprop != null && customprop is CustomProperty)
                {
                    result = customprop.Sequence;
                }
                else
                {
                    var bits = c.ColumnName.Split('.');
                    for (var i = 0; i < bits.Length; i++)
                    {
                        var sProp = string.Join(".", bits.Take(bits.Length - i));
                        PropertyInfo? prop;
                        try
                        {
                            prop = CoreUtils.GetProperty(typeof(T), sProp);
                        }
                        catch (Exception e)
                        {
                            Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
                            prop = null;
                        }

                        if (prop != null)
                        {
                            result = prop.GetSequence() + result / 1000.0M;
                        }
                        else
                        {
                            var cprop = DatabaseSchema.Property(typeof(T), sProp);
                            if (cprop != null)
                                result = cprop.Sequence;
                            else
                                result /= 1000.0M;
                        }
                    }
                }

                return result;
            };

            window.Load(data);

            if (window.ShowDialog() == true)
            {
                SaveItems(window.Updates);
                return true;
            }

            return false;
        }*/

        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;

                InvalidateGrid();
                SelectedRows = new[] { datarow };
                OnChanged?.Invoke(this, EventArgs.Empty);
            }
            else if (AddEditClick(null))
            {
                Refresh(false, true);
                OnChanged?.Invoke(this, EventArgs.Empty);
            }
        }

        private void Add_Click(object sender, RoutedEventArgs e)
        {
            if (CanCreateItems())
                DoAdd();
        }

        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 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 });
                }
            );
        }

        protected virtual void BeforeLoad(IDynamicEditorForm form, T[] items)
        {
            form.BeforeLoad();
        }

        public override void InitialiseEditorForm(IDynamicEditorForm editor, T[] items, Func<Type, CoreTable?>? pageDataHandler = null, bool preloadPages = false)
        {
            var pages = items.Length == 1 ? LoadEditorPages(items.First()) : new DynamicEditorPages();

            var buttons = new DynamicEditorButtons();
            if (items.Length == 1)
                LoadEditorButtons(items.First(), buttons);

            editor.Setup(items.Any() ? items.First().GetType() : typeof(T), pages, buttons, pageDataHandler, preloadPages);

            editor.OnCustomiseColumns = (sender, columns) =>
            {
                columns.Clear();
                columns.AddRange(MasterColumns);
                ConfigureColumns(columns);
            };

            editor.OnDefineEditor = (o, c) =>
            {
                var result = GetEditor(o, c);

                if (result != null)
                    result = result.CloneEditor();

                return result;
            };

            editor.OnFormCustomiseEditor += DoCustomiseEditor;

            editor.OnDefineFilter = (type) => { return DefineFilter(type, items); };

            //editor.OnDefineFilter += (o, e) => { return DefineFilter(items, e); };
            editor.OnDefineLookups = editor => DefineLookups(editor, items);

            editor.OnEditorValueChanged += (s, n, v) => EditorValueChanged(editor, items, n, v);

            editor.OnAfterEditorValueChanged += (g, args) => AfterEditorValueChanged(g, items, args);

            editor.OnReconfigureEditors = g => DoReconfigureEditors(g, items);

            editor.OnValidateData += (o, i) => ValidateData(items);

            editor.OnSelectPage += SelectPage;

            editor.OnSaveItem = (o, e) =>
            {
                try
                {
                    using var Wait = new WaitCursor();
                    DoBeforeSave(editor, items);


                    if (items.Length == 1)
                        editor.UnloadEditorPages(false);
                    foreach (var item in items)
                        SaveItem(item);
                    if (items.Length == 1)
                        editor.UnloadEditorPages(true);

                    DoAfterSave(editor, items);

                }
                catch (Exception err)
                {
                    MessageBox.Show(err.Message);
                    e.Cancel = true;
                }
            };

            BeforeLoad(editor, items);
            editor.Items = items;
            AfterLoad(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 CustomiseEditor(T[] items, DynamicGridColumn column, BaseEditor editor)
        {
        }

        protected virtual void DoAfterSave(IDynamicEditorForm editor, T[] items)
        {
            OnAfterSave?.Invoke(editor, items);
        }

        protected virtual void DoBeforeSave(IDynamicEditorForm editor, T[] items)
        {
            OnBeforeSave?.Invoke(editor, items);
        }


        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;
        }

        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;
        }

        protected virtual void DoValidate(T[] items, List<string> errors)
        {
        }



        protected virtual void AfterLoad(IDynamicEditorForm editor, T[] items)
        {
            editor.AfterLoad();
        }

        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];
            }

            return result;
        }



        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;

            var colname = sender.ColumnName;
            if (async)
            {
                Task.Run(() =>
                {
                    try
                    {
                        var values = editor.Values(colname, items);
                        Dispatcher.Invoke(
                            () =>
                            {
                                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);
                                }
                            }
                        );
                    }
                    catch (Exception e)
                    {
                        Logger.Send(LogType.Information, typeof(T).Name,
                            "Exception (1) in LoadLookups: " + e.Message + "\n" + e.StackTrace);
                    }
                });
            }
            else
            {
                var values = editor.Values(colname, items);
                sender.LoadLookups(values);
            }
        }

        /// <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 IFilter? DefineFilter(Type type, T[] items)
        {
            return LookupFactory.DefineFilter(items, type);
        }

        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 virtual object? GetEditorValue(object item, string name)
        {
            return CoreUtils.GetPropertyValue(item, name);
        }

        protected virtual bool CanCreateItems()
        {
            return true;
        }

        private bool AddEditClick(CoreRow[]? rows)
        {
            if (!IsEnabled || bRefreshing)
                return false;

            if (rows == null || !rows.Any())
            {

                if (!CanCreateItems())
                    return false;

                var item = CreateItem();

                // Yea, and this won't work, because we're actually usually showing the description of a linked item,
                // 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 :-(

                //foreach (String key in VisualFilters.Keys)
                //    CoreUtils.SetPropertyValue(item, key, VisualFilters[key]);

                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;
                }

                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))
                {
                    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;
                }

                return false;
            }

            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;
            }

            /*var ids = ExtractValues(x => x.ID, Selection.Selected).ToArray();
            if (!ids.Any())
            {
                MessageBox.Show("Please select at least one record to duplicate!");
                return false;
            }*/

            var duplicator = (new T() as IDuplicatable)?.GetDuplicator();
            if (duplicator is null)
            {
                MessageBox.Show($"Cannot duplicate {typeof(T)}");
                return false;
            }

            duplicator.Duplicate(LoadDuplicatorItems(rows));// new Filter<T>(x => x.ID).InList(ids));
            return true;
        }

        #endregion

        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 (!page.Ready)
                    page.Load(item, null);

                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())
                    );
                }

                var newforms = new List<TTargetForm>();
                foreach (var row in table.Rows)
                {
                    var sourceform = row.ToObject<TSourceForm>();
                    var targetform = new TTargetForm();

                    targetform.Form.ID = sourceform.Form.ID;
                    targetform.Form.Synchronise(sourceform.Form);
                    newforms.Add(targetform);
                }

                page.Items.Clear();
                page.LoadItems(newforms.ToArray());
            }
        }


        #region ClipBuffer

        private Tuple<ClipAction, CoreRow[]>? ClipBuffer;

        protected void ResetClipBuffer()
        {
            ClipBuffer = null;
        }

        protected void SetClipBuffer(ClipAction action, CoreRow[] rows)
        {
            ClipBuffer = new Tuple<ClipAction, CoreRow[]>(action, rows);
        }

        private void CutToClipBuffer()
        {
            SetClipBuffer(ClipAction.Cut, SelectedRows);
            InvalidateGrid();
        }


        private void CopyToClipBuffer()
        {
            SetClipBuffer(ClipAction.Copy, SelectedRows);
            InvalidateGrid();
        }

        private void PasteFromClipBuffer()
        {
            if (ClipBuffer == null)
                return;

            if (!IsSequenced)
                return;

            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();

                    var sequence = currow != null ? currow.Get<T, long>(c => ((ISequenceable)c).Sequence) : 0;

                    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;
                    }
                }

                if (updates.Any())
                {
                    SaveItems(updates.ToArray());
                    Refresh(false, true);
                }
            }
        }

        protected virtual bool BeforePaste(IEnumerable<T> items, ClipAction action)
        {
            return true;
        }
        private void Cut_Click(object sender, RoutedEventArgs e)
        {
            CutToClipBuffer();
        }

        private void Copy_Click(object sender, RoutedEventArgs e)
        {
            CopyToClipBuffer();
        }

        private void Paste_Click(object sender, RoutedEventArgs e)
        {
            PasteFromClipBuffer();
        }

        #endregion

        protected virtual void ObjectToRow(T obj, CoreRow row)
        {
            row.Table.LoadRow(row, obj);
        }

        #region Import / Export

        protected virtual CoreTable LoadImportKeys(String[] fields)
        {
            var result = new CoreTable();
            result.LoadColumns(new Columns<T>(fields));
            return result;
        }

        protected virtual Guid GetImportID()
        {
            return Guid.Empty;
        }

        protected virtual bool CustomiseImportItem(T item)
        {
            if (IsSequenced)
                ((ISequenceable)item).Sequence = CoreUtils.GenerateSequence();
            return true;
        }

        protected virtual string CustomiseImportFileName(string filename)
        {
            return filename;
        }

        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);
        }

        private void Import_Click(object sender, RoutedEventArgs e)
        {
            DoImport();
        }

        protected virtual void CustomiseExportColumns(List<string> columnnames)
        {
        }

        protected virtual string CustomiseExportFileName(string filename)
        {
            return filename;
        }

        protected virtual void CustomiseExportFilters(Filters<T> filters, CoreRow[] visiblerows)
        {
        }

        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,
            };
        }
        private 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;
        }

        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;

            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)
            {
                reloadColumns.Add(column);
            }
            foreach (var (column, _) in predicates)
            {
                reloadColumns.Add(column);
            }

            var sort = LookupFactory.DefineSort<T>();
            Reload(filters, reloadColumns, ref sort, (data, err) => Dispatcher.Invoke(() =>
            {
                if (data is not null)
                {
                    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)
                        {
                            if (!FilterByPredicate(row, column, predicate))
                            {
                                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();
        }

        /// <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 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);
        }

        #endregion

        public void ScrollIntoView(CoreRow row)
        {
            DataGrid.ScrollInView(new RowColumnIndex(row.Index + 1, 0));
        }

        #endregion

        #region Custom Buttons

        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;
        }

        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);

            if (image != null)
            {
                var img = new Image();
                img.Source = image;
                img.Margin = new Thickness(2);
                img.ToolTip = tooltip;
                stackPnl.Children.Add(img);
            }

            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;

            button.Content = stackPnl;
            button.ToolTip = tooltip;
        }

        private bool bFirstButtonAdded = true;

        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;
        }


        public override Button AddButton(string? caption, BitmapImage? image, string? tooltip, Func<Button, CoreRow[], bool> 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;
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            var button = (Button)sender;
            var action = (Func<Button, CoreRow[], bool>)button.Tag;

            //CoreRow row = (CurrentRow > -1) && (CurrentRow < Data.Rows.Count) ? Data.Rows[this.CurrentRow] : null;
            if (action.Invoke(button, SelectedRows))
                Refresh(false, true);
        }

        #endregion

        #region Header Actions

        private void SelectColumnsClick(object sender, RoutedEventArgs e)
        {
            var editor = new DynamicGridColumnsEditor(typeof(T));
            editor.DirectEdit = IsDirectEditMode();

            editor.Columns.AddRange(VisibleColumns);

            if (editor.ShowDialog().Equals(true))
            {
                VisibleColumns.Clear();
                VisibleColumns.AddRange(editor.Columns);
                SaveColumns(VisibleColumns);
                //OnSaveColumns?.Invoke(this, editor.Columns);
                Refresh(true, true);
            }
        }

        #endregion

        #region Drag + Drop

        private static string DragFormat => typeof(DynamicGridDragFormat).FullName ?? "";

        protected virtual void OnDragEnd(Type entity, CoreTable table, DragEventArgs e)
        {
            Logger.Send(LogType.Information,"","OnDragEnd");
        }

        private void DataGrid_DragOver(object sender, DragEventArgs e)
        {
            HandleDragOver(sender, e);
        }

        private void DataGrid_Drop(object sender, DragEventArgs e)
        {
            Logger.Send(LogType.Information,"","DataGrid_Drop");
            
            if (!HasOption(DynamicGridOption.DragTarget))
                return;
            
            Logger.Send(LogType.Information,"","DataGrid_Drop::DragTarget==true");
            
            if (e.Data.GetDataPresent(DragFormat))
            {
                Logger.Send(LogType.Information,"","DataGrid_Drop::DataPresent==true");
                var data = e.Data.GetData(DragFormat) as DynamicGridDragFormat;
                if (data is not null)
                { 
                    Logger.Send(LogType.Information,"","DataGrid_Drop::DragData==DynamicGridDragFormat");
                    var table = new CoreTable();
                    foreach (var column in data.Table.Columns)
                    {
                        if (column is DataColumn dataColumn)
                        {
                            table.Columns.Add(new CoreColumn { ColumnName = dataColumn.ColumnName.Replace('_', '.'), DataType = dataColumn.DataType });
                        }
                    }
                    foreach (var row in data.Table.Rows)
                    {
                        if (row is DataRow dataRow)
                        {
                            var coreRow = table.NewRow();
                            coreRow.LoadValues(dataRow.ItemArray);
                            table.Rows.Add(coreRow);
                        }
                    }

                    OnDragEnd(data.Entity, table, e);
                    DoChanged();
                }
            }
            else
            {
                HandleDragDrop(sender, e);
            }
        }

        protected virtual void HandleDragDrop(object sender, DragEventArgs e)
        {
        }

        protected virtual void HandleDragOver(object sender, DragEventArgs e)
        {
        }

        protected DragDropEffects DragTable(Type entity, CoreTable table)
        {
            Logger.Send(LogType.Information,"","DragTable");
            var data = new DataObject();
            data.SetData(DragFormat, new DynamicGridDragFormat(table.ToDataTable(), entity));
            var effect = DragDrop.DoDragDrop(this, data, DragDropEffects.All);
            return effect;
        }

        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);
        }

        private void RowDragDropController_DragStart(object? sender, GridRowDragStartEventArgs e)
        {
            Logger.Send(LogType.Information,"","RowDragDropController_DragStart");
            //e.Handled = true;
            
            if (!HasOption(DynamicGridOption.DragSource))
                return;
            
            var rows = new List<CoreRow>();
            foreach (var record in e.DraggingRecords)
            {
                var rowIndex = DataGrid.ResolveToRowIndex(record);
                rows.Add(GetRowFromIndex(rowIndex));
            }
            var rowArr = rows.ToArray();
            OnRowsDragStart(rowArr);
            
        }

        #endregion

        /* Removed as appears unused; removed as of 19/12/2022
        #region CellRendering

        private void PopulateDynamicActionCell(DynamicActionColumn column, int rowIndex, int columnIndex, GridStyleInfo style)
        {
            style.CellType = "ImageCell";
            var bi = column.Image?.Invoke(rowIndex < 0 ? null : Data.Rows[rowIndex]);
            if (bi != null)
            {
                style.CellValue = bi;
                style.BorderMargins = new CellMarginsInfo(4.0F);
            }
        }

        //bool rowstylehelperinitialised = false;

        //protected virtual void ProcessCellStyle(CoreRow row, int column, GridStyleInfo style)
        //{

        //    if (!rowstylehelperinitialised)
        //    {
        //        Script stylescript = new Client<Script>().Load(new Filter<Script>(x => x.Section).IsEqualTo(typeof(T).EntityName()).And(x => x.ScriptType).IsEqualTo(ScriptType.RowStyle)).FirstOrDefault(); ;
        //        if (stylescript != null)
        //        {

        //            rowstylehelper = new ScriptDocument(stylescript.Code);
        //            if (!rowstylehelper.Compile())
        //            {
        //                MessageBox.Show("Unable to Load Row Style Helper!\r\n\r\n" + rowstylehelper.Result);
        //                rowstylehelper = null;
        //            }

        //        }
        //        rowstylehelperinitialised = true;
        //    }

        //    if (rowstylehelper != null)
        //    {
        //        try
        //        {
        //            rowstylehelper.SetValue("Row", row);
        //            rowstylehelper.SetValue("Column", Data.Columns[column].ColumnName);
        //            rowstylehelper.SetValue("Background", style.Background);
        //            rowstylehelper.SetValue("Foreground", style.Foreground);
        //            rowstylehelper.SetValue("Style", style.Font.FontStyle);
        //            rowstylehelper.SetValue("Weight", style.Font.FontWeight);
        //            if (rowstylehelper.Execute())
        //            { 
        //                style.Background = (System.Windows.Media.Brush)rowstylehelper.GetValue("Background");
        //                style.Foreground = (System.Windows.Media.Brush)rowstylehelper.GetValue("Foreground");
        //                style.Font.FontStyle = (FontStyle)rowstylehelper.GetValue("Style");
        //                style.Font.FontWeight = (FontWeight)rowstylehelper.GetValue("Weight");
        //            }
        //        }
        //        catch (Exception e)
        //        {
        //            //MessageBox.Show("Unable to Invoke Row Style Helper!\r\n\r\n" + e.Message);
        //        }
        //    }
        //}

        private readonly Dictionary<string, BaseEditor> editorcache = new();

        private void PopulateDataCell(int rowIndex, int columnIndex, GridStyleInfo style)
        {
            if (columnIndex > -1 && columnIndex < ColumnList.Count)
            {
                var o = ColumnList[columnIndex];
                if (o is DynamicActionColumn)
                {
                    PopulateDynamicActionCell((DynamicActionColumn)o, rowIndex, columnIndex, style);
                }
                else
                {
                    var dgc = (DynamicGridColumn)o;

                    var dc = Data.Columns.FirstOrDefault(x => x.ColumnName.Equals(dgc.ColumnName));

                    var fmt = string.IsNullOrWhiteSpace(dgc.Format) ? "{0}" : "{0:" + dgc.Format.Replace("\\:", ":").Replace(":", "\\:") + "}";

                    object? val = null;
                    if (rowIndex < Data.Rows.Count)
                        val = Data.Rows[rowIndex][dgc.ColumnName];

                    if (val != null && dgc.Lookups.ContainsKey(val))
                        val = dgc.Lookups[val];

                    if (dc != null)
                    {
                        if(!editorcache.TryGetValue(dc.ColumnName, out var editor))
                        {
                            var prop = DatabaseSchema.Property(typeof(T), dc.ColumnName);
                            if (prop != null)
                                editor = prop.Editor;
                            else
                                editor = EditorUtils.GetEditor(dc.DataType) ?? new NullEditor();
                            editorcache[dc.ColumnName] = editor;
                        }

                        if (editor is CheckBoxEditor)
                        {
                            style.CellType = "CheckBox";
                            style.CellValue = val;

                            style.HorizontalAlignment = dc == null ? HorizontalAlignment.Left : dgc.HorizontalAlignment(dc.DataType);
                            style.VerticalAlignment = dgc.VerticalAlignment();
                            style.TextMargins = new CellMarginsInfo(2.0F);
                        }
                        else if (editor is ColorEditor)
                        {
                            style.CellType = "ImageCell";
                            var str = val?.ToString();
                            if (!string.IsNullOrWhiteSpace(str))
                            {
                                var color = ColorTranslator.FromHtml(str); // System.Drawing.Color.FromName(val.ToString());
                                var bitmap = ImageUtils.BitmapFromColor(color, (int)style.GridModel.ColumnWidths[columnIndex],
                                    (int)style.GridModel.RowHeights[rowIndex], Color.Black);
                                //bitmap.Save(val.ToString().Replace("#","") + ".png");
                                style.CellValue = bitmap.AsBitmapImage(false);
                                style.BorderMargins = new CellMarginsInfo(4.0F);
                            }
                        }
                        else
                        {
                            string value;
                            try
                            {
                                if(val is null)
                                {
                                    value = "";
                                }
                                else if (val.GetType().IsEnum)
                                {
                                    value = val.ToString()!;
                                }
                                else if (val.GetType().IsDefault(val))
                                {
                                    value = "";
                                }
                                else
                                {
                                    value = string.Format(new TimeSpanFormatter(), fmt, val);
                                }
                            }
                            catch (Exception e)
                            {
                                Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
                                value = val?.ToString() ?? "";
                            }

                            if (editor is PasswordEditor)
                                style.CellValue = "".PadLeft(value.Length, '●');
                            else
                                style.CellValue = value;
                            style.HorizontalAlignment = dc == null ? HorizontalAlignment.Left : dgc.HorizontalAlignment(dc.DataType);
                            style.VerticalAlignment = dgc.VerticalAlignment();
                            style.CellType = "Static";
                            style.TextMargins = new CellMarginsInfo(2.0F);
                        }
                    }
                }
            }
        }

        private void PopulateHeaderCell(int columnIndex, GridStyleInfo style)
        {
            if (columnIndex > -1 && columnIndex < ColumnList.Count)
            {
                var o = ColumnList[columnIndex];
                if (o is DynamicActionColumn)
                {
                    PopulateDynamicActionCell((DynamicActionColumn)o, -1, columnIndex, style);
                }
                else
                {
                    var dgc = (DynamicGridColumn)o;
                    if (Data != null)
                    {
                        var dc = Data.Columns.FirstOrDefault(x => x.ColumnName.Equals(dgc.ColumnName));
                        style.HorizontalAlignment = dc != null ? dgc.HorizontalAlignment(dc.DataType) : HorizontalAlignment.Left;
                    }

                    style.CellValue = string.IsNullOrWhiteSpace(dgc.Caption) ? dgc.ColumnName : dgc.Caption;
                    style.VerticalAlignment = dgc.VerticalAlignment();
                    style.CellType = "Static";
                    style.TextMargins = new CellMarginsInfo(2.0F);
                }
            }
        }

        #endregion
        */
        /* Removed as appears unused; removed as of 19/12/2022
        #region Drag and Drop

        private Point startpoint;

        private void CheckPreviewMouseDown(object sender, MouseButtonEventArgs e)
        {
            if (!HasOption(DynamicGridOption.DragSource))
                return;
            if (e.LeftButton == MouseButtonState.Pressed)
            {
                Logger.Send(LogType.Information, GetType().EntityName(), "Initiating Start Point");
                startpoint = e.GetPosition(DataGrid);
            }
        }

        //private void CheckPreviewMouseMove(object sender, MouseEventArgs e)
        //{
        //    Logger.Send(LogType.Information, this.GetType().EntityName(), String.Format("Checking Mouse Move: StartPoint = {0},{1}", startpoint.X, startpoint.Y));

        //    if (!_HasOption(DynamicGridOptions.DragSource))
        //        return;
        //    Vector diff = startpoint - e.GetPosition(null);
        //    Logger.Send(LogType.Information, this.GetType().EntityName(), String.Format("Checking Mouse Move: StartPoint = {0},{1} Diff = {2},{3}", startpoint.X, startpoint.Y, diff.X, diff.Y));

        //    if ( (startpoint.X != 0) && (startpoint.Y != 0))
        //    {
        //        if ((Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance) || (Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance))
        //        {
        //            Logger.Send(LogType.Information, this.GetType().EntityName(), "Starting Drag Drop Operation");
        //            DataObject dragData = new DataObject(typeof(T).EntityName(), SelectedRows);
        //            DragDrop.DoDragDrop(DataGrid, dragData, DragDropEffects.Copy);
        //        }
        //    }
        //}

        private void CheckPreviewMouseUp(object sender, MouseButtonEventArgs e)
        {
            if (!HasOption(DynamicGridOption.DragSource))
                return;

            Logger.Send(LogType.Information, GetType().EntityName(), "Clearing Start Point");
            startpoint = new Point();
        }

        #endregion
        */
    }
}