| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772 | using System;using System.Collections.Generic;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.Input;using System.Windows.Media;using System.Windows.Media.Animation;using System.Windows.Media.Imaging;using InABox.Clients;using InABox.Core;using InABox.Wpf;using InABox.WPF;using Syncfusion.Data;using Syncfusion.UI.Xaml.Grid;using Syncfusion.UI.Xaml.Grid.Helpers;using static InABox.DynamicGrid.IDynamicGrid;using Color = System.Drawing.Color;using Columns = InABox.Core.Columns;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;using System.Threading;namespace InABox.DynamicGrid;public abstract class DynamicGrid : ContentControl{    public static readonly DependencyProperty UseWaitCursorProperty =        DependencyProperty.Register(nameof(UseWaitCursor), typeof(bool), typeof(DynamicGrid<>));    public bool UseWaitCursor    {        get => (bool)GetValue(UseWaitCursorProperty);        set => SetValue(UseWaitCursorProperty, value);    }}public abstract class DynamicGrid<T> : DynamicGrid, IDynamicGridUIComponentParent<T>, IDynamicGrid<T>    where T : BaseObject, new(){    protected enum ClipAction    {        Cut,        Copy    }    private IDynamicGridUIComponent<T> UIComponent { get; set; }    private UIElement? _header;    private readonly Button Add;    public bool bRefreshing;    bool IDynamicGridUIComponentParent<T>.IsRefreshing => bRefreshing;    private readonly Label ClipboardSpacer;    private readonly Button Copy;    private readonly Label Count;    private readonly Border Disabler;    private readonly DynamicActionColumn? drag;    private readonly Button Delete;    private readonly DockPanel Docker;    private readonly Button Edit;    private readonly Label EditSpacer;    private readonly Button ExportButton;    private readonly Label ExportSpacer;    private readonly Button DuplicateBtn;    private readonly Button SwitchViewBtn;    private readonly Button Help;    private readonly Button ImportButton;    private readonly Grid Layout;    private readonly Label Loading;    private DoubleAnimation LoadingFader = new DoubleAnimation(1d, 0.2d, new Duration(TimeSpan.FromSeconds(2))) { AutoReverse = true };    private readonly Button Print;    private readonly Label PrintSpacer;    private readonly StackPanel LeftButtonStack;    private readonly StackPanel RightButtonStack;    protected DynamicGridRowStyleSelector<T> RowStyleSelector;    #region Events    private event IDynamicGrid.ReconfigureEvent? _onReconfigure;    public event IDynamicGrid.ReconfigureEvent? OnReconfigure    {        add        {            _onReconfigure += value;            Reconfigure();        }        remove        {            _onReconfigure -= value;            Reconfigure();        }    }    public OnGetDynamicGridRowStyle? OnGetRowStyle { get; set; }        public ValidateEvent<T>? OnValidate { get; set; }        public event OnPrintData? OnPrintData;        public event BeforeRefreshEventHandler? BeforeRefresh;    public event AfterRefreshEventHandler? AfterRefresh;    public event OnDefineFilter? OnDefineFilter;    public event OnCreateItem? OnCreateItem;    public event OnAfterCreateItem? OnAfterCreateItem;    /// <summary>    /// Called when an item is selected in the grid. It is not called if <see cref="IsReady"/> is not <see langword="true"/>.    /// </summary>    /// <remarks>    /// It is unnecessary to use this if within a grid. Instead, override <see cref="SelectItems(CoreRow[]?)"/>.    /// </remarks>    public event SelectItemHandler? OnSelectItem;    public event OnCellDoubleClick? OnCellDoubleClick;    public event EventHandler? OnChanged;    public delegate void BeforeSelectionEvent(CancelEventArgs cancel);    public event BeforeSelectionEvent? OnBeforeSelection;        protected virtual void Changed()    {    }    public virtual void DoChanged()      {        Changed();        OnChanged?.Invoke(this, EventArgs.Empty);    }    public event EditorValueChangedHandler? OnEditorValueChanged;    public event OnCustomiseEditor<T>? OnCustomiseEditor;    public event OnFilterRecord? OnFilterRecord;    public event OnDoubleClick? OnDoubleClick;    public event EntitySaveEvent? OnBeforeSave;    public event EntitySaveEvent? OnAfterSave;    public delegate void EditorLoaded(IDynamicEditorForm editor, T[] items);    public event EditorLoaded OnEditorLoaded;    public event OnLoadEditorButtons<T> OnLoadEditorButtons;    #endregion    protected DynamicGridSettings Settings { get; set; }    public DynamicGrid() : base()    {        UseWaitCursor = true;        Options = new DynamicGridOptions();        Options.OnChanged += () =>        {            _hasLoadedOptions = true;            OptionsChanged();        };        ActionColumns = new DynamicActionColumns();        ColumnGroupings = new DynamicGridColumnGroupings();                RowStyleSelector = GetRowStyleSelector();        RowStyleSelector.GetStyle += (row, style) => GetRowStyle(row, style);                IsReady = false;        Data = new CoreTable();        MasterColumns = new DynamicGridColumns();        MasterColumns.ExtractColumns(typeof(T));        HiddenColumns = new HiddenColumnsList();        drag = new DynamicImageColumn(InABox.Wpf.Resources.drag.AsBitmapImage()) { Position = DynamicActionColumnPosition.Start };        if (typeof(T).IsAssignableTo(typeof(ISequenceable)))        {            HiddenColumns.Add(x => (x as ISequenceable)!.Sequence);        }        VisibleColumns = new DynamicGridColumns();        UIComponent = CreateUIComponent();                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 };        Copy = CreateButton(Wpf.Resources.duplicate.AsBitmapImage(Color.White), tooltip: "Duplicate Rows");        Copy.Margin = new Thickness(0, 2, 2, 0);        Copy.Click += Copy_Click;        ClipboardSpacer = new Label { Width = 5 };        ExportButton = CreateButton(Wpf.Resources.doc_xls.AsBitmapImage(Color.White), "Export");        ExportButton.Margin = new Thickness(0, 2, 2, 0);        ExportButton.Click += ExportButtonClick;        ImportButton = CreateButton(Wpf.Resources.doc_xls.AsBitmapImage(Color.White), "Import");        ImportButton.Margin = new Thickness(0, 2, 2, 0);        ImportButton.Click += ImportButton_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(Copy);        LeftButtonStack.Children.Add(ClipboardSpacer);        LeftButtonStack.Children.Add(ExportButton);        LeftButtonStack.Children.Add(ImportButton);        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) });        var control = UIComponent.Control;        control.SetValue(Grid.RowProperty, 1);        Layout.Children.Add(control);        Layout.Children.Add(Loading);        Layout.Children.Add(Docker);                Disabler = new Border()        {            BorderBrush = new SolidColorBrush(Colors.Transparent),            Background = new SolidColorBrush(Colors.DimGray) { Opacity = 0.2 },            Visibility = Visibility.Collapsed,        };        Disabler.SetValue(Canvas.ZIndexProperty, 99);        Disabler.SetValue(Grid.RowSpanProperty, 3);        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();    }    #region IDynamicGridUIComponentParent    protected virtual IDynamicGridUIComponent<T> CreateUIComponent()    {        return new DynamicGridGridUIComponent<T>()        {            Parent = this        };    }    bool IDynamicGridUIComponentParent<T>.CanFilter()    {        return !Options.ReorderRows || !Options.EditRows;    }    bool IDynamicGridUIComponentParent<T>.CanSort()    {        return !Options.ReorderRows || !Options.EditRows;    }    T IDynamicGrid<T>.LoadItem(CoreRow row) => LoadItem(row);    DynamicGridRowStyleSelector<T> IDynamicGridUIComponentParent<T>.RowStyleSelector => RowStyleSelector;    void IDynamicGridUIComponentParent<T>.BeforeSelection(CancelEventArgs cancel)    {        BeforeSelection(cancel);    }    void IDynamicGridUIComponentParent<T>.SelectItems(CoreRow[] rows)    {        SelectItems(rows);    }    void IDynamicGridUIComponentParent<T>.EntityChanged(T obj, CoreRow row, string changedColumn, Dictionary<string, object?> changes)        => EntityChanged(obj, row, changedColumn, changes);    void IDynamicGridUIComponentParent<T>.UpdateData(T obj, CoreRow row, string changedColumn, Dictionary<CoreColumn, object?> updates)    {        var result = new Dictionary<string, object?>();        foreach (var (col, value) in updates)        {            UpdateRow(row, col.ColumnName, value, refresh: false);            DynamicGridUtils.UpdateEditorValue(new BaseObject[] { obj }, col.ColumnName, value, result);        }        EntityChanged(obj, row, changedColumn, result);    }    void IDynamicGridUIComponentParent<T>.HandleKey(KeyEventArgs e)    {        if (Options.ReorderRows)        {            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();            }        }    }    void IDynamicGridUIComponentParent<T>.DoubleClickCell(CoreRow? row, DynamicColumnBase? column)    {        var args = new DynamicGridCellClickEventArgs(row, column);        if (OnCellDoubleClick is not null)        {                        OnCellDoubleClick?.Invoke(this, args);            if (args.Handled)                return;        }        if (row is not null)            DoDoubleClick(this, args);    }    void IDynamicGridUIComponentParent<T>.ExecuteActionColumn(DynamicActionColumn column, CoreRow[]? rows)    {        var bRefresh = false;        if(rows is null)        {            bRefresh = column.Action?.Invoke(null) ?? false;        }        else        {            foreach (var row in rows)                if (column.Action?.Invoke(row) == true)                    bRefresh = true;        }        if (bRefresh)            Dispatcher.BeginInvoke(() => { Refresh(true, true); });    }    void IDynamicGridUIComponentParent<T>.OpenColumnMenu(DynamicColumnBase column)    {        if(column is DynamicMenuColumn menuColumn)        {            menuColumn.Action?.Invoke(SelectedRows.FirstOrDefault());        }        else if(column is DynamicActionColumn actionColumn)        {            var menu = actionColumn?.ContextMenu?.Invoke(SelectedRows);            if (menu != null && menu.Items.Count > 0)            {                menu.IsOpen = true;            }        }    }    void IDynamicGridUIComponentParent<T>.UpdateRecordCount(int count)    {        Count.Content = FormatRecordCount(count);    }    protected virtual string FormatRecordCount(int count) => $"{count} Records";        void IDynamicGridUIComponentParent<T>.LoadColumnsMenu(ContextMenu menu)    {        menu.AddItem("Select Columns", null, SelectColumnsClick);        LoadColumnsMenu(menu);    }    void IDynamicGridUIComponentParent<T>.DragOver(object sender, DragEventArgs e)    {        HandleDragOver(sender, e);    }    void IDynamicGridUIComponentParent<T>.Drop(object sender, DragEventArgs e)    {        if (!Options.DragTarget)            return;        if(DynamicGridUtils.TryGetDropData(e, out var entityType, out var table))        {            OnDragEnd(entityType, table, e);        }        else        {            HandleDragDrop(sender, e);        }    }    void IDynamicGridUIComponentParent<T>.DragStart(object? sender, CoreRow[] rows)    {        Logger.Send(LogType.Information, "", "RowDragDropController_DragStart");                if (!Options.DragSource)            return;                OnRowsDragStart(rows);    }        public void UIFilterChanged(object sender) => DoFilterChanged();        //void IDynamicGridUIComponentParent<T>.UIFilterChanged(object sender) => DoFilterChanged();    protected virtual void DoFilterChanged()    {            }    private Dictionary<DynamicColumnBase, IDynamicGridColumnFilter?> ColumnFilters { get; set; } = new();    IDynamicGridColumnFilter? IDynamicGrid.GetColumnFilter(DynamicColumnBase column) => GetColumnFilter(column);    protected IDynamicGridColumnFilter? GetColumnFilter(DynamicColumnBase column)    {        if(!ColumnFilters.TryGetValue(column, out var filter))        {            filter = GenerateColumnFilter(column);            ColumnFilters.Add(column, filter);        }        return filter;    }    protected virtual IDynamicGridColumnFilter? GenerateColumnFilter(DynamicColumnBase column)    {        if(column is DynamicGridColumn gc)        {            if(gc.Editor is DateTimeEditor || gc.Editor is DateEditor)            {                return new DateTreeDynamicGridColumnFilter(this, column);            }            else            {                return new StandardDynamicGridColumnFilter(this, column);            }        }        else if(column is DynamicActionColumn ac)        {            if(ac.GetFilter is not null)            {                return ac.GetFilter();            }            else if(ac is DynamicTextColumn textColumn)            {                return new StandardDynamicGridColumnFilter(this, textColumn);            }            else            {                return null;            }        }        else        {            return null;        }    }    #endregion    protected virtual DynamicGridRowStyleSelector<T> GetRowStyleSelector()    {        return new DynamicGridRowStyleSelector<T, DynamicGridRowStyle>();    }    protected virtual DynamicGridStyle GetRowStyle(CoreRow row, DynamicGridStyle style)    {        DynamicGridStyle? result = null;        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;            }        result ??= OnGetRowStyle != null ? OnGetRowStyle(row, style) : style;        return result;    }    protected virtual void BeforeSelection(CancelEventArgs cancel)    {        OnBeforeSelection?.Invoke(cancel);    }    public bool IsReady { get; protected 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 unfiltered data in the grid. This is <see langword="null"/> until <see cref="Refresh(bool, bool)"/> is called.    /// </summary>    /// <remarks>    /// This differs from <see cref="Data"/> in that <see cref="Data"/> has been filtered by <see cref="FilterRecord(CoreRow)"/>,    /// whereas <see cref="MasterData"/> contains every record loaded from the database.    /// </remarks>    public CoreTable? MasterData { get; set; }    public DynamicGridColumns MasterColumns { get; protected set; }    public DynamicGridColumns VisibleColumns { get; protected set; }    public DynamicActionColumns ActionColumns { get; protected set; }    private List<DynamicColumnBase> ColumnList = new();    IList<DynamicColumnBase> IDynamicGrid.ColumnList => ColumnList;    public CoreTable Data { get; set; }    public class HiddenColumnsList    {        private List<string> Columns { get; set; } = new();        public IEnumerable<string> ColumnNames => Columns;        public void Add(Expression<Func<T, object?>> column) => Add(CoreUtils.GetFullPropertyName(column, "."));        public void Add(IColumn column) => Add(column.Property);        public void Add(string column)        {            if (!Contains(column))                Columns.Add(column);        }        public bool Contains(string column) => Columns.Contains(column);    }    public void AddHiddenColumn(string column) => HiddenColumns.Add(column);    public HiddenColumnsList HiddenColumns { get; }    private static bool IsSequenced => typeof(T).GetInterfaces().Any(x => x.Equals(typeof(ISequenceable)));    public double RowHeight    {        get => UIComponent.RowHeight;        set => UIComponent.RowHeight = value;    }    public double HeaderHeight    {        get => UIComponent.HeaderRowHeight;        set => UIComponent.HeaderRowHeight = value;    }    #region Options    /// <summary>    /// Initialise things like custom buttons; called once during construction.    /// </summary>    protected abstract void Init();    protected abstract void DoReconfigure(DynamicGridOptions options);    private bool _hasLoadedOptions = false;    protected virtual void OptionsChanged()    {        var reloadColumns = false;        Help.Visibility = Options.ShowHelp ? Visibility.Visible : Visibility.Collapsed;        Add.Visibility = Options.AddRows ? Visibility.Visible : Visibility.Collapsed;        Edit.Visibility = Options.EditRows ? Visibility.Visible : Visibility.Collapsed;        EditSpacer.Visibility = Options.AddRows || Options.EditRows            ? Visibility.Visible            : Visibility.Collapsed;        Print.Visibility = Options.Print ? Visibility.Visible : Visibility.Collapsed;        PrintSpacer.Visibility = Options.Print ? Visibility.Visible : Visibility.Collapsed;        Copy.Visibility = Options.ReorderRows ? Visibility.Visible : Visibility.Collapsed;        ClipboardSpacer.Visibility = Options.ReorderRows ? Visibility.Visible : Visibility.Collapsed;        ExportButton.Visibility = Options.ExportData ? Visibility.Visible : Visibility.Collapsed;        ImportButton.Visibility = Options.ImportData ? Visibility.Visible : Visibility.Collapsed;        ExportSpacer.Visibility = Options.ExportData || Options.ImportData            ? Visibility.Visible            : Visibility.Collapsed;        SwitchViewBtn.Visibility = Options.DirectEdit             ? Options.HideDirectEditButton                ? Visibility.Collapsed                 : Visibility.Visible             : Visibility.Collapsed;        Count.Visibility = Options.RecordCount ? Visibility.Visible : Visibility.Collapsed;        Delete.Visibility = Options.DeleteRows ? Visibility.Visible : Visibility.Collapsed;        if (drag is not null)        {            var hasSequence = drag.Position == DynamicActionColumnPosition.Start;            if (Options.ReorderRows)            {                if (!ActionColumns.Contains(drag))                {                    ActionColumns.Insert(0, drag);                }            }            else            {                ActionColumns.Remove(drag);            }            if(hasSequence != Options.ReorderRows)            {                drag.Position = Options.ReorderRows ? DynamicActionColumnPosition.Start : DynamicActionColumnPosition.Hidden;                reloadColumns = true;            }        }        if (DuplicateBtn != null)            DuplicateBtn.Visibility = Visibility.Collapsed;        if (UIComponent.OptionsChanged())        {            reloadColumns = true;        }        if(reloadColumns && IsReady)        {            Refresh(true, false);        }    }    public bool IsDirectEditMode()    {        return Options.DirectEdit            && (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();    }    public DynamicGridOptions Options { get; }        protected void OnReconfigureEvent(DynamicGridOptions options)    {        _onReconfigure?.Invoke(options);    }    /// <summary>    /// Configure custom buttons and options.    /// </summary>    public void Reconfigure(DynamicGridOptions options)    {        options.BeginUpdate().Clear();        options.ReorderRows = IsSequenced;        DoReconfigure(options);        OnReconfigureEvent(options);        options.EndUpdate();        if (!_hasLoadedOptions)        {            _hasLoadedOptions = true;            OptionsChanged();        }    }        public void Reconfigure()    {        Reconfigure(Options);    }        public void Reconfigure(ReconfigureEvent onReconfigure)    {        OnReconfigure += onReconfigure;        Reconfigure();    }    #endregion    protected virtual DynamicGridSettings LoadSettings()    {        return new DynamicGridSettings();    }    protected virtual void SaveSettings(DynamicGridSettings settings)    {    }    protected virtual void LoadColumnsMenu(ContextMenu menu)    {    }    protected void UpdateCell(int row, string colname, object? value)    {        var coreRow = Data.Rows[row];        coreRow[colname] = value;        UIComponent.UpdateCell(coreRow, colname, value);    }    protected void UpdateCell(CoreRow row, DynamicColumnBase column)    {        UIComponent.UpdateCell(row, column);    }    private void EntityChanged(T obj, CoreRow row, string changedColumn, Dictionary<string, object?> changes)    {        OnAfterEditorValueChanged(null, [obj], new AfterEditorValueChangedArgs(changedColumn, changes), changes);        SaveItem(obj);        foreach (var (key, value) in changes)        {            row[key] = value;        }        UIComponent.UpdateRow(row);    }    #region Row Selections    protected CoreRow[] GetVisibleRows()    {        return UIComponent.GetVisibleRows();    }    public CoreRow[] SelectedRows    {        get => UIComponent.SelectedRows;        set => UIComponent.SelectedRows = value;    }    /// <summary>    /// Call the <see cref="OnSelectItem"/> event, and do any updating which needs to occur when items are selected.    /// </summary>    /// <param name="rows"></param>    protected virtual void SelectItems(CoreRow[]? rows)    {        if (IsReady)            OnSelectItem?.Invoke(this, new DynamicGridSelectionEventArgs(rows));        DuplicateBtn.Visibility =            typeof(T).IsAssignableTo(typeof(IDuplicatable)) && rows != null && rows.Length >= 1 ? Visibility.Visible : Visibility.Collapsed;    }    protected virtual void DoDoubleClick(object sender, DynamicGridCellClickEventArgs args)    {        if (IsDirectEditMode())            return;        //SelectItems(SelectedRows);        var e = new HandledEventArgs(false);        OnDoubleClick?.Invoke(sender, e);        if (e.Handled)            return;        if (Options.EditRows)            DoEdit();    }    #endregion    #region Column Handling    #region Column Grouping    public DynamicGridColumnGroupings ColumnGroupings { get; set; }    /// <summary>    /// Create a new column header group, and return it for editing.    /// </summary>    /// <returns></returns>    public DynamicGridColumnGrouping AddColumnGrouping()    {        var group = new DynamicGridColumnGrouping();        ColumnGroupings.Add(group);        return group;    }    /// <summary>    /// Gets the current column header group, and if there is none, create a new one.    /// </summary>    /// <returns></returns>    public DynamicGridColumnGrouping GetColumnGrouping()    {        if(ColumnGroupings.Count == 0)        {            return AddColumnGrouping();        }        return ColumnGroupings[^1];    }    #endregion        protected virtual DynamicGridColumns LoadColumns()    {        return GenerateColumns();    }    /// <summary>    /// Create a set of <see cref="DynamicGridColumns"/> according to the natural default sizes and formats and captions, based on the contents of <paramref name="columns"/>.    /// </summary>    /// <param name="columns"></param>    /// <returns></returns>    protected DynamicGridColumns ExtractColumns(Columns<T> columns)    {        var cols = new DynamicGridColumns();        foreach (var col in columns)        {            var mc = MasterColumns.FirstOrDefault(x => x.ColumnName.Equals(col.Property));            if (mc != null && mc.Editor is not NullEditor && mc.Editor.Visible != Visible.Hidden)                cols.Add(mc);        }        return cols;    }    /// <summary>    /// Provide a set of columns which is the default for this grid.    /// </summary>    public virtual DynamicGridColumns GenerateColumns()    {        var cols = IsDirectEditMode()            ? new Columns<T>(ColumnTypeFlags.IncludeVisible | ColumnTypeFlags.IncludeForeignKeys)            : new Columns<T>(ColumnTypeFlags.IncludeVisible | ColumnTypeFlags.IncludeLinked);                var columns = ExtractColumns(cols);        OnGenerateColumns?.Invoke(this, new GenerateColumnsEventArgs(columns));        return columns;    }        public event GenerateColumnsEvent OnGenerateColumns;    protected virtual void SaveColumns(DynamicGridColumns columns)    {    }    public int DesiredWidth()    {        return UIComponent.DesiredWidth();    }    /// <summary>    /// Handle to configure column groups.    /// </summary>    /// <remarks>    /// This is called after <see cref="LoadColumns"/>, so by the time this is called, both <see cref="VisibleColumns"/>    /// and <see cref="ActionColumns"/> will be loaded, which means one can reference these in the column groups.    /// <br/>    /// <b>Note:</b> <see cref="ColumnGroupings"/> is cleared before this function is called.    /// </remarks>    protected virtual void ConfigureColumnGroups()    {    }    protected virtual void ConfigureColumns(DynamicGridColumns columns)    {    }    public class ColumnsLoadedEventArgs : EventArgs    {        public List<DynamicColumnBase> Columns { get; private set; }        public DynamicGridColumnGroupings ColumnGroupings { get; private set; }        public IEnumerable<DynamicActionColumn> ActionColumns => Columns.OfType<DynamicActionColumn>();        public IEnumerable<DynamicGridColumn> DataColumns => Columns.OfType<DynamicGridColumn>();        public ColumnsLoadedEventArgs(List<DynamicColumnBase> columns, DynamicGridColumnGroupings columnGroupings)        {            Columns = columns;            ColumnGroupings = columnGroupings;        }        public DynamicGridColumn Add(            Expression<Func<T, object?>> member,            int? width = null,            string? caption = null,            string? format = null,            Alignment? alignment = null)        {            var col = DynamicGridColumns.CreateColumn(member, width: width, caption: caption, format: format, alignment: alignment);            Columns.Add(col);            return col;        }    }        public delegate void ColumnsLoadedEvent(DynamicGrid sender, ColumnsLoadedEventArgs args);    public event ColumnsLoadedEvent ColumnsLoaded;    protected virtual void OnColumnsLoaded(List<DynamicColumnBase> columns, DynamicGridColumnGroupings groupings)    {        ColumnsLoaded?.Invoke(this, new ColumnsLoadedEventArgs(columns, groupings));    }        private void ReloadColumns()    {        ColumnFilters.Clear();        ConfigureColumns(MasterColumns);        VisibleColumns = LoadColumns();        ConfigureColumns(VisibleColumns);                ColumnGroupings.Clear();        ConfigureColumnGroups();        ColumnList = new List<DynamicColumnBase>();        ColumnList.AddRange(ActionColumns.Where(x => x.Position == DynamicActionColumnPosition.Start));        ColumnList.AddRange(VisibleColumns);        ColumnList.AddRange(ActionColumns.Where(x => x.Position == DynamicActionColumnPosition.End));        OnColumnsLoaded(ColumnList, ColumnGroupings);                UIComponent.RefreshColumns(ColumnList, ColumnGroupings);    }    #endregion    #region Refresh / Reload    protected bool IsPaging { get; set; } = false;    protected abstract void Reload(        Filters<T> criteria, Columns<T> columns, ref SortOrder<T>? sort,        CancellationToken token, Action<CoreTable?, Exception?> action);    public Filter<T>? DefineFilter()    {        if (OnDefineFilter is null)            return null;        var result = OnDefineFilter.Invoke(typeof(T)) as Filter<T>;        return result;    }        protected virtual bool FilterRecord(CoreRow row)    {        if (OnFilterRecord is not null)            return OnFilterRecord(row);        return true;    }    public IEnumerable<TType> ExtractValues<TType>(Expression<Func<T, TType>> column, Selection selection)    {        var result = selection == Selection.None            ? Enumerable.Empty<TType>()            : selection == Selection.Selected                ? SelectedRows.Select(r => r.Get(column))                : Data.ExtractValues(column);        return result;    }    private class RowRange(int rowIdx, int size)    {        public int RowIdx { get; set; } = rowIdx;        public int Size { get; set; } = size;    }    private CancellationTokenSource? RefreshCancellationToken;    public virtual void Refresh(bool reloadcolumns, bool reloaddata)    {        if (bRefreshing)            return;        if (!DoBeforeRefresh())            return;        UIComponent.BeforeRefresh();        using var cursor = UseWaitCursor ? new WaitCursor() : null;        Loading.Visibility = Visibility.Visible;        Loading.BeginAnimation(Label.OpacityProperty, LoadingFader);        bRefreshing = true;        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");            RefreshCancellationToken?.Cancel();            var tokenSource = new CancellationTokenSource();            RefreshCancellationToken = tokenSource;            var token = tokenSource.Token;            Reload(                criteria, columns, ref sort,                token,                (table, exception) =>                {                    if(token.IsCancellationRequested) return; // Don't bother even checking exceptions if task was cancelled.                    if (exception != null)                    {                        Dispatcher.Invoke(() =>                        {                            MessageWindow.ShowError("Sorry! We couldn't load the data.", exception);                        });                    }                    else if (table is not null)                    {                        if(table.Offset == 0 || MasterData is null)                        {                            MasterData = table;                            Dispatcher.Invoke(() =>                            {                                try                                {                                    ProcessData(null);                                }                                catch (Exception e)                                {                                                                    }                                DoAfterRefresh();                                bRefreshing = false;                                IsReady = true;                            });                        }                        else                        {                            int idx = MasterData.Rows.Count;                            MasterData.AddPage(table);                            Dispatcher.Invoke(() =>                            {                                try                                {                                    ProcessData(new(idx, table.Rows.Count));                                }                                catch(Exception e)                                {                                }                            });                        }                    }                }            );        }        else        {            ProcessData(null);            DoAfterRefresh();            bRefreshing = false;            IsReady = true;        }    }    public void Shutdown()    {        RefreshCancellationToken?.Cancel();    }        protected void NotifyBeforeRefresh(BeforeRefreshEventArgs args) => BeforeRefresh?.Invoke(this, args);        protected void NotifyAfterRefresh(AfterRefreshEventArgs args) => AfterRefresh?.Invoke(this, args);    protected 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 virtual void OnAfterRefresh()    {    }    protected void DoAfterRefresh()    {        OnAfterRefresh();        NotifyAfterRefresh(new AfterRefreshEventArgs());    }    public Columns<T> DataColumns()    {        var columns = Columns.None<T>();        foreach (var column in VisibleColumns)            columns.Add(column.ColumnName);        foreach (var column in HiddenColumns.ColumnNames)            columns.Add(new Column<T>(column));        if (!Options.ReadOnly)        {            foreach (var column in LookupFactory.RequiredColumns<T>())            {                columns.Add(column);            }        }        return columns;    }    /// <summary>    /// Process the data from <see cref="MasterData"/> according to <paramref name="range"/>.    /// </summary>    /// <remarks>    /// Set <paramref name="range"/> to <see langword="null"/> if this is the first page of data to be loaded. This will thus update the grid accordingly,    /// clearing all current rows, resetting columns, selection, etc. If the <paramref name="range"/> is provided, this will add to the grid the rows    /// according to the range from <see cref="MasterData"/>.    /// </remarks>    /// <param name="initialLoad"></param>    private void ProcessData(RowRange? range)    {        if(range is null)        {            Data.Columns.Clear();            Data.Setters.Clear();            if (MasterData != null)                foreach (var column in MasterData.Columns)                    Data.Columns.Add(column);        }        LoadData(range);    }    protected readonly Dictionary<CoreRow, CoreRow> _recordmap = new();    public void UpdateRow<TRow, TType>(CoreRow row, Expression<Func<TRow, TType>> column, TType value, bool refresh = true)    {        row.Set(column, value);        _recordmap[row].Set(column, value);        if (refresh)            InvalidateRow(row);    }    public void UpdateRow<TType>(CoreRow row, string column, TType value, bool refresh = true)    {        row.Set(column, value);        _recordmap[row].Set(column, value);        if (refresh)            InvalidateRow(row);    }    public void UpdateRow(CoreRow row, T obj, bool invalidateRow = true)    {        ObjectToRow(obj, row);        ObjectToRow(obj, _recordmap[row]);        if (invalidateRow)        {            InvalidateRow(row);        }    }    public void UpdateRows(CoreRow[] rows, T[] objs, bool invalidateRows = true)    {        for(var i = 0; i < objs.Length; ++i)        {            UpdateRow(rows[i], objs[i], invalidateRow: invalidateRows);        }    }    public void AddRow(CoreRow row)    {        if (MasterData is null) return;        var masterrow = MasterData.NewRow();        MasterData.FillRow(masterrow, row);        Refresh(false, false);    }    public void AddRow(T data)    {        if (MasterData is null) return;        MasterData.LoadRow(data);        Refresh(false, false);    }    public void DeleteRow(CoreRow row)    {        if (MasterData is null) return;        var masterrow = _recordmap[row];        MasterData.Rows.Remove(masterrow);        Refresh(false, false);    }    /// <summary>    /// Filter all given rows into <paramref name="into"/>, given that they match <paramref name="filter"/> and <see cref="FilterRecord(CoreRow)"/>.    /// If <paramref name="recordMap"/> is given, also updates the map from <paramref name="from"/> to <paramref name="into"/>.    /// </summary>    private IList<CoreRow> FilterRows(        IEnumerable<CoreRow> from,        CoreTable into,        Dictionary<CoreRow, CoreRow>? recordMap = null,        Func<CoreRow, bool>? filter = null)    {        var newRows = new List<CoreRow>();        foreach (var row in from)            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;                    newrow.Values.Add(value);                }                newRows.Add(newrow);                into.Rows.Add(newrow);                recordMap?.TryAdd(newrow, row);            }        return newRows;    }        private readonly SemaphoreSlim semaphore = new(1,1);    private void LoadData(RowRange? range)    {        if (MasterData is null)            return;        if(range is null)        {            ResetClipBuffer();            Data.Rows.Clear();            _recordmap.Clear();            FilterRows(MasterData.Rows, Data, _recordmap);            InvalidateGrid();            SelectedRows = Array.Empty<CoreRow>();        }        else        {            var _newRows = FilterRows(Enumerable.Range(range.RowIdx, range.Size).Select(i => MasterData.Rows[i]), Data, _recordmap);            UIComponent.AddPage(_newRows);            /*            semaphore.Wait();            try            {                // This throws errors when overalpping refreshes cause a mismatch between MasterData and range                // Using try/finally and a Semaphore as a temporary measure until we can isolate the issue                var _newRows = FilterRows(Enumerable.Range(range.RowIdx, range.Size).Select(i => MasterData.Rows[i]), Data, _recordmap);                UIComponent.AddPage(_newRows);            }            finally            {                semaphore.Release();            }*/        }    }    //IncrementalList<T> _data = null;    public void InvalidateRow(CoreRow row)    {        UIComponent.InvalidateRow(row);    }    protected void InvalidateGrid()    {        if (RowStyleSelector != null)            RowStyleSelector.Data = Data;        UIComponent.RefreshData(Data);        Loading.BeginAnimation(Label.OpacityProperty, null);        Loading.Visibility = Visibility.Collapsed;    }    public void AddVisualFilter(string column, string value, FilterType filtertype = FilterType.Contains)    {        UIComponent.AddVisualFilter(column, value, filtertype);    }    protected List<Tuple<string, Func<CoreRow, bool>>> GetFilterPredicates()    {        return UIComponent.GetFilterPredicates();    }    public object? GetData(CoreRow row, DynamicColumnBase column)    {        if(column is DynamicActionColumn ac)        {            return ac.Data(row);        }        else if(column is DynamicGridColumn gc)        {            return row[gc.ColumnName];        }        else        {            return null;        }    }    #endregion    #region Item Manipulation    #region Load/Save/Delete    public virtual T[] LoadItems(IList<CoreRow> rows)    {        return rows.ToArray(LoadItem);    }    public abstract T LoadItem(CoreRow row);    public abstract void SaveItem(T item);    public virtual void SaveItems(IEnumerable<T> items)    {        foreach (var item in items)            SaveItem(item);    }    protected virtual bool CanDeleteItems(params CoreRow[] rows)    {        return true;    }    public 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>();                    Refresh(false, true);                    DoChanged();                    SelectItems(null);                }    }    private void Delete_Click(object sender, RoutedEventArgs e)    {        DoDelete();    }    #endregion    #region Edit    protected virtual void DoEdit()    {        if (SelectedRows.Length == 0)            return;        if (AddEditClick(SelectedRows))        {            SelectItems(SelectedRows);        }    }    private void Edit_Click(object sender, RoutedEventArgs e)    {        DoEdit();    }    protected virtual void DoAdd(bool openEditorOnDirectEdit = false)    {        //CoreRow row = (SelectedRow > -1) && (SelectedRow < Data.Rows.Count) ?  Data.Rows[this.SelectedRow] : null;        if (IsDirectEditMode() && !openEditorOnDirectEdit)        {            CreateItems(null);        }        else if (AddEditClick(null))        {            Refresh(false, true);        }    }    protected void CreateItems(Func<IEnumerable<T>>? create)    {        var newRows = new List<CoreRow>();        var items = create?.Invoke() ?? CoreUtils.One(CreateItem());        foreach (var item in items)        {            if (!AfterCreate(item))                return;            SaveItem(item);            var datarow = Data.NewRow();            ObjectToRow(item, datarow);            Data.Rows.Add(datarow);            newRows.Add(datarow);            var masterrow = MasterData.NewRow();            ObjectToRow(item, masterrow);            MasterData.Rows.Add(masterrow);            _recordmap[datarow] = masterrow;        }        InvalidateGrid();        SelectedRows = newRows.ToArray();        DoChanged();    }    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);        pages.RemoveAll(x => !x.Visible);        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 });            }        );        OnLoadEditorButtons?.Invoke(item, buttons);    }    protected virtual void BeforeLoad(IDynamicEditorForm form, T[] items)    {        form.BeforeLoad();    }    void IDynamicGrid.InitialiseEditorForm(IDynamicEditorForm editor, object[] items, Func<Type, CoreTable>? pageDataHandler, bool preloadPages)    {        InitialiseEditorForm(editor, items.Cast<T>().ToArray(), pageDataHandler, preloadPages);    }    public virtual bool EditItems(object[] items, Func<Type, CoreTable?>? PageDataHandler = null, bool PreloadPages = false)    {        var values = items.Cast<T>().ToArray();        return EditItems(values, PageDataHandler, PreloadPages);    }    public virtual 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);        };        editor.OnDefineEditor = (o, c) =>        {            var result = GetEditor(o, c);            if (result != null)                result = result.CloneEditor();            return result;        };        editor.OnFormCustomiseEditor += DoCustomiseEditor;        editor.OnDefineFilter = (type, column) => { return DefineLookupFilter(type, column, 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);    }    BaseEditor IDynamicGridUIComponentParent<T>.CustomiseEditor(DynamicGridColumn column, BaseEditor editor)    {        return editor.CloneEditor();    }    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);    }    /// <summary>    /// Edit the <paramref name="items"/> with a non-modal editor window, attaching them to the <paramref name="host"/> provided.    /// </summary>    /// <param name="items">List of objects to edit.</param>    /// <param name="callback">A callback for when the items are finished being edited.</param>    public virtual void EditItemsNonModal(ISubPanelHost host, T[] items, Action<T[], bool> callback, Func<Type, CoreTable?>? PageDataHandler = null, bool PreloadPages = false)    {        if (!DynamicGridUtils.TryEdit(items, out var editLock))        {            if(editLock.Panel is Window window)            {                Task.Delay(100).ContinueWith(task =>                {                    window.WindowState = WindowState.Normal;                    window.Activate();                }, TaskScheduler.FromCurrentSynchronizationContext());            }            else            {                MessageWindow.ShowMessage("One or more items are already being edited in an open window. You cannot edit the same entity multiple times simultaneously.", "Simultaneous edit.");            }            return;        }        DynamicEditorForm editor;        using (var cursor = new WaitCursor())        {            editor = new DynamicEditorForm();            editor.SetValue(Panel.ZIndexProperty, 999);            editor.Form.DisableOKIfUnchanged = true;            InitialiseEditorForm(editor, items, PageDataHandler, PreloadPages);            OnEditorLoaded?.Invoke(editor, items);        }        editLock.Panel = editor;        host.AddSubPanel(editor);        editor.SubPanelClosed += o =>        {            try            {                DynamicGridUtils.FinishEdit(items);                callback(items, editor.Result == true);            }            catch(Exception e)            {                MessageWindow.ShowError("Error occurred while closing editor.", e);            }        };        editor.Show();    }    /// <summary>    /// Edit the <paramref name="items"/> with a modal editor window.    /// </summary>    /// <param name="items">List of objects to edit.</param>    /// <returns><see langword="true"/> if the items were saved.</returns>    public virtual bool EditItems(T[] items, Func<Type, CoreTable?>? PageDataHandler = null, bool PreloadPages = false)    {        DynamicEditorForm editor;        using (var cursor = new WaitCursor())        {            editor = new DynamicEditorForm()            {                WindowStartupLocation = WindowStartupLocation.CenterScreen            };                        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 List<string>? ValidateData(T[] items)    {        var errors = new List<string>();        DoValidate(items, errors);        OnValidate?.Invoke(this, items, errors);        return errors.Count != 0 ? errors : 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(typeof(T), 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(typeof(T), 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? DefineLookupFilter(Type type, string column, T[] items)    {        return LookupFactory.DefineLookupFilter(typeof(T), type, column, items);    }    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.Length == 0)        {            if (!CanCreateItems())                return false;            var item = CreateItem();                        if (!AfterCreate(item))                return false;            // 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(Options.NonModalEditorHost is not null)            {                EditItemsNonModal(Options.NonModalEditorHost, [item], (items, result) =>                {                    if (result)                    {                        var row = Data.NewRow();                        ObjectToRow(item, row);                        Data.Rows.Add(row);                        InvalidateGrid();                        SelectedRows = [row];                        DoChanged();                    }                });                return false;            }            else            {                if (EditItems([item]))                {                    //_CurrentRow = Data.Rows.Count;                    var row = Data.NewRow();                    ObjectToRow(item, row);                    Data.Rows.Add(row);                    InvalidateGrid();                    SelectedRows = [row];                    DoChanged();                    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.Length != 0)        {            var snaps = items.ToArray(x => x.TakeSnapshot());            if(Options.NonModalEditorHost is not null)            {                EditItemsNonModal(Options.NonModalEditorHost, items, (items, result) =>                {                    if (result)                    {                        var sel = SelectedRows;                        UpdateRows(rows, items, invalidateRows: false);                        InvalidateGrid();                        SelectedRows = sel;                        DoChanged();                    }                    else                    {                        foreach(var snap in snaps)                        {                            snap.ResetObject();                        }                    }                });                return false;            }            else            {                if (EditItems(items))                {                    var sel = SelectedRows;                    UpdateRows(rows, items, invalidateRows: false);                    InvalidateGrid();                    SelectedRows = sel;                    DoChanged();                    return true;                }                else                {                    foreach(var snap in snaps)                    {                        snap.ResetObject();                    }                }             }            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    public virtual T CreateItem()    {        var result = new T();        OnCreateItem?.Invoke(this, result);        return result;    }    public virtual bool AfterCreate(T item)    {        return OnAfterCreateItem?.Invoke(this, item) ?? true;    }    protected virtual void DoPrint(object sender)    {        OnPrintData?.Invoke(sender);    }    protected virtual void ShowHelp(string slug)    {        Process.Start(new ProcessStartInfo("https://prsdigital.com.au/wiki/index.php/" + slug) { UseShellExecute = true });    }    protected void ReloadForms<TTargetType, TTargetForm, TSourceForm>(IDynamicEditorForm editor, TTargetType item,        Expression<Func<TSourceForm, object?>> sourcekey, Guid sourceid)        where TTargetType : Entity, new()        where TTargetForm : Entity, IRemotable, IPersistent, IDigitalFormInstance, new()        where TSourceForm : Entity, IRemotable, IPersistent, IDigitalForm<TTargetType>, new()    {        var type = typeof(IDynamicOneToManyGrid<,>).MakeGenericType(typeof(TTargetType), typeof(TTargetForm));        var page =            editor.Pages?.FirstOrDefault(x => x.GetType().GetInterfaces().Contains(type)) as IDynamicOneToManyGrid<TTargetType, TTargetForm>;        if (page != null && item != null)        {            if (!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());        }    }    void IDynamicGridUIComponentParent<T>.MoveRows(InABox.Core.CoreRow[] rows, int index) => MoveRows(rows, index);    #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();    }    protected virtual bool BeforeCopy(IList<T> items)    {        return true;    }    private void PasteFromClipBuffer()    {        if (ClipBuffer == null)            return;        var row = SelectedRows.FirstOrDefault();        MoveRows(ClipBuffer.Item2, row is not null ? (int)row.Index + 1 : Data.Rows.Count, isCopy: ClipBuffer.Item1 == ClipAction.Copy);    }    protected virtual void MoveRows(CoreRow[] rows, int index, bool isCopy = false)    {        if (!Options.ReorderRows || !IsSequenced) return;        var items = LoadItems(rows);        if (isCopy)        {            if (!BeforeCopy(items))            {                return;            }        }        var sequence = index < Data.Rows.Count            ? Data.Rows[index].Get<ISequenceable, long>(x => x.Sequence)            : Data.Rows[^1].Get<ISequenceable, long>(x => x.Sequence) + 1;        var postRows = Data.Rows.Where(r => !rows.Contains(r) && r.Get<ISequenceable, long>(x => x.Sequence) >= sequence);        var updates = items.Concatenate(LoadItems(postRows.ToArray()));        foreach (var update in updates)        {            ((ISequenceable)update).Sequence = sequence;            sequence++;        }        if (updates.Length != 0)        {            SaveItems(updates);            DoChanged();            Refresh(false, true);        }    }    private void Copy_Click(object sender, RoutedEventArgs e)    {        var rows = SelectedRows;        if (rows.Length == 0) return;        MoveRows(rows, rows[^1].Index + 1, isCopy: true);    }    #endregion    protected virtual void ObjectToRow(T obj, CoreRow row)    {        row.Table.FillRow(row, obj);    }    #region Import / Export    protected virtual CoreTable LoadImportKeys(String[] fields)    {        var result = new CoreTable();        result.LoadColumns(Columns.None<T>().Add(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 ImportButton_Click(object sender, RoutedEventArgs e)    {        DoImport();    }    public void Import() => 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)    {    }    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 = UIComponent.GetFilterPredicates();        var visiblerows = GetVisibleRows();        CustomiseExportFilters(filters, visiblerows);        var columns = Columns.None<T>().Add(form.Fields);        var otherColumns = form.GetChildFields()            .Select(x => new Tuple<Type, IColumns>(                x.Key,                Columns.None(x.Key).Add(x.Value)))            .Where(x => x.Item2.ColumnNames().Any()).ToList();        var reloadColumns = Columns.None<T>();        foreach (var column in columns)        {            reloadColumns.Add(column);        }        foreach (var column in HiddenColumns.ColumnNames)        {            reloadColumns.Add(column);        }        foreach (var column in LookupFactory.RequiredColumns<T>())        {            columns.Add(column);        }        foreach (var (column, _) in predicates)        {            reloadColumns.Add(column);        }        CoreTable? data = null;        void LoadExport()        {            var newData = new CoreTable();            newData.LoadColumns(columns);            FilterRows(data.Rows, newData, filter: row =>            {                foreach(var (_, predicate) in predicates)                {                    if (!predicate(row))                    {                        return false;                    }                }                return true;            });            var list = new List<Tuple<Type?, CoreTable>>() { new(typeof(T), newData) };            list.AddRange(LoadExportTables(filters, otherColumns));            DoExportTables(list);        }        var sort = LookupFactory.DefineSort<T>();        Reload(filters, reloadColumns, ref sort, CancellationToken.None, (table, err) =>        {            if (table is not null)            {                if (table.Offset == 0 || data is null)                {                    data = table;                    if (!IsPaging)                    {                        Dispatcher.Invoke(LoadExport);                    }                }                else                {                    data.AddPage(table);                    if (!IsPaging)                    {                        Dispatcher.Invoke(LoadExport);                    }                }            }            else if (err is not null)            {                Dispatcher.Invoke(() =>                {                    MessageWindow.ShowError("Error in export.", err);                });            }        });    }    private void ExportButtonClick(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)    {        UIComponent.ScrollIntoView(row);    }    #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 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 (ExportButton.Visibility != Visibility.Collapsed)            return true;        return false;    }    public Button AddButton(string? caption, BitmapImage? image, string? tooltip, DynamicGridButtonClickEvent action, DynamicGridButtonPosition position = DynamicGridButtonPosition.Left)    {        var button = CreateButton(image, caption, tooltip);        button.Margin = position == DynamicGridButtonPosition.Right            ? new Thickness(2, 2, 0, 0)            : bFirstButtonAdded && AnyButtonsVisible()                ? new Thickness(0, 2, 0, 0)                : new Thickness(0, 2, 2, 0);        button.Padding = !String.IsNullOrWhiteSpace(caption) ? new Thickness(5, 1, 5, 1) : new Thickness(1);        button.Tag = action;        button.Click += Button_Click;        if (position == DynamicGridButtonPosition.Right)            RightButtonStack.Children.Add(button);        else            LeftButtonStack.Children.Add(button);        bFirstButtonAdded = false;        return button;    }    public Button AddButton(string? caption, BitmapImage? image, DynamicGridButtonClickEvent action, DynamicGridButtonPosition position = DynamicGridButtonPosition.Left)    {        var result = AddButton(caption, image, null, action, position);        return result;    }    private void Button_Click(object sender, RoutedEventArgs e)    {        var button = (Button)sender;        var action = (DynamicGridButtonClickEvent)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()    {        var editor = new DynamicGridColumnsEditor(typeof(T)) { WindowStartupLocation = WindowStartupLocation.CenterScreen };        editor.DirectEdit = IsDirectEditMode();        editor.Columns.AddRange(VisibleColumns);        if (editor.ShowDialog().Equals(true))        {            VisibleColumns.Clear();            VisibleColumns.AddRange(editor.Columns);            SaveColumns(editor.Columns);            //OnSaveColumns?.Invoke(this, editor.Columns);            Refresh(true, true);        }    }    #endregion    #region Drag + Drop    /// <summary>    /// Handle a number of rows from a different <see cref="DynamicGrid{T}"/> being dragged into this one.    /// </summary>    /// <param name="entity">The type of entity that that the rows of <paramref name="table"/> represent.</param>    /// <param name="table">The data being dragged.</param>    /// <param name="e"></param>    protected virtual void OnDragEnd(Type entity, CoreTable table, DragEventArgs e)    {        Logger.Send(LogType.Information,"","OnDragEnd");    }    /// <summary>    /// Handle all types of items being dragged onto this grid that aren't handled by <see cref="OnDragEnd(Type, CoreTable, DragEventArgs)"/>,    /// i.e., data which is not a <see cref="CoreTable"/> from another <see cref="DynamicGrid{T}"/>    /// </summary>    /// <remarks>    /// Can be used to handle files, for example.    /// </remarks>    /// <param name="sender"></param>    /// <param name="e"></param>    protected virtual void HandleDragDrop(object sender, DragEventArgs e)    {    }    protected virtual void HandleDragOver(object sender, DragEventArgs e)    {    }    protected DragDropEffects DragTable(Type entity, CoreTable table)    {        Logger.Send(LogType.Information, "", "DragTable");        var data = new DataObject();        data.SetData(DynamicGridUtils.DragFormat, new DynamicGridDragFormat(table.ToDataTable(), entity));        var effect = DragDrop.DoDragDrop(this, data, DragDropEffects.All);        return effect;    }    protected virtual DragDropEffects OnRowsDragStart(CoreRow[] rows)    {        Logger.Send(LogType.Information, "", "OnRowsDragStart");        var table = new CoreTable();        table.LoadColumns(Data.Columns);        table.LoadRows(rows);        return DragTable(typeof(T), table);    }    #endregion}#region Stylingpublic 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;}// 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 truepublic class BoolToImageConverter : AbstractConverter<bool,ImageSource>{    public ImageSource TrueValue { get; set; }    public ImageSource FalseValue { get; set; }    public BoolToImageConverter()    {        TrueValue = Wpf.Resources.Bullet_Tick.AsBitmapImage();    }        public override ImageSource Convert(bool value)    {        return value ? TrueValue : FalseValue;    }    public override bool Deconvert(ImageSource value)    {        return ImageUtils.IsEqual(value as BitmapImage,TrueValue as BitmapImage);    }}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;    }}#endregion[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;    }}
 |