From ebd654b8534b0b162997d9a4100c96359fe009d7 Mon Sep 17 00:00:00 2001 From: "kovalev.ea" Date: Thu, 17 Sep 2015 12:40:36 +0300 Subject: [PATCH 01/23] Add a check list index --- AlphaChiTech.Virtualization/SourcePage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AlphaChiTech.Virtualization/SourcePage.cs b/AlphaChiTech.Virtualization/SourcePage.cs index f5eea2d..f5c0d64 100644 --- a/AlphaChiTech.Virtualization/SourcePage.cs +++ b/AlphaChiTech.Virtualization/SourcePage.cs @@ -184,7 +184,7 @@ public void ReplaceAt(int offset, T oldValue, T newValue, object updatedAt, IPag { if (IsSafeToUpdate(comparer, updatedAt)) { - _Items[offset] = newValue; + if (_Items.Count > offset) _Items[offset] = newValue; } } From 27f0b63c6c668a5d3d8765abf4550f7a48df2f81 Mon Sep 17 00:00:00 2001 From: "kovalev.ea" Date: Mon, 21 Sep 2015 12:17:05 +0300 Subject: [PATCH 02/23] Fix compute PlaceHolder position --- AlphaChiTech.Virtualization/PaginationManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AlphaChiTech.Virtualization/PaginationManager.cs b/AlphaChiTech.Virtualization/PaginationManager.cs index a986c91..79a18c7 100644 --- a/AlphaChiTech.Virtualization/PaginationManager.cs +++ b/AlphaChiTech.Virtualization/PaginationManager.cs @@ -1078,7 +1078,7 @@ protected ISourcePage SafeGetPage(int page, bool allowPlaceholders, object vo //Debug.WriteLine("Filling with placeholders, pagesize=" + pageSize); for (int loop = 0; loop < pageSize; loop++) { - newPage.Append(this.ProviderAsync.GetPlaceHolder(index+loop, newPage.Page, loop), null, this.ExpiryComparer); + newPage.Append(this.ProviderAsync.GetPlaceHolder(newPage.Page * pageSize + loop, newPage.Page, loop), null, this.ExpiryComparer); } ret = newPage; From 94336b8ad04290ff7c2564d50069463d520084ed Mon Sep 17 00:00:00 2001 From: Jean-Denis Kreiss Date: Mon, 24 Oct 2016 11:56:14 +0200 Subject: [PATCH 03/23] Support .Net 4 framework --- .gitignore | 10 +- .../ActionVirtualizationWrapper.cs | 46 + .../AlphaChiTech.Virtualization.Net4.csproj | 130 ++ ...ech.Virtualization.Net4.csproj.DotSettings | 2 + .../BaseActionVirtualization.cs | 32 + .../BasePagedSourceProvider.cs | 90 ++ .../BaseRepeatableActionVirtualization.cs | 86 ++ .../DateBasedPageExpiryComparer.cs | 51 + .../ExecuteResetWA.cs | 30 + .../IBaseSourceProvider.cs | 12 + .../IEditableProvider.cs | 15 + .../IItemSourceProvider.cs | 15 + .../IItemSourceProviderAsync.cs | 19 + .../INotifyCountChanged.cs | 21 + .../IPageExpiryComparer.cs | 12 + .../IPageReclaimer.cs | 38 + .../IPagedSourceProvider.cs | 16 + .../IPagedSourceProviderAsync.cs | 19 + .../IReclaimableService.cs | 12 + .../IRepeatingVirtualizationAction.cs | 13 + .../ISourcePage.cs | 47 + .../IVirtualizationAction.cs | 14 + AlphaChiTech.Virtualization.Net4/PageDelta.cs | 14 + .../PageFetchStateEnum.cs | 13 + .../PageReclaimOnTouched.cs | 64 + .../PagedSourceItemsPacket.cs | 20 + .../PagedSourceProviderMakeAsync.cs | 113 ++ .../PagedSourceProviderMakeSync.cs | 174 +++ .../PaginationManager.cs | 1355 +++++++++++++++++ .../PlaceholderReplaceWA.cs | 36 + .../Properties/AssemblyInfo.cs | 36 + .../ReclaimPagesWA.cs | 42 + .../SourcePage.cs | 234 +++ .../TaskExtension.cs | 27 + .../VirtualActionThreadModelEnum.cs | 13 + .../VirtualizationManager.cs | 130 ++ .../VirtualizingObservableCollection.cs | 900 +++++++++++ AlphaChiTech.Virtualization.Net4/app.config | 15 + .../packages.config | 6 + AlphaChiTech.Virtualization.sln | 16 +- DataGridAsyncDemo/DataGridAsyncDemo.csproj | 6 +- DataGridAsyncDemo/MainWindow.xaml.cs | 8 +- .../RemoteOrDbDataSourceAsyncProxy.cs | 29 +- 43 files changed, 3952 insertions(+), 29 deletions(-) create mode 100644 AlphaChiTech.Virtualization.Net4/ActionVirtualizationWrapper.cs create mode 100644 AlphaChiTech.Virtualization.Net4/AlphaChiTech.Virtualization.Net4.csproj create mode 100644 AlphaChiTech.Virtualization.Net4/AlphaChiTech.Virtualization.Net4.csproj.DotSettings create mode 100644 AlphaChiTech.Virtualization.Net4/BaseActionVirtualization.cs create mode 100644 AlphaChiTech.Virtualization.Net4/BasePagedSourceProvider.cs create mode 100644 AlphaChiTech.Virtualization.Net4/BaseRepeatableActionVirtualization.cs create mode 100644 AlphaChiTech.Virtualization.Net4/DateBasedPageExpiryComparer.cs create mode 100644 AlphaChiTech.Virtualization.Net4/ExecuteResetWA.cs create mode 100644 AlphaChiTech.Virtualization.Net4/IBaseSourceProvider.cs create mode 100644 AlphaChiTech.Virtualization.Net4/IEditableProvider.cs create mode 100644 AlphaChiTech.Virtualization.Net4/IItemSourceProvider.cs create mode 100644 AlphaChiTech.Virtualization.Net4/IItemSourceProviderAsync.cs create mode 100644 AlphaChiTech.Virtualization.Net4/INotifyCountChanged.cs create mode 100644 AlphaChiTech.Virtualization.Net4/IPageExpiryComparer.cs create mode 100644 AlphaChiTech.Virtualization.Net4/IPageReclaimer.cs create mode 100644 AlphaChiTech.Virtualization.Net4/IPagedSourceProvider.cs create mode 100644 AlphaChiTech.Virtualization.Net4/IPagedSourceProviderAsync.cs create mode 100644 AlphaChiTech.Virtualization.Net4/IReclaimableService.cs create mode 100644 AlphaChiTech.Virtualization.Net4/IRepeatingVirtualizationAction.cs create mode 100644 AlphaChiTech.Virtualization.Net4/ISourcePage.cs create mode 100644 AlphaChiTech.Virtualization.Net4/IVirtualizationAction.cs create mode 100644 AlphaChiTech.Virtualization.Net4/PageDelta.cs create mode 100644 AlphaChiTech.Virtualization.Net4/PageFetchStateEnum.cs create mode 100644 AlphaChiTech.Virtualization.Net4/PageReclaimOnTouched.cs create mode 100644 AlphaChiTech.Virtualization.Net4/PagedSourceItemsPacket.cs create mode 100644 AlphaChiTech.Virtualization.Net4/PagedSourceProviderMakeAsync.cs create mode 100644 AlphaChiTech.Virtualization.Net4/PagedSourceProviderMakeSync.cs create mode 100644 AlphaChiTech.Virtualization.Net4/PaginationManager.cs create mode 100644 AlphaChiTech.Virtualization.Net4/PlaceholderReplaceWA.cs create mode 100644 AlphaChiTech.Virtualization.Net4/Properties/AssemblyInfo.cs create mode 100644 AlphaChiTech.Virtualization.Net4/ReclaimPagesWA.cs create mode 100644 AlphaChiTech.Virtualization.Net4/SourcePage.cs create mode 100644 AlphaChiTech.Virtualization.Net4/TaskExtension.cs create mode 100644 AlphaChiTech.Virtualization.Net4/VirtualActionThreadModelEnum.cs create mode 100644 AlphaChiTech.Virtualization.Net4/VirtualizationManager.cs create mode 100644 AlphaChiTech.Virtualization.Net4/VirtualizingObservableCollection.cs create mode 100644 AlphaChiTech.Virtualization.Net4/app.config create mode 100644 AlphaChiTech.Virtualization.Net4/packages.config diff --git a/.gitignore b/.gitignore index 8fa7d9a..28c41a3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ -/AlphaChiTech.Virtualization/bin -/AlphaChiTech.Virtualization/obj -/DataGridDemo/bin -/DataGridDemo/obj -/packages +*\bin* +*\obj* +*.suo +packages +*/obj/* \ No newline at end of file diff --git a/AlphaChiTech.Virtualization.Net4/ActionVirtualizationWrapper.cs b/AlphaChiTech.Virtualization.Net4/ActionVirtualizationWrapper.cs new file mode 100644 index 0000000..7189d0e --- /dev/null +++ b/AlphaChiTech.Virtualization.Net4/ActionVirtualizationWrapper.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace AlphaChiTech.Virtualization +{ + /// + /// This is a VirtualAction that wraps an Action, optionally with a repeating schedule. + /// + public class ActionVirtualizationWrapper : BaseRepeatableActionVirtualization + { + private Action _Action = null; + + /// + /// Initializes a new instance of the class. + /// + /// The action. + /// The thread model. + /// if set to true [is repeating]. + /// The repeating schedule. + public ActionVirtualizationWrapper(Action action, + VirtualActionThreadModelEnum threadModel = VirtualActionThreadModelEnum.UseUIThread, + bool isRepeating = false, TimeSpan? repeatingSchedule = null) + : base(threadModel, isRepeating, repeatingSchedule) + { + _Action = action; + } + + /// + /// Does the action. + /// + public override void DoAction() + { + var a = _Action; + _LastRun = DateTime.Now; + + if (a != null) + { + a.Invoke(); + } + } + + } + +} diff --git a/AlphaChiTech.Virtualization.Net4/AlphaChiTech.Virtualization.Net4.csproj b/AlphaChiTech.Virtualization.Net4/AlphaChiTech.Virtualization.Net4.csproj new file mode 100644 index 0000000..19a5bf9 --- /dev/null +++ b/AlphaChiTech.Virtualization.Net4/AlphaChiTech.Virtualization.Net4.csproj @@ -0,0 +1,130 @@ + + + + + Debug + AnyCPU + {ECEDC613-843A-4D9A-8123-BD9600C9118E} + Library + Properties + AlphaChiTech.Virtualization + AlphaChiTech.Virtualization + v4.0 + 512 + Client + ..\ + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.dll + True + + + ..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.dll + True + + + ..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.Desktop.dll + True + + + + + ..\packages\Microsoft.Bcl.1.1.8\lib\net40\System.IO.dll + True + + + + ..\packages\Microsoft.Bcl.1.1.8\lib\net40\System.Runtime.dll + True + + + ..\packages\Microsoft.Bcl.1.1.8\lib\net40\System.Threading.Tasks.dll + True + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + + + + + \ No newline at end of file diff --git a/AlphaChiTech.Virtualization.Net4/AlphaChiTech.Virtualization.Net4.csproj.DotSettings b/AlphaChiTech.Virtualization.Net4/AlphaChiTech.Virtualization.Net4.csproj.DotSettings new file mode 100644 index 0000000..662f956 --- /dev/null +++ b/AlphaChiTech.Virtualization.Net4/AlphaChiTech.Virtualization.Net4.csproj.DotSettings @@ -0,0 +1,2 @@ + + CSharp50 \ No newline at end of file diff --git a/AlphaChiTech.Virtualization.Net4/BaseActionVirtualization.cs b/AlphaChiTech.Virtualization.Net4/BaseActionVirtualization.cs new file mode 100644 index 0000000..81d2e8f --- /dev/null +++ b/AlphaChiTech.Virtualization.Net4/BaseActionVirtualization.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace AlphaChiTech.Virtualization +{ + /// + /// Base Class that does an action on the dispatcher thread. Simply implement the DoAction method. + /// + public abstract class BaseActionVirtualization : IVirtualizationAction + { + /// + /// Gets or sets the thread model. + /// + /// + /// The thread model. + /// + public VirtualActionThreadModelEnum ThreadModel { get; set; } + + /// + /// Initializes a new instance of the class. + /// + /// The thread model. + public BaseActionVirtualization(VirtualActionThreadModelEnum threadModel) + { + this.ThreadModel = threadModel; + } + + public abstract void DoAction(); + } +} diff --git a/AlphaChiTech.Virtualization.Net4/BasePagedSourceProvider.cs b/AlphaChiTech.Virtualization.Net4/BasePagedSourceProvider.cs new file mode 100644 index 0000000..af27879 --- /dev/null +++ b/AlphaChiTech.Virtualization.Net4/BasePagedSourceProvider.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AlphaChiTech.Virtualization +{ + public class BasePagedSourceProvider : IPagedSourceProvider + { + public BasePagedSourceProvider() + { + } + + public BasePagedSourceProvider( + Func> funcGetItemsAt = null, + Func funcGetCount = null, + Func funcIndexOf = null, + Action actionOnReset = null + ) + { + this.FuncGetItemsAt = funcGetItemsAt; + this.FuncGetCount = funcGetCount; + this.FuncIndexOf = funcIndexOf; + this.ActionOnReset = actionOnReset; + } + + private Func> _FuncGetItemsAt = null; + + public Func> FuncGetItemsAt + { + get { return _FuncGetItemsAt; } + set { _FuncGetItemsAt = value; } + } + private Func _FuncGetCount = null; + + public Func FuncGetCount + { + get { return _FuncGetCount; } + set { _FuncGetCount = value; } + } + private Func _FuncIndexOf = null; + + public Func FuncIndexOf + { + get { return _FuncIndexOf; } + set { _FuncIndexOf = value; } + } + private Action _ActionOnReset = null; + + public Action ActionOnReset + { + get { return _ActionOnReset; } + set { _ActionOnReset = value; } + } + + public virtual PagedSourceItemsPacket GetItemsAt(int pageoffset, int count, bool usePlaceholder) + { + if (_FuncGetItemsAt != null) return _FuncGetItemsAt.Invoke(pageoffset, count); + + return null; + } + + public virtual int Count + { + get + { + int ret = 0; + + if (_FuncGetCount != null) ret = _FuncGetCount.Invoke(); + + return ret; + } + } + + public virtual int IndexOf(T item) + { + int ret = -1; + + if (_FuncIndexOf != null) ret = _FuncIndexOf.Invoke(item); + + return ret; + } + + public virtual void OnReset(int count) + { + if (_ActionOnReset != null) _ActionOnReset.Invoke(count); + } + } +} diff --git a/AlphaChiTech.Virtualization.Net4/BaseRepeatableActionVirtualization.cs b/AlphaChiTech.Virtualization.Net4/BaseRepeatableActionVirtualization.cs new file mode 100644 index 0000000..f30e0c1 --- /dev/null +++ b/AlphaChiTech.Virtualization.Net4/BaseRepeatableActionVirtualization.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace AlphaChiTech.Virtualization +{ + /// + /// Base class there the Action repeats on a periodic basis (the RepeatingSchedule) like BaseActionVirtualization + /// simply implement the DoAction method. + /// + public abstract class BaseRepeatableActionVirtualization : BaseActionVirtualization, IRepeatingVirtualizationAction + { + protected DateTime _LastRun = DateTime.MinValue; + private TimeSpan _RepeatingSchedule = TimeSpan.FromSeconds(1); + + /// + /// Initializes a new instance of the class. + /// + /// The thread model. + /// if set to true [is repeating]. + /// The repeating schedule. + public BaseRepeatableActionVirtualization(VirtualActionThreadModelEnum threadModel = VirtualActionThreadModelEnum.UseUIThread, + bool isRepeating = false, TimeSpan? repeatingSchedule = null) + : base(threadModel) + { + this.IsRepeating = isRepeating; + if (repeatingSchedule.HasValue) + { + this.RepeatingSchedule = repeatingSchedule.Value; + } + } + + /// + /// Gets or sets the repeating schedule. + /// + /// + /// The repeating schedule. + /// + public TimeSpan RepeatingSchedule + { + get { return _RepeatingSchedule; } + set { _RepeatingSchedule = value; } + } + + private bool _IsRepeating = false; + + /// + /// Gets or sets a value indicating whether [is repeating]. + /// + /// + /// true if [is repeating]; otherwise, false. + /// + protected bool IsRepeating + { + get { return _IsRepeating; } + set { _IsRepeating = value; } + } + + /// + /// check to see if the action should be kept. + /// + /// + public virtual bool KeepInActionsList() + { + return this.IsRepeating; + } + + /// + /// Determines whether [is due to run]. + /// + /// + public virtual bool IsDueToRun() + { + + if (DateTime.Now >= _LastRun.Add(this.RepeatingSchedule)) + { + return true; + } + + return false; + } + + } + +} diff --git a/AlphaChiTech.Virtualization.Net4/DateBasedPageExpiryComparer.cs b/AlphaChiTech.Virtualization.Net4/DateBasedPageExpiryComparer.cs new file mode 100644 index 0000000..76db637 --- /dev/null +++ b/AlphaChiTech.Virtualization.Net4/DateBasedPageExpiryComparer.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace AlphaChiTech.Virtualization +{ + /// + /// An implementation of a IPageExiryComparer that uses DateTime to see if the update should be applied + /// + public class DateBasedPageExpiryComparer : IPageExpiryComparer + { + + private static DateBasedPageExpiryComparer _Instance = new DateBasedPageExpiryComparer(); + + /// + /// Gets the default instance. + /// + /// + /// The default instance. + /// + public static DateBasedPageExpiryComparer DefaultInstance + { + get + { + return _Instance; + } + } + + /// + /// Determines whether [is update valid] [the specified page based on the updateAt]. + /// + /// The page update at - null or a DateTime. + /// The update at - null or a DateTime. + /// + public bool IsUpdateValid(object pageUpdateAt, object updateAt) + { + bool isStillValid = true; + + if (pageUpdateAt != null && updateAt != null && pageUpdateAt is DateTime && updateAt is DateTime) + { + if (((DateTime)pageUpdateAt) >= ((DateTime)updateAt)) + { + isStillValid = false; + } + } + + return isStillValid; + } + } +} diff --git a/AlphaChiTech.Virtualization.Net4/ExecuteResetWA.cs b/AlphaChiTech.Virtualization.Net4/ExecuteResetWA.cs new file mode 100644 index 0000000..ddddfd2 --- /dev/null +++ b/AlphaChiTech.Virtualization.Net4/ExecuteResetWA.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; +using System.Text; + +namespace AlphaChiTech.Virtualization +{ + public class ExecuteResetWA : BaseActionVirtualization + { + WeakReference _VOC; + + public ExecuteResetWA(VirtualizingObservableCollection voc) + : base(VirtualActionThreadModelEnum.UseUIThread) + { + _VOC = new WeakReference(voc); + } + + public override void DoAction() + { + var voc = (VirtualizingObservableCollection)_VOC.Target; + + if (voc != null && _VOC.IsAlive) + { + voc.RaiseCollectionChangedEvent(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + } + + } +} diff --git a/AlphaChiTech.Virtualization.Net4/IBaseSourceProvider.cs b/AlphaChiTech.Virtualization.Net4/IBaseSourceProvider.cs new file mode 100644 index 0000000..71482be --- /dev/null +++ b/AlphaChiTech.Virtualization.Net4/IBaseSourceProvider.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace AlphaChiTech.Virtualization +{ + public interface IBaseSourceProvider + { + void OnReset(int count); + } +} diff --git a/AlphaChiTech.Virtualization.Net4/IEditableProvider.cs b/AlphaChiTech.Virtualization.Net4/IEditableProvider.cs new file mode 100644 index 0000000..27312d0 --- /dev/null +++ b/AlphaChiTech.Virtualization.Net4/IEditableProvider.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace AlphaChiTech.Virtualization +{ + public interface IEditableProvider + { + int OnAppend(T item, object timestamp); + void OnInsert(int index, T item, object timestamp); + void OnRemove(int index, T item, object timestamp); + void OnReplace(int index, T oldItem, T newItem, object timestamp); + } +} diff --git a/AlphaChiTech.Virtualization.Net4/IItemSourceProvider.cs b/AlphaChiTech.Virtualization.Net4/IItemSourceProvider.cs new file mode 100644 index 0000000..a57b416 --- /dev/null +++ b/AlphaChiTech.Virtualization.Net4/IItemSourceProvider.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace AlphaChiTech.Virtualization +{ + public interface IItemSourceProvider : IBaseSourceProvider + { + T GetAt(int index, object voc, bool usePlaceholder); + int GetCount(bool asyncOK); + + int IndexOf(T item); + } +} diff --git a/AlphaChiTech.Virtualization.Net4/IItemSourceProviderAsync.cs b/AlphaChiTech.Virtualization.Net4/IItemSourceProviderAsync.cs new file mode 100644 index 0000000..c9358eb --- /dev/null +++ b/AlphaChiTech.Virtualization.Net4/IItemSourceProviderAsync.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AlphaChiTech.Virtualization +{ + public interface IItemSourceProviderAsync : IBaseSourceProvider + { + Task GetAt(int index, object voc, bool usePlaceholder); + + T GetPlaceHolder(int index); + + Task Count { get; } + + Task IndexOf(T item); + } +} diff --git a/AlphaChiTech.Virtualization.Net4/INotifyCountChanged.cs b/AlphaChiTech.Virtualization.Net4/INotifyCountChanged.cs new file mode 100644 index 0000000..c7f0715 --- /dev/null +++ b/AlphaChiTech.Virtualization.Net4/INotifyCountChanged.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AlphaChiTech.Virtualization +{ + public delegate void OnCountChanged(object sender, CountChangedEventArgs args); + + public class CountChangedEventArgs : EventArgs + { + public bool NeedsReset { get; set; } + public int Count { get; set; } + } + + public interface INotifyCountChanged + { + event OnCountChanged CountChanged; + } +} diff --git a/AlphaChiTech.Virtualization.Net4/IPageExpiryComparer.cs b/AlphaChiTech.Virtualization.Net4/IPageExpiryComparer.cs new file mode 100644 index 0000000..860df8c --- /dev/null +++ b/AlphaChiTech.Virtualization.Net4/IPageExpiryComparer.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace AlphaChiTech.Virtualization +{ + public interface IPageExpiryComparer + { + bool IsUpdateValid(object pageUpdateAt, object updateAt); + } +} diff --git a/AlphaChiTech.Virtualization.Net4/IPageReclaimer.cs b/AlphaChiTech.Virtualization.Net4/IPageReclaimer.cs new file mode 100644 index 0000000..9df1099 --- /dev/null +++ b/AlphaChiTech.Virtualization.Net4/IPageReclaimer.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace AlphaChiTech.Virtualization +{ + public interface IPageReclaimer + { + /// + /// Reclaims the pages. + /// + /// The pages. + /// The pages needed. + /// + IEnumerable> ReclaimPages(IEnumerable> pages, int pagesNeeded, string sectionContext); + + /// + /// Called when [page touched]. + /// + /// The page. + void OnPageTouched(ISourcePage page); + + /// + /// Called when [page released]. + /// + /// The page. + void OnPageReleased(ISourcePage page); + + /// + /// Makes the page. + /// + /// The page. + /// The size. + /// + ISourcePage MakePage(int page, int size); + } +} diff --git a/AlphaChiTech.Virtualization.Net4/IPagedSourceProvider.cs b/AlphaChiTech.Virtualization.Net4/IPagedSourceProvider.cs new file mode 100644 index 0000000..722d037 --- /dev/null +++ b/AlphaChiTech.Virtualization.Net4/IPagedSourceProvider.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace AlphaChiTech.Virtualization +{ + public interface IPagedSourceProvider : IBaseSourceProvider + { + PagedSourceItemsPacket GetItemsAt(int pageoffset, int count, bool usePlaceholder); + + int Count { get; } + + int IndexOf(T item); + } +} diff --git a/AlphaChiTech.Virtualization.Net4/IPagedSourceProviderAsync.cs b/AlphaChiTech.Virtualization.Net4/IPagedSourceProviderAsync.cs new file mode 100644 index 0000000..f85e216 --- /dev/null +++ b/AlphaChiTech.Virtualization.Net4/IPagedSourceProviderAsync.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AlphaChiTech.Virtualization +{ + public interface IPagedSourceProviderAsync : IPagedSourceProvider + { + Task> GetItemsAtAsync(int pageoffset, int count, bool usePlaceholder); + + T GetPlaceHolder(int index, int page, int offset); + + Task GetCountAsync(); + + Task IndexOfAsync( T item ); + } +} diff --git a/AlphaChiTech.Virtualization.Net4/IReclaimableService.cs b/AlphaChiTech.Virtualization.Net4/IReclaimableService.cs new file mode 100644 index 0000000..ec7ca47 --- /dev/null +++ b/AlphaChiTech.Virtualization.Net4/IReclaimableService.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace AlphaChiTech.Virtualization +{ + public interface IReclaimableService + { + void RunClaim(string sectionContext); + } +} diff --git a/AlphaChiTech.Virtualization.Net4/IRepeatingVirtualizationAction.cs b/AlphaChiTech.Virtualization.Net4/IRepeatingVirtualizationAction.cs new file mode 100644 index 0000000..a97421e --- /dev/null +++ b/AlphaChiTech.Virtualization.Net4/IRepeatingVirtualizationAction.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace AlphaChiTech.Virtualization +{ + public interface IRepeatingVirtualizationAction + { + bool KeepInActionsList(); + bool IsDueToRun(); + } +} diff --git a/AlphaChiTech.Virtualization.Net4/ISourcePage.cs b/AlphaChiTech.Virtualization.Net4/ISourcePage.cs new file mode 100644 index 0000000..d78c6a4 --- /dev/null +++ b/AlphaChiTech.Virtualization.Net4/ISourcePage.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; +using System.Text; + +namespace AlphaChiTech.Virtualization +{ + public class SourcePagePendingUpdates + { + public INotifyCollectionChanged Args { get; set; } + public Object UpdatedAt { get; set; } + } + + public interface ISourcePage + { + int Page { get; set; } + + int ItemsPerPage { get; set; } + + bool CanReclaimPage { get; } + + Object LastTouch { get; set; } + + T GetAt(int offset); + + int Append(T item, object updatedAt, IPageExpiryComparer comparer); + + int IndexOf(T item); + + void InsertAt(int offset, T item, object updatedAt, IPageExpiryComparer comparer); + + bool RemoveAt(int offset, object updatedAt, IPageExpiryComparer comparer); + + void ReplaceAt(int offset, T oldItem, T newItem, object updatedAt, IPageExpiryComparer comparer); + + PageFetchStateEnum PageFetchState { get; set; } + + Object WiredDateTime { get; set; } + + bool ReplaceNeeded(int offset); + + List PendingUpdates { get; } + + int ItemsCount { get; } + } +} diff --git a/AlphaChiTech.Virtualization.Net4/IVirtualizationAction.cs b/AlphaChiTech.Virtualization.Net4/IVirtualizationAction.cs new file mode 100644 index 0000000..3443d49 --- /dev/null +++ b/AlphaChiTech.Virtualization.Net4/IVirtualizationAction.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace AlphaChiTech.Virtualization +{ + public interface IVirtualizationAction + { + VirtualActionThreadModelEnum ThreadModel { get; } + + void DoAction(); + } +} diff --git a/AlphaChiTech.Virtualization.Net4/PageDelta.cs b/AlphaChiTech.Virtualization.Net4/PageDelta.cs new file mode 100644 index 0000000..0aafbc3 --- /dev/null +++ b/AlphaChiTech.Virtualization.Net4/PageDelta.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AlphaChiTech.Virtualization +{ + internal class PageDelta + { + public int Page { get; set; } + public int Delta { get; set; } + } +} diff --git a/AlphaChiTech.Virtualization.Net4/PageFetchStateEnum.cs b/AlphaChiTech.Virtualization.Net4/PageFetchStateEnum.cs new file mode 100644 index 0000000..280f995 --- /dev/null +++ b/AlphaChiTech.Virtualization.Net4/PageFetchStateEnum.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace AlphaChiTech.Virtualization +{ + public enum PageFetchStateEnum + { + Fetched, + Placeholders + } +} diff --git a/AlphaChiTech.Virtualization.Net4/PageReclaimOnTouched.cs b/AlphaChiTech.Virtualization.Net4/PageReclaimOnTouched.cs new file mode 100644 index 0000000..75f92df --- /dev/null +++ b/AlphaChiTech.Virtualization.Net4/PageReclaimOnTouched.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace AlphaChiTech.Virtualization +{ + /// + /// PageReclainOnTouched is a Page Reclaimer implementation that releases pages based on when + /// they where last touched. + /// + /// + public class PageReclaimOnTouched : IPageReclaimer + { + + /// + /// Reclaims the pages. + /// + /// The pages. + /// The pages needed. + /// The section context. + /// + public IEnumerable> ReclaimPages(IEnumerable> pages, int pagesNeeded, string sectionContext) + { + List> ret = new List>(); + + var candiadates = (from p in pages where p.CanReclaimPage == true orderby p.LastTouch select p).Take(pagesNeeded); + + foreach (var c in candiadates) ret.Add(c); + + return ret; + } + + /// + /// Called when [page touched]. + /// + /// The page. + public void OnPageTouched(ISourcePage page) + { + page.LastTouch = DateTime.Now; + } + + /// + /// Called when [page released]. + /// + /// The page. + public void OnPageReleased(ISourcePage page) + { + } + + /// + /// Makes the page. + /// + /// The page. + /// The size. + /// + public ISourcePage MakePage(int page, int size) + { + return new SourcePage() { Page = page, ItemsPerPage = size }; + } + + } + +} diff --git a/AlphaChiTech.Virtualization.Net4/PagedSourceItemsPacket.cs b/AlphaChiTech.Virtualization.Net4/PagedSourceItemsPacket.cs new file mode 100644 index 0000000..5df2f1d --- /dev/null +++ b/AlphaChiTech.Virtualization.Net4/PagedSourceItemsPacket.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace AlphaChiTech.Virtualization +{ + public class PagedSourceItemsPacket + { + public IEnumerable Items { get; set; } + private Object _LoadedAt = DateTime.Now; + + public Object LoadedAt + { + get { return _LoadedAt; } + set { _LoadedAt = value; } + } + + } +} diff --git a/AlphaChiTech.Virtualization.Net4/PagedSourceProviderMakeAsync.cs b/AlphaChiTech.Virtualization.Net4/PagedSourceProviderMakeAsync.cs new file mode 100644 index 0000000..c178c23 --- /dev/null +++ b/AlphaChiTech.Virtualization.Net4/PagedSourceProviderMakeAsync.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AlphaChiTech.Virtualization +{ + public class PagedSourceProviderMakeAsync : BasePagedSourceProvider, IPagedSourceProviderAsync, IProviderPreReset + { + private Func> _FuncIndexOfAsync = null; + private Func _FuncGetPlaceHolder = null; + + public PagedSourceProviderMakeAsync() + { + } + + public PagedSourceProviderMakeAsync( + Func> funcGetItemsAt = null, + Func funcGetCount = null, + Func> funcIndexOfAsync = null, + Action actionOnReset = null, + Func funcGetPlaceHolder = null, + Action actionOnBeforeReset = null + ) + : base(funcGetItemsAt, funcGetCount, null, actionOnReset) + //: base(funcGetItemsAt, funcGetCount, funcIndexOf, actionOnReset) + { + this.FuncGetPlaceHolder = funcGetPlaceHolder; + this.ActionOnBeforeReset = actionOnBeforeReset; + _FuncIndexOfAsync = funcIndexOfAsync; + } + + public virtual void OnBeforeReset() + { + if (this.ActionOnBeforeReset != null) + { + this.ActionOnBeforeReset.Invoke(); + } + } + + Action _ActionOnBeforeReset = null; + + public Action ActionOnBeforeReset + { + get { return _ActionOnBeforeReset; } + set { _ActionOnBeforeReset = value; } + } + + public Func FuncGetPlaceHolder + { + get { return _FuncGetPlaceHolder; } + set { _FuncGetPlaceHolder = value; } + } + + public Func> FuncIndexOfAsync + { + get { return _FuncIndexOfAsync; } + set { _FuncIndexOfAsync = value; } + } + + public Task> GetItemsAtAsync(int pageoffset, int count, bool usePlaceholder) + { + var tcs = new TaskCompletionSource>(); + + try + { + tcs.SetResult(this.GetItemsAt(pageoffset, count, usePlaceholder)); + } + catch (Exception e) + { + tcs.SetException(e); + } + + return tcs.Task; + } + + public virtual T GetPlaceHolder(int index, int page, int offset) + { + T ret = default(T); + + if (_FuncGetPlaceHolder != null) + ret = _FuncGetPlaceHolder.Invoke(index, page, offset); + + return ret; + } + + public Task GetCountAsync() + { + var tcs = new TaskCompletionSource(); + + try + { + tcs.SetResult(this.Count); + } catch(Exception e) + { + tcs.SetException(e); + } + + return tcs.Task; + } + + public Task IndexOfAsync( T item ) + { + var ret = default( Task ); + + if (_FuncIndexOfAsync != null) + ret = _FuncIndexOfAsync.Invoke( item ); + + return ret; + } + } +} diff --git a/AlphaChiTech.Virtualization.Net4/PagedSourceProviderMakeSync.cs b/AlphaChiTech.Virtualization.Net4/PagedSourceProviderMakeSync.cs new file mode 100644 index 0000000..1a0c99b --- /dev/null +++ b/AlphaChiTech.Virtualization.Net4/PagedSourceProviderMakeSync.cs @@ -0,0 +1,174 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace AlphaChiTech.Virtualization +{ + public class PagedSourceProviderMakeSync : IPagedSourceProviderAsync, IProviderPreReset + { + public PagedSourceProviderMakeSync() + { + } + + public PagedSourceProviderMakeSync( + Func>> funcGetItemsAtAsync = null, + Func> funcGetCountAsync = null, + Func funcIndexOf = null, + Func> funcIndexOfAsync = null, + Action actionOnReset = null, + Func funcGetPlaceHolder = null, + Action actionOnBeforeReset = null + ) + { + this.FuncGetItemsAtAsync = funcGetItemsAtAsync; + this.FuncGetCountAsync = funcGetCountAsync; + this.FuncIndexOf = funcIndexOf; + this.FuncIndexOfAsync = funcIndexOfAsync; + this.ActionOnReset = actionOnReset; + this.FuncGetPlaceHolder = funcGetPlaceHolder; + this.ActionOnBeforeReset = actionOnBeforeReset; + } + + public virtual void OnBeforeReset() + { + if(this.ActionOnBeforeReset != null) + { + this.ActionOnBeforeReset.Invoke(); + } + } + + Action _ActionOnBeforeReset = null; + + public Action ActionOnBeforeReset + { + get { return _ActionOnBeforeReset; } + set { _ActionOnBeforeReset = value; } + } + + + Func> _FuncIndexOfAsync = null; + + public Func> FuncIndexOfAsync + { + get { return _FuncIndexOfAsync; } + set { _FuncIndexOfAsync = value; } + } + + private Func>> _FuncGetItemsAtAsync = null; + + public Func>> FuncGetItemsAtAsync + { + get { return _FuncGetItemsAtAsync; } + set { _FuncGetItemsAtAsync = value; } + } + + private Func _FuncGetPlaceHolder = null; + + public Func FuncGetPlaceHolder + { + get { return _FuncGetPlaceHolder; } + set { _FuncGetPlaceHolder = value; } + } + + private Func> _FuncGetCountAsync = null; + + public Func> FuncGetCountAsync + { + get { return _FuncGetCountAsync; } + set { _FuncGetCountAsync = value; } + } + + private Func _FuncIndexOf = null; + + public Func FuncIndexOf + { + get { return _FuncIndexOf; } + set { _FuncIndexOf = value; } + } + + private Action _ActionOnReset = null; + + public Action ActionOnReset + { + get { return _ActionOnReset; } + set { _ActionOnReset = value; } + } + + public virtual Task> GetItemsAtAsync(int pageoffset, int count, bool usePlaceholder) + { + if (_FuncGetItemsAtAsync != null) + return _FuncGetItemsAtAsync.Invoke(pageoffset, count); + + return null; + } + + public virtual T GetPlaceHolder(int index, int page, int offset) + { + T ret = default(T); + + if (_FuncGetPlaceHolder != null) ret = _FuncGetPlaceHolder.Invoke(index, page, offset); + + return ret; + } + + public virtual Task GetCountAsync() + { + Task ret = null; + + if (_FuncGetCountAsync != null) + { + ret = _FuncGetCountAsync.Invoke(); + } + + return ret; + } + + public PagedSourceItemsPacket GetItemsAt(int pageoffset, int count, bool usePlaceholder) + { + PagedSourceItemsPacket ret = null; + + return Task.Factory.Run(() => GetItemsAtAsync(pageoffset, count, usePlaceholder)).Result; + + } + + public int Count + { + get { return Task.Factory.Run(() => GetCountAsync()).Result; } + } + + public virtual int IndexOf(T item) + { + int ret = -1; + + if (_FuncIndexOf != null) + { + ret = _FuncIndexOf.Invoke(item); + } + else if (_FuncIndexOfAsync != null) + { + ret = Task.Factory.Run( () => _FuncIndexOfAsync.Invoke(item)).Result; + } + else + { + ret = Task.Factory.Run(() => IndexOfAsync(item)).Result; + } + + return ret; + } + + public virtual async Task IndexOfAsync(T item) + { + int ret = -1; + + return ret; + } + + public virtual void OnReset(int count) + { + if (_ActionOnReset != null) _ActionOnReset.Invoke(count); + } + } +} diff --git a/AlphaChiTech.Virtualization.Net4/PaginationManager.cs b/AlphaChiTech.Virtualization.Net4/PaginationManager.cs new file mode 100644 index 0000000..3372f13 --- /dev/null +++ b/AlphaChiTech.Virtualization.Net4/PaginationManager.cs @@ -0,0 +1,1355 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace AlphaChiTech.Virtualization +{ + + public interface IAsyncResetProvider + { + Task GetCountAsync(); + } + + public interface IProviderPreReset + { + void OnBeforeReset(); + } + + public class PaginationManager : IItemSourceProvider, IEditableProvider, IReclaimableService, IAsyncResetProvider, IProviderPreReset, INotifyCountChanged + { + Dictionary> _Pages = new Dictionary>(); + + Dictionary _Deltas = new Dictionary(); + + Dictionary _Tasks = new Dictionary(); + + IPageReclaimer _Reclaimer = null; + + IPageExpiryComparer _ExpiryComparer = null; + + bool _HasGotCount = false; + int _LocalCount = 0; + + public IPageExpiryComparer ExpiryComparer + { + get { return _ExpiryComparer; } + set { _ExpiryComparer = value; } + } + + protected void CancelPageRequest(int page) + { + lock (_PageLock) + { + if (_Tasks.ContainsKey(page)) + { + try + { + _Tasks[page].Cancel(); + } + catch (Exception e1) + { + + } + + try + { + _Tasks.Remove(page); + } + catch (Exception e2) + { + + } + } + } + } + + protected void CancelAllRequests() + { + lock (_PageLock) + { + var c = _Tasks.Values.ToList(); + foreach (var t in c) + { + try + { + t.Cancel(false); + } + catch (Exception e) + { + + } + } + + _Tasks.Clear(); + } + } + + protected void RemovePageRequest(int page) + { + lock (_PageLock) + { + if (_Tasks.ContainsKey(page)) + { + try + { + _Tasks.Remove(page); + } + catch (Exception e) + { + + } + } + } + } + + protected CancellationTokenSource StartPageRequest(int page) + { + CancellationTokenSource cts = new CancellationTokenSource(); + + + CancelPageRequest(page); + + lock (_PageLock) + { + if (!_Tasks.ContainsKey(page)) + { + _Tasks.Add(page, cts); + } + else + { + _Tasks[page] = cts; + } + } + + return cts; + } + + /// + /// Initializes a new instance of the class. + /// + /// The provider. + /// The reclaimer. + /// The expiry comparer. + /// Size of the page. + /// The maximum pages. + /// The maximum deltas. + /// The maximum distance. + /// The section context. + public PaginationManager(IPagedSourceProvider provider, + IPageReclaimer reclaimer = null, + IPageExpiryComparer expiryComparer = null, + int pageSize = 100, + int maxPages = 100, + int maxDeltas = -1, + int maxDistance = -1, + string sectionContext = "" + ) + { + this.PageSize = pageSize; + this.MaxPages = maxPages; + this.MaxDeltas = maxDeltas; + this.MaxDistance = maxDistance; + + if (provider is IPagedSourceProviderAsync) + { + this.ProviderAsync = (IPagedSourceProviderAsync)provider; + } + else + { + this.Provider = provider; + } + + if (reclaimer != null) + { + _Reclaimer = reclaimer; + } + else + { + _Reclaimer = new PageReclaimOnTouched(); + } + + this.ExpiryComparer = expiryComparer; + + VirtualizationManager.Instance.AddAction(new ReclaimPagesWA(this, sectionContext)); + } + + + + /// + /// Adds the or update adjustment. + /// + /// The page. + /// The offset change. + public int AddOrUpdateAdjustment(int page, int offsetChange) + { + int ret = 0; + + lock (_PageLock) + { + if (!_Deltas.ContainsKey(page)) + { + if (this.MaxDeltas == -1 || _Deltas.Count < this.MaxDeltas) + { + ret = offsetChange; + _Deltas.Add(page, new PageDelta() { Page = page, Delta = offsetChange }); + } + else + { + DropAllDeltasAndPages(); + } + } + else + { + var adjustment = _Deltas[page]; + adjustment.Delta += offsetChange; + + if (adjustment.Delta == 0) + { + _Deltas.Remove(page); + } + + ret = adjustment.Delta; + } + } + + return ret; + } + + /// + /// Drops all deltas and pages. + /// + protected void DropAllDeltasAndPages() + { + lock (_PageLock) + { + _Deltas.Clear(); + _Pages.Clear(); + _BasePage = 0; + CancelAllRequests(); + } + } + + /// + /// Gets the provider as editable. + /// + /// + /// + protected IEditableProvider GetProviderAsEditable() + { + IEditableProvider ret = null; + + if (this.Provider != null) + { + ret = this.Provider as IEditableProvider; + + } + else + { + ret = this.ProviderAsync as IEditableProvider; + } + + return ret; + } + + // The _LastXXX are used for optimizations.. + private int _LastIndex = -1; + private int _LastPage = -1; + private int _LastOffset = -1; + + /// + /// Clears the optimizations. + /// + protected void ClearOptimizations() + { + _LastIndex = -1; + } + + /* Old implementation + + /// + /// Calculates the page and the offset from the index. + /// + /// The index. + /// The index adjustment. + /// The page. + /// The inneroffset. + protected void CalculateFromIndex(int index, int indexAdjustment, out int page, out int inneroffset, int adjustmentsAppliedToPages = -1) + { + if (adjustmentsAppliedToPages == -1 && 1==0) + { + // See if we can use some optimization.. aka its the same index as last time.. + if (_LastIndex != -1 && index == _LastIndex) + { + page = _LastPage; + inneroffset = _LastOffset; + return; + } + // See if we can use some optimization... aka its the next index from last.. + if (_LastIndex != -1 && index == _LastIndex + 1) + { + int basepageg = page = _LastPage; + + inneroffset = _LastOffset + 1; + + int items = this.PageSize; + if (_Deltas.ContainsKey(basepageg)) + { + + items += _Deltas[basepageg].Delta; + } + + + if (inneroffset >= items) + { + bool got = false; + inneroffset = 0; + basepageg = page = page + 1; + + while (!got) + { + int itemsg = this.PageSize; + if (_Deltas.ContainsKey(basepageg)) + { + + itemsg += _Deltas[basepageg].Delta; + } + + if (inneroffset < itemsg) got = true; + } + } + + _LastIndex = index; + _LastPage = page; + _LastOffset = inneroffset; + return; + } + } + + // First work out the base page from the index and the offset inside that page + int basepage = page = index / this.PageSize; + inneroffset = (index + indexAdjustment) - (page * this.PageSize); + + // We only need to do the rest if there have been modifications to the page sizes on pages (deltas) + if (_Deltas.Count > 0) + { + int adjustment = 0; + + lock (_PageLock) + { + // First, get the total adjustments for any pages BEFORE the current page.. + adjustment = (from d in _Deltas.Values where d.Page < basepage && d.Page > adjustmentsAppliedToPages select d.Delta).Sum(); + } + + // If we do have adjustments... + if (adjustment != 0) + { + // cull down the inner offset by the adjustments (so an extra item reduces the offset by one etc) + inneroffset -= adjustment; + + if (inneroffset < 0) + { + while (inneroffset < 0 && page >= 0) + { + page = --basepage; + var items = this.PageSize; + + if (_Deltas.ContainsKey(basepage)) + { + + items += _Deltas[basepage].Delta; + } + + + inneroffset = items + inneroffset; + } + + + // We should be on an earlier page, so recurse using the adjustments + //CalculateFromIndex(index - adjustment, adjustment, out page, out inneroffset, basepage); + } + else if (inneroffset >= this.PageSize) + { + // If the inneroffset seems to be on a later page, but we need to check to see if this page is expanded + if (!_Deltas.ContainsKey(basepage)) + { + // Its not expanded, so recurse in using the adjusted index + CalculateFromIndex(index - adjustment, 0, out page, out inneroffset, basepage); + } + else + { + var delta = _Deltas[basepage]; + // It is expanded, see if the expanded page contains this offset + if (inneroffset < (this.PageSize + delta.Delta)) + { + // Its just fine + } + else + { + // No it does not include this offset, so recurse in using the adjusted index + CalculateFromIndex(index + delta.Delta, 0, out page, out inneroffset, basepage); + } + } + } + } + else + { + // we dont have any earlier page adjustments, but we might have a short page, so check the offset is within range.. + PageDelta adjustmentForCurrentPage = null; + if (_Deltas.ContainsKey(basepage)) adjustmentForCurrentPage = _Deltas[basepage]; + if (adjustmentForCurrentPage != null && adjustmentForCurrentPage.Delta < 0) + { + if (inneroffset >= this.PageSize + adjustmentForCurrentPage.Delta) + { + // Recurse in using the adjustment for the current page to deal with the offset.. + //CalculateFromIndex(index - adjustmentForCurrentPage.Delta, 0, out page, out inneroffset); + + inneroffset += adjustmentForCurrentPage.Delta; + + while(inneroffset<0) + { + page = ++basepage; + + var items = this.PageSize; + + if (_Deltas.ContainsKey(basepage)) + { + + items += _Deltas[basepage].Delta; + } + + inneroffset += items; + } + } + } + } + } + + if (adjustmentsAppliedToPages == -1) + { + _LastIndex = index; + + _LastPage = basepage; + _LastOffset = inneroffset; + } + + } + */ + + int _BasePage = 0; + + protected void CalculateFromIndex(int index, out int page, out int inneroffset) + { + // First work out the base page from the index and the offset inside that page + int basepage = page = (index / this.PageSize) + _BasePage; + inneroffset = (index+(_BasePage*this.PageSize)) - (page * this.PageSize); + + // We only need to do the rest if there have been modifications to the page sizes on pages (deltas) + if (_Deltas.Count > 0) + { + // Get the adjustment BEFORE checking for a short page, because we are going to adjust for that first.. + int adjustment = 0; + + lock (_PageLock) + { + // First, get the total adjustments for any pages BEFORE the current page.. + adjustment = (from d in _Deltas.Values where d.Page < basepage select d.Delta).Sum(); + } + + // Now check to see if we are currently in a short page - in which case we need to adjust for that + if (_Deltas.ContainsKey(page)) + { + int delta = _Deltas[page].Delta; + + if(delta<0) + { + // In a short page, are we over the edge ? + if(inneroffset >= this.PageSize+delta) + { + int step = inneroffset-(this.PageSize+delta-1); + inneroffset -= step; + DoStepForward(ref page, ref inneroffset, step); + } + } + } + + // If we do have adjustments... + if (adjustment != 0) + { + if(adjustment>0) + { + // items have been added to earlier pages, so we need to step back + DoStepBackwards(ref page, ref inneroffset, adjustment); + } + else + { + // items have been removed from earlier pages, so we need to step forward + DoStepForward(ref page, ref inneroffset, Math.Abs(adjustment)); + } + } + + } + + } + + private int _StepToJumpThreashold = 10; + + public int StepToJumpThreashold + { + get { return _StepToJumpThreashold; } + set { _StepToJumpThreashold = value; } + } + + private void DoStepBackwards(ref int page, ref int offset, int stepAmount) + { + bool done = false; + int ignoreSteps = -1; + + while(!done) + { + + if (stepAmount > this.PageSize * StepToJumpThreashold && ignoreSteps <= 0) + { + int targetPage = page - stepAmount/this.PageSize; + int sourcePage = page; + var adj = (from a in _Deltas.Values where a.Page >= targetPage && a.Page <= sourcePage orderby a.Page select a); + if(adj.Count() == 0) + { + page = targetPage; + stepAmount -= (sourcePage - targetPage) * this.PageSize; + + if(stepAmount == 0) + { + done = true; + } + } else if(adj.Last().Page < page-2) + { + targetPage = adj.Last().Page + 1; + page = targetPage; + stepAmount -= (sourcePage - targetPage) * this.PageSize; + + if (stepAmount == 0) + { + done = true; + } + } + else + { + ignoreSteps = sourcePage - adj.Last().Page; + } + } + + if (!done) + { + int items = this.PageSize; + if (_Deltas.ContainsKey(page)) items += _Deltas[page].Delta; + if (offset - stepAmount < 0) + { + stepAmount -= (offset + 1); + page--; + items = this.PageSize; + if (_Deltas.ContainsKey(page)) items += _Deltas[page].Delta; + offset = items - 1; + } + else + { + offset -= stepAmount; + done = true; + } + + ignoreSteps--; + } + } + } + + private void DoStepForward(ref int page, ref int offset, int stepAmount) + { + bool done = false; + int ignoreSteps = -1; + + while(!done) + { + + if (stepAmount > this.PageSize * StepToJumpThreashold && ignoreSteps <= 0) + { + int targetPage = page + stepAmount / this.PageSize; + int sourcePage = page; + var adj = (from a in _Deltas.Values where a.Page <= targetPage && a.Page >= sourcePage orderby a.Page select a); + if (adj.Count() == 0) + { + page = targetPage; + stepAmount -= (targetPage - sourcePage) * this.PageSize; + + if (stepAmount == 0) + { + done = true; + } + } + else if (adj.Last().Page > page - 2) + { + targetPage = adj.Last().Page - 1; + page = targetPage; + stepAmount -= (targetPage - sourcePage) * this.PageSize; + + if (stepAmount == 0) + { + done = true; + } + } else + { + ignoreSteps = adj.Last().Page - sourcePage; + } + } + + if (!done) + { + int items = this.PageSize; + if (_Deltas.ContainsKey(page)) items += _Deltas[page].Delta; + if (items <= offset + stepAmount) + { + stepAmount -= (items) - offset; + offset = 0; + page++; + } + else + { + offset += stepAmount; + done = true; + } + + ignoreSteps--; + } + } + } + IPagedSourceProvider _Provider = null; + + /// + /// Gets or sets the provider. + /// + /// + /// The provider. + /// + public IPagedSourceProvider Provider + { + get { return _Provider; } + set { _Provider = value; } + } + + IPagedSourceProviderAsync _ProviderAsync = null; + + /// + /// Gets or sets the provider asynchronous. + /// + /// + /// The provider asynchronous. + /// + public IPagedSourceProviderAsync ProviderAsync + { + get { return _ProviderAsync; } + set { _ProviderAsync = value; } + } + + int _PageSize = 100; + + /// + /// Gets or sets the size of the page. + /// + /// + /// The size of the page. + /// + public int PageSize + { + get { return _PageSize; } + set + { + DropAllDeltasAndPages(); + _PageSize = value; + } + } + + int _MaxPages = 100; + + /// + /// Gets or sets the maximum pages. + /// + /// + /// The maximum pages. + /// + public int MaxPages + { + get { return _MaxPages; } + set { _MaxPages = value; } + } + + int _MaxDeltas = -1; + + /// + /// Gets or sets the maximum deltas. + /// + /// + /// The maximum deltas. + /// + public int MaxDeltas + { + get { return _MaxDeltas; } + set { _MaxDeltas = value; } + } + + int _MaxDistance = -1; + + /// + /// Gets or sets the maximum distance. + /// + /// + /// The maximum distance. + /// + public int MaxDistance + { + get { return _MaxDistance; } + set { _MaxDistance = value; } + } + + protected object _PageLock = new Object(); + + /// + /// Gets at. + /// + /// The index. + /// The voc. + /// if set to true [use placeholder]. + /// + public T GetAt(int index, object voc, bool usePlaceholder = true) + { + T ret = default(T); + + int page; + int offset; + + CalculateFromIndex(index, out page, out offset); + + var datapage = SafeGetPage(page, usePlaceholder, voc, index); + + if (datapage != null) ret = datapage.GetAt(offset); + + if (ret == null) + { + + } + + //Debug.WriteLine("Get at index:" + index + "returned:" + ret.ToString() + " page=" + page + " offset=" + offset); + return ret; + } + + /// + /// Fills the page. + /// + /// The new page. + /// The page offset. + void FillPage(ISourcePage newPage, int pageOffset) + { + + var data = this.Provider.GetItemsAt(pageOffset, newPage.ItemsPerPage, false); + newPage.WiredDateTime = data.LoadedAt; + foreach (var o in data.Items) + { + newPage.Append(o, null, this.ExpiryComparer); + } + + newPage.PageFetchState = PageFetchStateEnum.Fetched; + } + + /// + /// Fills the page from asynchronous provider. + /// + /// The new page. + /// The page offset. + void FillPageFromAsyncProvider(ISourcePage newPage, int pageOffset) + { + var data = this.ProviderAsync.GetItemsAt(pageOffset, newPage.ItemsPerPage, false); + newPage.WiredDateTime = data.LoadedAt; + foreach (var o in data.Items) + { + newPage.Append(o, null, this.ExpiryComparer); + } + newPage.PageFetchState = PageFetchStateEnum.Fetched; + + } + + /// + /// Gets the count. + /// + /// + /// The count. + /// + public int GetCount(bool asyncOK) + { + + int ret = 0; + + if (!_HasGotCount) + { + lock (this) + { + if (!IsAsync) + { + ret = this.Provider.Count; + _LocalCount = ret; + } + else + { + if (!asyncOK) + { + ret = this.ProviderAsync.GetCountAsync().Result; + //ret = this.ProviderAsync.Count; + _LocalCount = ret; + } + else + { + ret = 0; + var cts = StartPageRequest(Int32.MinValue); + GetCountAsync(cts); + } + } + + _HasGotCount = true; + } + } + + return _LocalCount; + + } + + public async Task GetCountAsync() + { + int ret = 0; + + + if (!IsAsync) + { + ret = this.Provider.Count; + } + else + { + ret = await this.ProviderAsync.GetCountAsync(); + } + + _HasGotCount = true; + + return ret; + } + + private async void GetCountAsync(CancellationTokenSource cts) + { + if (!cts.IsCancellationRequested) + { + int ret = await this.ProviderAsync.GetCountAsync(); + + if (!cts.IsCancellationRequested) + { + lock (this) + { + _HasGotCount = true; + _LocalCount = ret; + } + } + + if (!cts.IsCancellationRequested) + this.RaiseCountChanged(true, _LocalCount); + } + + RemovePageRequest(Int32.MinValue); + } + + /// + /// Gets the Index of item. + /// + /// The item. + /// the index of the item, or -1 if not found + public int IndexOf(T item) + { + // Attempt to get the item from the pages, else call the provider to get it.. + lock (_PageLock) + { + + foreach (var p in _Pages) + { + int o = p.Value.IndexOf(item); + if (o >= 0) + { + return o + ((p.Key - _BasePage) * this.PageSize) + (from d in _Deltas.Values where d.Page < p.Key select d.Delta).Sum(); + } + } + } + + if (!IsAsync) + { + return this.Provider.IndexOf(item); + } + else + { + return this.ProviderAsync.IndexOfAsync( item ).Result; + //return this.ProviderAsync.IndexOf(item); + } + } + + /// + /// Resets the specified count. + /// + /// The count. + public void OnReset(int count) + { + CancelAllRequests(); + + lock (_PageLock) + { + DropAllDeltasAndPages(); + } + + ClearOptimizations(); + + if (count < 0) + { + _HasGotCount = false; + } + else + { + lock (this) + { + _LocalCount = count; + _HasGotCount = true; + } + } + + if (!IsAsync) + { + this.Provider.OnReset(count); + } + else + { + this.ProviderAsync.OnReset(count); + } + + if(count >= -1) + RaiseCountChanged(true, count); + + } + + public void OnBeforeReset() + { + if(!IsAsync) + { + if(this.Provider is IProviderPreReset) + { + (this.Provider as IProviderPreReset).OnBeforeReset(); + } + } + else + { + if(this.ProviderAsync is IProviderPreReset) + { + (this.ProviderAsync as IProviderPreReset).OnBeforeReset(); + } + } + } + + /// + /// Raises the count changed. + /// + /// The count. + protected void RaiseCountChanged(bool needsReset, int count) + { + var evnt = this.CountChanged; + if (evnt != null) + { + evnt(this, new CountChangedEventArgs() { NeedsReset = needsReset, Count = count }); + } + } + + /// + /// Occurs when [count changed]. + /// + public event OnCountChanged CountChanged; + + #region IEditableProvider Implementation + + /// + /// Called when [append]. + /// + /// The item. + /// The timestamp. + /// + public int OnAppend(T item, object timestamp) + { + ClearOptimizations(); + + int index = _LocalCount; + + int page; int offset; + + if (!_HasGotCount) EnsureCount(); + + CalculateFromIndex(index, out page, out offset); + + if (IsPageWired(page)) + { + bool shortpage = false; + var dataPage = SafeGetPage(page, false, null, index); + if (dataPage.ItemsPerPage < this.PageSize) shortpage = true; + + dataPage.Append(item, timestamp, this.ExpiryComparer); + + if(shortpage) + { + dataPage.ItemsPerPage++; + } + else + { + AddOrUpdateAdjustment(page, 1); + } + + } + + _LocalCount++; + + ClearOptimizations(); + + if (this.IsAsync) + { + var test = this.GetAt(index, this, false); + } + + + var edit = GetProviderAsEditable(); + if (edit != null) + { + edit.OnInsert(index, item, timestamp); + } + + ClearOptimizations(); + + return index; + } + + /// + /// Gets the page, if use placeholders is false - then gets page sync else async. + /// + /// The page. + /// if set to true [allow placeholders]. + /// The voc. + /// The index that this page refers to (effectively the pageoffset. + /// + protected ISourcePage SafeGetPage(int page, bool allowPlaceholders, object voc, int index) + { + ISourcePage ret = null; + + lock (_PageLock) + { + if (_Pages.ContainsKey(page)) + { + ret = _Pages[page]; + _Reclaimer.OnPageTouched(ret); + } + else + { + PageDelta delta = null; + if (_Deltas.ContainsKey(page)) delta = _Deltas[page]; + int pageOffset = (page - _BasePage) * this.PageSize + (from d in _Deltas.Values where d.Page < page select d.Delta).Sum(); + int pageSize = Math.Min(this.PageSize, this.GetCount(false)-pageOffset); + if (delta != null) pageSize += delta.Delta; + var newPage = this._Reclaimer.MakePage(page, pageSize); + _Pages.Add(page, newPage); + + if (!IsAsync) + { + FillPage(newPage, pageOffset); + + ret = newPage; + } + else + { + bool up = allowPlaceholders; + + if (up && voc != null) + { + // Fill with placeholders + //Debug.WriteLine("Filling with placeholders, pagesize=" + pageSize); + for (int loop = 0; loop < pageSize; loop++) + { + newPage.Append(this.ProviderAsync.GetPlaceHolder(newPage.Page * pageSize + loop, newPage.Page, loop), null, this.ExpiryComparer); + } + + ret = newPage; + + CancellationTokenSource cts = StartPageRequest(newPage.Page); + Task.Factory.Run(() => DoRealPageGet(voc, newPage, pageOffset, index, cts)); + } + else + { + FillPageFromAsyncProvider(newPage, pageOffset); + ret = newPage; + } + } + } + } + + return ret; + } + + private async void DoRealPageGet(Object voc, ISourcePage page, int pageOffset, int index, CancellationTokenSource cts) + { + //Debug.WriteLine("DoRealPageGet: pageOffset=" + pageOffset + " index=" + index); + VirtualizingObservableCollection realVOC = (VirtualizingObservableCollection)voc; + List> listOfReplaces = new List>(); + + if (realVOC != null) + { + if (cts.IsCancellationRequested) return; + + var data = await ProviderAsync.GetItemsAtAsync(pageOffset, page.ItemsPerPage, false); + + if (cts.IsCancellationRequested) return; + + page.WiredDateTime = data.LoadedAt; + + int i = 0; + foreach (var item in data.Items) + { + if (cts.IsCancellationRequested) + { + RemovePageRequest(page.Page); + return; + } + + ClearOptimizations(); + if(page.ReplaceNeeded(i)) + { + var old = page.GetAt(i); + if (old == null) + { + + } + + ClearOptimizations(); + //Debug.WriteLine("Replacing:" + old.ToString() + " with " + item.ToString()); + + page.ReplaceAt(i, old, item, null, null); + //VirtualizationManager.Instance.RunOnUI(new PlaceholderReplaceWA(realVOC, old, item, pageOffset+i)); + listOfReplaces.Add(new PlaceholderReplaceWA(realVOC, old, item, pageOffset + i)); + } + else + { + page.ReplaceAt(i, default(T), item, null, null); + } + + i++; + } + + } + + page.PageFetchState = PageFetchStateEnum.Fetched; + + ClearOptimizations(); + foreach (var replace in listOfReplaces) + { + if (cts.IsCancellationRequested) + { + RemovePageRequest(page.Page); + return; + } + VirtualizationManager.Instance.RunOnUI(replace); + } + + RemovePageRequest(page.Page); + } + + protected bool IsPageWired(int page) + { + bool wired = false; + + lock (_PageLock) + { + if (_Pages.ContainsKey(page)) wired = true; + } + + return wired; + } + + public void OnInsert(int index, T item, object timestamp) + { + int page; int offset; + + if (!_HasGotCount) EnsureCount(); + + CalculateFromIndex(index, out page, out offset); + + if (IsPageWired(page)) + { + var dataPage = SafeGetPage(page, false, null, index); + dataPage.InsertAt(offset, item, timestamp, this.ExpiryComparer); + } + int adj = AddOrUpdateAdjustment(page, 1); + + if(page == _BasePage && adj == this.PageSize*2) + { + lock (_PageLock) + { + if (IsPageWired(page)) + { + var dataPage = SafeGetPage(page, false, null, index); + ISourcePage newdataPage = null; + if (IsPageWired(page - 1)) + { + newdataPage = SafeGetPage(page-1, false, null, index); + } + else + { + newdataPage = this._Reclaimer.MakePage(page - 1, this.PageSize); + _Pages.Add(page - 1, newdataPage); + } + + for (int loop = 0; loop < this.PageSize; loop++) + { + var i = dataPage.GetAt(0); + + dataPage.RemoveAt(0, null, null); + newdataPage.Append(i, null, null); + } + + } + + AddOrUpdateAdjustment(page, -this.PageSize); + + _BasePage--; + } + } + + if (this.IsAsync) + { + var test = this.GetAt(index, this, false); + } + + var edit = GetProviderAsEditable(); + if (edit != null) + { + edit.OnInsert(index, item, timestamp); + } + + _LocalCount++; + + ClearOptimizations(); + } + + void EnsureCount() + { + GetCount(false); + } + + protected bool IsAsync + { + get + { + return _ProviderAsync != null ? true : false; + } + } + + public void OnRemove(int index, T item, object timestamp) + { + int page; int offset; + + if (!_HasGotCount) EnsureCount(); + + CalculateFromIndex(index, out page, out offset); + + if (IsPageWired(page)) + { + var dataPage = SafeGetPage(page, false, null, index); + dataPage.RemoveAt(offset, timestamp, this.ExpiryComparer); + } + AddOrUpdateAdjustment(page, -1); + + if (page == _BasePage) + { + int items = this.PageSize; + if (_Deltas.ContainsKey(page)) items += _Deltas[page].Delta; + if (items == 0) + { + _Deltas.Remove(page); + _BasePage++; + } + } + + if (this.IsAsync) + { + var test = this.GetAt(index, this, false); + } + + var edit = GetProviderAsEditable(); + if (edit != null) + { + edit.OnRemove(index, item, timestamp); + } + + _LocalCount--; + + ClearOptimizations(); + } + + public void OnReplace(int index, T oldItem, T newItem, object timestamp) + { + int page; int offset; + + CalculateFromIndex(index, out page, out offset); + + if (IsPageWired(page)) + { + var dataPage = SafeGetPage(page, false, null, index); + dataPage.ReplaceAt(offset, oldItem, newItem, timestamp, this.ExpiryComparer); + } + + var edit = GetProviderAsEditable(); + if (edit != null) + { + edit.OnReplace(index, oldItem, newItem, timestamp); + } + } + + #endregion IEditableProvider Implementation + + + public void RunClaim(string sectionContext = "") + { + if (_Reclaimer != null) + { + int needed = 0; + + lock (_PageLock) + { + needed = Math.Max(0, _Pages.Count - this.MaxPages); + if (needed != 0) + { + var l = _Reclaimer.ReclaimPages(_Pages.Values, needed, sectionContext).ToList(); + + foreach (var p in l) + { + if (p.Page != _BasePage) + { + lock (_Pages) + { + if (_Pages.ContainsKey(p.Page)) + { + _Pages.Remove(p.Page); + _Reclaimer.OnPageReleased(p); + } + } + } + } + } + } + } + } + } + +} diff --git a/AlphaChiTech.Virtualization.Net4/PlaceholderReplaceWA.cs b/AlphaChiTech.Virtualization.Net4/PlaceholderReplaceWA.cs new file mode 100644 index 0000000..4f4d17b --- /dev/null +++ b/AlphaChiTech.Virtualization.Net4/PlaceholderReplaceWA.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace AlphaChiTech.Virtualization +{ + public class PlaceholderReplaceWA : BaseActionVirtualization + { + private T _OldValue; + private T _NewValue; + private int _Index; + + WeakReference _VOC; + + public PlaceholderReplaceWA(VirtualizingObservableCollection voc, T oldValue, T newValue, int index) + : base(VirtualActionThreadModelEnum.UseUIThread) + { + _VOC = new WeakReference(voc); + _OldValue = oldValue; + _NewValue = newValue; + _Index = index; + } + + public override void DoAction() + { + var voc = (VirtualizingObservableCollection)_VOC.Target; + + if (voc != null && _VOC.IsAlive) + { + voc.ReplaceAt(_Index, _OldValue, _NewValue, null); + } + } + } + +} diff --git a/AlphaChiTech.Virtualization.Net4/Properties/AssemblyInfo.cs b/AlphaChiTech.Virtualization.Net4/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..6598238 --- /dev/null +++ b/AlphaChiTech.Virtualization.Net4/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// Les informations générales relatives à un assembly dépendent de +// l'ensemble d'attributs suivant. Changez les valeurs de ces attributs pour modifier les informations +// associées à un assembly. +[assembly: AssemblyTitle("AlphaChiTech.Virtualization.Net4")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("AlphaChiTech.Virtualization.Net4")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// L'affectation de la valeur false à ComVisible rend les types invisibles dans cet assembly +// aux composants COM. Si vous devez accéder à un type dans cet assembly à partir de +// COM, affectez la valeur true à l'attribut ComVisible sur ce type. +[assembly: ComVisible(false)] + +// Le GUID suivant est pour l'ID de la typelib si ce projet est exposé à COM +[assembly: Guid("ecedc613-843a-4d9a-8123-bd9600c9118e")] + +// Les informations de version pour un assembly se composent des quatre valeurs suivantes : +// +// Version principale +// Version secondaire +// Numéro de build +// Révision +// +// Vous pouvez spécifier toutes les valeurs ou indiquer les numéros de build et de révision par défaut +// en utilisant '*', comme indiqué ci-dessous : +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/AlphaChiTech.Virtualization.Net4/ReclaimPagesWA.cs b/AlphaChiTech.Virtualization.Net4/ReclaimPagesWA.cs new file mode 100644 index 0000000..6a8a11b --- /dev/null +++ b/AlphaChiTech.Virtualization.Net4/ReclaimPagesWA.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace AlphaChiTech.Virtualization +{ + public class ReclaimPagesWA : BaseRepeatableActionVirtualization + { + public ReclaimPagesWA(IReclaimableService provider, string sectionContext) + : base(VirtualActionThreadModelEnum.Background, true, TimeSpan.FromMinutes(1)) + { + _WRProvider = new WeakReference(provider); + } + + WeakReference _WRProvider = null; + + string _SectionContext = ""; + + public override void DoAction() + { + _LastRun = DateTime.Now; + + var reclaimer = _WRProvider.Target as IReclaimableService; + + if (reclaimer != null) + { + reclaimer.RunClaim(_SectionContext); + } + } + + public override bool KeepInActionsList() + { + bool ret = base.KeepInActionsList(); + + if (!_WRProvider.IsAlive) ret = false; + + return ret; + } + } + +} diff --git a/AlphaChiTech.Virtualization.Net4/SourcePage.cs b/AlphaChiTech.Virtualization.Net4/SourcePage.cs new file mode 100644 index 0000000..f5c0d64 --- /dev/null +++ b/AlphaChiTech.Virtualization.Net4/SourcePage.cs @@ -0,0 +1,234 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace AlphaChiTech.Virtualization +{ + public class SourcePage : ISourcePage + { + protected List _Items = new List(); + + + /// + /// Gets or sets the page. + /// + /// + /// The page. + /// + public int Page { get; set; } + + private List _PendingUpdates = new List(); + + public List PendingUpdates + { + get { return _PendingUpdates; } + } + + /// + /// Gets or sets the items per page. + /// + /// + /// The items per page. + /// + public int ItemsPerPage { get; set; } + + public int ItemsCount + { + get + { + return _Items.Count; + } + } + + private List _ReplaceNeededList = new List(); + + /// + /// Gets a value indicating whether [can reclaim page]. + /// + /// + /// true if [can reclaim page]; otherwise, false. + /// + public bool CanReclaimPage + { + get + { + bool ret = true; + if (this._PageFetchState == PageFetchStateEnum.Placeholders) ret = false; + return ret; + } + } + + /// + /// Determines whether it is safe to update into a page where the pending update was generated at a given time. + /// + /// The updated happened at this datetime. + /// + public bool IsSafeToUpdate(IPageExpiryComparer comparer, object updatedAt) + { + bool ret = true; + + if (comparer != null) + { + ret = comparer.IsUpdateValid(this.WiredDateTime, updatedAt); + } + + //if(updatedAt.HasValue && updatedAt.Value != DateTime.MinValue) + //{ + // if(updatedAt.Value< this.WiredDateTime) + // { + // ret = false; + // } + //} + + return ret; + } + + /// + /// Gets or sets the last touch. + /// + /// + /// The last touch. + /// + public Object LastTouch { get; set; } + + /// + /// Gets at. + /// + /// The offset. + /// + public T GetAt(int offset) + { + T ret = default(T); + + if (this._PageFetchState == PageFetchStateEnum.Placeholders) _ReplaceNeededList.Add(offset); + + if (_Items.Count > offset) ret = _Items[offset]; + + LastTouch = DateTime.Now; + + return ret; + } + + public bool ReplaceNeeded(int offset) + { + bool ret = false; + + if (_ReplaceNeededList.Contains(offset)) ret = true; + + return ret; + } + + /// + /// Appends the specified item. + /// + /// The item. + /// + public int Append(T item, object updatedAt, IPageExpiryComparer comparer) + { + _Items.Add(item); + + LastTouch = DateTime.Now; + + return _Items.IndexOf(item); + } + + /// + /// Inserts at. + /// + /// The offset. + /// The item. + /// The updated at. + public void InsertAt(int offset, T item, object updatedAt, IPageExpiryComparer comparer) + { + if (IsSafeToUpdate(comparer, updatedAt)) + { + if (_Items.Count > offset) + { + _Items.Insert(offset, item); + } + else + { + _Items.Add(item); + } + + } + } + + /// + /// Removes at. + /// + /// The offset. + /// The updated at. + /// + public bool RemoveAt(int offset, object updatedAt, IPageExpiryComparer comparer) + { + bool removed = true; + + if (IsSafeToUpdate(comparer, updatedAt)) + { + _Items.RemoveAt(offset); + } + + return removed; + } + + /// + /// Replaces at. + /// + /// The offset. + /// The old value. + /// The new value. + /// The updated at. + public void ReplaceAt(int offset, T oldValue, T newValue, object updatedAt, IPageExpiryComparer comparer) + { + if (IsSafeToUpdate(comparer, updatedAt)) + { + if (_Items.Count > offset) _Items[offset] = newValue; + } + } + + /// + /// Indexes the of. + /// + /// The item. + /// + public int IndexOf(T item) + { + LastTouch = DateTime.Now; + + return _Items.IndexOf(item); + } + + private PageFetchStateEnum _PageFetchState = PageFetchStateEnum.Placeholders; + + /// + /// Gets or sets the state of the page fetch state. + /// + /// + /// The state of the page fetch. + /// + public PageFetchStateEnum PageFetchState + { + get { return _PageFetchState; } + set { _PageFetchState = value; } + } + + private Object _WiredDateTime = DateTime.MinValue; + + /// + /// Gets or sets the wired date time. + /// + /// + /// The wired date time. + /// + public object WiredDateTime + { + get { return _WiredDateTime; } + set { _WiredDateTime = value; } + } + + + } + +} diff --git a/AlphaChiTech.Virtualization.Net4/TaskExtension.cs b/AlphaChiTech.Virtualization.Net4/TaskExtension.cs new file mode 100644 index 0000000..52a8a3c --- /dev/null +++ b/AlphaChiTech.Virtualization.Net4/TaskExtension.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace AlphaChiTech.Virtualization +{ + public static class TaskExtension + { + public static Task Run(this TaskFactory factory, Action action) + { + return Task.Factory.StartNew(action, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default); + } + + public static Task Run(this TaskFactory factory, Func func) + { + return Task.Factory.StartNew(func, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default); + } + + public static Task Run(this TaskFactory factory, Func> func) + { + return Task.Factory.StartNew(func, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default).Unwrap(); + } + } +} diff --git a/AlphaChiTech.Virtualization.Net4/VirtualActionThreadModelEnum.cs b/AlphaChiTech.Virtualization.Net4/VirtualActionThreadModelEnum.cs new file mode 100644 index 0000000..37839c3 --- /dev/null +++ b/AlphaChiTech.Virtualization.Net4/VirtualActionThreadModelEnum.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace AlphaChiTech.Virtualization +{ + public enum VirtualActionThreadModelEnum + { + UseUIThread, + Background + } +} diff --git a/AlphaChiTech.Virtualization.Net4/VirtualizationManager.cs b/AlphaChiTech.Virtualization.Net4/VirtualizationManager.cs new file mode 100644 index 0000000..ce06a2d --- /dev/null +++ b/AlphaChiTech.Virtualization.Net4/VirtualizationManager.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AlphaChiTech.Virtualization +{ + public class VirtualizationManager + { + private static VirtualizationManager _Instance = new VirtualizationManager(); + + private List _Actions = new List(); + private object _ActionLock = new object(); + + private static bool _IsInitialized = false; + + public static bool IsInitialized + { + get + { + return _IsInitialized; + } + } + + public static VirtualizationManager Instance + { + get + { + return _Instance; + } + } + + bool _Processing = false; + + private Action _UIThreadExcecuteAction = null; + + public Action UIThreadExcecuteAction + { + get { return _UIThreadExcecuteAction; } + set + { + _UIThreadExcecuteAction = value; + _IsInitialized = true; + } + } + + public void ProcessActions() + { + if (_Processing) return; + + _Processing = true; + + List lst; + lock (_ActionLock) + { + lst = _Actions.ToList(); + } + + foreach (var action in lst) + { + bool bdo = true; + + if (action is IRepeatingVirtualizationAction) + { + bdo = (action as IRepeatingVirtualizationAction).IsDueToRun(); + } + + if (bdo) + { + switch (action.ThreadModel) + { + case VirtualActionThreadModelEnum.UseUIThread: + if (UIThreadExcecuteAction == null) // PLV + throw new Exception( "VirtualizationManager isn’t already initialized ! set the VirtualizationManager’s UIThreadExcecuteAction (VirtualizationManager.Instance.UIThreadExcecuteAction = a => Dispatcher.Invoke( a );)" ); + UIThreadExcecuteAction.Invoke(() => action.DoAction()); + break; + case VirtualActionThreadModelEnum.Background: + Task.Factory.Run(() => action.DoAction()); + break; + } + + if (action is IRepeatingVirtualizationAction) + { + if (!(action as IRepeatingVirtualizationAction).KeepInActionsList()) + { + lock (_ActionLock) + { + _Actions.Remove(action); + } + } + } + else + { + lock (_ActionLock) + { + _Actions.Remove(action); + } + } + } + } + + _Processing = false; + } + public void AddAction(IVirtualizationAction action) + { + lock (_ActionLock) + { + _Actions.Add(action); + } + } + + public void AddAction(Action action) + { + AddAction(new ActionVirtualizationWrapper(action)); + } + + public void RunOnUI(IVirtualizationAction action) + { + if (UIThreadExcecuteAction == null) // PLV + throw new Exception( "VirtualizationManager isn’t already initialized ! set the VirtualizationManager’s UIThreadExcecuteAction (VirtualizationManager.Instance.UIThreadExcecuteAction = a => Dispatcher.Invoke( a );)" ); + UIThreadExcecuteAction.Invoke(() => action.DoAction()); + } + + public void RunOnUI(Action action) + { + RunOnUI(new ActionVirtualizationWrapper(action)); + } + } +} diff --git a/AlphaChiTech.Virtualization.Net4/VirtualizingObservableCollection.cs b/AlphaChiTech.Virtualization.Net4/VirtualizingObservableCollection.cs new file mode 100644 index 0000000..73c717f --- /dev/null +++ b/AlphaChiTech.Virtualization.Net4/VirtualizingObservableCollection.cs @@ -0,0 +1,900 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace AlphaChiTech.Virtualization +{ + public class VirtualizingObservableCollection : IEnumerable, IEnumerable, ICollection, ICollection, IList, IList, INotifyCollectionChanged, INotifyPropertyChanged + { + #region Ctors Etc + + /// + /// Initializes a new instance of the class. + /// + /// The provider. + public VirtualizingObservableCollection(IItemSourceProvider provider) + { + this.Provider = provider; + } + + /// + /// Initializes a new instance of the class. + /// + /// The asynchronous provider. + public VirtualizingObservableCollection(IItemSourceProviderAsync asyncProvider) + { + this.ProviderAsync = asyncProvider; + } + + /// + /// Initializes a new instance of the class. + /// + /// The provider. + /// The optional reclaimer. + /// The optional expiry comparer. + /// Size of the page. + /// The maximum pages. + /// The maximum deltas. + /// The maximum distance. + public VirtualizingObservableCollection( + IPagedSourceProvider provider, + IPageReclaimer reclaimer = null, + IPageExpiryComparer expiryComparer = null, + int pageSize = 100, + int maxPages = 100, + int maxDeltas = -1, + int maxDistance = -1 + ) + { + this.Provider = new PaginationManager(provider, reclaimer, expiryComparer, pageSize, maxPages, maxDeltas, maxDistance); + } + + + #endregion Ctors Etc + + #region IEnumerable Implementation + + /// + /// Returns an enumerator that iterates through a collection. + /// + /// + /// An object that can be used to iterate through the collection. + /// + public IEnumerator GetEnumerator() + { + string sc = new Guid().ToString(); + + EnsureCountIsGotNONASync(); + + int count = InternalGetCount(); + + for (int iLoop = 0; iLoop < count; iLoop++) + { + yield return InternalGetValue(iLoop, sc); + } + } + + #endregion IEnumerable Implementation + + #region IEnumerable Implementation + + /// + /// Returns an enumerator that iterates through the collection. + /// + /// + /// A that can be used to iterate through the collection. + /// + IEnumerator IEnumerable.GetEnumerator() + { + string sc = new Guid().ToString(); + + EnsureCountIsGotNONASync(); + + int count = InternalGetCount(); + + for (int iLoop = 0; iLoop < count; iLoop++) + { + yield return InternalGetValue(iLoop, sc); + } + } + + #endregion IEnumerable Implementation + + #region ICollection Implementation + + public void CopyTo(Array array, int index) + { + throw new NotImplementedException(); + } + + /// + /// Gets the number of elements contained in the . + /// + /// The number of elements contained in the . + public int Count + { + get { return InternalGetCount(); } + } + + /// + /// Gets a value indicating whether access to the is synchronized (thread safe). + /// + /// true if access to the is synchronized (thread safe); otherwise, false. + public bool IsSynchronized + { + get { return false; } + } + + private object _SyncRoot = new object(); + + /// + /// Gets an object that can be used to synchronize access to the . + /// + /// An object that can be used to synchronize access to the . + public object SyncRoot + { + get { return _SyncRoot; } + } + + #endregion ICollection Implementation + + #region ICollection Implementation + + /// + /// Adds an item to the . + /// + /// The object to add to the . + public void Add(T item) + { + InternalAdd(item, null); + } + + /// + /// Resets the collection - aka forces a get all data, including the count . + /// + public void Clear() + { + InternalClear(); + } + + /// + /// Determines whether the contains a specific value. + /// + /// The object to locate in the . + /// + /// true if is found in the ; otherwise, false. + /// + public bool Contains(T item) + { + return IndexOf(item) != -1 ? true : false; + } + + public void CopyTo(T[] array, int arrayIndex) + { + throw new NotImplementedException(); + } + + /// + /// Gets a value indicating whether the is read-only. + /// + /// true if the is read-only; otherwise, false. + public bool IsReadOnly + { + get { return false; } + } + + /// + /// Removes the first occurrence of a specific object from the . + /// + /// The object to remove from the . + /// + /// true if was successfully removed from the ; otherwise, false. This method also returns false if is not found in the original . + /// + public bool Remove(T item) + { + return InternalRemoveAt(IndexOf(item)); + } + + #endregion ICollection Implementation + + #region Extended CRUD operators that take into account the DateTime of the change + + /// + /// Removes the specified item - extended to only remove the item if the page was not pulled before the updatedat DateTime. + /// + /// The item. + /// The updated at. + /// + public bool Remove(T item, object updatedAt) + { + return InternalRemoveAt(IndexOf(item), updatedAt); + } + + /// + /// Removes at the given index - extended to only remove the item if the page was not pulled before the updatedat DateTime. + /// + /// The index. + /// The updated at. + /// + public bool RemoveAt(int index, object updatedAt) + { + return InternalRemoveAt(index, updatedAt); + } + + /// + /// Adds (appends) the specified item - extended to only add the item if the page was not pulled before the updatedat DateTime. + /// + /// The item. + /// The updated at. + /// + public int Add(T item, object updatedAt) + { + return InternalAdd(item, updatedAt); + } + + /// + /// Inserts the specified index - extended to only insert the item if the page was not pulled before the updatedat DateTime. + /// + /// The index. + /// The item. + /// The updated at. + public void Insert(int index, T item, object updatedAt) + { + InternalInsertAt(index, item, updatedAt); + } + /// + /// Adds the range. + /// + /// The new values. + /// The updatedat object. + /// Index of the last appended object + public int AddRange(IEnumerable newValues, object timestamp = null) + { + var edit = GetProviderAsEditable(); + + int index = -1; + List items = new List(); + + foreach (var item in newValues) + { + items.Add(item); + index = edit.OnAppend(item, timestamp); + + NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index); + RaiseCollectionChangedEvent(args); + } + + + OnCountTouched(); + + + return index; + } + + #endregion Extended CRUD operators that take into account the DateTime of the change + + #region IList Implementation + + /// + /// Adds an item to the . + /// + /// The object to add to the . + /// + /// The position into which the new element was inserted, or -1 to indicate that the item was not inserted into the collection. + /// + public int Add(object value) + { + return InternalAdd((T)value, null); + } + + /// + /// Determines whether the contains a specific value. + /// + /// The object to locate in the . + /// + /// true if the is found in the ; otherwise, false. + /// + public bool Contains(object value) + { + return Contains((T)value); + } + + /// + /// Determines the index of a specific item in the . + /// + /// The object to locate in the . + /// + /// The index of if found in the list; otherwise, -1. + /// + public int IndexOf(object value) + { + return IndexOf((T)value); + } + + /// + /// Inserts an item to the at the specified index. + /// + /// The zero-based index at which should be inserted. + /// The object to insert into the . + public void Insert(int index, object value) + { + Insert(index, (T)value); + } + + /// + /// Gets a value indicating whether the has a fixed size. + /// + /// true if the has a fixed size; otherwise, false. + public bool IsFixedSize + { + get { return false; } + } + + /// + /// Removes the first occurrence of a specific object from the . + /// + /// The object to remove from the . + public void Remove(object value) + { + Remove((T)value); + } + + /// + /// Removes the item at the specified index. + /// + /// The zero-based index of the item to remove. + public void RemoveAt(int index) + { + InternalRemoveAt(index); + } + + /// + /// Gets or sets the element at the specified index. + /// + /// The index. + /// + public object this[int index] + { + get + { + return InternalGetValue(index, _DefaultSelectionContext); + } + set + { + InternalSetValue(index, (T)value); + } + } + + #endregion IList Implementation + + #region IList Implementation + + /// + /// Determines the index of a specific item in the . + /// + /// The object to locate in the . + /// + /// The index of if found in the list; otherwise, -1. + /// + public int IndexOf(T item) + { + return InternalIndexOf(item); + } + + /// + /// Inserts an item to the at the specified index. + /// + /// The zero-based index at which should be inserted. + /// The object to insert into the . + public void Insert(int index, T item) + { + InternalInsertAt(index, item); + } + + /// + /// Gets or sets the element at the specified index. + /// + /// The index. + /// + T IList.this[int index] + { + get + { + return InternalGetValue(index, _DefaultSelectionContext); + } + set + { + InternalSetValue(index, value); + } + } + + #endregion IList Implementation + + #region Public Properties + + /// + /// Gets or sets the provider if its asynchronous. + /// + /// + /// The provider asynchronous. + /// + public IItemSourceProviderAsync ProviderAsync + { + get { return _ProviderAsync; } + set + { + ClearCountChangedHooks(); + _ProviderAsync = value; + if (_ProviderAsync is INotifyCountChanged) + { + (_ProviderAsync as INotifyCountChanged).CountChanged += VirtualizingObservableCollection_CountChanged; + } + } + } + + void VirtualizingObservableCollection_CountChanged(object sender, CountChangedEventArgs args) + { + if (args.NeedsReset) + { + // Send a reset.. + RaiseCollectionChangedEvent(_CC_ResetArgs); + } + OnCountTouched(); + } + + /// + /// Gets or sets the provider if its not asynchronous. + /// + /// + /// The provider. + /// + public IItemSourceProvider Provider + { + get { return _Provider; } + set + { + ClearCountChangedHooks(); + _Provider = value; + + if (_Provider is INotifyCountChanged) + { + (_Provider as INotifyCountChanged).CountChanged += VirtualizingObservableCollection_CountChanged; + } + } + } + + void ClearCountChangedHooks() + { + if(_Provider is INotifyCountChanged) + { + (_Provider as INotifyCountChanged).CountChanged -= VirtualizingObservableCollection_CountChanged; + } + + if(_ProviderAsync is INotifyCountChanged) + { + (_ProviderAsync as INotifyCountChanged).CountChanged -= VirtualizingObservableCollection_CountChanged; + } + } + + #endregion Public Properties + + #region INotifyCollectionChanged Implementation + + private bool _SupressEventErrors = false; + + public bool SupressEventErrors + { + get + { + return _SupressEventErrors; + } + + set + { + _SupressEventErrors = value; + } + } + + public event NotifyCollectionChangedEventHandler CollectionChanged; + + /// + /// Raises the collection changed event. + /// + /// The instance containing the event data. + internal void RaiseCollectionChangedEvent(NotifyCollectionChangedEventArgs args) + { + if (_BulkCount > 0) return; + + var evnt = CollectionChanged; + + if (evnt != null) + { + try + { + evnt(this, args); + } + catch (Exception ex) + { + if (!this.SupressEventErrors) + { + throw ex; + } + } + } + } + + #endregion INotifyCollectionChanged Implementation + + #region INotifyPropertyChanged implementation + + public event PropertyChangedEventHandler PropertyChanged; + + private static PropertyChangedEventArgs _PC_CountArgs = new PropertyChangedEventArgs("Count"); + private static NotifyCollectionChangedEventArgs _CC_ResetArgs = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset); + + private void OnCountTouched() + { + RaisePropertyChanged(_PC_CountArgs); + } + + protected void RaisePropertyChanged(PropertyChangedEventArgs args) + { + if (_BulkCount > 0) return; + + var evnt = PropertyChanged; + + if (evnt != null) + { + evnt(this, args); + } + } + + #endregion INotifyPropertyChanged implementation + + #region Bulk Operation implementation + + /// + /// Releases the bulk mode. + /// + internal void ReleaseBulkMode() + { + if (_BulkCount > 0) _BulkCount--; + + if (_BulkCount == 0) + { + RaiseCollectionChangedEvent(_CC_ResetArgs); + RaisePropertyChanged(_PC_CountArgs); + } + } + + /// + /// Enters the bulk mode. + /// + /// + public BulkMode EnterBulkMode() + { + _BulkCount++; + + return new BulkMode(this); + } + + /// + /// The Bulk mode IDisposable proxy + /// + public class BulkMode : IDisposable + { + public BulkMode(VirtualizingObservableCollection voc) + { + _voc = voc; + } + + private VirtualizingObservableCollection _voc = null; + + bool _IsDisposed = false; + + public void Dispose() + { + OnDispose(); + } + + void OnDispose() + { + if (!_IsDisposed) + { + _IsDisposed = true; + if (_voc != null) _voc.ReleaseBulkMode(); + } + } + + ~BulkMode() + { + OnDispose(); + } + } + + #endregion Bulk Operation implementation + + #region Private Properties + + protected String _DefaultSelectionContext = new Guid().ToString(); + private IItemSourceProvider _Provider = null; + private IItemSourceProviderAsync _ProviderAsync = null; + private int _BulkCount = 0; + + #endregion Private Properties + + #region Internal implementation + + + /// + /// Gets the provider as editable. + /// + /// + /// + protected IEditableProvider GetProviderAsEditable() + { + IEditableProvider ret = null; + + if (this.Provider != null) + { + ret = this.Provider as IEditableProvider; + + } + else + { + ret = this.ProviderAsync as IEditableProvider; + } + + if (ret == null) + { + throw new NotSupportedException(); + } + + return ret; + } + + /// + /// Replaces oldValue with newValue at index if updatedat is newer or null. + /// + /// The index. + /// The old value. + /// The new value. + /// The timestamp. + internal void ReplaceAt(int index, T oldValue, T newValue, object timestamp) + { + var edit = this.GetProviderAsEditable(); + + if (edit != null) + { + edit.OnReplace(index, oldValue, newValue, timestamp); + + NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newValue, oldValue, index); + RaiseCollectionChangedEvent(args); + } + } + + void InternalClear() + { + if (this.Provider != null) + { + if (this.Provider is IProviderPreReset) + { + (this.Provider as IProviderPreReset).OnBeforeReset(); + } + this.Provider.OnReset(-1); + } + else + { + if (this.ProviderAsync is IProviderPreReset) + { + (this.ProviderAsync as IProviderPreReset).OnBeforeReset(); + } + this.ProviderAsync.OnReset(-1); + } + } + + CancellationTokenSource _ResetToken = null; + + public async void ResetAsync() + { + CancellationTokenSource cts = null; + + lock(this) + { + if(_ResetToken != null) + { + _ResetToken.Cancel(); + _ResetToken = null; + } + + cts = _ResetToken = new CancellationTokenSource(); + } + + if (this.Provider != null) + { + if (this.Provider is IProviderPreReset) + { + (this.Provider as IProviderPreReset).OnBeforeReset(); + if (cts.IsCancellationRequested) + { + return; + } + + } + + //this.Provider.OnReset(-2); + + Task.Factory.Run(async () => + { + if (this.Provider is IAsyncResetProvider) + { + int count = await (this.Provider as IAsyncResetProvider).GetCountAsync(); + if (!cts.IsCancellationRequested) + { + VirtualizationManager.Instance.RunOnUI(() => + this.Provider.OnReset(count) + ); + } + + } + else + { + int count = this.Provider.GetCount(false); + if (!cts.IsCancellationRequested) + { + VirtualizationManager.Instance.RunOnUI(() => + this.Provider.OnReset(count) + ); + } + } + + }); + + } + else + { + if (this.ProviderAsync is IProviderPreReset) + { + (this.ProviderAsync as IProviderPreReset).OnBeforeReset(); + } + this.ProviderAsync.OnReset(await this.ProviderAsync.Count); + } + + lock(this) + { + if(_ResetToken == cts) + { + _ResetToken = null; + } + } + } + + T InternalGetValue(int index, string selectionContext) + { + bool allowPlaceholder = true; + if (selectionContext != _DefaultSelectionContext) allowPlaceholder = false; + + if (this.Provider != null) + { + return this.Provider.GetAt(index, this, allowPlaceholder); + } + else + { + return Task.Factory.Run(() => this.ProviderAsync.GetAt(index, this, allowPlaceholder)).Result; + } + } + + T InternalSetValue(int index, T newValue) + { + T oldValue = InternalGetValue(index, _DefaultSelectionContext); + var edit = GetProviderAsEditable(); + edit.OnReplace(index, oldValue, newValue, null); + + List newItems = new List(); newItems.Add(newValue); + List oldItems = new List(); oldItems.Add(oldValue); + + NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newItems, oldItems, index); + RaiseCollectionChangedEvent(args); + + return oldValue; + } + + int InternalAdd(T newValue, object timestamp) + { + var edit = GetProviderAsEditable(); + var index = edit.OnAppend(newValue, timestamp); + + NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, newValue, index); + RaiseCollectionChangedEvent(args); + + OnCountTouched(); + + return index; + } + + int InternalGetCount() + { + int ret = 0; + + if (this.Provider != null) + { + ret = this.Provider.GetCount(true); + } + else + { + ret = Task.Factory.Run( () => this.ProviderAsync.Count).Result; + } + + + return ret; + } + + void InternalInsertAt(int index, T item, object timestamp = null) + { + + var edit = GetProviderAsEditable(); + edit.OnInsert(index, item, timestamp); + + NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index); + RaiseCollectionChangedEvent(args); + + OnCountTouched(); + } + + bool InternalRemoveAt(int index, object timestamp = null) + { + T oldValue = InternalGetValue(index, _DefaultSelectionContext); + + if (oldValue == null) + { + return false; + } + else + { + var edit = GetProviderAsEditable(); + edit.OnRemove(index, oldValue, timestamp); + + NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, oldValue, index); + RaiseCollectionChangedEvent(args); + + OnCountTouched(); + + return true; + } + } + + int InternalIndexOf(T item) + { + if (this.Provider != null) + { + return this.Provider.IndexOf(item); + } + else + { + return Task.Factory.Run( () => this.ProviderAsync.IndexOf(item)).Result; + } + } + + void EnsureCountIsGotNONASync() + { + if(this.Provider != null) + { + this.Provider.GetCount(false); + } + else + { + + } + } + + #endregion Internal implementation + + } + +} diff --git a/AlphaChiTech.Virtualization.Net4/app.config b/AlphaChiTech.Virtualization.Net4/app.config new file mode 100644 index 0000000..3c73782 --- /dev/null +++ b/AlphaChiTech.Virtualization.Net4/app.config @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AlphaChiTech.Virtualization.Net4/packages.config b/AlphaChiTech.Virtualization.Net4/packages.config new file mode 100644 index 0000000..7a8f88f --- /dev/null +++ b/AlphaChiTech.Virtualization.Net4/packages.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/AlphaChiTech.Virtualization.sln b/AlphaChiTech.Virtualization.sln index c5525cd..2646d8d 100644 --- a/AlphaChiTech.Virtualization.sln +++ b/AlphaChiTech.Virtualization.sln @@ -1,10 +1,8 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2013 -VisualStudioVersion = 12.0.31101.0 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AlphaChiTech.Virtualization", "AlphaChiTech.Virtualization\AlphaChiTech.Virtualization.csproj", "{5455EE53-36B9-48E8-B1F0-EB7C6ABB7A57}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataGridAsyncDemo", "DataGridAsyncDemo\DataGridAsyncDemo.csproj", "{B1235F30-C8A9-4A18-B870-A7F23F214039}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{90A3E488-4EFC-4E8C-BE36-B03651A95625}" @@ -14,20 +12,22 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{90A3E4 .nuget\NuGet.targets = .nuget\NuGet.targets EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AlphaChiTech.Virtualization.Net4", "AlphaChiTech.Virtualization.Net4\AlphaChiTech.Virtualization.Net4.csproj", "{ECEDC613-843A-4D9A-8123-BD9600C9118E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {5455EE53-36B9-48E8-B1F0-EB7C6ABB7A57}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5455EE53-36B9-48E8-B1F0-EB7C6ABB7A57}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5455EE53-36B9-48E8-B1F0-EB7C6ABB7A57}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5455EE53-36B9-48E8-B1F0-EB7C6ABB7A57}.Release|Any CPU.Build.0 = Release|Any CPU {B1235F30-C8A9-4A18-B870-A7F23F214039}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B1235F30-C8A9-4A18-B870-A7F23F214039}.Debug|Any CPU.Build.0 = Debug|Any CPU {B1235F30-C8A9-4A18-B870-A7F23F214039}.Release|Any CPU.ActiveCfg = Release|Any CPU {B1235F30-C8A9-4A18-B870-A7F23F214039}.Release|Any CPU.Build.0 = Release|Any CPU + {ECEDC613-843A-4D9A-8123-BD9600C9118E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ECEDC613-843A-4D9A-8123-BD9600C9118E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ECEDC613-843A-4D9A-8123-BD9600C9118E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ECEDC613-843A-4D9A-8123-BD9600C9118E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/DataGridAsyncDemo/DataGridAsyncDemo.csproj b/DataGridAsyncDemo/DataGridAsyncDemo.csproj index 29d1fbc..3c857a3 100644 --- a/DataGridAsyncDemo/DataGridAsyncDemo.csproj +++ b/DataGridAsyncDemo/DataGridAsyncDemo.csproj @@ -111,9 +111,9 @@ - - {5455ee53-36b9-48e8-b1f0-eb7c6abb7a57} - AlphaChiTech.Virtualization + + {ecedc613-843a-4d9a-8123-bd9600c9118e} + AlphaChiTech.Virtualization.Net4 diff --git a/DataGridAsyncDemo/MainWindow.xaml.cs b/DataGridAsyncDemo/MainWindow.xaml.cs index 2d3c6c2..8f18ed6 100644 --- a/DataGridAsyncDemo/MainWindow.xaml.cs +++ b/DataGridAsyncDemo/MainWindow.xaml.cs @@ -51,7 +51,7 @@ public MainWindow() //to the dispatcher thread, and using a DispatcherTimer to run the background //operations the VirtualizationManager needs to run to reclaim pages and manage memory. VirtualizationManager.Instance.UIThreadExcecuteAction = a => Dispatcher.Invoke( a ); - new DispatcherTimer( TimeSpan.FromSeconds( 1 ), + new DispatcherTimer( TimeSpan.FromMilliseconds( 10 ), DispatcherPriority.Background, delegate { @@ -170,9 +170,9 @@ public VirtualizingObservableCollection MyDataVirtualizedAsy if ( _myDataVirtualizedAsyncFilterSortObservableCollection == null ) { _myRemoteOrDbDataSourceAsyncProxy = new RemoteOrDbDataSourceAsyncProxy( new RemoteOrDbDataSourceEmulation() ); - _myDataVirtualizedAsyncFilterSortObservableCollection = - new VirtualizingObservableCollection( - new PaginationManager( _myRemoteOrDbDataSourceAsyncProxy ) ); + _myDataVirtualizedAsyncFilterSortObservableCollection = + new VirtualizingObservableCollection( + new PaginationManager(_myRemoteOrDbDataSourceAsyncProxy, pageSize: 10, maxPages:2) ); } return _myDataVirtualizedAsyncFilterSortObservableCollection; } diff --git a/DataGridAsyncDemo/RemoteOrDbDataSourceAsyncProxy.cs b/DataGridAsyncDemo/RemoteOrDbDataSourceAsyncProxy.cs index 9917b66..721c2d8 100644 --- a/DataGridAsyncDemo/RemoteOrDbDataSourceAsyncProxy.cs +++ b/DataGridAsyncDemo/RemoteOrDbDataSourceAsyncProxy.cs @@ -21,6 +21,8 @@ public class RemoteOrDbDataSourceAsyncProxy private readonly RemoteOrDbDataSourceEmulation _remoteDatas; + private Random _rand = new Random(); + #endregion public RemoteOrDbDataSourceAsyncProxy( RemoteOrDbDataSourceEmulation remoteDatas ) @@ -52,15 +54,23 @@ public SortDescriptionList SortDescriptionList int IPagedSourceProvider.IndexOf( RemoteOrDbDataItem item ) { - throw new NotImplementedException(); - } + return _remoteDatas.FilteredOrderedItems.IndexOf(item); + } + public PagedSourceItemsPacket GetItemsAt( int pageoffset, int count, bool usePlaceholder ) { - throw new NotImplementedException(); - } + Task.Delay(50 + (int)Math.Round(_rand.NextDouble() * 100)).Wait(); // Just to slow it down ! + return new PagedSourceItemsPacket + { + LoadedAt = DateTime.Now, + Items = (from items in _remoteDatas.FilteredOrderedItems select items).Skip(pageoffset).Take(count) + }; + } public int Count { - get { throw new NotImplementedException(); } + get { + Task.Delay(20 + (int)Math.Round(_rand.NextDouble() * 30)).Wait(); // Just to slow it down ! + return _remoteDatas.FilteredOrderedItems.Count; ; } } #endregion @@ -71,19 +81,20 @@ public int Count public Task GetCountAsync() { - return Task.Run( () => + return Task.Run(() => { - Task.Delay( 1000 ).Wait(); // Just to slow it down ! + Task.Delay(20 + (int)Math.Round(_rand.NextDouble() * 30)).Wait(); // Just to slow it down ! return _remoteDatas.FilteredOrderedItems.Count; } ); } public Task> GetItemsAtAsync( int pageoffset, int count, bool usePlaceholder ) { + Console.WriteLine("Get"); return Task.Run( () => { - Task.Delay( 1000 ).Wait(); // Just to slow it down ! - return new PagedSourceItemsPacket + Task.Delay(50 + (int)Math.Round(_rand.NextDouble() * 100)).Wait(); // Just to slow it down ! + return new PagedSourceItemsPacket { LoadedAt = DateTime.Now, Items = ( from items in _remoteDatas.FilteredOrderedItems select items ).Skip( pageoffset ).Take( count ) From 16ec8ac81c7317fabe909d780dc960a8fb82ae05 Mon Sep 17 00:00:00 2001 From: Jean-Denis Kreiss Date: Tue, 25 Oct 2016 14:47:24 +0200 Subject: [PATCH 04/23] Revert to .net 4.0 --- .../AlphaChiTech.Virtualization.Net4.csproj | 40 ++-- .../PagedSourceProviderMakeSync.cs | 6 +- .../PaginationManager.cs | 201 ++++++++++-------- .../VirtualizingObservableCollection.cs | 64 +++--- AlphaChiTech.Virtualization.Net4/app.config | 12 +- .../packages.config | 4 +- DataGridAsyncDemo/App.config | 6 +- DataGridAsyncDemo/DataGridAsyncDemo.csproj | 9 +- .../Properties/Resources.Designer.cs | 24 +-- .../Properties/Settings.Designer.cs | 10 +- DataGridAsyncDemo/packages.config | 2 +- 11 files changed, 201 insertions(+), 177 deletions(-) diff --git a/AlphaChiTech.Virtualization.Net4/AlphaChiTech.Virtualization.Net4.csproj b/AlphaChiTech.Virtualization.Net4/AlphaChiTech.Virtualization.Net4.csproj index 19a5bf9..f3e6c80 100644 --- a/AlphaChiTech.Virtualization.Net4/AlphaChiTech.Virtualization.Net4.csproj +++ b/AlphaChiTech.Virtualization.Net4/AlphaChiTech.Virtualization.Net4.csproj @@ -11,9 +11,12 @@ AlphaChiTech.Virtualization v4.0 512 - Client + + ..\ true + + true @@ -31,41 +34,27 @@ TRACE prompt 4 + 4 - - ..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.dll - True - - - ..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.dll - True - - - ..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.Desktop.dll - True - + - - ..\packages\Microsoft.Bcl.1.1.8\lib\net40\System.IO.dll + + ..\packages\Microsoft.Bcl.1.1.10\lib\net40\System.IO.dll True - - ..\packages\Microsoft.Bcl.1.1.8\lib\net40\System.Runtime.dll - True - - - ..\packages\Microsoft.Bcl.1.1.8\lib\net40\System.Threading.Tasks.dll + + ..\packages\Microsoft.Bcl.1.1.10\lib\net40\System.Threading.Tasks.dll True - + @@ -114,12 +103,9 @@ This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/DataGridAsyncDemoMVVM/Properties/Settings.Designer.cs b/DataGridAsyncDemoMVVM/Properties/Settings.Designer.cs new file mode 100644 index 0000000..6ae14ef --- /dev/null +++ b/DataGridAsyncDemoMVVM/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace DataGridAsyncDemoMVVM.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/DataGridAsyncDemoMVVM/Properties/Settings.settings b/DataGridAsyncDemoMVVM/Properties/Settings.settings new file mode 100644 index 0000000..c14891b --- /dev/null +++ b/DataGridAsyncDemoMVVM/Properties/Settings.settings @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/DataGridAsyncDemoMVVM/RemoteOrDbDataItem.cs b/DataGridAsyncDemoMVVM/RemoteOrDbDataItem.cs new file mode 100644 index 0000000..0d8c004 --- /dev/null +++ b/DataGridAsyncDemoMVVM/RemoteOrDbDataItem.cs @@ -0,0 +1,27 @@ +namespace DataGridAsyncDemoMVVM +{ + public class RemoteOrDbDataItem + { + public RemoteOrDbDataItem() + {} + + public RemoteOrDbDataItem( string name, string str1, string str2, int int1, double double1 ) + { + this.Name = name; + this.Str1 = str1; + this.Str2 = str2; + this.Int1 = int1; + this.Double1 = double1; + } + + #region properties + + public double Double1 { get; set; } + public int Int1 { get; set; } + public string Name { get; set; } + public string Str1 { get; set; } + public string Str2 { get; set; } + + #endregion + } +} diff --git a/DataGridAsyncDemoMVVM/RemoteOrDbDataSourceAsyncProxy.cs b/DataGridAsyncDemoMVVM/RemoteOrDbDataSourceAsyncProxy.cs new file mode 100644 index 0000000..a051998 --- /dev/null +++ b/DataGridAsyncDemoMVVM/RemoteOrDbDataSourceAsyncProxy.cs @@ -0,0 +1,128 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using AlphaChiTech.Virtualization; +using DataGridAsyncDemoMVVM.filtersort; + +namespace DataGridAsyncDemoMVVM +{ + #region + + #endregion + + /// + /// Remote/disk async data proxy. + /// Also add a delay on calls to simulate network/disk delay. + /// + public class RemoteOrDbDataSourceAsyncProxy + : IPagedSourceProviderAsync, IFilteredSortedSourceProviderAsync + { + #region fields + + private readonly RemoteOrDbDataSourceEmulation _remoteDatas; + + private readonly Random _rand = new Random(); + + #endregion + + public RemoteOrDbDataSourceAsyncProxy( RemoteOrDbDataSourceEmulation remoteDatas ) + { + this._remoteDatas = remoteDatas; + } + + #region properties + + + + public FilterDescriptionList FilterDescriptionList => this._remoteDatas.FilterDescriptionList; + + #endregion + + #region IFilteredSortedSourceProviderAsync Members + + public SortDescriptionList SortDescriptionList => this._remoteDatas.SortDescriptionList; + + #endregion + + #region IPagedSourceProvider Members (synchronous not available members) + + int IPagedSourceProvider.IndexOf( RemoteOrDbDataItem item ) + { + return this._remoteDatas.FilteredOrderedItems.IndexOf(item); + } + + public PagedSourceItemsPacket GetItemsAt( int pageoffset, int count, bool usePlaceholder ) + { + Task.Delay(50 + (int)Math.Round(this._rand.NextDouble() * 100)).Wait(); // Just to slow it down ! + return new PagedSourceItemsPacket + { + LoadedAt = DateTime.Now, + Items = (from items in this._remoteDatas.FilteredOrderedItems select items).Skip(pageoffset).Take(count) + }; + } + public int Count + { + get { + Task.Delay(20 + (int)Math.Round(this._rand.NextDouble() * 30)).Wait(); // Just to slow it down ! + return this._remoteDatas.FilteredOrderedItems.Count; ; } + } + + #endregion + + #region public members + + + + public Task GetCountAsync() + { + return Task.Run(() => + { + Task.Delay(20 + (int)Math.Round(this._rand.NextDouble() * 30)).Wait(); // Just to slow it down ! + return this._remoteDatas.FilteredOrderedItems.Count; + } ); + } + + public Task> GetItemsAtAsync( int pageoffset, int count, bool usePlaceholder ) + { + Console.WriteLine("Get"); + return Task.Run( () => + { + Task.Delay(50 + (int)Math.Round(this._rand.NextDouble() * 100)).Wait(); // Just to slow it down ! + return new PagedSourceItemsPacket + { + LoadedAt = DateTime.Now, + Items = ( from items in this._remoteDatas.FilteredOrderedItems select items ).Skip( pageoffset ).Take( count ) + }; + } ); + } + + public RemoteOrDbDataItem GetPlaceHolder( int index, int page, int offset ) + { + return new RemoteOrDbDataItem {Name = "Waiting [" + page + "/" + offset + "]"}; + } + + /// + /// This returns the index of a specific item. This method is optional – you can just return -1 if you + /// don’t need to use IndexOf. It’s not strictly required if don’t need to be able to seeking to a + /// specific item, but if you are selecting items implementing this method is recommended. + /// + /// + /// + public Task IndexOfAsync( RemoteOrDbDataItem item ) + { + return Task.Run( () => { return this._remoteDatas.FilteredOrderedItems.IndexOf( item ); } ); + } + + /// + /// This is a callback that runs when a Reset is called on a provider. Implementing this is also optional. + /// If you don’t need to do anything in particular when resets occur, you can leave this method body empty. + /// + /// + public void OnReset( int count ) + { + // Do nothing for now + } + + #endregion + } +} diff --git a/DataGridAsyncDemoMVVM/RemoteOrDbDataSourceEmulation.cs b/DataGridAsyncDemoMVVM/RemoteOrDbDataSourceEmulation.cs new file mode 100644 index 0000000..02ec279 --- /dev/null +++ b/DataGridAsyncDemoMVVM/RemoteOrDbDataSourceEmulation.cs @@ -0,0 +1,263 @@ +#region + + + +#endregion + +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Text.RegularExpressions; +using DataGridAsyncDemoMVVM.filtersort; +using System.Linq; +using System.Linq.Dynamic; + +namespace DataGridAsyncDemoMVVM +{ + using SortDescription = filtersort.SortDescription; + + /// + /// Emulate a remote data repository (list of item + sort & filter values) + /// + public class RemoteOrDbDataSourceEmulation : IFilteredSortedSourceProviderAsync + { + #region statics + + //private static RemoteOrDbDataSourceEmulation _instance; + private static readonly object _syncRoot = new Object(); + + #endregion + + #region fields + + private readonly FilterDescriptionList _filterDescriptionList = new FilterDescriptionList(); + private readonly List _items = new List(); + private readonly List _orderedItems = new List(); + private readonly SortDescriptionList _sortDescriptionList = new SortDescriptionList(); + private bool _isFilteredItemsValid; + private string _orderByLinqExpression = ""; + private string _whereLinqExpression = ""; + + #endregion + + public RemoteOrDbDataSourceEmulation() + { + for ( int i = 0; i < 100000; i++ ) + { + this._items.Add( new RemoteOrDbDataItem( "Name_" + i, "Str1_" + i, "Str1_" + i, i, i ) ); + } + + this._sortDescriptionList.CollectionChanged += this.SortDescriptionListOnCollectionChanged; + this._filterDescriptionList.CollectionChanged += this.FilterDescriptionListOnCollectionChanged; + } + + #region properties + + public IList FilteredOrderedItems + { + get + { + if (this._isFilteredItemsValid) return this._orderedItems; + + lock (this) + { + this._orderedItems.Clear(); + + try + { + if ( string.IsNullOrWhiteSpace( this.WhereLinqExpression ) && string.IsNullOrWhiteSpace( this.OrderByLinqExpression ) ) + this._orderedItems.AddRange( this._items ); + else if ( !string.IsNullOrWhiteSpace( this.WhereLinqExpression ) && string.IsNullOrWhiteSpace( this.OrderByLinqExpression ) ) + this._orderedItems.AddRange( this._items.Where( this.WhereLinqExpression ) ); + else if ( string.IsNullOrWhiteSpace( this.WhereLinqExpression ) && !string.IsNullOrWhiteSpace( this.OrderByLinqExpression ) ) + this._orderedItems.AddRange( this._items.OrderBy( this.OrderByLinqExpression ) ); + else if ( !string.IsNullOrWhiteSpace( this.WhereLinqExpression ) && !string.IsNullOrWhiteSpace( this.OrderByLinqExpression ) ) + this._orderedItems.AddRange( this._items.Where( this.WhereLinqExpression ).OrderBy( this.OrderByLinqExpression ) ); + } + catch + { + } + this._isFilteredItemsValid = true; + } + return this._orderedItems; + } + } + + public string OrderByLinqExpression + { + get => this._orderByLinqExpression; + set + { + if ( !string.Equals( this._orderByLinqExpression, value ) ) + { + this._orderByLinqExpression = value; + this._isFilteredItemsValid = false; + } + } + } + + public string WhereLinqExpression + { + get => this._whereLinqExpression; + set + { + if ( !string.Equals( this._whereLinqExpression, value ) ) + { + this._whereLinqExpression = value; + this._isFilteredItemsValid = false; + } + } + } + + #endregion + + #region public members + + public void OrderBy( string orderByExpression ) + { + if ( !string.Equals( orderByExpression, this.OrderByLinqExpression ) ) + this.OrderByLinqExpression = orderByExpression; + } + + public void Where( string whereExpression ) + { + if ( !string.Equals( whereExpression, this.WhereLinqExpression ) ) + this.WhereLinqExpression = whereExpression; + } + + #endregion + + #region filter & sort Descrioption list + + public SortDescriptionList SortDescriptionList => this._sortDescriptionList; + + public FilterDescriptionList FilterDescriptionList => this._filterDescriptionList; + + private void SortDescriptionListOnCollectionChanged( object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs ) + { + string sort = ""; + + bool sortFound = false; + foreach ( SortDescription sortDescription in this._sortDescriptionList ) + { + if ( sortFound ) + sort += ", "; + + sortFound = true; + + sort += sortDescription.PropertyName; + sort += ( sortDescription.Direction == ListSortDirection.Ascending ) ? " ASC" : " DESC"; + } + + //if ((!sortFound) && (!string.IsNullOrWhiteSpace( primaryKey ))) + // sort += primaryKey + " ASC"; + + this.OrderByLinqExpression = sort; + } + + private void FilterDescriptionListOnCollectionChanged( object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs ) + { + if ( notifyCollectionChangedEventArgs.Action == NotifyCollectionChangedAction.Reset ) + { + string filter = ""; + + bool filterFound = false; + foreach ( FilterDescription filterDescription in this._filterDescriptionList ) + { + string subFilter = GetLinqQueryString( filterDescription ); + if ( !string.IsNullOrWhiteSpace( subFilter ) ) + { + if ( filterFound ) + filter += " and "; + filterFound = true; + filter += " " + subFilter + " "; + } + } + + this.WhereLinqExpression = filter; + } + } + + #region query builder + + private static readonly Regex _regexSplit = new Regex( @"(and)|(or)|(==)|(<>)|(!=)|(<=)|(>=)|(&&)|(\|\|)|(=)|(>)|(<)|(\*[\-_a-zA-Z0-9]+)|([\-_a-zA-Z0-9]+\*)|([\-_a-zA-Z0-9]+)", + RegexOptions.IgnoreCase ); + + private static readonly Regex _regexOp = new Regex( @"(and)|(or)|(==)|(<>)|(!=)|(<=)|(>=)|(&&)|(\|\|)|(=)|(>)|(<)", RegexOptions.IgnoreCase ); + private static readonly Regex _regexComparOp = new Regex( @"(==)|(<>)|(!=)|(<=)|(>=)|(=)|(>)|(<)", RegexOptions.None ); + + private static string GetLinqQueryString( FilterDescription filterDescription ) + { + string ret = ""; + + if ( !string.IsNullOrWhiteSpace( filterDescription.Filter ) ) + { + // using user str + linq.dynamic + try + { + // xceed syntax : empty (contains), AND (uppercase), OR (uppercase), <>, * (end with), =, >, >=, <, <=, * (start with) + // see http://doc.xceedsoft.com/products/XceedWpfDataGrid/Filter_Row.html + // linq.dynamic syntax : =, ==, <>, !=, <, >, <=, >=, &&, and, ||, or, x.m(…) (where x is the attrib and m the function (ex: Contains, StartsWith, EndsWith ...) + // see D:\DevC#\VirtualisingCollectionTest1\DynamicQuery\Dynamic Expressions.html + // ex : RemoteOrDbDataSourceEmulation.Instance.Items.Where( "Name.Contains(\"e_1\") or Name.Contains(\"e_2\")" ); + + string exp = filterDescription.Filter; + + // arrange expression + + bool previousTermIsOperator = false; + foreach ( Match match in _regexSplit.Matches( exp ) ) + { + if ( match.Success ) + { + //TODO processing results + if ( _regexOp.IsMatch( match.Value ) ) + { + if ( _regexComparOp.IsMatch( match.Value ) ) + { + // simple operator >, <, ==, != ... + ret += " " + filterDescription.PropertyName + " " + match.Value; + previousTermIsOperator = true; + } + else + { + // and, or ... + ret += " " + match.Value; + previousTermIsOperator = false; + } + } + else + { + // Value + if ( previousTermIsOperator ) + { + ret += " " + match.Value; + previousTermIsOperator = false; + } + else + { + if ( match.Value.StartsWith( "*" ) ) + ret += " " + filterDescription.PropertyName + ".EndsWith( \"" + match.Value.Substring( 1 ) + "\" )"; + else if ( match.Value.EndsWith( "*" ) ) + ret += " " + filterDescription.PropertyName + ".StartsWith( \"" + match.Value.Substring( 0, match.Value.Length - 1 ) + "\" )"; + else + ret += " " + filterDescription.PropertyName + ".Contains( \"" + match.Value + "\" )"; + previousTermIsOperator = false; + } + } + } + } + } + catch ( Exception ) + {} + } + + return ret; + } + + #endregion query builder + + #endregion filter & sort Descrioption list + } +} diff --git a/DataGridAsyncDemoMVVM/filtersort/DescriptionList.cs b/DataGridAsyncDemoMVVM/filtersort/DescriptionList.cs new file mode 100644 index 0000000..d88bb85 --- /dev/null +++ b/DataGridAsyncDemoMVVM/filtersort/DescriptionList.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; + +namespace DataGridAsyncDemoMVVM.filtersort +{ + public class DescriptionList : IEnumerable, IEnumerable, INotifyCollectionChanged + where T : IFilterOrderDescription + { + private readonly List _filterDescriptions = new List(); + + IEnumerator IEnumerable.GetEnumerator() + { + return this._filterDescriptions.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return this._filterDescriptions.GetEnumerator(); + } + + public event NotifyCollectionChangedEventHandler CollectionChanged; + + /// + /// If it exist, remove existing filter that apply on same property name. The add item arg at first position into filter list. + /// + /// + public void Add( T item ) + { + int index = this._filterDescriptions.FindIndex( description => description.PropertyName.Equals( item.PropertyName, StringComparison.Ordinal ) ); + if (index >= 0) + { + T removed = this._filterDescriptions[index]; + this._filterDescriptions.RemoveAt( index ); + //OnCollectionChanged( new NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction.Remove, removed, index ) ); + this._filterDescriptions.Insert( 0, item ); + //OnCollectionChanged( new NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction.Add, item, 0 ) ); + + this.OnCollectionChanged( new NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction.Move, removed, 0, index ) ); + } + else + { + this._filterDescriptions.Insert( 0, item ); + this.OnCollectionChanged( new NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction.Add, item, 0 ) ); + } + } + + protected void OnCollectionChanged( NotifyCollectionChangedEventArgs arg ) + { + var evnt = this.CollectionChanged; + + if (evnt != null) + evnt( this, arg ); + } + + /// + /// If it exist, remove existing filter that apply on same property name. The add item arg at first position into filter list. + /// + /// + public void Remove( string propertyName ) + { + int index = this._filterDescriptions.FindIndex( description => description.PropertyName.Equals( propertyName, StringComparison.Ordinal ) ); + if (index >= 0) + { + T removed = this._filterDescriptions[index]; + this._filterDescriptions.RemoveAt( index ); + this.OnCollectionChanged( new NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction.Remove, removed, index ) ); + } + } + } +} \ No newline at end of file diff --git a/DataGridAsyncDemoMVVM/filtersort/FilterDescription.cs b/DataGridAsyncDemoMVVM/filtersort/FilterDescription.cs new file mode 100644 index 0000000..5957684 --- /dev/null +++ b/DataGridAsyncDemoMVVM/filtersort/FilterDescription.cs @@ -0,0 +1,14 @@ +namespace DataGridAsyncDemoMVVM.filtersort +{ + public class FilterDescription : IFilterOrderDescription + { + public FilterDescription(string propertyName, string filter) + { + this.PropertyName = propertyName; + this.Filter = filter; + } + + public string Filter { get; set; } + public string PropertyName { get; set; } + } +} \ No newline at end of file diff --git a/DataGridAsyncDemoMVVM/filtersort/FilterDescriptionList.cs b/DataGridAsyncDemoMVVM/filtersort/FilterDescriptionList.cs new file mode 100644 index 0000000..b2b0ff4 --- /dev/null +++ b/DataGridAsyncDemoMVVM/filtersort/FilterDescriptionList.cs @@ -0,0 +1,12 @@ +using System.Collections.Specialized; + +namespace DataGridAsyncDemoMVVM.filtersort +{ + public class FilterDescriptionList : DescriptionList + { + public void OnCollectionReset() + { + this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + } +} \ No newline at end of file diff --git a/DataGridAsyncDemoMVVM/filtersort/IFilterOrderDescription.cs b/DataGridAsyncDemoMVVM/filtersort/IFilterOrderDescription.cs new file mode 100644 index 0000000..6d91312 --- /dev/null +++ b/DataGridAsyncDemoMVVM/filtersort/IFilterOrderDescription.cs @@ -0,0 +1,7 @@ +namespace DataGridAsyncDemoMVVM.filtersort +{ + public interface IFilterOrderDescription + { + string PropertyName { get; set; } + } +} \ No newline at end of file diff --git a/DataGridAsyncDemoMVVM/filtersort/IFilteredSortedSourceProviderAsync.cs b/DataGridAsyncDemoMVVM/filtersort/IFilteredSortedSourceProviderAsync.cs new file mode 100644 index 0000000..0131f5e --- /dev/null +++ b/DataGridAsyncDemoMVVM/filtersort/IFilteredSortedSourceProviderAsync.cs @@ -0,0 +1,7 @@ +namespace DataGridAsyncDemoMVVM.filtersort +{ + public interface IFilteredSortedSourceProviderAsync + { + SortDescriptionList SortDescriptionList { get; } + } +} \ No newline at end of file diff --git a/DataGridAsyncDemoMVVM/filtersort/ParentOfTypeExtensions.cs b/DataGridAsyncDemoMVVM/filtersort/ParentOfTypeExtensions.cs new file mode 100644 index 0000000..1933f2b --- /dev/null +++ b/DataGridAsyncDemoMVVM/filtersort/ParentOfTypeExtensions.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Windows; +using System.Windows.Media; + +namespace DataGridAsyncDemoMVVM.filtersort +{ + internal static class ArgumentVerificationExtensions + { + public static void TestNotNull(this object parameter, string parameterName) + { + if (parameter == null) + throw new ArgumentNullException(parameterName); + } + + public static void TestNotEmptyString(this string parameter, string parameterName) + { + if (string.IsNullOrEmpty(parameter)) + throw new ArgumentException( + string.Format("The parameter '{0}' should not be empty string.", parameterName), parameterName); + } + } + + /// + /// Contains extension methods for enumerating the parents of an element. + /// + public static class ParentOfTypeExtensions + { + /// + /// Gets the parent element from the visual tree by given type. + /// + public static T ParentOfType(this DependencyObject element) where T : DependencyObject + { + if (element == null) + return null; + + return element.GetParents().OfType().FirstOrDefault(); + } + + + /// + /// Determines whether the element is an ancestor of the descendant. + /// + /// true if the visual object is an ancestor of descendant; otherwise, false. + public static bool IsAncestorOf(this DependencyObject element, DependencyObject descendant) + { + element.TestNotNull("element"); + descendant.TestNotNull("descendant"); + + return descendant == element || descendant.GetParents().Contains(element); + } + + /// + /// Searches up in the visual tree for parent element of the specified type. + /// + /// + /// The type of the parent that will be searched up in the visual object hierarchy. + /// The type should be . + /// + /// + /// The target which visual parents will be traversed. + /// + /// Visual parent of the specified type if there is any, otherwise null. + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")] + public static T GetVisualParent(this DependencyObject element) where T : DependencyObject + { + return element.ParentOfType(); + } + + /// + /// This recurses the visual tree for ancestors of a specific type. + /// + public static IEnumerable GetAncestors(this DependencyObject element) where T : class + { + return element.GetParents().OfType(); + } + + /// + /// This recurses the visual tree for a parent of a specific type. + /// + public static T GetParent(this DependencyObject element) where T : FrameworkElement + { + return element.ParentOfType(); + } + + /// + /// Enumerates through element's parents in the visual tree. + /// + public static IEnumerable GetParents(this DependencyObject element) + { + if (element == null) + throw new ArgumentNullException("element"); + + while ((element = element.GetParent()) != null) + yield return element; + } + + private static DependencyObject GetParent(this DependencyObject element) + { + var parent = VisualTreeHelper.GetParent(element); + if (parent == null) + { + var frameworkElement = element as FrameworkElement; + if (frameworkElement != null) + parent = frameworkElement.Parent; + } + return parent; + } + } +} \ No newline at end of file diff --git a/DataGridAsyncDemoMVVM/filtersort/SortDescription.cs b/DataGridAsyncDemoMVVM/filtersort/SortDescription.cs new file mode 100644 index 0000000..3d8374a --- /dev/null +++ b/DataGridAsyncDemoMVVM/filtersort/SortDescription.cs @@ -0,0 +1,16 @@ +using System.ComponentModel; + +namespace DataGridAsyncDemoMVVM.filtersort +{ + public class SortDescription : IFilterOrderDescription + { + public SortDescription(string propertyName, ListSortDirection? direction) + { + this.Direction = direction; + this.PropertyName = propertyName; + } + + public ListSortDirection? Direction { get; set; } + public string PropertyName { get; set; } + } +} \ No newline at end of file diff --git a/DataGridAsyncDemoMVVM/filtersort/SortDescriptionList.cs b/DataGridAsyncDemoMVVM/filtersort/SortDescriptionList.cs new file mode 100644 index 0000000..32ed9be --- /dev/null +++ b/DataGridAsyncDemoMVVM/filtersort/SortDescriptionList.cs @@ -0,0 +1,6 @@ +namespace DataGridAsyncDemoMVVM.filtersort +{ + public class SortDescriptionList : DescriptionList + { + } +} \ No newline at end of file diff --git a/DataGridAsyncDemoMVVM/packages.config b/DataGridAsyncDemoMVVM/packages.config new file mode 100644 index 0000000..a08ea93 --- /dev/null +++ b/DataGridAsyncDemoMVVM/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file From 210fdba92a2732c245dc85d0320487b6c72d0fb6 Mon Sep 17 00:00:00 2001 From: Jan Pluskal Date: Sun, 17 Dec 2017 16:32:22 +0100 Subject: [PATCH 08/23] Add Filtering. --- DataGridAsyncDemoMVVM/App.xaml | 2 +- DataGridAsyncDemoMVVM/App.xaml.cs | 12 +- .../DataGridAsyncDemoMVVM.csproj | 17 + DataGridAsyncDemoMVVM/MainViewModel.cs | 58 ++- DataGridAsyncDemoMVVM/MainWindow.xaml | 27 +- DataGridAsyncDemoMVVM/MainWindow.xaml.cs | 1 + .../Properties/Resources.Designer.cs | 2 +- DataGridAsyncDemoMVVM/RemoteOrDbDataItem.cs | 43 +- .../RemoteOrDbDataSourceAsyncProxy.cs | 174 ++++---- .../RemoteOrDbDataSourceEmulation.cs | 401 +++++++++--------- ...ChangedEventArgsToDatagridHeaderAndText.cs | 33 ++ .../filtersort/DescriptionList.cs | 114 ++--- .../filtersort/MemberPathFilterText.cs | 8 + .../filtersort/ParentOfTypeExtensions.cs | 15 +- DataGridAsyncDemoMVVM/packages.config | 3 +- 15 files changed, 500 insertions(+), 410 deletions(-) create mode 100644 DataGridAsyncDemoMVVM/converters/TextChangedEventArgsToDatagridHeaderAndText.cs create mode 100644 DataGridAsyncDemoMVVM/filtersort/MemberPathFilterText.cs diff --git a/DataGridAsyncDemoMVVM/App.xaml b/DataGridAsyncDemoMVVM/App.xaml index 2a0b8b1..f90e982 100644 --- a/DataGridAsyncDemoMVVM/App.xaml +++ b/DataGridAsyncDemoMVVM/App.xaml @@ -8,4 +8,4 @@ - + \ No newline at end of file diff --git a/DataGridAsyncDemoMVVM/App.xaml.cs b/DataGridAsyncDemoMVVM/App.xaml.cs index 38c44bd..d71b991 100644 --- a/DataGridAsyncDemoMVVM/App.xaml.cs +++ b/DataGridAsyncDemoMVVM/App.xaml.cs @@ -1,17 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Configuration; -using System.Data; -using System.Linq; -using System.Threading.Tasks; -using System.Windows; +using System.Windows; namespace DataGridAsyncDemoMVVM { /// - /// Interaction logic for App.xaml + /// Interaction logic for App.xaml /// public partial class App : Application { } -} +} \ No newline at end of file diff --git a/DataGridAsyncDemoMVVM/DataGridAsyncDemoMVVM.csproj b/DataGridAsyncDemoMVVM/DataGridAsyncDemoMVVM.csproj index 98fa948..76088dd 100644 --- a/DataGridAsyncDemoMVVM/DataGridAsyncDemoMVVM.csproj +++ b/DataGridAsyncDemoMVVM/DataGridAsyncDemoMVVM.csproj @@ -34,11 +34,26 @@ 4 + + ..\packages\MvvmLightLibs.5.3.0.0\lib\net45\GalaSoft.MvvmLight.dll + + + ..\packages\MvvmLightLibs.5.3.0.0\lib\net45\GalaSoft.MvvmLight.Extras.dll + + + ..\packages\MvvmLightLibs.5.3.0.0\lib\net45\GalaSoft.MvvmLight.Platform.dll + + + ..\packages\CommonServiceLocator.1.3\lib\portable-net4+sl5+netcore45+wpa81+wp8\Microsoft.Practices.ServiceLocation.dll + ..\packages\System.Linq.Dynamic.1.0.7\lib\net40\System.Linq.Dynamic.dll + + ..\packages\MvvmLightLibs.5.3.0.0\lib\net45\System.Windows.Interactivity.dll + @@ -57,6 +72,7 @@ MSBuild:Compile Designer + @@ -69,6 +85,7 @@ + MSBuild:Compile Designer diff --git a/DataGridAsyncDemoMVVM/MainViewModel.cs b/DataGridAsyncDemoMVVM/MainViewModel.cs index 1a46b59..7cfae0c 100644 --- a/DataGridAsyncDemoMVVM/MainViewModel.cs +++ b/DataGridAsyncDemoMVVM/MainViewModel.cs @@ -1,30 +1,54 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using System.ComponentModel; +using System.Diagnostics; +using System.Threading; using System.Threading.Tasks; +using System.Windows.Data; using AlphaChiTech.Virtualization; +using DataGridAsyncDemoMVVM.filtersort; +using GalaSoft.MvvmLight.Command; namespace DataGridAsyncDemoMVVM { - class MainViewModel + internal class MainViewModel { - private VirtualizingObservableCollection _myDataVirtualizedAsyncFilterSortObservableCollection = null; - private RemoteOrDbDataSourceAsyncProxy _myRemoteOrDbDataSourceAsyncProxy = null; + private readonly RemoteOrDbDataSourceAsyncProxy _myRemoteOrDbDataSourceAsyncProxy; + private VirtualizingObservableCollection myDataVirtualizedAsyncFilterSortObservableCollection; - public VirtualizingObservableCollection MyDataVirtualizedAsyncFilterSortObservableCollection + public MainViewModel() { - get + this._myRemoteOrDbDataSourceAsyncProxy = new RemoteOrDbDataSourceAsyncProxy(new RemoteOrDbDataSourceEmulation(100)); + this.myDataVirtualizedAsyncFilterSortObservableCollection = + new VirtualizingObservableCollection( + new PaginationManager(this._myRemoteOrDbDataSourceAsyncProxy, + pageSize: 10, maxPages: 2)); + this.MyDataVirtualizedAsyncFilterSortObservableCollectionCollectionView = + CollectionViewSource.GetDefaultView(myDataVirtualizedAsyncFilterSortObservableCollection); + + this.FilterCommand = new RelayCommand(async o => await this.Filter(o as MemberPathFilterText)); + } + + private int _filterWaitingCount = 0; + private async Task Filter(MemberPathFilterText memberPathFilterText) + { + if (String.IsNullOrWhiteSpace(memberPathFilterText.FilterText)) { - if (this._myDataVirtualizedAsyncFilterSortObservableCollection == null) - { - this._myRemoteOrDbDataSourceAsyncProxy = new RemoteOrDbDataSourceAsyncProxy(new RemoteOrDbDataSourceEmulation()); - this._myDataVirtualizedAsyncFilterSortObservableCollection = - new VirtualizingObservableCollection( - new PaginationManager(this._myRemoteOrDbDataSourceAsyncProxy, pageSize: 10, maxPages: 2)); - } - return this._myDataVirtualizedAsyncFilterSortObservableCollection; + this._myRemoteOrDbDataSourceAsyncProxy.FilterDescriptionList.Remove(memberPathFilterText + .ColumnSortMemberPath); } + else + { + this._myRemoteOrDbDataSourceAsyncProxy.FilterDescriptionList.Add(new FilterDescription(memberPathFilterText.ColumnSortMemberPath, memberPathFilterText.FilterText)); + } + Interlocked.Increment(ref this._filterWaitingCount); + await Task.Delay(500); + if (Interlocked.Decrement(ref this._filterWaitingCount) != 0) return; + this._myRemoteOrDbDataSourceAsyncProxy.FilterDescriptionList.OnCollectionReset(); + this.myDataVirtualizedAsyncFilterSortObservableCollection.Clear(); } + + public ICollectionView MyDataVirtualizedAsyncFilterSortObservableCollectionCollectionView { get; } + + public RelayCommand FilterCommand { get; } } -} +} \ No newline at end of file diff --git a/DataGridAsyncDemoMVVM/MainWindow.xaml b/DataGridAsyncDemoMVVM/MainWindow.xaml index 44430e3..1508f5e 100644 --- a/DataGridAsyncDemoMVVM/MainWindow.xaml +++ b/DataGridAsyncDemoMVVM/MainWindow.xaml @@ -1,11 +1,17 @@  + DataContext="{StaticResource MainViewModel}"> + + + @@ -15,7 +21,7 @@ + ItemsSource="{Binding MyDataVirtualizedAsyncFilterSortObservableCollectionCollectionView}">