Browse Source

Added cancellationtoken to Dynamic Grid; Added Shutdown() method to DynamicGrid

Kenric Nugteren 1 year ago
parent
commit
776193f89a

+ 4 - 1
inabox.wpf/DigitalForms/Designer/DynamicFormControlGrid.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Threading;
 using InABox.Clients;
 using InABox.Core;
 
@@ -37,7 +38,9 @@ namespace InABox.DynamicGrid
             return Items[row.Index];
         }
 
-        protected override void Reload(Filters<T> criteria, Columns<T> columns, ref SortOrder<T>? sort, Action<CoreTable?, Exception?> action)
+        protected override void Reload(
+            Filters<T> criteria, Columns<T> columns, ref SortOrder<T>? sort, 
+            CancellationToken token, Action<CoreTable?, Exception?> action)
         {
             var table = new CoreTable();
             table.LoadColumns(typeof(T));

+ 5 - 3
inabox.wpf/DigitalForms/DigitalFormGrid.cs

@@ -4,6 +4,7 @@ using System.Globalization;
 using System.IO;
 using System.Linq;
 using System.Text;
+using System.Threading;
 using System.Windows;
 using System.Windows.Controls;
 using System.Windows.Media.Imaging;
@@ -354,12 +355,13 @@ namespace InABox.DynamicGrid
             return true;
         }
 
-        protected override void Reload(Filters<DigitalForm> criteria, Columns<DigitalForm> columns, ref SortOrder<DigitalForm>? sort,
-            Action<CoreTable?, Exception?> action)
+        protected override void Reload(
+            Filters<DigitalForm> criteria, Columns<DigitalForm> columns, ref SortOrder<DigitalForm>? sort, 
+            CancellationToken token, Action<CoreTable?, Exception?> action)
         {
             if (!_showall)
                 criteria.Add(new Filter<DigitalForm>(x => x.Active).IsEqualTo(true));
-            base.Reload(criteria, columns, ref sort, action);
+            base.Reload(criteria, columns, ref sort, token, action);
         }
 
         public override DynamicEditorPages LoadEditorPages(DigitalForm item)

+ 5 - 3
inabox.wpf/DynamicGrid/AuditGrid.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Threading;
 using InABox.Core;
 
 namespace InABox.DynamicGrid
@@ -20,12 +21,13 @@ namespace InABox.DynamicGrid
 
         public Guid EntityID { get; set; }
 
-        protected override void Reload(Filters<AuditTrail> criteria, Columns<AuditTrail> columns, ref SortOrder<AuditTrail>? sort,
-            Action<CoreTable?, Exception?> action)
+        protected override void Reload(
+            Filters<AuditTrail> criteria, Columns<AuditTrail> columns, ref SortOrder<AuditTrail>? sort,
+            CancellationToken token, Action<CoreTable?, Exception?> action)
         {
             criteria.Add(new Filter<AuditTrail>(x => x.EntityID).IsEqualTo(EntityID));
             sort = new SortOrder<AuditTrail>(x => x.Timestamp, SortDirection.Descending);
-            base.Reload(criteria, columns, ref sort, action);
+            base.Reload(criteria, columns, ref sort, token, action);
         }
     }
 }

+ 4 - 2
inabox.wpf/DynamicGrid/DynamicColumnGrid.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Threading;
 using InABox.Core;
 
 namespace InABox.DynamicGrid
@@ -126,8 +127,9 @@ namespace InABox.DynamicGrid
 
         #region Save / Load
 
-        protected override void Reload(Filters<DynamicGridColumn> criteria, Columns<DynamicGridColumn> columns, ref SortOrder<DynamicGridColumn>? sort,
-            Action<CoreTable, Exception?> action)
+        protected override void Reload(
+            Filters<DynamicGridColumn> criteria, Columns<DynamicGridColumn> columns, ref SortOrder<DynamicGridColumn>? sort, 
+            CancellationToken token, Action<CoreTable, Exception?> action)
         {
             var result = new CoreTable();
             if (columns == null || columns.Count == 0)

+ 5 - 2
inabox.wpf/DynamicGrid/DynamicCrossJoinGrid.cs

@@ -4,6 +4,7 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Linq.Expressions;
 using System.Text;
+using System.Threading;
 using System.Threading.Tasks;
 
 namespace InABox.DynamicGrid;
@@ -32,13 +33,15 @@ public abstract class DynamicCrossJoinGrid<TEntity, TLeft> : DynamicDataGrid<TEn
         return columns;
     }
 
-    protected override void Reload(Filters<TEntity> criteria, Columns<TEntity> columns, ref SortOrder<TEntity>? sort, Action<CoreTable?, Exception?> action)
+    protected override void Reload(
+        Filters<TEntity> criteria, Columns<TEntity> columns, ref SortOrder<TEntity>? sort, 
+        CancellationToken token, Action<CoreTable?, Exception?> action)
     {
         var filter = new Filter<TEntity>();
         filter.Expression = CoreUtils.ExtractMemberExpression<TEntity, Guid>(LeftMapping);
         filter.Operator = Operator.IsEqualTo;
         filter.Value = CoreUtils.GetPropertyValue(Left, CoreUtils.GetFullPropertyName(LeftProperty, "."));
         criteria.Add(filter);
-        base.Reload(criteria, columns, ref sort, action);
+        base.Reload(criteria, columns, ref sort, token, action);
     }
 }

+ 11 - 7
inabox.wpf/DynamicGrid/DynamicDataGrid.cs

@@ -172,8 +172,9 @@ public class DynamicDataGrid<TEntity> : DynamicGrid<TEntity>, IDynamicDataGrid w
             : base.FormatRecordCount(count);
     }
 
-    protected override void Reload(Filters<TEntity> criteria, Columns<TEntity> columns, ref SortOrder<TEntity>? sort,
-        Action<CoreTable?, Exception?> action)
+    protected override void Reload(
+        Filters<TEntity> criteria, Columns<TEntity> columns, ref SortOrder<TEntity>? sort,
+        CancellationToken token, Action<CoreTable?, Exception?> action)
     {
         criteria.Add(FilterComponent.GetFilter());
 
@@ -188,14 +189,19 @@ public class DynamicDataGrid<TEntity> : DynamicGrid<TEntity>, IDynamicDataGrid w
                 var filter = criteria.Combine();
                 
                 IsPaging = true;
-                while (true)
+                while (!token.IsCancellationRequested)
                 {
-                    
                     try
                     {
                         var data = Client.Query(filter, columns, inSort, page);
                         data.Offset = page.Offset;
                         IsPaging = data.Rows.Count == page.Limit;
+
+                        if (token.IsCancellationRequested)
+                        {
+                            break;
+                        }
+
                         action(data, null);
                         if (!IsPaging)
                             break;
@@ -211,9 +217,7 @@ public class DynamicDataGrid<TEntity> : DynamicGrid<TEntity>, IDynamicDataGrid w
                         break;
                     }
                 }
-
-                
-            });
+            }, token);
         }
         else
         {

+ 497 - 491
inabox.wpf/DynamicGrid/DynamicDocumentGrid.cs

@@ -16,591 +16,597 @@ using Microsoft.Win32;
 using Microsoft.Xaml.Behaviors.Core;
 using Image = System.Windows.Controls.Image;
 using InABox.Wpf;
+using System.Threading;
 
-namespace InABox.DynamicGrid
+namespace InABox.DynamicGrid;
+
+
+public class DocumentConverter : AbstractConverter<object, object>
 {
+    public override object Convert(object value)
+    {
+        return value;
+    }
+}
 
-    public class DocumentConverter : AbstractConverter<object, object>
+public class TimeStampToBrushConverter : AbstractConverter<DateTime, System.Windows.Media.Brush?>
+{
+    public System.Windows.Media.Brush? Empty { get; init; }
+    public System.Windows.Media.Brush? Set { get; init; }
+    
+    public override System.Windows.Media.Brush? Convert(DateTime value)
     {
-        public override object Convert(object value)
-        {
-            return value;
-        }
+        return value.IsEmpty()
+            ? Empty
+            : Set;
     }
+}
+
+public class DynamicDocumentGrid<TDocument, TEntity, TEntityLink> : DynamicManyToManyGrid<TDocument, TEntity>
+    where TEntity : Entity, IPersistent, IRemotable, new()
+    where TDocument : Entity, IEntityDocument<TEntityLink>, IPersistent, IRemotable, new() // Entity, IPersistent, IRemotable, IManyToMany<TEntity, Document>, new()
+    where TEntityLink : EntityLink<TEntity>, new()
+{
     
-    public class TimeStampToBrushConverter : AbstractConverter<DateTime, System.Windows.Media.Brush?>
+    public bool ShowSupercededColumn { get; set; }
+
+    private bool _simpleTemplate;
+    public bool SimpleTemplate
     {
-        public System.Windows.Media.Brush? Empty { get; init; }
-        public System.Windows.Media.Brush? Set { get; init; }
-        
-        public override System.Windows.Media.Brush? Convert(DateTime value)
+        get => _simpleTemplate;
+        set
         {
-            return value.IsEmpty()
-                ? Empty
-                : Set;
-        }
+            _simpleTemplate = value;
+            RowHeight = value
+                ? 150
+                : 100;
+        } 
     }
+
+    private DynamicTemplateColumn _template;
     
-    public class DynamicDocumentGrid<TDocument, TEntity, TEntityLink> : DynamicManyToManyGrid<TDocument, TEntity>
-        where TEntity : Entity, IPersistent, IRemotable, new()
-        where TDocument : Entity, IEntityDocument<TEntityLink>, IPersistent, IRemotable, new() // Entity, IPersistent, IRemotable, IManyToMany<TEntity, Document>, new()
-        where TEntityLink : EntityLink<TEntity>, new()
+    public DynamicDocumentGrid()
     {
-        
-        public bool ShowSupercededColumn { get; set; }
+        MultiSelect = false;
+        HiddenColumns.Add(x => x.DocumentLink.ID);
+        HiddenColumns.Add(x => x.Superceded);
+        HiddenColumns.Add(x => x.DocumentLink.FileName);
+        HiddenColumns.Add(x => x.Thumbnail);
+        HiddenColumns.Add(x => x.Notes);
+        //ActionColumns.Add(new DynamicImageColumn(DocumentImage, ViewDocument) { Position = DynamicActionColumnPosition.Start });
+        //ActionColumns.Add(new DynamicImageColumn(DiskImage, SaveDocument) { Position = DynamicActionColumnPosition.Start });
+        _template = new DynamicTemplateColumn(DocumentTemplate)
+        {
+            Position = DynamicActionColumnPosition.Start,
+            Width = 0,
+            HeaderText = "Attached Documents"
+        };
+        ActionColumns.Add(_template);
+        //supercedecolumn = new DynamicImageColumn(SupercededImage, SupercedeDocument);
+        //ActionColumns.Add(supercedecolumn);
+        RowHeight = 100;
+    }
 
-        private bool _simpleTemplate;
-        public bool SimpleTemplate
+    protected override void DoDoubleClick(object sender, DynamicGridCellClickEventArgs args)
+    {
+        var doc = SelectedRows.FirstOrDefault()?.ToObject<TDocument>();
+        if (doc != null)
         {
-            get => _simpleTemplate;
-            set
-            {
-                _simpleTemplate = value;
-                RowHeight = value
-                    ? 150
-                    : 100;
-            } 
+            var editor = new DocumentEditor(new IEntityDocument[] { doc });
+            //editor.PrintAllowed = Security.IsAllowed<CanPrintFactoryFloorDrawings>();
+            editor.SaveAllowed = false;
+            editor.ShowDialog();
         }
+    }
 
-        private DynamicTemplateColumn _template;
-        
-        public DynamicDocumentGrid()
-        {
-            MultiSelect = false;
-            HiddenColumns.Add(x => x.DocumentLink.ID);
-            HiddenColumns.Add(x => x.Superceded);
-            HiddenColumns.Add(x => x.DocumentLink.FileName);
-            HiddenColumns.Add(x => x.Thumbnail);
-            HiddenColumns.Add(x => x.Notes);
-            //ActionColumns.Add(new DynamicImageColumn(DocumentImage, ViewDocument) { Position = DynamicActionColumnPosition.Start });
-            //ActionColumns.Add(new DynamicImageColumn(DiskImage, SaveDocument) { Position = DynamicActionColumnPosition.Start });
-            _template = new DynamicTemplateColumn(DocumentTemplate)
-            {
-                Position = DynamicActionColumnPosition.Start,
-                Width = 0,
-                HeaderText = "Attached Documents"
-            };
-            ActionColumns.Add(_template);
-            //supercedecolumn = new DynamicImageColumn(SupercededImage, SupercedeDocument);
-            //ActionColumns.Add(supercedecolumn);
-            RowHeight = 100;
-        }
+    private FrameworkElement DocumentTemplate(CoreRow row)
+    {
+        return SimpleTemplate
+            ? CreateSimpleTemplate()
+            : CreateDetailedTemplate();
+    }
 
-        protected override void DoDoubleClick(object sender, DynamicGridCellClickEventArgs args)
+    private FrameworkElement CreateDetailedTemplate()
+    {
+        
+        Grid grid = new Grid()
         {
-            var doc = SelectedRows.FirstOrDefault()?.ToObject<TDocument>();
-            if (doc != null)
-            {
-                var editor = new DocumentEditor(new IEntityDocument[] { doc });
-                //editor.PrintAllowed = Security.IsAllowed<CanPrintFactoryFloorDrawings>();
-                editor.SaveAllowed = false;
-                editor.ShowDialog();
+            Height = 100,
+            ContextMenu = CreateContextMenu(),
+            RowDefinitions = 
+            {
+                new RowDefinition() { Height = new GridLength(1, GridUnitType.Auto) },
+                new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) },
+            },
+            ColumnDefinitions = 
+            {
+                new ColumnDefinition() { Width = new GridLength(100) },
+                new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) },
+                new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Auto) },
             }
-        }
-
-        private FrameworkElement DocumentTemplate(CoreRow row)
+        };
+        
+        // grid.SetBinding(
+        //     Grid.BackgroundProperty,
+        //     new Binding("Superceded")
+        //     {
+        //         Converter = new TimeStampToBrushConverter()
+        //         {
+        //             Empty = new SolidColorBrush(Colors.LightYellow),
+        //             Set = new SolidColorBrush(Colors.Silver)
+        //         }
+        //     }
+        // );
+
+        Image thumbnail = new Image()
         {
-            return SimpleTemplate
-                ? CreateSimpleTemplate()
-                : CreateDetailedTemplate();
-        }
+            Stretch = Stretch.Uniform,
+            Margin = new Thickness(5, 2, 5, 2),
 
-        private FrameworkElement CreateDetailedTemplate()
-        {
-            
-            Grid grid = new Grid()
-            {
-                Height = 100,
-                ContextMenu = CreateContextMenu(),
-                RowDefinitions = 
-                {
-                    new RowDefinition() { Height = new GridLength(1, GridUnitType.Auto) },
-                    new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) },
-                },
-                ColumnDefinitions = 
-                {
-                    new ColumnDefinition() { Width = new GridLength(100) },
-                    new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) },
-                    new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Auto) },
-                }
-            };
-            
-            // grid.SetBinding(
-            //     Grid.BackgroundProperty,
-            //     new Binding("Superceded")
-            //     {
-            //         Converter = new TimeStampToBrushConverter()
-            //         {
-            //             Empty = new SolidColorBrush(Colors.LightYellow),
-            //             Set = new SolidColorBrush(Colors.Silver)
-            //         }
-            //     }
-            // );
-
-            Image thumbnail = new Image()
-            {
-                Stretch = Stretch.Uniform,
-                Margin = new Thickness(5, 2, 5, 2),
+        };
 
-            };
+        var ttImage = new Image();
+        ttImage.SetBinding(Image.SourceProperty,
+            new Binding("Thumbnail") { Converter = new BytesToBitmapImageConverter() });
+        thumbnail.ToolTip = new ToolTip()
+        {
+            Content = ttImage
+        };
+
+        thumbnail.SetBinding(Image.SourceProperty,
+            new Binding("Thumbnail") { Converter = new BytesToBitmapImageConverter() });
+        thumbnail.SetValue(Grid.RowProperty, 0);
+        thumbnail.SetValue(Grid.RowSpanProperty, 2);
+        thumbnail.SetValue(Grid.ColumnProperty, 0);
+        grid.Children.Add(thumbnail);
+
+        var dock = new DockPanel();
+        dock.SetValue(Grid.RowProperty, 0);
+        dock.SetValue(Grid.ColumnProperty, 1);
+        grid.Children.Add(dock);
+
+        var superceded = new Label()
+        {
+            FontWeight = FontWeights.Bold,
+            Content = "*** SUPERCEDED ***",
+            Margin = new Thickness(0, 0, 5, 0)
+        };
+        superceded.SetBinding(Label.VisibilityProperty,
+            new Binding("Superceded") { Converter = new DateTimeToVisibilityConverter() });
+        superceded.SetValue(DockPanel.DockProperty, Dock.Left);
+        dock.Children.Add(superceded);
+
+        var filename = new Label()
+        {
+            FontWeight = FontWeights.Bold
+        };
+        filename.SetBinding(Label.ContentProperty, new Binding("DocumentLink_FileName"));
+        filename.SetValue(DockPanel.DockProperty, Dock.Left);
+        dock.Children.Add(filename);
 
-            var ttImage = new Image();
-            ttImage.SetBinding(Image.SourceProperty,
-                new Binding("Thumbnail") { Converter = new BytesToBitmapImageConverter() });
-            thumbnail.ToolTip = new ToolTip()
-            {
-                Content = ttImage
-            };
-
-            thumbnail.SetBinding(Image.SourceProperty,
-                new Binding("Thumbnail") { Converter = new BytesToBitmapImageConverter() });
-            thumbnail.SetValue(Grid.RowProperty, 0);
-            thumbnail.SetValue(Grid.RowSpanProperty, 2);
-            thumbnail.SetValue(Grid.ColumnProperty, 0);
-            grid.Children.Add(thumbnail);
-
-            var dock = new DockPanel();
-            dock.SetValue(Grid.RowProperty, 0);
-            dock.SetValue(Grid.ColumnProperty, 1);
-            grid.Children.Add(dock);
-
-            var superceded = new Label()
-            {
-                FontWeight = FontWeights.Bold,
-                Content = "*** SUPERCEDED ***",
-                Margin = new Thickness(0, 0, 5, 0)
-            };
-            superceded.SetBinding(Label.VisibilityProperty,
-                new Binding("Superceded") { Converter = new DateTimeToVisibilityConverter() });
-            superceded.SetValue(DockPanel.DockProperty, Dock.Left);
-            dock.Children.Add(superceded);
-
-            var filename = new Label()
-            {
-                FontWeight = FontWeights.Bold
-            };
-            filename.SetBinding(Label.ContentProperty, new Binding("DocumentLink_FileName"));
-            filename.SetValue(DockPanel.DockProperty, Dock.Left);
-            dock.Children.Add(filename);
+        var buttons = new StackPanel()
+        {
+            Orientation = Orientation.Horizontal
+        };
+        buttons.SetValue(Grid.RowProperty, 0);
+        buttons.SetValue(Grid.ColumnProperty, 2);
+        grid.Children.Add(buttons);
 
-            var buttons = new StackPanel()
-            {
-                Orientation = Orientation.Horizontal
-            };
-            buttons.SetValue(Grid.RowProperty, 0);
-            buttons.SetValue(Grid.ColumnProperty, 2);
-            grid.Children.Add(buttons);
+        var view = new Button()
+        {
+            Content = new Image() { Source = Wpf.Resources.multi_image.AsBitmapImage() },
+            BorderBrush = new SolidColorBrush(Colors.Transparent),
+            Background = new SolidColorBrush(Colors.Transparent),
+            Height = 32,
+            Width = 32,
+            ToolTip = "View Documents",
+            Command = new ActionCommand(ViewDocuments)
+        };
+        buttons.Children.Add(view);
+
+
+        var copy = new Button()
+        {
+            Content = new Image() { Source = Wpf.Resources.copy.AsBitmapImage() },
+            BorderBrush = new SolidColorBrush(Colors.Transparent),
+            Background = new SolidColorBrush(Colors.Transparent),
+            Height = 32,
+            Width = 32,
+            ToolTip = "Copy to Clipboard",
+            Command = new ActionCommand(CopyDocuments)
+        };
+        buttons.Children.Add(copy);
+
+        var save = new Button()
+        {
+            Content = new Image() { Source = Wpf.Resources.download.AsBitmapImage() },
+            BorderBrush = new SolidColorBrush(Colors.Transparent),
+            Background = new SolidColorBrush(Colors.Transparent),
+            Height = 32,
+            Width = 32,
+            ToolTip = "Save Documents",
+            Command = new ActionCommand(SaveDocuments)
+        };
+        buttons.Children.Add(save);
+
+        var print = new Button()
+        {
+            Content = new Image() { Source = Wpf.Resources.print.AsBitmapImage(), Margin = new Thickness(2) },
+            BorderBrush = new SolidColorBrush(Colors.Transparent),
+            Background = new SolidColorBrush(Colors.Transparent),
+            Height = 32,
+            Width = 32,
+            ToolTip = "Print Documents",
+            Command = new ActionCommand(PrintDocuments)
+        };
+        buttons.Children.Add(print);
+
+        var notes = new Label()
+        {
+        };
+        notes.SetBinding(Label.ContentProperty, new Binding("Notes"));
+        notes.SetValue(Grid.RowProperty, 1);
+        notes.SetValue(Grid.ColumnProperty, 1);
+        notes.SetValue(Grid.ColumnSpanProperty, 2);
+        grid.Children.Add(notes);
+
+        return grid;
+    }
 
-            var view = new Button()
-            {
-                Content = new Image() { Source = Wpf.Resources.multi_image.AsBitmapImage() },
-                BorderBrush = new SolidColorBrush(Colors.Transparent),
-                Background = new SolidColorBrush(Colors.Transparent),
-                Height = 32,
-                Width = 32,
-                ToolTip = "View Documents",
-                Command = new ActionCommand(ViewDocuments)
-            };
-            buttons.Children.Add(view);
-
-
-            var copy = new Button()
-            {
-                Content = new Image() { Source = Wpf.Resources.copy.AsBitmapImage() },
-                BorderBrush = new SolidColorBrush(Colors.Transparent),
-                Background = new SolidColorBrush(Colors.Transparent),
-                Height = 32,
-                Width = 32,
-                ToolTip = "Copy to Clipboard",
-                Command = new ActionCommand(CopyDocuments)
-            };
-            buttons.Children.Add(copy);
-
-            var save = new Button()
-            {
-                Content = new Image() { Source = Wpf.Resources.download.AsBitmapImage() },
-                BorderBrush = new SolidColorBrush(Colors.Transparent),
-                Background = new SolidColorBrush(Colors.Transparent),
-                Height = 32,
-                Width = 32,
-                ToolTip = "Save Documents",
-                Command = new ActionCommand(SaveDocuments)
-            };
-            buttons.Children.Add(save);
-
-            var print = new Button()
-            {
-                Content = new Image() { Source = Wpf.Resources.print.AsBitmapImage(), Margin = new Thickness(2) },
-                BorderBrush = new SolidColorBrush(Colors.Transparent),
-                Background = new SolidColorBrush(Colors.Transparent),
-                Height = 32,
-                Width = 32,
-                ToolTip = "Print Documents",
-                Command = new ActionCommand(PrintDocuments)
-            };
-            buttons.Children.Add(print);
-
-            var notes = new Label()
-            {
-            };
-            notes.SetBinding(Label.ContentProperty, new Binding("Notes"));
-            notes.SetValue(Grid.RowProperty, 1);
-            notes.SetValue(Grid.ColumnProperty, 1);
-            notes.SetValue(Grid.ColumnSpanProperty, 2);
-            grid.Children.Add(notes);
-
-            return grid;
-        }
+    private ContextMenu CreateContextMenu()
+    {
+        var menu = new ContextMenu();
+        menu.Items.Add(new MenuItem()
+        {
+            Header = "View Documents",
+            Command = new ActionCommand(ViewDocuments)
+        });
+        menu.Items.Add(new MenuItem()
+        {
+            Header = "Copy To Clipboard",
+            Command = new ActionCommand(CopyDocuments)
+        });
+        menu.Items.Add(new MenuItem()
+        {
+            Header = "Save Documents",
+            Command = new ActionCommand(SaveDocuments)
+        });
+        return menu;
+    }
 
-        private ContextMenu CreateContextMenu()
+    private FrameworkElement CreateSimpleTemplate()
+    {
+        Grid grid = new Grid()
         {
-            var menu = new ContextMenu();
-            menu.Items.Add(new MenuItem()
+            //Height = 150,
+            ContextMenu = CreateContextMenu(),
+            RowDefinitions = 
             {
-                Header = "View Documents",
-                Command = new ActionCommand(ViewDocuments)
-            });
-            menu.Items.Add(new MenuItem()
+                new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) },
+                new RowDefinition() { Height = new GridLength(1, GridUnitType.Auto) },
+            },
+            ColumnDefinitions = 
             {
-                Header = "Copy To Clipboard",
-                Command = new ActionCommand(CopyDocuments)
-            });
-            menu.Items.Add(new MenuItem()
-            {
-                Header = "Save Documents",
-                Command = new ActionCommand(SaveDocuments)
-            });
-            return menu;
-        }
+                new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) },
+            }
+        };
 
-        private FrameworkElement CreateSimpleTemplate()
+        Image thumbnail = new Image()
         {
-            Grid grid = new Grid()
-            {
-                //Height = 150,
-                ContextMenu = CreateContextMenu(),
-                RowDefinitions = 
-                {
-                    new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) },
-                    new RowDefinition() { Height = new GridLength(1, GridUnitType.Auto) },
-                },
-                ColumnDefinitions = 
-                {
-                    new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) },
-                }
-            };
-  
-            Image thumbnail = new Image()
-            {
-                Stretch = Stretch.Uniform,
-                Margin = new Thickness(5),
-                //HorizontalAlignment = HorizontalAlignment.Stretch,
-                //VerticalAlignment = VerticalAlignment.Stretch
-            };
-                
-            thumbnail.SetBinding(Image.SourceProperty, new Binding("Thumbnail") { Converter = new BytesToBitmapImageConverter() });
-            thumbnail.SetValue(Grid.RowProperty,0);
-            grid.Children.Add(thumbnail);
+            Stretch = Stretch.Uniform,
+            Margin = new Thickness(5),
+            //HorizontalAlignment = HorizontalAlignment.Stretch,
+            //VerticalAlignment = VerticalAlignment.Stretch
+        };
             
-            var filename = new Label()
-            {
-                HorizontalContentAlignment = HorizontalAlignment.Center,
-                FontSize = 10
-            };
-            filename.SetBinding(Label.ContentProperty, new Binding("DocumentLink_FileName"));
-            filename.SetValue(Grid.RowProperty,1);
-            grid.Children.Add(filename);
-            
-            return grid;
-        }
-
-        private void GetDocuments(Action<Dictionary<string,byte[]>> action)
+        thumbnail.SetBinding(Image.SourceProperty, new Binding("Thumbnail") { Converter = new BytesToBitmapImageConverter() });
+        thumbnail.SetValue(Grid.RowProperty,0);
+        grid.Children.Add(thumbnail);
+        
+        var filename = new Label()
         {
-            var ids = SelectedRows.Select(r => r.Get<IEntityDocument, Guid>(c => c.DocumentLink.ID)).ToArray();
-            var files = Client.Query(
-                new Filter<Document>(x => x.ID).InList(ids),
-                Columns.None<Document>().Add(x => x.FileName).Add(x => x.Data)
-            ).ToDictionary<Document, String, byte[]>(x => x.FileName, x => x.Data);
-            action?.Invoke(files);
-        }
+            HorizontalContentAlignment = HorizontalAlignment.Center,
+            FontSize = 10
+        };
+        filename.SetBinding(Label.ContentProperty, new Binding("DocumentLink_FileName"));
+        filename.SetValue(Grid.RowProperty,1);
+        grid.Children.Add(filename);
+        
+        return grid;
+    }
 
-        private static string SanitiseFileName(string filename)
-        {
-            var basefilename = Path.GetFileNameWithoutExtension(filename);
-            var extension = Path.GetExtension(filename);
-            return Path.ChangeExtension(string.Join("_", basefilename.Split(Path.GetInvalidFileNameChars())), extension);
-        }
+    private void GetDocuments(Action<Dictionary<string,byte[]>> action)
+    {
+        var ids = SelectedRows.Select(r => r.Get<IEntityDocument, Guid>(c => c.DocumentLink.ID)).ToArray();
+        var files = Client.Query(
+            new Filter<Document>(x => x.ID).InList(ids),
+            Columns.None<Document>().Add(x => x.FileName).Add(x => x.Data)
+        ).ToDictionary<Document, String, byte[]>(x => x.FileName, x => x.Data);
+        action?.Invoke(files);
+    }
+
+    private static string SanitiseFileName(string filename)
+    {
+        var basefilename = Path.GetFileNameWithoutExtension(filename);
+        var extension = Path.GetExtension(filename);
+        return Path.ChangeExtension(string.Join("_", basefilename.Split(Path.GetInvalidFileNameChars())), extension);
+    }
 
-        private void ViewDocuments()
+    private void ViewDocuments()
+    {
+        GetDocuments((files) =>
         {
-            GetDocuments((files) =>
+            
+            foreach (var file in files)
             {
-                
-                foreach (var file in files)
+                Task.Run(() =>
                 {
-                    Task.Run(() =>
+                    var tempfile = Path.Combine(System.IO.Path.GetTempPath(), SanitiseFileName(file.Key));
+                    try
                     {
-                        var tempfile = Path.Combine(System.IO.Path.GetTempPath(), SanitiseFileName(file.Key));
-                        try
-                        {
-                            File.WriteAllBytes(tempfile, file.Value);
-                        }
-                        catch
-                        {
-                            // Outlook likes to keep files open apparently, which breaks this code.
-                        }
-                        var info = new System.Diagnostics.ProcessStartInfo(tempfile);
-                        info.UseShellExecute = true;
-                        info.Verb = "Open";
-                        Process.Start(info);
-                    });
-                }
-            });
-        }
+                        File.WriteAllBytes(tempfile, file.Value);
+                    }
+                    catch
+                    {
+                        // Outlook likes to keep files open apparently, which breaks this code.
+                    }
+                    var info = new System.Diagnostics.ProcessStartInfo(tempfile);
+                    info.UseShellExecute = true;
+                    info.Verb = "Open";
+                    Process.Start(info);
+                });
+            }
+        });
+    }
 
-        private void CopyDocuments()
+    private void CopyDocuments()
+    {
+        if (SelectedRows?.Any() != true)
+            return;
+        GetDocuments((files) =>
         {
-            if (SelectedRows?.Any() != true)
-                return;
-            GetDocuments((files) =>
+            System.Collections.Specialized.StringCollection FileCollection = new System.Collections.Specialized.StringCollection();
+            foreach(var file in files)
             {
-                System.Collections.Specialized.StringCollection FileCollection = new System.Collections.Specialized.StringCollection();
-                foreach(var file in files)
-                {
-                    var tempfile = Path.Combine(System.IO.Path.GetTempPath(), SanitiseFileName(file.Key));
-                    File.WriteAllBytes(tempfile, file.Value);
-                    FileCollection.Add(tempfile);
-                }
-                Clipboard.SetFileDropList(FileCollection);
-            });
-        }
+                var tempfile = Path.Combine(System.IO.Path.GetTempPath(), SanitiseFileName(file.Key));
+                File.WriteAllBytes(tempfile, file.Value);
+                FileCollection.Add(tempfile);
+            }
+            Clipboard.SetFileDropList(FileCollection);
+        });
+    }
 
-        private void SaveDocuments()
+    private void SaveDocuments()
+    {
+        if (SelectedRows?.Any() != true)
+            return;
+        
+        using(var fbd = new System.Windows.Forms.FolderBrowserDialog())
         {
-            if (SelectedRows?.Any() != true)
-                return;
-            
-            using(var fbd = new System.Windows.Forms.FolderBrowserDialog())
+            var result = fbd.ShowDialog();
+            if (result == System.Windows.Forms.DialogResult.OK && !string.IsNullOrWhiteSpace(fbd.SelectedPath))
             {
-                var result = fbd.ShowDialog();
-                if (result == System.Windows.Forms.DialogResult.OK && !string.IsNullOrWhiteSpace(fbd.SelectedPath))
+                var path = fbd.SelectedPath;
+                GetDocuments(files =>
                 {
-                    var path = fbd.SelectedPath;
-                    GetDocuments(files =>
-                    {
-                        foreach (var file in files)
-                            File.WriteAllBytes(Path.Combine(path, SanitiseFileName(file.Key)), file.Value);
-                    });
-                }
+                    foreach (var file in files)
+                        File.WriteAllBytes(Path.Combine(path, SanitiseFileName(file.Key)), file.Value);
+                });
             }
-
         }
 
-        private void PrintDocuments()
+    }
+
+    private void PrintDocuments()
+    {
+        if (SelectedRows?.Any() != true)
+            return;
+        GetDocuments(files =>
         {
-            if (SelectedRows?.Any() != true)
-                return;
-            GetDocuments(files =>
+            Task.Run(() =>
             {
-                Task.Run(() =>
+                foreach (var file in files)
                 {
-                    foreach (var file in files)
-                    {
-                        var tempfile = Path.Combine(System.IO.Path.GetTempPath(), SanitiseFileName(file.Key));
-                        File.WriteAllBytes(tempfile, file.Value);
-                        var info = new System.Diagnostics.ProcessStartInfo(tempfile);
-                        info.CreateNoWindow = true;
-                        info.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
-                        info.UseShellExecute = true;
-                        info.Verb = "print";
-                        Process.Start(info);
-                    }
-                });
+                    var tempfile = Path.Combine(System.IO.Path.GetTempPath(), SanitiseFileName(file.Key));
+                    File.WriteAllBytes(tempfile, file.Value);
+                    var info = new System.Diagnostics.ProcessStartInfo(tempfile);
+                    info.CreateNoWindow = true;
+                    info.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
+                    info.UseShellExecute = true;
+                    info.Verb = "print";
+                    Process.Start(info);
+                }
             });
+        });
 
-        }
+    }
 
-        protected override DynamicGridColumns LoadColumns()
-        {
-            return new DynamicGridColumns();
-        }
-        
-        protected override void DoReconfigure(DynamicGridOptions options)
-        {
-            base.DoReconfigure(options);
-            options.SelectColumns = false;
-            options.DragTarget = true;
-        }
-        
+    protected override DynamicGridColumns LoadColumns()
+    {
+        return new DynamicGridColumns();
+    }
+    
+    protected override void DoReconfigure(DynamicGridOptions options)
+    {
+        base.DoReconfigure(options);
+        options.SelectColumns = false;
+        options.DragTarget = true;
+    }
+    
+
+    public override int Order()
+    {
+        return int.MaxValue;
+    }
 
-        public override int Order()
+    protected override void HandleDragOver(object sender, DragEventArgs e)
+    {
+        if (e.Data.GetDataPresent(DataFormats.FileDrop) || e.Data.GetDataPresent("FileGroupDescriptor"))
         {
-            return int.MaxValue;
+            e.Effects = DragDropEffects.Copy;
         }
-
-        protected override void HandleDragOver(object sender, DragEventArgs e)
+        else
         {
-            if (e.Data.GetDataPresent(DataFormats.FileDrop) || e.Data.GetDataPresent("FileGroupDescriptor"))
-            {
-                e.Effects = DragDropEffects.Copy;
-            }
-            else
-            {
-                e.Effects = DragDropEffects.None;
-            }
-            e.Handled = true;
+            e.Effects = DragDropEffects.None;
         }
+        e.Handled = true;
+    }
 
-        protected override void HandleDragDrop(object sender, DragEventArgs e)
+    protected override void HandleDragDrop(object sender, DragEventArgs e)
+    {
+        var result = DocumentUtils.HandleFileDrop(e);
+        if(result is not null)
         {
-            var result = DocumentUtils.HandleFileDrop(e);
-            if(result is not null)
+            var docs = new List<Document>();
+            foreach (var (filename, stream) in result)
             {
-                var docs = new List<Document>();
-                foreach (var (filename, stream) in result)
+                var doc = new Document();
+                doc.FileName = Path.GetFileName(filename).ToLower();
+                if (stream is null)
                 {
-                    var doc = new Document();
-                    doc.FileName = Path.GetFileName(filename).ToLower();
-                    if (stream is null)
-                    {
-                        doc.Data = File.ReadAllBytes(filename);
-                        doc.TimeStamp = new FileInfo(filename).LastWriteTime;
-                    }
-                    else
-                    {
-                        using var memoryStream = new MemoryStream();
-                        stream.CopyTo(memoryStream);
-                        doc.Data = memoryStream.ToArray();
-                        doc.TimeStamp = DateTime.Now;
-                    }
-                    doc.CRC = CoreUtils.CalculateCRC(doc.Data);
-                    docs.Add(doc);
+                    doc.Data = File.ReadAllBytes(filename);
+                    doc.TimeStamp = new FileInfo(filename).LastWriteTime;
                 }
-                AddDocuments(docs);
+                else
+                {
+                    using var memoryStream = new MemoryStream();
+                    stream.CopyTo(memoryStream);
+                    doc.Data = memoryStream.ToArray();
+                    doc.TimeStamp = DateTime.Now;
+                }
+                doc.CRC = CoreUtils.CalculateCRC(doc.Data);
+                docs.Add(doc);
             }
+            AddDocuments(docs);
         }
+    }
 
-        protected override void OnDragEnd(Type entity, CoreTable table, DragEventArgs e)
+    protected override void OnDragEnd(Type entity, CoreTable table, DragEventArgs e)
+    {
+        if (entity == typeof(Document))
         {
-            if (entity == typeof(Document))
-            {
-                var refresh = false;
-
-                var docIDS = table.Rows.Select(x => x.Get<Document, Guid>(x => x.ID)).ToArray();
+            var refresh = false;
 
-                var columns = Columns.None<Document>().Add(x => x.ID);
-                foreach (var column in VisibleColumns)
-                {
-                    if (column.ColumnName.StartsWith("DocumentLink."))
-                    {
-                        columns.Add(string.Join('.', column.ColumnName.Split('.').Skip(1)));
-                    }
-                }
-                var docs = new Client<Document>()
-                    .Query(
-                        new Filter<Document>(x => x.ID).InList(docIDS),
-                        columns);
+            var docIDS = table.Rows.Select(x => x.Get<Document, Guid>(x => x.ID)).ToArray();
 
-                foreach (var doc in docs.ToObjects<Document>())
-                {
-                    var entityDocument = new TDocument();
-                    entityDocument.EntityLink.ID = Item.ID;
-                    entityDocument.DocumentLink.ID = doc.ID;
-                    entityDocument.DocumentLink.Synchronise(doc);
-                    SaveItem(entityDocument);
-                    refresh = true;
-                }
-                if (refresh)
+            var columns = Columns.None<Document>().Add(x => x.ID);
+            foreach (var column in VisibleColumns)
+            {
+                if (column.ColumnName.StartsWith("DocumentLink."))
                 {
-                    DoChanged();
-                    Refresh(false, true);
+                    columns.Add(string.Join('.', column.ColumnName.Split('.').Skip(1)));
                 }
             }
-            else
+            var docs = new Client<Document>()
+                .Query(
+                    new Filter<Document>(x => x.ID).InList(docIDS),
+                    columns);
+
+            foreach (var doc in docs.ToObjects<Document>())
+            {
+                var entityDocument = new TDocument();
+                entityDocument.EntityLink.ID = Item.ID;
+                entityDocument.DocumentLink.ID = doc.ID;
+                entityDocument.DocumentLink.Synchronise(doc);
+                SaveItem(entityDocument);
+                refresh = true;
+            }
+            if (refresh)
             {
-                base.OnDragEnd(entity, table, e);
+                DoChanged();
+                Refresh(false, true);
             }
         }
+        else
+        {
+            base.OnDragEnd(entity, table, e);
+        }
+    }
 
-        private void AddDocuments(IList<Document> documents)
+    private void AddDocuments(IList<Document> documents)
+    {
+        if (documents.Any())
         {
-            if (documents.Any())
-            {
-                new Client<Document>().Save(documents, "Initial Upload");
-                foreach (var doc in documents)
-                {
-                    var newitem = CreateItem();
-                    var prop = GetOtherLink(newitem);
-                    prop.ID = doc.ID;
-                    prop.Synchronise(doc);
-                    SaveItem(newitem);
-                }
-                DoChanged();
-                Refresh(false, true);
+            new Client<Document>().Save(documents, "Initial Upload");
+            foreach (var doc in documents)
+            {
+                var newitem = CreateItem();
+                var prop = GetOtherLink(newitem);
+                prop.ID = doc.ID;
+                prop.Synchronise(doc);
+                SaveItem(newitem);
             }
+            DoChanged();
+            Refresh(false, true);
         }
+    }
 
-        protected override void DoAdd(bool OpenEditorOnDirectEdit = false)
+    protected override void DoAdd(bool OpenEditorOnDirectEdit = false)
+    {
+        var dlg = new OpenFileDialog();
+        dlg.Multiselect = true;
+        if (dlg.ShowDialog() == true)
         {
-            var dlg = new OpenFileDialog();
-            dlg.Multiselect = true;
-            if (dlg.ShowDialog() == true)
+            using (new WaitCursor())
             {
-                using (new WaitCursor())
+                var docs = new List<Document>();
+                foreach (var filename in dlg.FileNames)
                 {
-                    var docs = new List<Document>();
-                    foreach (var filename in dlg.FileNames)
-                    {
-                        // Create a Document
-                        var doc = new Document();
-                        doc.FileName = Path.GetFileName(filename).ToLower();
-                        doc.TimeStamp = new FileInfo(dlg.FileName).LastWriteTime;
-                        doc.Data = File.ReadAllBytes(filename);
-                        doc.CRC = CoreUtils.CalculateCRC(doc.Data);
-                        docs.Add(doc);
-                    }
-                    AddDocuments(docs);
+                    // Create a Document
+                    var doc = new Document();
+                    doc.FileName = Path.GetFileName(filename).ToLower();
+                    doc.TimeStamp = new FileInfo(dlg.FileName).LastWriteTime;
+                    doc.Data = File.ReadAllBytes(filename);
+                    doc.CRC = CoreUtils.CalculateCRC(doc.Data);
+                    docs.Add(doc);
                 }
+                AddDocuments(docs);
             }
         }
+    }
 
-        protected override void Reload(Filters<TDocument> criteria, Columns<TDocument> columns, ref SortOrder<TDocument>? sort, Action<CoreTable?, Exception?> action)
+    protected override void Reload(
+        Filters<TDocument> criteria, Columns<TDocument> columns, ref SortOrder<TDocument>? sort, 
+        CancellationToken token, Action<CoreTable?, Exception?> action)
+    {
+        base.Reload(criteria, columns, ref sort, token, (t,e) =>
         {
-            base.Reload(criteria, columns, ref sort, (t,e) =>
-            {
-                action(t,e);
-                
-                // Download Hi Res images in the background and replace them when available
-                if (t != null && SimpleTemplate)
-                {
-                    var ids = t.ExtractValues<TDocument, Guid>(x => x.DocumentLink.ID).Distinct().ToArray();
-                    Client.Query(
-                        new Filter<Document>(x => x.ID).InList(ids),
-                        Columns.None<Document>().Add(x => x.ID).Add(x => x.Data),
-                        null,
-                        null,
-                        (d, _) =>
+            if (token.IsCancellationRequested) return;
+
+            action(t,e);
+            
+            // Download Hi Res images in the background and replace them when available
+            if (t != null && SimpleTemplate)
+            {
+                var ids = t.ExtractValues<TDocument, Guid>(x => x.DocumentLink.ID).Distinct().ToArray();
+                Client.Query(
+                    new Filter<Document>(x => x.ID).InList(ids),
+                    Columns.None<Document>().Add(x => x.ID).Add(x => x.Data),
+                    null,
+                    null,
+                    (d, _) =>
+                    {
+                        if (token.IsCancellationRequested) return;
+
+                        if (d == null)
+                            return;
+                        var docs = d.ToDictionary<Document, Guid, byte[]>(x => x.ID, x => x.Data);
+                        foreach (var row in t.Rows)
                         {
-                            if (d == null)
-                                return;
-                            var docs = d.ToDictionary<Document, Guid, byte[]>(x => x.ID, x => x.Data);
-                            foreach (var row in t.Rows)
+                            if (docs.TryGetValue(row.Get<TDocument, Guid>(x => x.DocumentLink.ID),
+                                    out byte[]? data) && (data?.Any() == true))
                             {
-                                if (docs.TryGetValue(row.Get<TDocument, Guid>(x => x.DocumentLink.ID),
-                                        out byte[]? data) && (data?.Any() == true))
-                                {
-                                    if (ImageUtils.IsPdf(data))
-                                        data = ImageUtils.PDFToBitmap(data, 0);
-                                    row.Set<TDocument, byte[]>(x => x.Thumbnail!, data);
-                                }
+                                if (ImageUtils.IsPdf(data))
+                                    data = ImageUtils.PDFToBitmap(data, 0);
+                                row.Set<TDocument, byte[]>(x => x.Thumbnail!, data);
                             }
-                            Dispatcher.BeginInvoke(() => Refresh(false,false));
                         }
-                    );
-                }
+                        Dispatcher.BeginInvoke(() => Refresh(false,false));
+                    }
+                );
+            }
 
 
-            });
-        }
+        });
     }
 }

+ 4 - 2
inabox.wpf/DynamicGrid/DynamicEnclosedListGrid.cs

@@ -4,6 +4,7 @@ using System.Collections.Generic;
 using System.Globalization;
 using System.Linq;
 using System.Reflection;
+using System.Threading;
 using System.Threading.Tasks;
 using System.Windows;
 using System.Windows.Controls;
@@ -163,8 +164,9 @@ namespace InABox.DynamicGrid
                 Items.RemoveAt(row.Index);
         }
 
-        protected override void Reload(Filters<TMany> criteria, Columns<TMany> columns, ref SortOrder<TMany>? sort,
-            Action<CoreTable?, Exception?> action)
+        protected override void Reload(
+            Filters<TMany> criteria, Columns<TMany> columns, ref SortOrder<TMany>? sort, 
+            CancellationToken token, Action<CoreTable?, Exception?> action)
         {
             var results = new CoreTable();
             results.LoadColumns(typeof(TMany));

+ 6 - 3
inabox.wpf/DynamicGrid/DynamicEntityFormGrid.cs

@@ -5,6 +5,7 @@ using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
+using System.Threading;
 using System.Threading.Tasks;
 using System.Windows.Media.Imaging;
 
@@ -77,10 +78,12 @@ namespace InABox.DynamicGrid
             return result;
         }
 
-        protected override void Reload(Filters<TForm> criteria, Columns<TForm> columns, ref SortOrder<TForm>? sort, Action<CoreTable?, Exception?> action)
+        protected override void Reload(
+            Filters<TForm> criteria, Columns<TForm> columns, ref SortOrder<TForm>? sort, 
+            CancellationToken token, Action<CoreTable?, Exception?> action)
         {
-            criteria.Add(new Filter<TForm>("Parent.ID").IsEqualTo(Entity.ID));
-            base.Reload(criteria, columns, ref sort, action);
+            criteria.Add(new Filter<TForm>(x => x.Parent.ID).IsEqualTo(Entity.ID));
+            base.Reload(criteria, columns, ref sort, token, action);
         }
 
         private void BeforeSave(IDynamicEditorForm editor, BaseObject[] items)

+ 4 - 2
inabox.wpf/DynamicGrid/DynamicExportMappingGrid.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Threading;
 using System.Windows.Media.Imaging;
 using InABox.Core;
 using InABox.WPF;
@@ -51,8 +52,9 @@ namespace InABox.DynamicGrid
             return Items[row.Index];
         }
 
-        protected override void Reload(Filters<ImportMapping> criteria, Columns<ImportMapping> columns, ref SortOrder<ImportMapping>? sort,
-            Action<CoreTable?, Exception?> action)
+        protected override void Reload(
+            Filters<ImportMapping> criteria, Columns<ImportMapping> columns, ref SortOrder<ImportMapping>? sort, 
+            CancellationToken token, Action<CoreTable?, Exception?> action)
         {
             var result = new CoreTable();
             result.LoadColumns(typeof(ImportMapping));

+ 25 - 7
inabox.wpf/DynamicGrid/DynamicGrid.cs

@@ -969,7 +969,9 @@ 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);
+    protected abstract void Reload(
+        Filters<T> criteria, Columns<T> columns, ref SortOrder<T>? sort,
+        CancellationToken token, Action<CoreTable?, Exception?> action);
 
     public Filter<T>? DefineFilter()
     {
@@ -1009,6 +1011,7 @@ public abstract class DynamicGrid<T> : DynamicGrid, IDynamicGridUIComponentParen
         public int Size { get; set; } = size;
     }
 
+    private CancellationTokenSource? RefreshCancellationToken;
     public virtual void Refresh(bool reloadcolumns, bool reloaddata)
     {
         if (bRefreshing)
@@ -1044,12 +1047,19 @@ public abstract class DynamicGrid<T> : DynamicGrid, IDynamicGridUIComponentParen
             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
-                , (table, exception) =>
+                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(() =>
@@ -1100,6 +1110,11 @@ public abstract class DynamicGrid<T> : DynamicGrid, IDynamicGridUIComponentParen
             cursor = null;
         }
     }
+
+    public void Shutdown()
+    {
+        RefreshCancellationToken?.Cancel();
+    }
     
     protected void NotifyBeforeRefresh(BeforeRefreshEventArgs args) => BeforeRefresh?.Invoke(this, args);
     
@@ -1267,6 +1282,9 @@ public abstract class DynamicGrid<T> : DynamicGrid, IDynamicGridUIComponentParen
         }
         else
         {
+            var _newRows = FilterRows(Enumerable.Range(range.RowIdx, range.Size).Select(i => MasterData.Rows[i]), Data, _recordmap);
+            UIComponent.AddPage(_newRows);
+            /*
             semaphore.Wait();
             try
             {
@@ -1278,7 +1296,7 @@ public abstract class DynamicGrid<T> : DynamicGrid, IDynamicGridUIComponentParen
             finally
             {
                 semaphore.Release();
-            }
+            }*/
         }
     }
 
@@ -2096,7 +2114,7 @@ public abstract class DynamicGrid<T> : DynamicGrid, IDynamicGridUIComponentParen
         }
 
         var sort = LookupFactory.DefineSort<T>();
-        Reload(filters, reloadColumns, ref sort, (data, err) => Dispatcher.Invoke(() =>
+        Reload(filters, reloadColumns, ref sort, CancellationToken.None, (data, err) => Dispatcher.Invoke(() =>
         {
             if (data is not null)
             {

+ 4 - 1
inabox.wpf/DynamicGrid/DynamicGridFilterGrid.cs

@@ -4,6 +4,7 @@ using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
+using System.Threading;
 using System.Threading.Tasks;
 
 namespace InABox.DynamicGrid
@@ -54,7 +55,9 @@ namespace InABox.DynamicGrid
             return Filters[index];
         }
 
-        protected override void Reload(Filters<CoreFilterDefinition> criteria, Columns<CoreFilterDefinition> columns, ref SortOrder<CoreFilterDefinition>? sort, Action<CoreTable?, Exception?> action)
+        protected override void Reload(
+            Filters<CoreFilterDefinition> criteria, Columns<CoreFilterDefinition> columns, ref SortOrder<CoreFilterDefinition>? sort, 
+            CancellationToken token, Action<CoreTable?, Exception?> action)
         {
             var result = new CoreTable();
             if (columns == null || columns.Count == 0)

+ 5 - 3
inabox.wpf/DynamicGrid/DynamicImportGrid.cs

@@ -4,6 +4,7 @@ using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
 using System.IO;
 using System.Linq;
+using System.Threading;
 using System.Windows;
 using System.Windows.Forms.Design;
 using System.Windows.Media.Imaging;
@@ -258,15 +259,16 @@ namespace InABox.DynamicGrid
             //throw new NotImplementedException();
         }
 
-        protected override void Reload(Filters<Importer> criteria, Columns<Importer> columns, ref SortOrder<Importer>? sort,
-            Action<CoreTable?, Exception?> action)
+        protected override void Reload(
+            Filters<Importer> criteria, Columns<Importer> columns, ref SortOrder<Importer>? sort, 
+            CancellationToken token, Action<CoreTable?, Exception?> action)
         {
             criteria.Add(new Filter<Importer>(x => x.EntityName).IsEqualTo(EntityType.EntityName()));
 
             if (EntityID != Guid.Empty)
                 criteria.Add(new Filter<Importer>(x => x.EntityID).IsEqualTo(EntityID));
 
-            base.Reload(criteria, columns, ref sort, action);
+            base.Reload(criteria, columns, ref sort, token, action);
         }
 
         public override Importer CreateItem()

+ 4 - 2
inabox.wpf/DynamicGrid/DynamicImportMappingGrid.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Threading;
 using System.Windows;
 using System.Windows.Media.Imaging;
 using InABox.Core;
@@ -59,8 +60,9 @@ namespace InABox.DynamicGrid
             return Items[row.Index];
         }
 
-        protected override void Reload(Filters<ImportMapping> criteria, Columns<ImportMapping> columns, ref SortOrder<ImportMapping>? sort,
-            Action<CoreTable?, Exception?> action)
+        protected override void Reload(
+            Filters<ImportMapping> criteria, Columns<ImportMapping> columns, ref SortOrder<ImportMapping>? sort, 
+            CancellationToken token, Action<CoreTable?, Exception?> action)
         {
             //Lookups.Clear();
             var result = new CoreTable();

+ 4 - 1
inabox.wpf/DynamicGrid/DynamicItemsListGrid.cs

@@ -7,6 +7,7 @@ using System.Text;
 using System.Threading.Tasks;
 using Microsoft.CodeAnalysis.CSharp.Syntax;
 using Syncfusion.Windows.Tools.Controls;
+using System.Threading;
 
 namespace InABox.DynamicGrid;
 
@@ -62,7 +63,9 @@ public class DynamicItemsListGrid<T> : DynamicGrid<T>, IDynamicItemsListGrid
         return Items[_recordmap[row].Index];
     }
 
-    protected override void Reload(Filters<T> criteria, Columns<T> columns, ref SortOrder<T>? sort, Action<CoreTable?, Exception?> action)
+    protected override void Reload(
+        Filters<T> criteria, Columns<T> columns, ref SortOrder<T>? sort, 
+        CancellationToken token, Action<CoreTable?, Exception?> action)
     {
         var result = new CoreTable();
         result.LoadColumns(columns);

+ 4 - 1
inabox.wpf/DynamicGrid/DynamicManyToManyCrossTab.cs

@@ -6,6 +6,7 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Reflection;
 using System.Text;
+using System.Threading;
 using System.Threading.Tasks;
 using System.Windows.Controls;
 using System.Windows.Media.Imaging;
@@ -245,7 +246,9 @@ public abstract class DynamicManyToManyCrossTab<TManyToMany, TRow, TColumn> : Dy
         return true;
     }
 
-    protected override void Reload(Filters<TRow> criteria, Columns<TRow> columns, ref SortOrder<TRow>? sort, Action<CoreTable?, Exception?> action)
+    protected override void Reload(
+        Filters<TRow> criteria, Columns<TRow> columns, ref SortOrder<TRow>? sort, 
+        CancellationToken token, Action<CoreTable?, Exception?> action)
     {
         var filter = criteria.Add(RowFilter()).Combine();
 

+ 4 - 1
inabox.wpf/DynamicGrid/DynamicManyToManyDataGrid.cs

@@ -2,6 +2,7 @@
 using System.Collections.Generic;
 using System.Linq;
 using System.Reflection;
+using System.Threading;
 using InABox.Clients;
 using InABox.Core;
 
@@ -42,7 +43,9 @@ namespace InABox.DynamicGrid
             return result;
         }
 
-        protected override void Reload(Filters<TManyToMany> criteria, Columns<TManyToMany> columns, ref SortOrder<TManyToMany>? sort, Action<CoreTable?, Exception?> action)
+        protected override void Reload(
+            Filters<TManyToMany> criteria, Columns<TManyToMany> columns, ref SortOrder<TManyToMany>? sort, 
+            CancellationToken token, Action<CoreTable?, Exception?> action)
         {
             var expr = CoreUtils.CreateLambdaExpression<TManyToMany>(prop.Name + ".ID");
             criteria.Add(new Filter<TManyToMany>(expr).IsEqualTo(ID));

+ 4 - 2
inabox.wpf/DynamicGrid/DynamicManyToManyGrid.cs

@@ -4,6 +4,7 @@ using System.Collections.Generic;
 using System.Globalization;
 using System.Linq;
 using System.Reflection;
+using System.Threading;
 using System.Threading.Tasks;
 using System.Windows;
 using System.Windows.Controls;
@@ -369,8 +370,9 @@ public class DynamicManyToManyGrid<TManyToMany, TThis> : DynamicGrid<TManyToMany
         Ready = true;
     }
 
-    protected override void Reload(Filters<TManyToMany> criteria, Columns<TManyToMany> columns, ref SortOrder<TManyToMany>? sort,
-        Action<CoreTable?, Exception?> action)
+    protected override void Reload(
+        Filters<TManyToMany> criteria, Columns<TManyToMany> columns, ref SortOrder<TManyToMany>? sort, 
+        CancellationToken token, Action<CoreTable?, Exception?> action)
     {
         var results = new CoreTable();
         results.LoadColumns(typeof(TManyToMany));

+ 4 - 2
inabox.wpf/DynamicGrid/DynamicOneToManyGrid.cs

@@ -4,6 +4,7 @@ using System.Collections.Immutable;
 using System.Globalization;
 using System.Linq;
 using System.Reflection;
+using System.Threading;
 using System.Threading.Tasks;
 using System.Windows;
 using System.Windows.Controls;
@@ -313,8 +314,9 @@ public class DynamicOneToManyGrid<TOne, TMany> : DynamicGrid<TMany>,
     }
 
 
-    protected override void Reload(Filters<TMany> criteria, Columns<TMany> columns, ref SortOrder<TMany>? sort,
-        Action<CoreTable?, Exception?> action)
+    protected override void Reload(
+        Filters<TMany> criteria, Columns<TMany> columns, ref SortOrder<TMany>? sort,
+        CancellationToken token, Action<CoreTable?, Exception?> action)
     {
         var results = new CoreTable();
         results.LoadColumns(typeof(TMany));

+ 4 - 1
inabox.wpf/DynamicGrid/Editors/JsonEditor/JsonEditorControl.cs

@@ -2,6 +2,7 @@
 using System.Collections;
 using System.Collections.Generic;
 using System.Linq;
+using System.Threading;
 using System.Windows;
 using System.Windows.Controls;
 using System.Windows.Media;
@@ -33,7 +34,9 @@ namespace InABox.DynamicGrid
             return row.ToObject<T>();
         }
 
-        protected override void Reload(Filters<T> criteria, Columns<T> columns, ref SortOrder<T>? sort, Action<CoreTable?, Exception?> action)
+        protected override void Reload(
+            Filters<T> criteria, Columns<T> columns, ref SortOrder<T>? sort, 
+            CancellationToken token, Action<CoreTable?, Exception?> action)
         {
         }
 

+ 8 - 0
inabox.wpf/DynamicGrid/IDynamicGrid.cs

@@ -39,6 +39,14 @@ public interface IDynamicGrid
     double FontSize { get; set; }
     void Refresh(bool columns, bool data);
 
+    /// <summary>
+    /// To be called when the grid is no longer needed.
+    /// </summary>
+    /// <remarks>
+    ///     <b>Note:</b> There is no requirement to call this method, it just allows the grid to decide to stop loading data.
+    /// </remarks>
+    void Shutdown();
+
     void DoChanged();
 
     void InitialiseEditorForm(IDynamicEditorForm editor, object[] items, Func<Type, CoreTable>? pageDataHandler = null, bool preloadPages = false);

+ 5 - 3
inabox.wpf/Reports/ReportGrid.cs

@@ -11,6 +11,7 @@ using InABox.WPF;
 using OpenFileDialog = Microsoft.Win32.OpenFileDialog;
 using SaveFileDialog = Microsoft.Win32.SaveFileDialog;
 using ScriptEditor = InABox.DynamicGrid.ScriptEditorWindow;
+using System.Threading;
 
 namespace InABox.Wpf.Reports
 {
@@ -92,11 +93,12 @@ namespace InABox.Wpf.Reports
             return columns;
         }
 
-        protected override void Reload(Filters<ReportTemplate> criteria, Columns<ReportTemplate> columns, ref SortOrder<ReportTemplate>? sort,
-            Action<CoreTable?, Exception?> action)
+        protected override void Reload(
+            Filters<ReportTemplate> criteria, Columns<ReportTemplate> columns, ref SortOrder<ReportTemplate>? sort,
+            CancellationToken token, Action<CoreTable?, Exception?> action)
         {
             criteria.Add(new Filter<ReportTemplate>(x => x.DataModel).IsEqualTo(DataModel.Name).And(x => x.Section).IsEqualTo(Section));
-            base.Reload(criteria, columns, ref sort, action);
+            base.Reload(criteria, columns, ref sort, token, action);
         }
 
         private bool ReportGrid_OnEditItem(object sender, object item)