Ver Fonte

Initial thinkings of DynamicData component stuff

Kenric Nugteren há 1 ano atrás
pai
commit
3d296d146e

+ 47 - 0
InABox.Core/Client/Client.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
 using System.Linq;
 using System.Reflection;
 using System.Timers;
@@ -13,6 +14,52 @@ namespace InABox.Clients
         RPC
     }
 
+    public class QueryResult
+    {
+        private CoreTable? Table;
+        private Exception? Exception;
+
+        public QueryResult(CoreTable table)
+        {
+            Table = table;
+        }
+        public QueryResult(Exception exception)
+        {
+            Exception = exception;
+        }
+
+        public bool GetResult([NotNullWhen(true)] out CoreTable? table, [NotNullWhen(false)] out Exception? exception)
+        {
+            table = Table;
+            exception = Exception;
+            return table != null;
+        }
+
+        public void DoWith(Action<CoreTable> dataAction, Action<Exception> exceptionAction)
+        {
+            if(Table != null)
+            {
+                dataAction(Table);
+            }
+            else
+            {
+                exceptionAction(Exception!);
+            }
+        }
+
+        public TResult DoWith<TResult>(Func<CoreTable, TResult> dataAction, Func<Exception, TResult> exceptionAction)
+        {
+            if(Table != null)
+            {
+                return dataAction(Table);
+            }
+            else
+            {
+                return exceptionAction(Exception!);
+            }
+        }
+    }
+
     public class QueryMultipleResults
     {
         private readonly Dictionary<string, CoreTable> Results;

+ 13 - 0
inabox.wpf/DynamicGrid/DataComponent/DynamicGridClientDataComponent.cs

@@ -0,0 +1,13 @@
+using InABox.Core;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace InABox.DynamicGrid;
+
+public class DynamicGridClientDataComponent<T> : IDynamicGridDataComponent<T>
+    where T : Entity, IRemotable, IPersistent
+{
+}

+ 25 - 0
inabox.wpf/DynamicGrid/DataComponent/IDynamicGridDataComponent.cs

@@ -0,0 +1,25 @@
+using InABox.Clients;
+using InABox.Core;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace InABox.DynamicGrid;
+
+public interface IDynamicGridDataComponent<T>
+    where T : BaseObject
+{
+    void Reload(Filters<T> criteria, Columns<T> columns, SortOrder<T>? sort, Action<QueryResult> action);
+
+    T LoadItem(CoreRow rows);
+
+    T[] LoadItems(CoreRow[] rows);
+
+    void SaveItem(T item);
+
+    void SaveItems(T[] items);
+
+    void DeleteItems(CoreRow[] rows);
+}

+ 234 - 263
inabox.wpf/DynamicGrid/DynamicGrid.cs

@@ -52,191 +52,6 @@ using VirtualizingCellsControl = Syncfusion.UI.Xaml.Grid.VirtualizingCellsContro
 
 namespace InABox.DynamicGrid;
 
-public class DynamicGridRowStyle : DynamicGridStyle<VirtualizingCellsControl>
-{
-    public DynamicGridRowStyle() : base(null)
-    {
-    }
-
-    public DynamicGridRowStyle(IDynamicGridStyle source) : base(source)
-    {
-    }
-
-    public override DependencyProperty FontSizeProperty => Control.FontSizeProperty;
-
-    public override DependencyProperty FontStyleProperty => Control.FontStyleProperty;
-
-    public override DependencyProperty FontWeightProperty => Control.FontWeightProperty;
-
-    public override DependencyProperty BackgroundProperty => Control.BackgroundProperty;
-
-    public override DependencyProperty ForegroundProperty => Control.ForegroundProperty;
-}
-
-public class DynamicGridCellStyle : DynamicGridStyle<Control>
-{
-    public DynamicGridCellStyle() : base(null)
-    {
-    }
-
-    public DynamicGridCellStyle(IDynamicGridStyle source) : base(source)
-    {
-    }
-
-    public override DependencyProperty FontSizeProperty => Control.FontSizeProperty;
-
-    public override DependencyProperty FontStyleProperty => Control.FontStyleProperty;
-
-    public override DependencyProperty FontWeightProperty => Control.FontWeightProperty;
-
-    public override DependencyProperty BackgroundProperty => Control.BackgroundProperty;
-
-    public override DependencyProperty ForegroundProperty => Control.ForegroundProperty;
-}
-
-public class GridSelectionControllerExt : GridSelectionController
-{
-    public GridSelectionControllerExt(SfDataGrid datagrid)
-        : base(datagrid)
-    {
-    }
-
-    protected override void ProcessSelectedItemChanged(SelectionPropertyChangedHandlerArgs handle)
-    {
-        base.ProcessSelectedItemChanged(handle);
-        if (handle.NewValue != null)
-        {
-            //this.DataGrid.ScrollInView(this.CurrentCellManager.CurrentRowColumnIndex);
-            //int rowIndex = this.CurrentCellManager.CurrentRowColumnIndex.RowIndex;
-            var columnIndex = CurrentCellManager.CurrentRowColumnIndex.ColumnIndex;
-            var scrollRowIndex = DataGrid.GetVisualContainer().ScrollRows.LastBodyVisibleLineIndex;
-            DataGrid.ScrollInView(new RowColumnIndex(scrollRowIndex, columnIndex));
-        }
-    }
-}
-
-public class DynamicGridSummaryStyleSelector : StyleSelector
-{
-    private readonly IDynamicGrid _grid;
-
-    public DynamicGridSummaryStyleSelector(IDynamicGrid grid)
-    {
-        _grid = grid;
-    }
-
-    public override Style SelectStyle(object item, DependencyObject container)
-    {
-        var vcol = ((GridTableSummaryCell)container).ColumnBase.ColumnIndex;
-        var col = vcol > -1 && vcol < _grid.VisibleColumns.Count ? _grid.VisibleColumns[vcol] : null;
-
-        var style = new Style(typeof(GridTableSummaryCell));
-        style.Setters.Add(new Setter(Control.BackgroundProperty, new SolidColorBrush(Colors.Gainsboro)));
-        style.Setters.Add(new Setter(Control.ForegroundProperty, new SolidColorBrush(Colors.Black)));
-        style.Setters.Add(new Setter(Control.HorizontalContentAlignmentProperty,
-            col != null ? col.HorizontalAlignment(typeof(double)) : HorizontalAlignment.Right));
-        style.Setters.Add(new Setter(Control.BorderBrushProperty, new SolidColorBrush(Colors.Gray)));
-        style.Setters.Add(new Setter(Control.BorderThicknessProperty, new Thickness(0, 0, 0.75, 0)));
-        style.Setters.Add(new Setter(Control.FontSizeProperty, 12D));
-        style.Setters.Add(new Setter(Control.FontWeightProperty, FontWeights.DemiBold));
-        return style;
-    }
-}
-
-// Used to render boolean columns (the default "false" value shows what appears to be an intermediate state, which is ugly
-// This should show nothing for false, and a tick in a box for true
-public class BoolToImageConverter : 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;
-    }
-}
-
-[Serializable]
-class DynamicGridDragFormat
-{
-    private string entity;
-
-    public DataTable Table { get; set; }
-
-    public Type Entity { get => CoreUtils.GetEntity(entity); set => entity = value.EntityName(); }
-
-    public DynamicGridDragFormat(DataTable table, Type entity)
-    {
-        Table = table;
-        Entity = entity;
-    }
-}
 
 public abstract class DynamicGrid : ContentControl
 {
@@ -261,6 +76,7 @@ public abstract class DynamicGrid<T> : DynamicGrid, IDynamicGridUIComponentParen
     }
 
     private IDynamicGridUIComponent<T> UIComponent { get; set; }
+    protected IDynamicGridDataComponent<T> DataComponent { get; set; }
 
     private UIElement? _header;
     private readonly Button Add;
@@ -583,7 +399,7 @@ public abstract class DynamicGrid<T> : DynamicGrid, IDynamicGridUIComponentParen
         return !IsSequenced;
     }
 
-    T IDynamicGrid<T>.LoadItem(CoreRow row) => LoadItem(row);
+    T IDynamicGrid<T>.LoadItem(CoreRow row) => DataComponent.LoadItem(row);
 
     DynamicGridRowStyleSelector<T> IDynamicGridUIComponentParent<T>.RowStyleSelector => RowStyleSelector;
 
@@ -970,7 +786,7 @@ public abstract class DynamicGrid<T> : DynamicGrid, IDynamicGridUIComponentParen
     {
         OnAfterEditorValueChanged(null, new T[] { obj }, new AfterEditorValueChangedArgs(changedColumn, changes), changes);
 
-        SaveItem(obj);
+        DataComponent.SaveItem(obj);
 
         foreach (var (key, value) in changes)
         {
@@ -1066,14 +882,14 @@ public abstract class DynamicGrid<T> : DynamicGrid, IDynamicGridUIComponentParen
     {
 
         CoreRow[] rows = Data.Rows.Where(x => x.Index.Equals(row1) || x.Index.Equals(row2)).ToArray();
-        var items = LoadItems(rows);
+        var items = DataComponent.LoadItems(rows);
         var first = (items.First() as ISequenceable)!;
         var last = (items.Last() as ISequenceable)!;
         var iBuf1 = first.Sequence;
         var iBuf2 = last.Sequence;
         first.Sequence = iBuf2;
         last.Sequence = iBuf1;
-        SaveItems(items);
+        DataComponent.SaveItems(items);
         return true;
     }
 
@@ -1106,8 +922,6 @@ public abstract class DynamicGrid<T> : DynamicGrid, IDynamicGridUIComponentParen
 
     #region Refresh / Reload
 
-    protected abstract void Reload(Filters<T> criteria, Columns<T> columns, ref SortOrder<T>? sort, Action<CoreTable?, Exception?> action);
-
     public Filter<T>? DefineFilter()
     {
         if (OnDefineFilter is null)
@@ -1173,20 +987,10 @@ public abstract class DynamicGrid<T> : DynamicGrid, IDynamicGridUIComponentParen
             if (sort == null && IsSequenced)
                 sort = new SortOrder<T>("Sequence");
 
-            Reload(
-                criteria
-                , columns
-                , ref sort
-                , (table, exception) =>
-                {
-                    if (exception != null)
-                    {
-                        Dispatcher.Invoke(() =>
-                        {
-                            MessageWindow.ShowError("Sorry! We couldn't load the data.", exception);
-                        });
-                    }
-                    else if (table is not null)
+            DataComponent.Reload(criteria, columns, sort, result =>
+            {
+                result.DoWith(
+                    table =>
                     {
                         MasterData = table;
                         Dispatcher.Invoke(() =>
@@ -1196,9 +1000,15 @@ public abstract class DynamicGrid<T> : DynamicGrid, IDynamicGridUIComponentParen
                             bRefreshing = false;
                             IsReady = true;
                         });
-                    }
-                }
-            );
+                    },
+                    exception =>
+                    {
+                        Dispatcher.Invoke(() =>
+                        {
+                            MessageWindow.ShowError("Sorry! We couldn't load the data.", exception);
+                        });
+                    });
+            });
         }
         else
         {
@@ -1386,35 +1196,11 @@ public abstract class DynamicGrid<T> : DynamicGrid, IDynamicGridUIComponentParen
 
     #region Load/Save/Delete
 
-    public virtual T[] LoadItems(CoreRow[] rows)
-    {
-        var result = new List<T>();
-        foreach (var row in rows)
-        {
-            var index = Data.Rows.IndexOf(row);
-            result.Add(LoadItem(row));
-        }
-
-        return result.ToArray();
-    }
-
-    public abstract T LoadItem(CoreRow row);
-
-    public abstract void SaveItem(T item);
-
-    public virtual void SaveItems(T[] items)
-    {
-        foreach (var item in items)
-            SaveItem(item);
-    }
-
     protected virtual bool CanDeleteItems(params CoreRow[] rows)
     {
         return true;
     }
 
-    public abstract void DeleteItems(params CoreRow[] rows);
-
     protected virtual void DoDelete()
     {
         var rows = SelectedRows.ToArray();
@@ -1424,7 +1210,7 @@ public abstract class DynamicGrid<T> : DynamicGrid, IDynamicGridUIComponentParen
                 if (MessageBox.Show("Are you sure you wish to delete the selected records?", "Confirm Delete", MessageBoxButton.YesNo) ==
                     MessageBoxResult.Yes)
                 {
-                    DeleteItems(rows);
+                    DataComponent.DeleteItems(rows);
                     SelectedRows = Array.Empty<CoreRow>();
                     OnChanged?.Invoke(this, EventArgs.Empty);
                     Refresh(false, true);
@@ -1464,7 +1250,7 @@ public abstract class DynamicGrid<T> : DynamicGrid, IDynamicGridUIComponentParen
         if (IsDirectEditMode() && !OpenEditorOnDirectEdit)
         {
             var item = CreateItem();
-            SaveItem(item);
+            DataComponent.SaveItem(item);
 
             var datarow = Data.NewRow();
             ObjectToRow(item, datarow);
@@ -1591,7 +1377,7 @@ public abstract class DynamicGrid<T> : DynamicGrid, IDynamicGridUIComponentParen
                 if (items.Length == 1)
                     editor.UnloadEditorPages(false);
                 foreach (var item in items)
-                    SaveItem(item);
+                    DataComponent.SaveItem(item);
                 if (items.Length == 1)
                     editor.UnloadEditorPages(true);
 
@@ -1829,7 +1615,7 @@ public abstract class DynamicGrid<T> : DynamicGrid, IDynamicGridUIComponentParen
         {
             Stopwatch sw = new Stopwatch();
             sw.Start();
-            items = LoadItems(rows);
+            items = DataComponent.LoadItems(rows);
             //Logger.Send(LogType.Information, "DG:LoadItems", String.Format("Loaded Items: {0}ms", sw.ElapsedMilliseconds));
             sw.Stop();
         }
@@ -1862,7 +1648,7 @@ public abstract class DynamicGrid<T> : DynamicGrid, IDynamicGridUIComponentParen
 
     protected virtual IEnumerable<T> LoadDuplicatorItems(CoreRow[] rows)
     {
-        return LoadItems(rows);
+        return DataComponent.LoadItems(rows);
     }
 
     private bool DoDuplicate(Button button, CoreRow[] rows)
@@ -2000,7 +1786,7 @@ public abstract class DynamicGrid<T> : DynamicGrid, IDynamicGridUIComponentParen
                 var sequence = currow != null ? currow.Get<T, long>(c => ((ISequenceable)c).Sequence) : 0;
 
                 var postrows = Data.Rows.Where(r => !ClipBuffer.Item2.Contains(r) && r.Get<ISequenceable, long>(x => x.Sequence) >= sequence);
-                updates.AddRange(LoadItems(postrows.ToArray()));
+                updates.AddRange(DataComponent.LoadItems(postrows.ToArray()));
 
                 foreach (var update in updates)
                 {
@@ -2011,7 +1797,7 @@ public abstract class DynamicGrid<T> : DynamicGrid, IDynamicGridUIComponentParen
 
             if (updates.Any())
             {
-                SaveItems(updates.ToArray());
+                DataComponent.SaveItems(updates.ToArray());
                 Refresh(false, true);
             }
         }
@@ -2077,7 +1863,7 @@ public abstract class DynamicGrid<T> : DynamicGrid, IDynamicGridUIComponentParen
         );
         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.OnSave += (sender, entity) => DataComponent.SaveItem(entity as T);
         list.OnLoad += (sender, type, fields, id) => LoadImportKeys(fields);
         list.ShowDialog();
         Refresh(false, true);
@@ -2143,35 +1929,35 @@ public abstract class DynamicGrid<T> : DynamicGrid, IDynamicGridUIComponentParen
         }
 
         var sort = LookupFactory.DefineSort<T>();
-        Reload(filters, reloadColumns, ref sort, (data, err) => Dispatcher.Invoke(() =>
+        DataComponent.Reload(filters, reloadColumns, sort, result => Dispatcher.Invoke(() =>
         {
-            if (data is not null)
-            {
-                var newData = new CoreTable();
-                foreach (var column in columns.Items)
-                    newData.Columns.Add(new CoreColumn { ColumnName = column.Property, DataType = column.Type });
-
-                FilterRows(data, newData, filter: row =>
+            result.DoWith(
+                data =>
                 {
-                    foreach(var (_, predicate) in predicates)
+                    var newData = new CoreTable();
+                    foreach (var column in columns.Items)
+                        newData.Columns.Add(new CoreColumn { ColumnName = column.Property, DataType = column.Type });
+
+                    FilterRows(data, newData, filter: row =>
                     {
-                        if (!predicate(row))
+                        foreach (var (_, predicate) in predicates)
                         {
-                            return false;
+                            if (!predicate(row))
+                            {
+                                return false;
+                            }
                         }
-                    }
-                    return true;
+                        return true;
+                    });
+
+                    var list = new List<Tuple<Type?, CoreTable>>() { new(typeof(T), newData) };
+                    list.AddRange(LoadExportTables(filters, otherColumns));
+                    DoExportTables(list);
+                },
+                err =>
+                {
+                    MessageWindow.ShowError("Error in export", err);
                 });
-
-                var list = new List<Tuple<Type?, CoreTable>>() { new(typeof(T), newData) };
-                list.AddRange(LoadExportTables(filters, otherColumns));
-                DoExportTables(list);
-            }
-            else if (err is not null)
-            {
-                Logger.Send(LogType.Error, "", $"Error in export: {CoreUtils.FormatException(err)}");
-                MessageBox.Show(err.Message);
-            }
         }));
     }
 
@@ -2382,4 +2168,189 @@ public abstract class DynamicGrid<T> : DynamicGrid, IDynamicGridUIComponentParen
     }
 
     #endregion
+}
+public class DynamicGridRowStyle : DynamicGridStyle<VirtualizingCellsControl>
+{
+    public DynamicGridRowStyle() : base(null)
+    {
+    }
+
+    public DynamicGridRowStyle(IDynamicGridStyle source) : base(source)
+    {
+    }
+
+    public override DependencyProperty FontSizeProperty => Control.FontSizeProperty;
+
+    public override DependencyProperty FontStyleProperty => Control.FontStyleProperty;
+
+    public override DependencyProperty FontWeightProperty => Control.FontWeightProperty;
+
+    public override DependencyProperty BackgroundProperty => Control.BackgroundProperty;
+
+    public override DependencyProperty ForegroundProperty => Control.ForegroundProperty;
+}
+
+public class DynamicGridCellStyle : DynamicGridStyle<Control>
+{
+    public DynamicGridCellStyle() : base(null)
+    {
+    }
+
+    public DynamicGridCellStyle(IDynamicGridStyle source) : base(source)
+    {
+    }
+
+    public override DependencyProperty FontSizeProperty => Control.FontSizeProperty;
+
+    public override DependencyProperty FontStyleProperty => Control.FontStyleProperty;
+
+    public override DependencyProperty FontWeightProperty => Control.FontWeightProperty;
+
+    public override DependencyProperty BackgroundProperty => Control.BackgroundProperty;
+
+    public override DependencyProperty ForegroundProperty => Control.ForegroundProperty;
+}
+
+public class GridSelectionControllerExt : GridSelectionController
+{
+    public GridSelectionControllerExt(SfDataGrid datagrid)
+        : base(datagrid)
+    {
+    }
+
+    protected override void ProcessSelectedItemChanged(SelectionPropertyChangedHandlerArgs handle)
+    {
+        base.ProcessSelectedItemChanged(handle);
+        if (handle.NewValue != null)
+        {
+            //this.DataGrid.ScrollInView(this.CurrentCellManager.CurrentRowColumnIndex);
+            //int rowIndex = this.CurrentCellManager.CurrentRowColumnIndex.RowIndex;
+            var columnIndex = CurrentCellManager.CurrentRowColumnIndex.ColumnIndex;
+            var scrollRowIndex = DataGrid.GetVisualContainer().ScrollRows.LastBodyVisibleLineIndex;
+            DataGrid.ScrollInView(new RowColumnIndex(scrollRowIndex, columnIndex));
+        }
+    }
+}
+
+public class DynamicGridSummaryStyleSelector : StyleSelector
+{
+    private readonly IDynamicGrid _grid;
+
+    public DynamicGridSummaryStyleSelector(IDynamicGrid grid)
+    {
+        _grid = grid;
+    }
+
+    public override Style SelectStyle(object item, DependencyObject container)
+    {
+        var vcol = ((GridTableSummaryCell)container).ColumnBase.ColumnIndex;
+        var col = vcol > -1 && vcol < _grid.VisibleColumns.Count ? _grid.VisibleColumns[vcol] : null;
+
+        var style = new Style(typeof(GridTableSummaryCell));
+        style.Setters.Add(new Setter(Control.BackgroundProperty, new SolidColorBrush(Colors.Gainsboro)));
+        style.Setters.Add(new Setter(Control.ForegroundProperty, new SolidColorBrush(Colors.Black)));
+        style.Setters.Add(new Setter(Control.HorizontalContentAlignmentProperty,
+            col != null ? col.HorizontalAlignment(typeof(double)) : HorizontalAlignment.Right));
+        style.Setters.Add(new Setter(Control.BorderBrushProperty, new SolidColorBrush(Colors.Gray)));
+        style.Setters.Add(new Setter(Control.BorderThicknessProperty, new Thickness(0, 0, 0.75, 0)));
+        style.Setters.Add(new Setter(Control.FontSizeProperty, 12D));
+        style.Setters.Add(new Setter(Control.FontWeightProperty, FontWeights.DemiBold));
+        return style;
+    }
+}
+
+// Used to render boolean columns (the default "false" value shows what appears to be an intermediate state, which is ugly
+// This should show nothing for false, and a tick in a box for true
+public class BoolToImageConverter : 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;
+    }
+}
+
+[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;
+    }
 }