Procházet zdrojové kódy

Implemented support for pagination in DynamicDataGrid

Kenric Nugteren před 11 měsíci
rodič
revize
832e5ace82

+ 3 - 3
InABox.Core/Client/Client.cs

@@ -109,10 +109,10 @@ namespace InABox.Clients
         public static CoreTable Query<TEntity>(Filter<TEntity>? filter = null, Columns<TEntity>? columns = null, SortOrder<TEntity>? orderby = null, CoreRange? range = null)
             where TEntity : Entity, IRemotable, IPersistent, new()
         {
-            return new Client<TEntity>().Query(filter, columns, orderby);
+            return new Client<TEntity>().Query(filter, columns, orderby, range);
         }
 
-        public static void Query<TEntity>(Filter<TEntity>? filter, Columns<TEntity>? columns, SortOrder<TEntity>? orderby, CoreRange range, Action<CoreTable?, Exception?> callback)
+        public static void Query<TEntity>(Filter<TEntity>? filter, Columns<TEntity>? columns, SortOrder<TEntity>? orderby, CoreRange? range, Action<CoreTable?, Exception?> callback)
             where TEntity : Entity, IRemotable, IPersistent, new()
         {
             new Client<TEntity>().Query(filter, columns, orderby, range, callback);
@@ -387,7 +387,7 @@ namespace InABox.Clients
             return Query(filter as Filter<TEntity>, columns as Columns<TEntity>, sortOrder as SortOrder<TEntity>, range);
         }
 
-        public void Query(Filter<TEntity>? filter, Columns<TEntity>? columns, SortOrder<TEntity>? sort, CoreRange range, Action<CoreTable?, Exception?>? callback)
+        public void Query(Filter<TEntity>? filter, Columns<TEntity>? columns, SortOrder<TEntity>? sort, CoreRange? range, Action<CoreTable?, Exception?>? callback)
         {
             try
             {

+ 18 - 1
InABox.Core/CoreTable/CoreTable.cs

@@ -26,7 +26,7 @@ namespace InABox.Core
         public string TableName { get => tableName; }
         
         public int TotalRecords { get; set; }
-        public int Offset { get; set; }
+        public int Offset { get; set; } = 0;
 
         public IList<CoreColumn> Columns { get => columns; }
         public IList<CoreRow> Rows
@@ -101,6 +101,23 @@ namespace InABox.Core
                 Columns.Add(new CoreColumn() { ColumnName = col.ColumnName, DataType = col.DataType }); 
         }
 
+        /// <summary>
+        /// Add the rows from the given <paramref name="table"/> to this table.
+        /// </summary>
+        /// <remarks>
+        /// This method assumes that the columns of this table and <paramref name="table"/> are the same; it is intended for paging functionality.
+        /// </remarks>
+        /// <param name="table"></param>
+        public void AddPage(CoreTable table)
+        {
+            foreach(var row in table.Rows)
+            {
+                var newRow = NewRow(populate: false);
+                newRow.LoadValues(row.Values);
+                Rows.Add(newRow);
+            }
+        }
+
         public void LoadRows(IEnumerable<object> objects)
         {
             foreach (var obj in objects)

+ 33 - 1
inabox.wpf/DynamicGrid/DynamicDataGrid.cs

@@ -168,7 +168,39 @@ public class DynamicDataGrid<TEntity> : DynamicGrid<TEntity>, IDynamicDataGrid w
         criteria.Add(FilterComponent.GetFilter());
 
         OnReload?.Invoke(this, criteria, columns, ref sort);
-        new Client<TEntity>().Query(criteria.Combine(), columns, sort, null, action);
+        if(Options.PageSize > 0)
+        {
+            var inSort = sort;
+            Task.Run(() =>
+            {
+                var page = CoreRange.Database(Options.PageSize);
+                var filter = criteria.Combine();
+
+                while (true)
+                {
+                    try
+                    {
+                        var data = Client.Query(filter, columns, inSort, page);
+                        data.Offset = page.Offset;
+                        action(data, null);
+                        if (data.Rows.Count < page.Limit)
+                        {
+                            break;
+                        }
+                        page.Next();
+                    }
+                    catch (Exception e)
+                    {
+                        action(null, e);
+                        break;
+                    }
+                }
+            });
+        }
+        else
+        {
+            Client.Query(criteria.Combine(), columns, sort, action);
+        }
     }
 
     private CoreRow[]? SelectedBeforeRefresh;

+ 75 - 30
inabox.wpf/DynamicGrid/DynamicGrid.cs

@@ -998,6 +998,13 @@ public abstract class DynamicGrid<T> : DynamicGrid, IDynamicGridUIComponentParen
         return result;
     }
 
+    private class RowRange(int rowIdx, int size)
+    {
+        public int RowIdx { get; set; } = rowIdx;
+
+        public int Size { get; set; } = size;
+    }
+
     public virtual void Refresh(bool reloadcolumns, bool reloaddata)
     {
         if (bRefreshing)
@@ -1048,21 +1055,33 @@ public abstract class DynamicGrid<T> : DynamicGrid, IDynamicGridUIComponentParen
                     }
                     else if (table is not null)
                     {
-                        MasterData = table;
-                        Dispatcher.Invoke(() =>
+                        if(table.Offset == 0 || MasterData is null)
                         {
-                            ProcessData();
-                            DoAfterRefresh();
-                            bRefreshing = false;
-                            IsReady = true;
-                        });
+                            MasterData = table;
+                            Dispatcher.Invoke(() =>
+                            {
+                                ProcessData(null);
+                                DoAfterRefresh();
+                                bRefreshing = false;
+                                IsReady = true;
+                            });
+                        }
+                        else
+                        {
+                            int idx = MasterData.Rows.Count;
+                            MasterData.AddPage(table);
+                            Dispatcher.Invoke(() =>
+                            {
+                                ProcessData(new(idx, table.Rows.Count));
+                            });
+                        }
                     }
                 }
             );
         }
         else
         {
-            ProcessData();
+            ProcessData(null);
             DoAfterRefresh();
             bRefreshing = false;
             IsReady = true;
@@ -1121,15 +1140,27 @@ public abstract class DynamicGrid<T> : DynamicGrid, IDynamicGridUIComponentParen
     }
 
 
-    private void ProcessData()
+    /// <summary>
+    /// Process the data from <see cref="MasterData"/> according to <paramref name="range"/>.
+    /// </summary>
+    /// <remarks>
+    /// Set <paramref name="range"/> to <see langword="null"/> if this is the first page of data to be loaded. This will thus update the grid accordingly,
+    /// clearing all current rows, resetting columns, selection, etc. If the <paramref name="range"/> is provided, this will add to the grid the rows
+    /// according to the range from <see cref="MasterData"/>.
+    /// </remarks>
+    /// <param name="initialLoad"></param>
+    private void ProcessData(RowRange? range)
     {
-        Data.Columns.Clear();
-        Data.Setters.Clear();
-        if (MasterData != null)
-            foreach (var column in MasterData.Columns)
-                Data.Columns.Add(column);
+        if(range is null)
+        {
+            Data.Columns.Clear();
+            Data.Setters.Clear();
+            if (MasterData != null)
+                foreach (var column in MasterData.Columns)
+                    Data.Columns.Add(column);
+        }
 
-        LoadData();
+        LoadData(range);
     }
 
     protected readonly Dictionary<CoreRow, CoreRow> _recordmap = new();
@@ -1183,11 +1214,18 @@ public abstract class DynamicGrid<T> : DynamicGrid, IDynamicGridUIComponentParen
         Refresh(false, false);
     }
 
-    private void FilterRows(CoreTable from, CoreTable into, Dictionary<CoreRow, CoreRow>? recordMap = null, Func<CoreRow, bool>? filter = null)
+    /// <summary>
+    /// Filter all given rows into <paramref name="into"/>, given that they match <paramref name="filter"/> and <see cref="FilterRecord(CoreRow)"/>.
+    /// If <paramref name="recordMap"/> is given, also updates the map from <paramref name="from"/> to <paramref name="into"/>.
+    /// </summary>
+    private IList<CoreRow> FilterRows(
+        IEnumerable<CoreRow> from,
+        CoreTable into,
+        Dictionary<CoreRow, CoreRow>? recordMap = null,
+        Func<CoreRow, bool>? filter = null)
     {
-        into.Rows.Clear();
-        recordMap?.Clear();
-        foreach (var row in from.Rows.ToArray())
+        var newRows = new List<CoreRow>();
+        foreach (var row in from)
             if (FilterRecord(row) && filter?.Invoke(row) != false)
             {
                 var newrow = into.NewRow();
@@ -1196,29 +1234,36 @@ public abstract class DynamicGrid<T> : DynamicGrid, IDynamicGridUIComponentParen
                     var value = i < row.Values.Count ? row.Values[i] : null;
                     if (into.Columns[i].DataType.IsNumeric())
                         value = into.Columns[i].DataType.IsDefault(value) ? null : value;
-                    //else if (Data.Columns[i].DataType == typeof(String[]))
-                    //    value = String.Join("\n", value as String[]);
                     newrow.Values.Add(value);
                 }
 
-                //newrow.Values.AddRange(row.Values);
-                //if ((OnFilterRecord == null) || (OnFilterRecord(row)))
+                newRows.Add(newrow);
                 into.Rows.Add(newrow);
                 recordMap?.TryAdd(newrow, row);
             }
+        return newRows;
     }
 
-    private void LoadData()
+    private void LoadData(RowRange? range)
     {
-        ResetClipBuffer();
         if (MasterData is null)
             return;
-        FilterRows(MasterData, Data, _recordmap);
+        if(range is null)
+        {
+            ResetClipBuffer();
+            Data.Rows.Clear();
+            _recordmap.Clear();
 
-        InvalidateGrid();
+            FilterRows(MasterData.Rows, Data, _recordmap);
 
-        //ScrollBar.Value = _CurrentRow <= 0 ? 0 : _CurrentRow;
-        SelectedRows = Array.Empty<CoreRow>();
+            InvalidateGrid();
+            SelectedRows = Array.Empty<CoreRow>();
+        }
+        else
+        {
+            var newRows = FilterRows(Enumerable.Range(range.RowIdx, range.Size).Select(i => MasterData.Rows[i]), Data, _recordmap);
+            UIComponent.AddPage(newRows);
+        }
     }
 
     //IncrementalList<T> _data = null;
@@ -2042,7 +2087,7 @@ public abstract class DynamicGrid<T> : DynamicGrid, IDynamicGridUIComponentParen
                 var newData = new CoreTable();
                 newData.LoadColumns(columns);
 
-                FilterRows(data, newData, filter: row =>
+                FilterRows(data.Rows, newData, filter: row =>
                 {
                     foreach(var (_, predicate) in predicates)
                     {

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

@@ -77,7 +77,7 @@ public class DynamicGridOptions
         RecordCount = false;
         HideDatabaseFilters = false;
         HideDirectEditButton = false;
-		PageSize = int.MaxValue;
+		PageSize = 0;
         return EndUpdate();
     }
 
@@ -319,8 +319,11 @@ public class DynamicGridOptions
 			}
 		}
 	}
-    
-	private int _pageSize = int.MaxValue;
+
+	private int _pageSize = 0;
+	/// <summary>
+	/// The page size for loading data in pages; set to 0 for no paging functionality.
+	/// </summary>
 	public int PageSize
 	{
 		get => _pageSize;

+ 31 - 2
inabox.wpf/DynamicGrid/UIComponent/DynamicGridGridUIComponent.cs

@@ -24,6 +24,7 @@ using System.Windows.Input;
 using System.Windows.Media;
 using System.Windows.Media.Imaging;
 using System.Windows.Threading;
+using DataColumn = System.Data.DataColumn;
 using DataRow = System.Data.DataRow;
 using TimeSpanEdit = Syncfusion.Windows.Shared.TimeSpanEdit;
 
@@ -1333,9 +1334,9 @@ public class DynamicGridGridUIComponent<T> : IDynamicGridUIComponent<T>, IDynami
 
     public void RefreshData(CoreTable data)
     {
-        var defaults = new List<object?>();
         var result = new DataTable();
 
+        var defaults = new List<object?>();
         foreach (var column in data.Columns)
         {
             var colname = column.ColumnName.Replace('.', '_');
@@ -1373,6 +1374,34 @@ public class DynamicGridGridUIComponent<T> : IDynamicGridUIComponent<T>, IDynami
         UpdateRecordCount();
     }
 
+    public void AddPage(IEnumerable<CoreRow> page)
+    {
+        var table = DataGridItems;
+        if(table is null)
+        {
+            return;
+        }
+        _invalidating = true;
+
+        var defaults = new List<object?>();
+        if (!Parent.IsDirectEditMode())
+            foreach (var column in table.Columns.Cast<DataColumn>())
+            {
+                defaults.Add(column.DataType.GetDefault());
+            }
+
+        foreach(var row in page)
+        {
+            var newRow = table.NewRow();
+            CoreRowToDataRow(newRow, row, defaults);
+            table.Rows.Add(newRow);
+        }
+
+        UpdateRecordCount();
+
+        _invalidating = false;
+    }
+
     public void InvalidateRow(CoreRow row)
     {
         var table = DataGridItems;
@@ -1413,7 +1442,7 @@ public class DynamicGridGridUIComponent<T> : IDynamicGridUIComponent<T>, IDynami
 
     private static IEnumerable<object?> ProcessRow(List<object?> values, List<object?> defaults)
     {
-        if (defaults == null || !defaults.Any())
+        if (defaults == null || defaults.Count == 0)
             return values;
         var result = new List<object?>();
         for (var i = 0; i < values.Count; i++)

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

@@ -1083,6 +1083,25 @@ public class DynamicGridTreeUIComponent<T> : IDynamicGridUIComponent<T>, IDynami
         UpdateRecordCount();
     }
 
+    public void AddPage(IEnumerable<CoreRow> page)
+    {
+        if (_innerTable is null) return;
+
+        foreach(var row in page)
+        {
+            var newRow = _innerTable.NewRow();
+            ProcessRow(newRow, row);
+            _innerTable.Rows.Add(newRow);
+
+            var _id = row.Get<Guid>(IDColumn.Property);
+            var _parent = row.Get<Guid>(ParentColumn.Property);
+            Nodes.Add(_id, _parent, newRow);
+        }
+
+        CalculateRowHeight();
+        UpdateRecordCount();
+    }
+
     private void ProcessRow(CoreRow innerRow, CoreRow row)
     {
         innerRow.LoadValues(row.Values);

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

@@ -75,6 +75,7 @@ public interface IDynamicGridUIComponent<T>
     void BeforeRefresh();
     void RefreshColumns(DynamicGridColumns columns, DynamicActionColumns actionColumns, DynamicGridColumnGroupings groupings);
     void RefreshData(CoreTable data);
+    void AddPage(IEnumerable<CoreRow> page);
     void InvalidateRow(CoreRow row);
 
     void ScrollIntoView(CoreRow row);