├── .gitattributes
├── .github
└── workflows
│ ├── dotnet-core.yml
│ └── onVersionTag.yml
├── .gitignore
├── BFF.DataVirtualizingCollection.Complete.sln
├── BFF.DataVirtualizingCollection.sln
├── BFF.DataVirtualizingCollection
├── BFF.DataVirtualizingCollection.csproj
├── DataVirtualizingCollection
│ ├── AsyncDataVirtualizingCollection.cs
│ ├── DataVirtualizingCollectionBase.cs
│ ├── DataVirtualizingCollectionBuilder.cs
│ ├── IDataVirtualizingCollection.cs
│ └── SyncDataVirtualizingCollection.cs
├── DataVirtualizingCollectionBuilderBase.cs
├── IBuilderInterfaces.cs
├── IVirtualizationBase.cs
├── InternalsVisibleTo.cs
├── PageRemoval
│ ├── HoardingPageNonRemoval.cs
│ ├── LeastRecentlyUsedPageRemoval.cs
│ └── PageReplacementStrategyException.cs
├── PageStorage
│ ├── AsyncPageBase.cs
│ ├── IAsyncPageFetchScheduler.cs
│ ├── IPage.cs
│ ├── ImmediateAsyncPageFetchScheduler.cs
│ ├── LifoAsyncPageFetchScheduler.cs
│ ├── PageStorage.cs
│ ├── PreloadingPageStorage.cs
│ └── SyncNonPreloadingPageBase.cs
├── Public.puml
├── SlidingWindow
│ ├── ISlidingWindow.cs
│ ├── SlidingWindow.cs
│ └── SlidingWindowBuilder.cs
├── Utilities
│ ├── DateTimeTimestampProvider.cs
│ ├── ErrorMessage.cs
│ └── ITimestampProvider.cs
└── VirtualizationBase.cs
├── Directory.Build.props
├── Documentation
├── API.md
└── Diagrams
│ ├── Context.png
│ ├── Context.uxf
│ ├── DataVirtualizingCollection.png
│ ├── DataVirtualizingCollection.svg
│ ├── PublicInterfaceHierarchy.png
│ ├── PublicInterfaceHierarchy.svg
│ ├── PublicInterfaceHierarchy.uxf
│ ├── SlidingWindow.png
│ └── SlidingWindow.svg
├── Icon
├── Yeah69Logo.ico
└── Yeah69Logo_256.png
├── LICENSE.md
├── README.md
├── Sample.GettingStarted
├── App.xaml
├── App.xaml.cs
├── AssemblyInfo.cs
├── MainWindow.xaml
├── MainWindow.xaml.cs
└── Sample.GettingStarted.csproj
├── Sample.Model
├── AutofacModule.cs
├── BackendAccesses
│ ├── AllNumbersFakeBackendAccess.cs
│ ├── HighWorkloadFakeBackendAccess.cs
│ ├── IBackendAccess.cs
│ ├── MillionNumbersBackendAccess.cs
│ └── ProfilesFakeBackendAccess.cs
├── Models
│ ├── HighWorkloadObject.cs
│ ├── LowWorkloadObject.cs
│ ├── Profile.cs
│ └── SomeWorkloadObject.cs
├── PersistenceLink
│ └── IFetchMillionNumbersFromBackend.cs
└── Sample.Model.csproj
├── Sample.Persistence.Proxy
├── AutofacModule.cs
└── Sample.Persistence.Proxy.csproj
├── Sample.Persistence
├── AutofacModule.cs
├── Databases
│ └── BFF.DataVirtualizingCollection.MillionNumbers.sqlite
├── PersistenceLink
│ └── FetchMillionNumbersFromBackend.cs
└── Sample.Persistence.csproj
├── Sample.View
├── App.xaml
├── App.xaml.cs
├── AssemblyInfo.cs
├── AutofacModule.cs
├── ProfilePics
│ ├── 00_Wide.png
│ ├── 01_Paria.png
│ ├── 02_Morexandra.png
│ ├── 03_Smennifer.png
│ ├── 04_Anyetlana.png
│ ├── 05_Korko.png
│ ├── 06_Kowel.png
│ ├── 07_Sinia.png
│ ├── 08_Wonathan.png
│ ├── 09_Matban.png
│ ├── 10_Surgiana.png
│ └── 11_Jogory.png
├── Resources
│ ├── BackendCollectionDataTemplates.xaml
│ └── ItemDataTemplates.xaml
├── Sample.View.csproj
├── Utilities
│ ├── Converters.cs
│ ├── DataTemplateSelectors.cs
│ └── ProfileViewStatic.cs
├── ViewModelInterfaceImplementations
│ └── GetScheduler.cs
└── Views
│ ├── DataVirtualizingCollectionView.xaml
│ ├── DataVirtualizingCollectionView.xaml.cs
│ ├── Decisions
│ ├── FetcherKindView.xaml
│ ├── FetcherKindView.xaml.cs
│ ├── IndexAccessBehaviorViewModel.xaml
│ ├── IndexAccessBehaviorViewModel.xaml.cs
│ ├── PageLoadingBehaviorView.xaml
│ ├── PageLoadingBehaviorView.xaml.cs
│ ├── PageRemovalBehaviorView.xaml
│ └── PageRemovalBehaviorView.xaml.cs
│ ├── Functions
│ ├── GeneralFunctionsView.xaml
│ ├── GeneralFunctionsView.xaml.cs
│ ├── SpecificFunctionsView.xaml
│ └── SpecificFunctionsView.xaml.cs
│ ├── MainWindowView.xaml
│ ├── MainWindowView.xaml.cs
│ └── Options
│ ├── GeneralOptionsView.xaml
│ ├── GeneralOptionsView.xaml.cs
│ ├── SlidingWindowOptionsView.xaml
│ └── SlidingWindowOptionsView.xaml.cs
├── Sample.ViewModel
├── Adapters
│ ├── AllNumbersCollectionAdapter.cs
│ ├── HighWorkloadCollectionAdapter.cs
│ ├── IBackendAccessAdapter.cs
│ ├── MillionNumbersCollectionAdapter.cs
│ └── ProfileCollectionAdapter.cs
├── AutofacModule.cs
├── Interfaces
│ └── IGetSchedulers.cs
├── Sample.ViewModel.csproj
├── Utility
│ ├── RxRelayCommand.cs
│ └── TransformingBackendAccess.cs
└── ViewModels
│ ├── DataVirtualizingCollectionViewModel.cs
│ ├── Decisions
│ ├── FetcherKindViewModel.cs
│ ├── IndexAccessBehaviorViewModel.cs
│ ├── PageLoadingBehaviorViewModel.cs
│ └── PageRemovalBehaviorViewModel.cs
│ ├── Functions
│ ├── GeneralFunctionsViewModel.cs
│ └── SpecificFunctionsViewModel.cs
│ ├── MainWindowViewModel.cs
│ ├── ObservableObject.cs
│ ├── Options
│ ├── GeneralOptionsViewModel.cs
│ └── SlidingWindowOptionsViewModel.cs
│ ├── ProfileViewModel.cs
│ └── SomeWorkloadObjectViewModel.cs
├── Tests.Integration
├── DataVirtualizingCollectionDataAccess
│ ├── AsyncDataAccessTests.cs
│ └── SyncDataAccessTests.cs
├── DataVirtualizingCollectionFactory.cs
├── FactoryEnums.cs
├── PageRemoval
│ ├── AsyncHoardingTests.cs
│ ├── AsyncLeastRecentlyUsedTests.cs
│ ├── SyncHoardingTests.cs
│ └── SyncLeastRecentlyUsedTests.cs
├── SlidingWindowDataAccess
│ ├── AsyncDataAccessTests.cs
│ └── SyncDataAccessTests.cs
├── SlidingWindowFactory.cs
├── SlidingWindowSpecific
│ ├── AsyncDataAccessTests.cs
│ └── SyncDataAccessTests.cs
└── Tests.Integration.csproj
└── Tests.Unit
├── DataVirtualizingCollection
└── AsyncDataVirtualizingCollectionTests.cs
├── PageRemoval
├── HoardingPageNonRemovalTests.cs
└── LeastRecentlyUsedPageRemovalTests.cs
├── PageStorage
├── AsyncPageBaseTests.cs
├── PageStorageTests.cs
├── PageTestsBase.cs
├── PreloadingPageStorageTests.cs
└── SyncNonPreloadingPageBaseTests.cs
└── Tests.Unit.csproj
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/.github/workflows/dotnet-core.yml:
--------------------------------------------------------------------------------
1 | name: Build & Unit test
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | jobs:
10 | windows:
11 |
12 | strategy:
13 | matrix:
14 | configuration: [ Release ]
15 | runs-on: windows-latest
16 |
17 | env:
18 | Solution_Name: BFF.DataVirtualizingCollection.sln # Replace with your solution name, i.e. MyWpfApp.sln.
19 | Test_Project_Path: Tests.Unit\Tests.Unit.csproj # Replace with the path to your test project, i.e. MyWpfApp.Tests\MyWpfApp.Tests.csproj.
20 |
21 | steps:
22 | - uses: actions/checkout@v2
23 | with:
24 | fetch-depth: 0
25 | - name: Setup .NET Core
26 | uses: actions/setup-dotnet@v1
27 | with:
28 | dotnet-version: 5.0.102
29 | - run: set DOTNET_CLI_TELEMETRY_OPTOUT=1
30 | - name: Install dependencies
31 | run: dotnet restore .\BFF.DataVirtualizingCollection.sln
32 | - name: Build
33 | run: dotnet build .\BFF.DataVirtualizingCollection.sln --configuration Release --no-restore
34 | - name: Test
35 | run: dotnet test .\Tests.Unit\Tests.Unit.csproj --no-restore --verbosity normal
36 |
--------------------------------------------------------------------------------
/.github/workflows/onVersionTag.yml:
--------------------------------------------------------------------------------
1 | name: OnVersionTag
2 |
3 | on:
4 | push:
5 | tags:
6 | - '*'
7 | jobs:
8 | windows:
9 |
10 | strategy:
11 | matrix:
12 | configuration: [ Release ]
13 | runs-on: windows-latest
14 |
15 | env:
16 | Solution_Name: BFF.DataVirtualizingCollection.sln # Replace with your solution name, i.e. MyWpfApp.sln.
17 | Test_Project_Path: Tests.Unit\Tests.Unit.csproj # Replace with the path to your test project, i.e. MyWpfApp.Tests\MyWpfApp.Tests.csproj.
18 |
19 | steps:
20 | - uses: actions/checkout@v2
21 | - name: Setup .NET Core
22 | uses: actions/setup-dotnet@v1
23 | with:
24 | dotnet-version: 5.0.102
25 | - run: set DOTNET_CLI_TELEMETRY_OPTOUT=1
26 | - name: Install dependencies
27 | run: dotnet restore .\BFF.DataVirtualizingCollection.sln
28 | - name: Build
29 | run: dotnet build .\BFF.DataVirtualizingCollection.sln --configuration Release --no-restore
30 | - name: Test
31 | run: dotnet test .\Tests.Unit\Tests.Unit.csproj --no-restore --verbosity normal
32 | - name: Publish to NuGet
33 | uses: brandedoutcast/publish-nuget@v2
34 | with:
35 | PROJECT_FILE_PATH: BFF.DataVirtualizingCollection/BFF.DataVirtualizingCollection.csproj
36 | VERSION_FILE_PATH: Directory.Build.props
37 | TAG_COMMIT: false
38 | NUGET_KEY: ${{secrets.NUGET_API_KEY}}
39 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | build/
21 | bld/
22 | [Bb]in/
23 | [Oo]bj/
24 |
25 | # Visual Studio 2015 cache/options directory
26 | .vs/
27 | .vscode/
28 | # Uncomment if you have tasks that create the project's static files in wwwroot
29 | #wwwroot/
30 |
31 | # MSTest test Results
32 | [Tt]est[Rr]esult*/
33 | [Bb]uild[Ll]og.*
34 |
35 | # NUNIT
36 | *.VisualState.xml
37 | TestResult.xml
38 |
39 | # Build Results of an ATL Project
40 | [Dd]ebugPS/
41 | [Rr]eleasePS/
42 | dlldata.c
43 |
44 | # DNX
45 | project.lock.json
46 | artifacts/
47 |
48 | *_i.c
49 | *_p.c
50 | *_i.h
51 | *.ilk
52 | *.meta
53 | *.obj
54 | *.pch
55 | *.pdb
56 | *.pgc
57 | *.pgd
58 | *.rsp
59 | *.sbr
60 | *.tlb
61 | *.tli
62 | *.tlh
63 | *.tmp
64 | *.tmp_proj
65 | *.log
66 | *.vspscc
67 | *.vssscc
68 | .builds
69 | *.pidb
70 | *.svclog
71 | *.scc
72 |
73 | # Chutzpah Test files
74 | _Chutzpah*
75 |
76 | # Visual C++ cache files
77 | ipch/
78 | *.aps
79 | *.ncb
80 | *.opensdf
81 | *.sdf
82 | *.cachefile
83 |
84 | # Visual Studio profiler
85 | *.psess
86 | *.vsp
87 | *.vspx
88 |
89 | # TFS 2012 Local Workspace
90 | $tf/
91 |
92 | # Guidance Automation Toolkit
93 | *.gpState
94 |
95 | # ReSharper is a .NET coding add-in
96 | _ReSharper*/
97 | *.[Rr]e[Ss]harper
98 | *.DotSettings.user
99 |
100 | # JustCode is a .NET coding add-in
101 | .JustCode
102 |
103 | # TeamCity is a build add-in
104 | _TeamCity*
105 |
106 | # DotCover is a Code Coverage Tool
107 | *.dotCover
108 |
109 | # NCrunch
110 | _NCrunch_*
111 | .*crunch*.local.xml
112 | nCrunchTemp_*
113 |
114 | # MightyMoose
115 | *.mm.*
116 | AutoTest.Net/
117 |
118 | # Web workbench (sass)
119 | .sass-cache/
120 |
121 | # Installshield output folder
122 | [Ee]xpress/
123 |
124 | # DocProject is a documentation generator add-in
125 | DocProject/buildhelp/
126 | DocProject/Help/*.HxT
127 | DocProject/Help/*.HxC
128 | DocProject/Help/*.hhc
129 | DocProject/Help/*.hhk
130 | DocProject/Help/*.hhp
131 | DocProject/Help/Html2
132 | DocProject/Help/html
133 |
134 | # Click-Once directory
135 | publish/
136 |
137 | # Publish Web Output
138 | *.[Pp]ublish.xml
139 | *.azurePubxml
140 | # TODO: Comment the next line if you want to checkin your web deploy settings
141 | # but database connection strings (with potential passwords) will be unencrypted
142 | *.pubxml
143 | *.publishproj
144 |
145 | # NuGet Packages
146 | *.nupkg
147 | # The packages folder can be ignored because of Package Restore
148 | **/packages/*
149 | # except build/, which is used as an MSBuild target.
150 | !**/packages/build/
151 | # Uncomment if necessary however generally it will be regenerated when needed
152 | #!**/packages/repositories.config
153 |
154 | # Windows Azure Build Output
155 | csx/
156 | *.build.csdef
157 |
158 | # Windows Store app package directory
159 | AppPackages/
160 |
161 | # Visual Studio cache files
162 | # files ending in .cache can be ignored
163 | *.[Cc]ache
164 | # but keep track of directories ending in .cache
165 | !*.[Cc]ache/
166 |
167 | # Others
168 | ClientBin/
169 | [Ss]tyle[Cc]op.*
170 | ~$*
171 | *~
172 | *.dbmdl
173 | *.dbproj.schemaview
174 | *.pfx
175 | *.publishsettings
176 | node_modules/
177 | orleans.codegen.cs
178 |
179 | # RIA/Silverlight projects
180 | Generated_Code/
181 |
182 | # Backup & report files from converting an old project file
183 | # to a newer Visual Studio version. Backup files are not needed,
184 | # because we have git ;-)
185 | _UpgradeReport_Files/
186 | Backup*/
187 | UpgradeLog*.XML
188 | UpgradeLog*.htm
189 |
190 | # SQL Server files
191 | *.mdf
192 | *.ldf
193 |
194 | # Business Intelligence projects
195 | *.rdl.data
196 | *.bim.layout
197 | *.bim_*.settings
198 |
199 | # Microsoft Fakes
200 | FakesAssemblies/
201 |
202 | # Node.js Tools for Visual Studio
203 | .ntvs_analysis.dat
204 |
205 | # Visual Studio 6 build log
206 | *.plg
207 |
208 | # Visual Studio 6 workspace options file
209 | *.opt
210 |
211 | # Visual Studio LightSwitch build output
212 | **/*.HTMLClient/GeneratedArtifacts
213 | **/*.DesktopClient/GeneratedArtifacts
214 | **/*.DesktopClient/ModelManifest.xml
215 | **/*.Server/GeneratedArtifacts
216 | **/*.Server/ModelManifest.xml
217 | _Pvt_Extensions
218 |
219 | # JetBrains IDEA
220 | .idea/
--------------------------------------------------------------------------------
/BFF.DataVirtualizingCollection.Complete.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.28917.182
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BFF.DataVirtualizingCollection", "BFF.DataVirtualizingCollection\BFF.DataVirtualizingCollection.csproj", "{D4D769DA-F7A2-43F0-ADA7-5895D36D585F}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Icon", "Icon", "{2536F943-EEC8-4CF7-B0B2-E3569E164152}"
9 | ProjectSection(SolutionItems) = preProject
10 | Icon\Yeah69Logo.ico = Icon\Yeah69Logo.ico
11 | Icon\Yeah69Logo_256.png = Icon\Yeah69Logo_256.png
12 | EndProjectSection
13 | EndProject
14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample.ViewModel", "Sample.ViewModel\Sample.ViewModel.csproj", "{A12A5816-3F2E-4DC6-A73A-231CA8EB56E9}"
15 | EndProject
16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample.Model", "Sample.Model\Sample.Model.csproj", "{546A9B8C-0BA6-4FDC-802F-878F460D1211}"
17 | EndProject
18 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample.Persistence", "Sample.Persistence\Sample.Persistence.csproj", "{36C00EF5-5A92-4545-B6F5-6320F8C52C63}"
19 | EndProject
20 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample.Persistence.Proxy", "Sample.Persistence.Proxy\Sample.Persistence.Proxy.csproj", "{A3852A8E-01B4-4B70-8D3A-B168E042008B}"
21 | EndProject
22 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample.View", "Sample.View\Sample.View.csproj", "{29AB6C8F-A71B-4D87-9C64-C0BE3B5CE229}"
23 | EndProject
24 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Utilities", "Utilities", "{DB002923-3355-4FBC-92E1-CC0FDFA3FCAD}"
25 | ProjectSection(SolutionItems) = preProject
26 | Directory.Build.props = Directory.Build.props
27 | EndProjectSection
28 | EndProject
29 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.Unit", "Tests.Unit\Tests.Unit.csproj", "{D51F5D54-EDD2-4334-8CAE-2988C26B9E3F}"
30 | EndProject
31 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.Integration", "Tests.Integration\Tests.Integration.csproj", "{2B1264E8-045B-454B-8485-4A6F415955FE}"
32 | EndProject
33 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample.GettingStarted", "Sample.GettingStarted\Sample.GettingStarted.csproj", "{FEC9D854-809C-46AE-BA29-4A0C60CCBB3A}"
34 | EndProject
35 | Global
36 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
37 | Debug|Any CPU = Debug|Any CPU
38 | Release|Any CPU = Release|Any CPU
39 | EndGlobalSection
40 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
41 | {D4D769DA-F7A2-43F0-ADA7-5895D36D585F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
42 | {D4D769DA-F7A2-43F0-ADA7-5895D36D585F}.Debug|Any CPU.Build.0 = Debug|Any CPU
43 | {D4D769DA-F7A2-43F0-ADA7-5895D36D585F}.Release|Any CPU.ActiveCfg = Release|Any CPU
44 | {D4D769DA-F7A2-43F0-ADA7-5895D36D585F}.Release|Any CPU.Build.0 = Release|Any CPU
45 | {A12A5816-3F2E-4DC6-A73A-231CA8EB56E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
46 | {A12A5816-3F2E-4DC6-A73A-231CA8EB56E9}.Debug|Any CPU.Build.0 = Debug|Any CPU
47 | {A12A5816-3F2E-4DC6-A73A-231CA8EB56E9}.Release|Any CPU.ActiveCfg = Release|Any CPU
48 | {A12A5816-3F2E-4DC6-A73A-231CA8EB56E9}.Release|Any CPU.Build.0 = Release|Any CPU
49 | {546A9B8C-0BA6-4FDC-802F-878F460D1211}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
50 | {546A9B8C-0BA6-4FDC-802F-878F460D1211}.Debug|Any CPU.Build.0 = Debug|Any CPU
51 | {546A9B8C-0BA6-4FDC-802F-878F460D1211}.Release|Any CPU.ActiveCfg = Release|Any CPU
52 | {546A9B8C-0BA6-4FDC-802F-878F460D1211}.Release|Any CPU.Build.0 = Release|Any CPU
53 | {36C00EF5-5A92-4545-B6F5-6320F8C52C63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
54 | {36C00EF5-5A92-4545-B6F5-6320F8C52C63}.Debug|Any CPU.Build.0 = Debug|Any CPU
55 | {36C00EF5-5A92-4545-B6F5-6320F8C52C63}.Release|Any CPU.ActiveCfg = Release|Any CPU
56 | {36C00EF5-5A92-4545-B6F5-6320F8C52C63}.Release|Any CPU.Build.0 = Release|Any CPU
57 | {A3852A8E-01B4-4B70-8D3A-B168E042008B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
58 | {A3852A8E-01B4-4B70-8D3A-B168E042008B}.Debug|Any CPU.Build.0 = Debug|Any CPU
59 | {A3852A8E-01B4-4B70-8D3A-B168E042008B}.Release|Any CPU.ActiveCfg = Release|Any CPU
60 | {A3852A8E-01B4-4B70-8D3A-B168E042008B}.Release|Any CPU.Build.0 = Release|Any CPU
61 | {29AB6C8F-A71B-4D87-9C64-C0BE3B5CE229}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
62 | {29AB6C8F-A71B-4D87-9C64-C0BE3B5CE229}.Debug|Any CPU.Build.0 = Debug|Any CPU
63 | {29AB6C8F-A71B-4D87-9C64-C0BE3B5CE229}.Release|Any CPU.ActiveCfg = Release|Any CPU
64 | {29AB6C8F-A71B-4D87-9C64-C0BE3B5CE229}.Release|Any CPU.Build.0 = Release|Any CPU
65 | {D51F5D54-EDD2-4334-8CAE-2988C26B9E3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
66 | {D51F5D54-EDD2-4334-8CAE-2988C26B9E3F}.Debug|Any CPU.Build.0 = Debug|Any CPU
67 | {D51F5D54-EDD2-4334-8CAE-2988C26B9E3F}.Release|Any CPU.ActiveCfg = Release|Any CPU
68 | {D51F5D54-EDD2-4334-8CAE-2988C26B9E3F}.Release|Any CPU.Build.0 = Release|Any CPU
69 | {2B1264E8-045B-454B-8485-4A6F415955FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
70 | {2B1264E8-045B-454B-8485-4A6F415955FE}.Debug|Any CPU.Build.0 = Debug|Any CPU
71 | {2B1264E8-045B-454B-8485-4A6F415955FE}.Release|Any CPU.ActiveCfg = Release|Any CPU
72 | {2B1264E8-045B-454B-8485-4A6F415955FE}.Release|Any CPU.Build.0 = Release|Any CPU
73 | {FEC9D854-809C-46AE-BA29-4A0C60CCBB3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
74 | {FEC9D854-809C-46AE-BA29-4A0C60CCBB3A}.Debug|Any CPU.Build.0 = Debug|Any CPU
75 | {FEC9D854-809C-46AE-BA29-4A0C60CCBB3A}.Release|Any CPU.ActiveCfg = Release|Any CPU
76 | {FEC9D854-809C-46AE-BA29-4A0C60CCBB3A}.Release|Any CPU.Build.0 = Release|Any CPU
77 | EndGlobalSection
78 | GlobalSection(SolutionProperties) = preSolution
79 | HideSolutionNode = FALSE
80 | EndGlobalSection
81 | GlobalSection(ExtensibilityGlobals) = postSolution
82 | SolutionGuid = {06A68F93-74CC-4C15-BD02-62B43AB8A585}
83 | EndGlobalSection
84 | EndGlobal
85 |
--------------------------------------------------------------------------------
/BFF.DataVirtualizingCollection.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.28917.182
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BFF.DataVirtualizingCollection", "BFF.DataVirtualizingCollection\BFF.DataVirtualizingCollection.csproj", "{D4D769DA-F7A2-43F0-ADA7-5895D36D585F}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Icon", "Icon", "{2536F943-EEC8-4CF7-B0B2-E3569E164152}"
9 | ProjectSection(SolutionItems) = preProject
10 | Icon\Yeah69Logo.ico = Icon\Yeah69Logo.ico
11 | Icon\Yeah69Logo_256.png = Icon\Yeah69Logo_256.png
12 | EndProjectSection
13 | EndProject
14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Utilities", "Utilities", "{DB002923-3355-4FBC-92E1-CC0FDFA3FCAD}"
15 | ProjectSection(SolutionItems) = preProject
16 | Directory.Build.props = Directory.Build.props
17 | EndProjectSection
18 | EndProject
19 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.Integration", "Tests.Integration\Tests.Integration.csproj", "{3F7BB7A3-7950-45B9-9968-3BBF6F186B30}"
20 | EndProject
21 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.Unit", "Tests.Unit\Tests.Unit.csproj", "{E0EBFFF9-3F4F-4E81-AC3D-69937E0393CA}"
22 | EndProject
23 | Global
24 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
25 | Debug|Any CPU = Debug|Any CPU
26 | Release|Any CPU = Release|Any CPU
27 | EndGlobalSection
28 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
29 | {D4D769DA-F7A2-43F0-ADA7-5895D36D585F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
30 | {D4D769DA-F7A2-43F0-ADA7-5895D36D585F}.Debug|Any CPU.Build.0 = Debug|Any CPU
31 | {D4D769DA-F7A2-43F0-ADA7-5895D36D585F}.Release|Any CPU.ActiveCfg = Release|Any CPU
32 | {D4D769DA-F7A2-43F0-ADA7-5895D36D585F}.Release|Any CPU.Build.0 = Release|Any CPU
33 | {3F7BB7A3-7950-45B9-9968-3BBF6F186B30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
34 | {3F7BB7A3-7950-45B9-9968-3BBF6F186B30}.Debug|Any CPU.Build.0 = Debug|Any CPU
35 | {3F7BB7A3-7950-45B9-9968-3BBF6F186B30}.Release|Any CPU.ActiveCfg = Release|Any CPU
36 | {3F7BB7A3-7950-45B9-9968-3BBF6F186B30}.Release|Any CPU.Build.0 = Release|Any CPU
37 | {E0EBFFF9-3F4F-4E81-AC3D-69937E0393CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
38 | {E0EBFFF9-3F4F-4E81-AC3D-69937E0393CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
39 | {E0EBFFF9-3F4F-4E81-AC3D-69937E0393CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
40 | {E0EBFFF9-3F4F-4E81-AC3D-69937E0393CA}.Release|Any CPU.Build.0 = Release|Any CPU
41 | EndGlobalSection
42 | GlobalSection(SolutionProperties) = preSolution
43 | HideSolutionNode = FALSE
44 | EndGlobalSection
45 | GlobalSection(ExtensibilityGlobals) = postSolution
46 | SolutionGuid = {06A68F93-74CC-4C15-BD02-62B43AB8A585}
47 | EndGlobalSection
48 | EndGlobal
49 |
--------------------------------------------------------------------------------
/BFF.DataVirtualizingCollection/BFF.DataVirtualizingCollection.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 | BFF.DataVirtualizingCollection
6 | This is an approach to data virtualizing collections intended to be used in WPF projects.
7 | false
8 | - breaking changes compared to version 2.* and before
9 | - no blocking calls anymore
10 | - separate background scheduler for count fetching, page fetching and preloading configurable
11 |
12 | data-virtualization wpf csharp
13 | true
14 | https://github.com/Yeah69/BFF.DataVirtualizingCollection
15 | https://github.com/Yeah69/BFF.DataVirtualizingCollection.git
16 | git
17 | Yeah69Logo_256.png
18 |
19 | LICENSE.md
20 | ../Icon/Yeah69_256.png
21 | BFF.DataVirtualizingCollection
22 | true
23 | $(BaseIntermediateOutputPath)\GeneratedFiles
24 |
25 |
26 |
27 | bin\Release\netstandard1.4\BFF.DataVirtualizingCollection.xml
28 |
29 |
30 |
31 | bin\Debug\BFF.DataVirtualizingCollection.xml
32 |
33 |
34 |
35 | ..\Documentation\API.md
36 |
37 |
38 |
39 |
40 |
41 | all
42 | runtime; build; native; contentfiles; analyzers; buildtransitive
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | True
53 |
54 |
55 |
56 |
57 |
58 |
59 | true
60 |
61 |
62 |
63 | true
64 |
65 |
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/BFF.DataVirtualizingCollection/DataVirtualizingCollection/AsyncDataVirtualizingCollection.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reactive;
3 | using System.Reactive.Concurrency;
4 | using System.Reactive.Disposables;
5 | using System.Reactive.Linq;
6 | using System.Reactive.Subjects;
7 | using System.Reactive.Threading.Tasks;
8 | using System.Threading;
9 | using System.Threading.Tasks;
10 | using BFF.DataVirtualizingCollection.PageStorage;
11 | using MrMeeseeks.Reactive.Extensions;
12 |
13 | namespace BFF.DataVirtualizingCollection.DataVirtualizingCollection
14 | {
15 | internal sealed class AsyncDataVirtualizingCollection : DataVirtualizingCollectionBase
16 | {
17 | private readonly Func> _countFetcher;
18 | private readonly IScheduler _notificationScheduler;
19 | private readonly IScheduler _countBackgroundScheduler;
20 | private readonly IPageStorage _pageStorage;
21 | private readonly Subject _resetSubject = new ();
22 | private readonly SerialDisposable _pendingCountRequestCancellation = new ();
23 |
24 | private int _count;
25 |
26 | internal AsyncDataVirtualizingCollection(
27 | Func> pageStoreFactory,
28 | Func> countFetcher,
29 | IObservable<(int Offset, int PageSize, T[] PreviousPage, T[] Page)> observePageFetches,
30 | IDisposable disposeOnDisposal,
31 | IScheduler notificationScheduler,
32 | IScheduler countBackgroundScheduler)
33 | : base(observePageFetches, disposeOnDisposal, notificationScheduler)
34 | {
35 | _countFetcher = countFetcher;
36 | _notificationScheduler = notificationScheduler;
37 | _countBackgroundScheduler = countBackgroundScheduler;
38 | _count = 0;
39 |
40 | _resetSubject.CompositeDisposalWith(CompositeDisposable);
41 |
42 | _pageStorage = pageStoreFactory(0);
43 |
44 | InitializationCompleted = _resetSubject.FirstAsync().ToTask();
45 |
46 | _resetSubject
47 | .SelectMany(async ct =>
48 | {
49 | await ResetInner(ct).ConfigureAwait(false);
50 | return Unit.Default;
51 | })
52 | .Subscribe(_ => {})
53 | .CompositeDisposalWith(CompositeDisposable);
54 |
55 | Reset();
56 | }
57 |
58 | public override int Count => _count;
59 |
60 | protected override T GetItemInner(int index) => _pageStorage[index];
61 |
62 | private async Task ResetInner(CancellationToken ct)
63 | {
64 | try
65 | {
66 | await Observable.FromAsync(_countFetcher, _countBackgroundScheduler)
67 | .SelectMany(async count =>
68 | {
69 | _count = count;
70 | await _pageStorage.Reset(_count).ConfigureAwait(false);
71 | return Unit.Default;
72 | })
73 | .ObserveOn(_notificationScheduler)
74 | .Do(_ =>
75 | {
76 | OnPropertyChanged(nameof(Count));
77 | OnCollectionChangedReset();
78 | OnIndexerChanged();
79 | })
80 | .ToTask(ct)
81 | .ConfigureAwait(false);
82 | }
83 | catch (OperationCanceledException)
84 | {
85 | // ignore cancellation from now on
86 | }
87 | }
88 |
89 | public override void Reset()
90 | {
91 | var cancellationDisposable = new CancellationDisposable();
92 | _pendingCountRequestCancellation.Disposable = cancellationDisposable;
93 | _resetSubject.OnNext(cancellationDisposable.Token);
94 | }
95 |
96 | public override Task InitializationCompleted { get; }
97 |
98 | public override async ValueTask DisposeAsync()
99 | {
100 | _pendingCountRequestCancellation.Dispose();
101 | await base.DisposeAsync().ConfigureAwait(false);
102 | await _pageStorage.DisposeAsync().ConfigureAwait(false);
103 | }
104 | }
105 | }
--------------------------------------------------------------------------------
/BFF.DataVirtualizingCollection/DataVirtualizingCollection/DataVirtualizingCollectionBase.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reactive.Concurrency;
3 | using System.Reactive.Linq;
4 | using MrMeeseeks.Reactive.Extensions;
5 |
6 | namespace BFF.DataVirtualizingCollection.DataVirtualizingCollection
7 | {
8 | internal abstract class DataVirtualizingCollectionBase : VirtualizationBase, IDataVirtualizingCollection
9 | {
10 | private int _selectedIndex;
11 |
12 | protected DataVirtualizingCollectionBase(
13 | IObservable<(int Offset, int PageSize, T[] PreviousPage, T[] Page)> observePageFetches,
14 | IDisposable disposeOnDisposal,
15 | IScheduler notificationScheduler)
16 | {
17 | disposeOnDisposal.CompositeDisposalWith(CompositeDisposable);
18 |
19 | observePageFetches
20 | .ObserveOn(notificationScheduler)
21 | .Subscribe(t =>
22 | {
23 | var (offset, pageSize, previousPage, page) = t;
24 | for (var i = 0; i < pageSize; i++)
25 | {
26 | OnCollectionChangedReplace(page[i], previousPage[i], i + offset);
27 | }
28 | OnIndexerChanged();
29 | })
30 | .CompositeDisposalWith(CompositeDisposable);
31 | }
32 |
33 | protected override T IndexerInnerGet(int index) =>
34 | index >= Count || index < 0
35 | ? throw new IndexOutOfRangeException("Index was out of range. Must be non-negative and less than the size of the collection.")
36 | : GetItemInner(index);
37 |
38 | protected abstract T GetItemInner(int index);
39 |
40 | private int _preResetSelectedIndex = -1;
41 |
42 | protected override void OnCollectionChangedReset()
43 | {
44 | _preResetSelectedIndex = _selectedIndex;
45 | SelectedIndex = -1; // deselection in order to workaround issue of Selectors
46 | base.OnCollectionChangedReset();
47 | SelectedIndex = _preResetSelectedIndex;
48 | }
49 |
50 | protected override T GetItemForEnumerator(int i) => GetItemInner(i);
51 |
52 | public override int SelectedIndex
53 | {
54 | get => _selectedIndex;
55 | set
56 | {
57 | if (_selectedIndex == value) return;
58 | _selectedIndex = value;
59 | OnPropertyChanged();
60 | }
61 | }
62 | }
63 | }
--------------------------------------------------------------------------------
/BFF.DataVirtualizingCollection/DataVirtualizingCollection/DataVirtualizingCollectionBuilder.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Specialized;
3 | using System.ComponentModel;
4 | using System.Reactive.Concurrency;
5 | using System.Reactive.Linq;
6 | using System.Reactive.Subjects;
7 | using System.Threading.Tasks;
8 |
9 | namespace BFF.DataVirtualizingCollection.DataVirtualizingCollection
10 | {
11 | ///
12 | /// Initial entry point for creating a data virtualizing collection.
13 | ///
14 | public static class DataVirtualizingCollectionBuilder
15 | {
16 | ///
17 | /// Use to configure general virtualization settings.
18 | /// Further settings are applied via method chaining.
19 | /// Page size is set to the default value 100.
20 | /// The background scheduler is per default the .
21 | ///
22 | /// A scheduler for sending the notifications (, ).
23 | public static IPageLoadingBehaviorCollectionBuilder> Build(
24 | IScheduler notificationScheduler) =>
25 | Build(DataVirtualizingCollectionBuilderBase.DefaultPageSize, notificationScheduler);
26 |
27 | ///
28 | /// Use to configure general virtualization settings.
29 | /// Further settings are applied via method chaining.
30 | /// The background scheduler is per default the .
31 | ///
32 | /// Maximum size of a single page.
33 | /// A scheduler for sending the notifications (, ).
34 | public static IPageLoadingBehaviorCollectionBuilder> Build(
35 | int pageSize,
36 | IScheduler notificationScheduler) =>
37 | new DataVirtualizingCollectionBuilder(pageSize, notificationScheduler);
38 |
39 | ///
40 | /// Use to configure general virtualization settings.
41 | /// Further settings are applied via method chaining.
42 | ///
43 | /// Maximum size of a single page.
44 | /// A scheduler for sending the notifications (, ).
45 | /// Per default this scheduler is used for all background operations (page and count fetches, preloading). In further settings you'll have the option to override this scheduler with another for specific background operations.
46 | public static IPageLoadingBehaviorCollectionBuilder> Build(
47 | int pageSize,
48 | IScheduler notificationScheduler,
49 | IScheduler backgroundScheduler) =>
50 | new DataVirtualizingCollectionBuilder(pageSize, notificationScheduler, backgroundScheduler);
51 | }
52 |
53 | internal sealed class DataVirtualizingCollectionBuilder
54 | : DataVirtualizingCollectionBuilderBase>
55 | {
56 |
57 | internal DataVirtualizingCollectionBuilder(int pageSize, IScheduler notificationScheduler)
58 | : base(pageSize, notificationScheduler)
59 | {
60 | }
61 |
62 | internal DataVirtualizingCollectionBuilder(int pageSize, IScheduler notificationScheduler, IScheduler backgroundScheduler)
63 | : base(pageSize, notificationScheduler, backgroundScheduler)
64 | {
65 | }
66 |
67 | protected override IDataVirtualizingCollection GenerateTaskBasedAsynchronousCollection(
68 | Subject<(int Offset, int PageSize, TItem[] PreviousPage, TItem[] Page)> pageFetchEvents)
69 | {
70 | var taskBasedCountFetcher = TaskBasedCountFetcher ??
71 | throw new NullReferenceException(UninitializedElementsExceptionMessage);
72 | return new AsyncDataVirtualizingCollection(
73 | GenerateTaskBasedAsynchronousPageStorage(pageFetchEvents),
74 | taskBasedCountFetcher,
75 | pageFetchEvents.AsObservable(),
76 | pageFetchEvents,
77 | NotificationScheduler,
78 | CountBackgroundScheduler);
79 | }
80 |
81 | protected override IDataVirtualizingCollection GenerateAsyncEnumerableBasedAsynchronousCollection(Subject<(int Offset, int PageSize, TItem[] PreviousPage, TItem[] Page)> pageFetchEvents)
82 | {
83 | var taskBasedCountFetcher = TaskBasedCountFetcher ??
84 | throw new NullReferenceException(UninitializedElementsExceptionMessage);
85 | return new AsyncDataVirtualizingCollection(
86 | GenerateAsyncEnumerableBasedAsynchronousPageStorage(pageFetchEvents),
87 | taskBasedCountFetcher,
88 | pageFetchEvents.AsObservable(),
89 | pageFetchEvents,
90 | NotificationScheduler,
91 | CountBackgroundScheduler);
92 | }
93 |
94 | protected override IDataVirtualizingCollection GenerateNonTaskBasedAsynchronousCollection(
95 | Subject<(int Offset, int PageSize, TItem[] PreviousPage, TItem[] Page)> pageFetchEvents)
96 | {
97 | var countFetcher = CountFetcher ?? throw new NullReferenceException(UninitializedElementsExceptionMessage);
98 | return new AsyncDataVirtualizingCollection(
99 | GenerateNonTaskBasedAsynchronousPageStorage(pageFetchEvents),
100 | ct => Task.FromResult(countFetcher(ct)),
101 | pageFetchEvents.AsObservable(),
102 | pageFetchEvents,
103 | NotificationScheduler,
104 | CountBackgroundScheduler);
105 | }
106 |
107 | protected override IDataVirtualizingCollection GenerateNonTaskBasedSynchronousCollection(
108 | Subject<(int Offset, int PageSize, TItem[] PreviousPage, TItem[] Page)> pageFetchEvents)
109 | {
110 | var countFetcher = CountFetcher ?? throw new NullReferenceException(UninitializedElementsExceptionMessage);
111 | return new SyncDataVirtualizingCollection(
112 | GenerateNonTaskBasedSynchronousPageStorage(pageFetchEvents),
113 | countFetcher,
114 | pageFetchEvents.AsObservable(),
115 | pageFetchEvents,
116 | NotificationScheduler);
117 | }
118 | }
119 | }
--------------------------------------------------------------------------------
/BFF.DataVirtualizingCollection/DataVirtualizingCollection/IDataVirtualizingCollection.cs:
--------------------------------------------------------------------------------
1 | namespace BFF.DataVirtualizingCollection.DataVirtualizingCollection
2 | {
3 | // ReSharper disable once PossibleInterfaceMemberAmbiguity
4 | // Ambiguous Members should be implemented explicitly
5 | ///
6 | /// Marks a nongeneric data virtualizing collection.
7 | /// The data virtualizing collection represents the whole backend as a list. However, the items are not loaded all at once but page by page on demand.
8 | ///
9 | public interface IDataVirtualizingCollection : IVirtualizationBase
10 | {
11 | }
12 |
13 | // ReSharper disable once PossibleInterfaceMemberAmbiguity
14 | // Ambiguous Members should be implemented explicitly
15 | ///
16 | /// Marks a generic data virtualizing collection.
17 | /// The data virtualizing collection represents the whole backend as a list. However, the items are not loaded all at once but page by page on demand.
18 | ///
19 | /// Item type.
20 | public interface IDataVirtualizingCollection :
21 | IVirtualizationBase,
22 | IDataVirtualizingCollection
23 | {
24 | }
25 | }
--------------------------------------------------------------------------------
/BFF.DataVirtualizingCollection/DataVirtualizingCollection/SyncDataVirtualizingCollection.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reactive;
3 | using System.Reactive.Concurrency;
4 | using System.Reactive.Subjects;
5 | using System.Threading;
6 | using System.Threading.Tasks;
7 | using BFF.DataVirtualizingCollection.PageStorage;
8 | using MrMeeseeks.Reactive.Extensions;
9 |
10 | namespace BFF.DataVirtualizingCollection.DataVirtualizingCollection
11 | {
12 | internal sealed class SyncDataVirtualizingCollection : DataVirtualizingCollectionBase
13 | {
14 | private readonly Func _countFetcher;
15 | private readonly IScheduler _notificationScheduler;
16 | private readonly IPageStorage _pageStorage;
17 | private readonly Subject _resetSubject = new();
18 |
19 | private int _count;
20 |
21 | internal SyncDataVirtualizingCollection(
22 | Func> pageStoreFactory,
23 | Func countFetcher,
24 | IObservable<(int Offset, int PageSize, T[] PreviousPage, T[] Page)> observePageFetches,
25 | IDisposable disposeOnDisposal,
26 | IScheduler notificationScheduler)
27 | : base (observePageFetches, disposeOnDisposal, notificationScheduler)
28 | {
29 | _countFetcher = countFetcher;
30 | _notificationScheduler = notificationScheduler;
31 | _count = _countFetcher(CancellationToken.None);
32 | _pageStorage = pageStoreFactory(_count);
33 |
34 | _resetSubject.CompositeDisposalWith(CompositeDisposable);
35 |
36 | _resetSubject
37 | .Subscribe(_ => ResetInner())
38 | .CompositeDisposalWith(CompositeDisposable);
39 | }
40 |
41 | public override int Count => _count;
42 |
43 | protected override T GetItemInner(int index)
44 | {
45 | return _pageStorage[index];
46 | }
47 |
48 | private void ResetInner()
49 | {
50 | _count = _countFetcher(CancellationToken.None);
51 | _pageStorage.Reset(_count);
52 | _notificationScheduler.Schedule(Unit.Default, (_, __) =>
53 | {
54 | OnPropertyChanged(nameof(Count));
55 | OnCollectionChangedReset();
56 | OnIndexerChanged();
57 | });
58 | }
59 |
60 | public override void Reset() => _resetSubject.OnNext(Unit.Default);
61 |
62 | public override Task InitializationCompleted { get; } = Task.CompletedTask;
63 |
64 | public override async ValueTask DisposeAsync()
65 | {
66 | await base.DisposeAsync().ConfigureAwait(false);
67 | await _pageStorage.DisposeAsync().ConfigureAwait(false);
68 | }
69 | }
70 | }
--------------------------------------------------------------------------------
/BFF.DataVirtualizingCollection/IVirtualizationBase.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Collections.Specialized;
5 | using System.ComponentModel;
6 | using System.Threading.Tasks;
7 |
8 | namespace BFF.DataVirtualizingCollection
9 | {
10 | ///
11 | /// Root interface of all virtualizing collection types. Implements all necessary .Net interfaces (IList, INotifyCollectionChanged, INotifyPropertyChanged) in order to be usable by standard UI controls (like ItemsControl from WPF).
12 | /// Additionally, it has virtualization-specific members of its own. See documentation of the members for further details.
13 | ///
14 | ///
15 | /// Implements IList in order to mock a standard .Net list. Controls like the ItemsControl from WPF in combination with the VirtualizingStackPanel use the indexer in order to load items on demand. Also the Count-property is used to arrange the scroll bar accordingly.
16 | /// The virtualized collections automatically changes its items. For example, if an async index access or preloading is set up, it will be eventually necessary to replace placeholders with the actually loaded items. In order to notify this changes to the UI the INotifyCollectionChanged interface is implemented.
17 | /// Also the properties of the collection itself may change, especially the Count-property. Hence notifications for such changes are send with INotifyPropertyChanged.
18 | /// Finally, each virtualizing collection has to be disposed after use, because it uses Rx subscriptions which have to be cleaned up. Also the currently active pages including their items which implement IDisposable are disposed as well.
19 | ///
20 | public interface IVirtualizationBase :
21 | IList,
22 | INotifyCollectionChanged,
23 | INotifyPropertyChanged,
24 | IAsyncDisposable
25 | {
26 | ///
27 | /// Task is successfully completed when initialization is completed.
28 | ///
29 | ///
30 | /// Initialization depends on the initial calculation of the Count-property. Because of the asynchronicity of task-based fetchers the Count-property might not be calculated at the end of construction of the virtualized collection.
31 | ///
32 | Task InitializationCompleted { get; }
33 |
34 | ///
35 | /// Can be bound to SelectedIndexProperty on Selector controls in order to workaround issue with resets and selected items.
36 | ///
37 | ///
38 | /// In WPF the Selector control will search for the previously selected item after each reset by iterating over all items until found. This behavior is the opposite of virtualization. Hence, the virtualizing collection would set this property to -1 (deselection) and notify the change before notifying any reset.
39 | ///
40 | int SelectedIndex { get; set; }
41 |
42 | ///
43 | /// Disposes of all current pages and notifies that possibly everything changed.
44 | /// The Reset-function should be called any time when something in the virtualized backend has changed.
45 | ///
46 | ///
47 | /// Consequently, the Count-property is recalculated. The UI will request all currently rendered items anew, so this items get reloaded.
48 | ///
49 | void Reset();
50 | }
51 |
52 | ///
53 | /// The generic version of . Analogously, it implements and .
54 | ///
55 | /// Item type.
56 | // ReSharper disable once PossibleInterfaceMemberAmbiguity *** the "new" members of this interface resolve the ambiguities
57 | public interface IVirtualizationBase :
58 | IVirtualizationBase,
59 | IList,
60 | IReadOnlyList
61 | {
62 | ///
63 | /// The Count-property is newed here in order to resolve ambiguities cause by implementing , and at the same time.
64 | ///
65 | new int Count { get; }
66 |
67 | ///
68 | /// The indexer is newed here in order to resolve ambiguities cause by implementing , and at the same time.
69 | ///
70 | new T this[int index] { get; }
71 | }
72 | }
--------------------------------------------------------------------------------
/BFF.DataVirtualizingCollection/InternalsVisibleTo.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 | [assembly: InternalsVisibleTo("BFF.DataVirtualizingCollection.Tests.Unit")]
3 | [assembly: InternalsVisibleTo("BFF.DataVirtualizingCollection.IntegrationTests")]
4 | [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
--------------------------------------------------------------------------------
/BFF.DataVirtualizingCollection/PageRemoval/HoardingPageNonRemoval.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Reactive.Linq;
4 |
5 | namespace BFF.DataVirtualizingCollection.PageRemoval
6 | {
7 | internal static class HoardingPageNonRemoval
8 | {
9 | internal static Func, IObservable>> Create() =>
10 | _ => Observable.Never>();
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/BFF.DataVirtualizingCollection/PageRemoval/LeastRecentlyUsedPageRemoval.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.ObjectModel;
4 | using System.Linq;
5 | using System.Reactive.Linq;
6 | using BFF.DataVirtualizingCollection.Utilities;
7 |
8 | namespace BFF.DataVirtualizingCollection.PageRemoval
9 | {
10 | internal static class LeastRecentlyUsedPageRemoval
11 | {
12 | internal static Func, IObservable>> Create(
13 | int pageLimit,
14 | int removalCount,
15 | bool isPreloading,
16 | ITimestampProvider timestampProvider)
17 | {
18 | IDictionary lastUsage = new Dictionary();
19 | return pageRequests =>
20 | {
21 | if (isPreloading)
22 | {
23 | // Make sure at least three pages (because of the preloading the two pages need to be
24 | // considered for the neighbor pages of the last fetched page) stay in the page store
25 | pageLimit = pageLimit < 4 ? 4 : pageLimit;
26 | removalCount = removalCount < 1
27 | ? 1
28 | : removalCount > pageLimit - 3
29 | ? pageLimit - 3
30 | : removalCount;
31 | }
32 | else
33 | {
34 | // Make sure at least one page stays in the page store
35 | pageLimit = pageLimit < 2 ? 2 : pageLimit;
36 | removalCount = removalCount < 1
37 | ? 1
38 | : removalCount > pageLimit - 1
39 | ? pageLimit - 1
40 | : removalCount;
41 | }
42 |
43 | return pageRequests.Select(tuple =>
44 | {
45 | lastUsage[tuple.PageKey] = timestampProvider.Now;
46 |
47 | // Don't request any page removals if page limit isn't reached
48 | if (lastUsage.Count <= pageLimit) return new ReadOnlyCollection(new List());
49 |
50 | // Remove at least one page but as much as required to get below page limit
51 | // The "-1" is necessary because the currently requested page is included in the count of the last usages
52 | var actualRemovalCount = lastUsage.Count - 1 - pageLimit + removalCount;
53 | var removalRequests = new ReadOnlyCollection(
54 | lastUsage
55 | // Sort by least recent usage
56 | .OrderBy(kvp => kvp.Value)
57 | .Take(actualRemovalCount)
58 | .Select(kvp => kvp.Key)
59 | .ToList());
60 | // Assuming the page will be removed, they need to be removed from the last-usage tracking here as well
61 | foreach (var removalRequest in removalRequests)
62 | {
63 | lastUsage.Remove(removalRequest);
64 | }
65 |
66 | return removalRequests;
67 | })
68 | .Where(list => list.Any());
69 | };
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/BFF.DataVirtualizingCollection/PageRemoval/PageReplacementStrategyException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace BFF.DataVirtualizingCollection.PageRemoval
4 | {
5 | ///
6 | /// Thrown whenever an exception occurs during the process of page replacement.
7 | ///
8 | public class PageReplacementStrategyException : Exception
9 | {
10 | internal PageReplacementStrategyException(string message, Exception innerException) : base(message, innerException)
11 | {
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/BFF.DataVirtualizingCollection/PageStorage/IAsyncPageFetchScheduler.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 |
3 | namespace BFF.DataVirtualizingCollection.PageStorage
4 | {
5 | internal interface IAsyncPageFetchScheduler
6 | {
7 | Task Schedule();
8 | }
9 | }
--------------------------------------------------------------------------------
/BFF.DataVirtualizingCollection/PageStorage/IPage.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 |
4 | namespace BFF.DataVirtualizingCollection.PageStorage
5 | {
6 | internal interface IPage : IAsyncDisposable
7 | {
8 | Task PageFetchCompletion { get; }
9 | }
10 |
11 | internal interface IPage : IPage
12 | {
13 | T this[int index] { get; }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/BFF.DataVirtualizingCollection/PageStorage/ImmediateAsyncPageFetchScheduler.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 |
3 | namespace BFF.DataVirtualizingCollection.PageStorage
4 | {
5 | internal class ImmediateAsyncPageFetchScheduler : IAsyncPageFetchScheduler
6 | {
7 | public Task Schedule() => Task.CompletedTask;
8 | }
9 | }
--------------------------------------------------------------------------------
/BFF.DataVirtualizingCollection/PageStorage/LifoAsyncPageFetchScheduler.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Reactive;
5 | using System.Reactive.Concurrency;
6 | using System.Reactive.Linq;
7 | using System.Reactive.Subjects;
8 | using System.Threading.Tasks;
9 |
10 | namespace BFF.DataVirtualizingCollection.PageStorage
11 | {
12 | internal class LifoAsyncPageFetchScheduler : IAsyncPageFetchScheduler
13 | {
14 | private readonly Stack> _stack = new();
15 | private readonly Subject> _subject = new();
16 |
17 | public LifoAsyncPageFetchScheduler(
18 | TimeSpan throttleDueTime,
19 | IScheduler pageRequestBackgroundScheduler) =>
20 | _subject
21 | .Synchronize()
22 | .Do(tcs => _stack.Push(tcs))
23 | .Throttle(throttleDueTime, pageRequestBackgroundScheduler)
24 | .Subscribe(_ =>
25 | {
26 | while (_stack.Any())
27 | {
28 | var tcs = _stack.Pop();
29 | tcs.SetResult(Unit.Default);
30 | }
31 | });
32 |
33 | public Task Schedule()
34 | {
35 | var taskCompletionSource = new TaskCompletionSource();
36 | _subject.OnNext(taskCompletionSource);
37 | return taskCompletionSource.Task;
38 | }
39 | }
40 | }
--------------------------------------------------------------------------------
/BFF.DataVirtualizingCollection/PageStorage/PageStorage.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Concurrent;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Reactive;
6 | using System.Reactive.Disposables;
7 | using System.Reactive.Linq;
8 | using System.Reactive.Subjects;
9 | using System.Threading.Tasks;
10 | using BFF.DataVirtualizingCollection.PageRemoval;
11 | using MrMeeseeks.Reactive.Extensions;
12 |
13 | namespace BFF.DataVirtualizingCollection.PageStorage
14 | {
15 | internal interface IPageStorage : IAsyncDisposable
16 | {
17 | T this[int index] { get; }
18 |
19 | Task Reset(int newCount);
20 | }
21 |
22 | internal class PageStorage : IPageStorage
23 | {
24 | private readonly int _pageSize;
25 | private readonly Func> _nonPreloadingPageFactory;
26 | private readonly CompositeDisposable _compositeDisposable = new();
27 |
28 | protected bool IsDisposed;
29 | protected readonly object IsDisposedLock = new();
30 | protected readonly ConcurrentDictionary> Pages = new();
31 | protected readonly ISubject<(int PageKey, int PageIndex)> Requests;
32 |
33 | private int _count;
34 |
35 | internal PageStorage(
36 | int pageSize,
37 | int count,
38 | Func> nonPreloadingPageFactory,
39 | Func, IObservable>> pageReplacementStrategyFactory)
40 | {
41 | _pageSize = pageSize;
42 | _count = count;
43 | PageCount = CalculateCurrentPageCount();
44 | _nonPreloadingPageFactory = nonPreloadingPageFactory;
45 | Requests = new Subject<(int PageKey, int PageIndex)>().CompositeDisposalWith(_compositeDisposable);
46 | pageReplacementStrategyFactory(Requests)
47 | .SelectMany(async pageKeysToRemove =>
48 | {
49 | var disposables = new List();
50 | foreach (var pageKey in pageKeysToRemove)
51 | {
52 | if (Pages.TryGetValue(pageKey, out var page))
53 | disposables.Add(page.DisposeAsync().AsTask());
54 | }
55 |
56 | await Task.WhenAll(disposables);
57 | return Unit.Default;
58 | })
59 | .Subscribe(_ => { },
60 | exception => throw new PageReplacementStrategyException(
61 | "LeastRecentlyUsed strategy: Something unexpected happened during page removal! See inner exception.",
62 | exception))
63 | .CompositeDisposalWith(_compositeDisposable);
64 | }
65 |
66 | protected int PageCount { get; private set; }
67 |
68 | public T this[int index]
69 | {
70 | get
71 | {
72 | var pageKey = index / _pageSize;
73 | var pageIndex = index % _pageSize;
74 |
75 | lock (IsDisposedLock)
76 | {
77 | if (!IsDisposed) Requests.OnNext((pageKey, pageIndex));
78 | }
79 |
80 | var ret = Pages
81 | .GetOrAdd(
82 | pageKey,
83 | FetchNonPreloadingPage)
84 | [pageIndex];
85 |
86 | Preloading(pageKey);
87 |
88 | return ret;
89 |
90 | IPage FetchNonPreloadingPage(int key) => FetchPage(key, _nonPreloadingPageFactory);
91 | }
92 | }
93 |
94 | public Task Reset(int newCount)
95 | {
96 | _count = newCount;
97 | PageCount = CalculateCurrentPageCount();
98 |
99 | return Task.WhenAll(
100 | Pages.Values.ToList().Select(p => p.DisposeAsync().AsTask()));
101 |
102 | }
103 |
104 | protected virtual void Preloading(int pageKey)
105 | { }
106 |
107 | protected IPage FetchPage(int key, Func> fetcher)
108 | {
109 | var offset = key * _pageSize;
110 | var actualPageSize = Math.Min(_pageSize, _count - offset);
111 | var disposable = Disposable.Create(() => Pages.TryRemove(key, out _));
112 | return fetcher(key, offset, actualPageSize, disposable);
113 | }
114 |
115 | public async ValueTask DisposeAsync()
116 | {
117 | var pages = Pages.Values.ToList();
118 | Pages.Clear();
119 |
120 | await Task.WhenAll(
121 | pages.Select(p => p.DisposeAsync().AsTask()));
122 |
123 | lock (IsDisposedLock)
124 | {
125 | _compositeDisposable.Dispose();
126 | IsDisposed = true;
127 | }
128 | }
129 |
130 | private int CalculateCurrentPageCount() => _count % _pageSize == 0
131 | ? _count / _pageSize
132 | : _count / _pageSize + 1;
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/BFF.DataVirtualizingCollection/PageStorage/PreloadingPageStorage.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace BFF.DataVirtualizingCollection.PageStorage
5 | {
6 | internal class PreloadingPageStorage : PageStorage
7 | {
8 | private readonly Func> _preloadingPageFactory;
9 |
10 | internal PreloadingPageStorage(
11 | int pageSize,
12 | int count,
13 | Func> nonPreloadingPageFactory,
14 | Func> preloadingPageFactory,
15 | Func, IObservable>> pageReplacementStrategyFactory)
16 | : base (
17 | pageSize,
18 | count,
19 | nonPreloadingPageFactory,
20 | pageReplacementStrategyFactory)
21 | {
22 | _preloadingPageFactory = preloadingPageFactory;
23 | }
24 |
25 | protected override void Preloading(int pageKey)
26 | {
27 | var nextPageKey = pageKey + 1;
28 | if (nextPageKey < PageCount)
29 | Pages.GetOrAdd(
30 | nextPageKey,
31 | FetchPreloadingPage);
32 | var previousPageKey = pageKey - 1;
33 | if (previousPageKey >= 0)
34 | Pages.GetOrAdd(
35 | previousPageKey,
36 | FetchPreloadingPage);
37 |
38 | IPage FetchPreloadingPage(int key)
39 | {
40 | lock (IsDisposedLock)
41 | {
42 | if (!IsDisposed) Requests.OnNext((key, -1));
43 | }
44 |
45 | return FetchPage(key, _preloadingPageFactory);
46 | }
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/BFF.DataVirtualizingCollection/PageStorage/SyncNonPreloadingPageBase.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 |
6 | namespace BFF.DataVirtualizingCollection.PageStorage
7 | {
8 | internal class SyncNonPreloadingNonTaskBasedPage : IPage
9 | {
10 | private readonly int _pageSize;
11 | private readonly IDisposable _onDisposalAfterFetchCompleted;
12 |
13 | internal SyncNonPreloadingNonTaskBasedPage(
14 | // parameter
15 | int offset,
16 | int pageSize,
17 | IDisposable onDisposalAfterFetchCompleted,
18 |
19 | // dependencies
20 | Func pageFetcher)
21 | {
22 | _pageSize = pageSize;
23 | _onDisposalAfterFetchCompleted = onDisposalAfterFetchCompleted;
24 | PageContent = pageFetcher(offset, pageSize, CancellationToken.None);
25 | }
26 |
27 | private T[] PageContent { get; }
28 |
29 | public T this[int index] =>
30 | index >= _pageSize || index < 0
31 | ? throw new IndexOutOfRangeException(
32 | "Index was out of range. Must be non-negative and less than the size of the collection.")
33 | : PageContent[index];
34 |
35 | public Task PageFetchCompletion => Task.CompletedTask;
36 |
37 | public async ValueTask DisposeAsync()
38 | {
39 | await PageFetchCompletion.ConfigureAwait(false);
40 | _onDisposalAfterFetchCompleted.Dispose();
41 | foreach (var disposable in PageContent.OfType())
42 | {
43 | disposable.Dispose();
44 | }
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/BFF.DataVirtualizingCollection/SlidingWindow/ISlidingWindow.cs:
--------------------------------------------------------------------------------
1 | namespace BFF.DataVirtualizingCollection.SlidingWindow
2 | {
3 | // ReSharper disable once PossibleInterfaceMemberAmbiguity
4 | // Ambiguous Members should be implemented explicitly
5 | ///
6 | /// Defines a nongeneric window to the backend (accessed by the page- and count-fetchers).
7 | /// A window is intended to be a much smaller section of the backend. It is specified by an offset and a size.
8 | /// Outwards it looks like a small list which contains only a few items of the whole backend. However, the sliding functionality
9 | /// makes it possible to go through the whole backend.
10 | ///
11 | public interface ISlidingWindow :
12 | IVirtualizationBase
13 | {
14 | ///
15 | /// Current offset of the window inside of the range of the items from the backend. The Offset marks the first item of the backend which is represented in the sliding window.
16 | ///
17 | int Offset { get; }
18 |
19 | ///
20 | /// Current maximum possible offset. Depends on the count of all backend items and the size of the window.
21 | ///
22 | int MaximumOffset { get; }
23 |
24 | ///
25 | /// Slides the window () to the backend one step to the start (left).
26 | ///
27 | void SlideLeft();
28 |
29 | ///
30 | /// Slides the window () to the backend one step to the end (right).
31 | ///
32 | void SlideRight();
33 |
34 | ///
35 | /// Sets the first entry of the window () to the given index of the backend.
36 | ///
37 | void JumpTo(int index);
38 |
39 | ///
40 | /// Increases windows size by one.
41 | ///
42 | void IncreaseWindowSize();
43 |
44 | ///
45 | /// Decreases windows size by one.
46 | ///
47 | void DecreaseWindowSize();
48 |
49 | ///
50 | /// Increases windows size by given increment.
51 | ///
52 | void IncreaseWindowSizeBy(int sizeIncrement);
53 |
54 | ///
55 | /// Decreases windows size by given increment.
56 | ///
57 | void DecreaseWindowSizeBy(int sizeIncrement);
58 |
59 | ///
60 | /// Sets windows size to given size.
61 | ///
62 | void SetWindowSizeTo(int size);
63 | }
64 |
65 |
66 | // ReSharper disable once PossibleInterfaceMemberAmbiguity
67 | // Ambiguous Members should be implemented explicitly
68 | ///
69 | /// Defines a generic window to the backend (accessed by the page- and count-fetchers).
70 | /// A window is intended to be a much smaller section of the backend. It is specified by an offset and a size.
71 | /// Outwards it looks like a small list which contains only a few items of the whole backend. However, the sliding functionality
72 | /// makes it possible to go through the whole backend.
73 | ///
74 | /// Item type.
75 | public interface ISlidingWindow :
76 | IVirtualizationBase,
77 | ISlidingWindow
78 | {
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/BFF.DataVirtualizingCollection/Utilities/DateTimeTimestampProvider.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace BFF.DataVirtualizingCollection.Utilities
4 | {
5 | internal class DateTimeTimestampProvider : ITimestampProvider
6 | {
7 | public DateTime Now => DateTime.Now;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/BFF.DataVirtualizingCollection/Utilities/ErrorMessage.cs:
--------------------------------------------------------------------------------
1 | namespace BFF.DataVirtualizingCollection.Utilities
2 | {
3 | internal static class ErrorMessage
4 | {
5 | internal static string ImpossibleExceptionMessage =>
6 | "Unfortunately, an error occured during data virtualization, which should have been prevented. Please open an issue on https://github.com/Yeah69/BFF.DataVirtualizingCollection.";
7 | }
8 | }
--------------------------------------------------------------------------------
/BFF.DataVirtualizingCollection/Utilities/ITimestampProvider.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace BFF.DataVirtualizingCollection.Utilities
4 | {
5 | internal interface ITimestampProvider
6 | {
7 | DateTime Now { get; }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/BFF.DataVirtualizingCollection/VirtualizationBase.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Collections.Specialized;
5 | using System.ComponentModel;
6 | using System.Reactive.Disposables;
7 | using System.Runtime.CompilerServices;
8 | using System.Threading.Tasks;
9 |
10 | namespace BFF.DataVirtualizingCollection
11 | {
12 | internal abstract class VirtualizationBase :
13 | IVirtualizationBase
14 | {
15 | int ICollection.Count => GetCountInner();
16 |
17 | public bool IsSynchronized => false;
18 |
19 | public object SyncRoot { get; } = new();
20 |
21 | public abstract int Count { get; }
22 |
23 | protected readonly CompositeDisposable CompositeDisposable = new();
24 |
25 | int ICollection.Count => GetCountInner();
26 |
27 | int IReadOnlyCollection.Count => GetCountInner();
28 |
29 | bool ICollection.IsReadOnly => true;
30 | object? IList.this[int index]
31 | {
32 | get => IndexerInnerGet(index);
33 | set => throw new NotSupportedException();
34 | }
35 |
36 | protected abstract T IndexerInnerGet(int index);
37 |
38 | public bool IsReadOnly => true;
39 |
40 | public T this[int index]
41 | {
42 | get => IndexerInnerGet(index);
43 | set => throw new NotSupportedException();
44 | }
45 |
46 | private int GetCountInner() => Count;
47 |
48 | public IEnumerator GetEnumerator()
49 | {
50 | return Iterate().GetEnumerator();
51 |
52 | IEnumerable Iterate()
53 | {
54 | for (var i = 0; i < Count; i++)
55 | yield return GetItemForEnumerator(i);
56 | }
57 | }
58 |
59 | protected abstract T GetItemForEnumerator(int i);
60 |
61 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
62 |
63 | private static int IndexOfInner() => -1;
64 |
65 | public int IndexOf(T item) => IndexOfInner();
66 |
67 | private static bool ContainsInner() => IndexOfInner() != -1;
68 |
69 | public bool Contains(T item) => ContainsInner();
70 |
71 | public bool IsFixedSize => true;
72 |
73 | public bool Contains(object value) => ContainsInner();
74 |
75 | public int IndexOf(object value) => IndexOf((T) value);
76 |
77 | public abstract void Reset();
78 |
79 | public event NotifyCollectionChangedEventHandler? CollectionChanged;
80 | public event PropertyChangedEventHandler? PropertyChanged;
81 |
82 | protected void OnCollectionChangedReplace(T newItem, T oldItem, int index) =>
83 | CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newItem, oldItem, index));
84 |
85 | protected virtual void OnCollectionChangedReset() =>
86 | CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
87 |
88 | protected void OnPropertyChanged([CallerMemberName] string? propertyName = null) =>
89 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
90 |
91 | // ReSharper disable once ExplicitCallerInfoArgument
92 | protected void OnIndexerChanged() => OnPropertyChanged("Item[]");
93 |
94 | public abstract Task InitializationCompleted { get; }
95 | public abstract int SelectedIndex { get; set; }
96 |
97 | #region NotSupported
98 |
99 | public void CopyTo(Array array, int index) => throw new NotSupportedException();
100 |
101 | public void Add(T item) => throw new NotSupportedException();
102 |
103 | public void Insert(int index, T item) => throw new NotSupportedException();
104 |
105 | public bool Remove(T item) => throw new NotSupportedException();
106 |
107 | public void Remove(object value) => throw new NotSupportedException();
108 |
109 | public void RemoveAt(int index) => throw new NotSupportedException();
110 |
111 | void IList.RemoveAt(int index) => throw new NotSupportedException();
112 |
113 | public int Add(object value) => throw new NotSupportedException();
114 |
115 | public void Clear() => throw new NotSupportedException();
116 |
117 | public void Insert(int index, object value) => throw new NotSupportedException();
118 |
119 | void ICollection.Clear() => throw new NotSupportedException();
120 |
121 | public void CopyTo(T[] array, int arrayIndex) => throw new NotSupportedException();
122 |
123 | #endregion
124 |
125 | public virtual async ValueTask DisposeAsync()
126 | {
127 | try
128 | {
129 | await InitializationCompleted.ConfigureAwait(false);
130 | }
131 | catch (OperationCanceledException)
132 | {
133 | // Ignore cancellation
134 | }
135 | CompositeDisposable.Dispose();
136 | }
137 | }
138 | }
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | v
4 | $(MinVerVersion)
5 | Dimitri Enns
6 | true
7 | 9
8 | enable
9 | nullable
10 |
11 |
--------------------------------------------------------------------------------
/Documentation/Diagrams/Context.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yeah69/BFF.DataVirtualizingCollection/6001b74136ffd77c7d54e4b4c7f161613dd88974/Documentation/Diagrams/Context.png
--------------------------------------------------------------------------------
/Documentation/Diagrams/Context.uxf:
--------------------------------------------------------------------------------
1 |
2 |
3 | 10
4 |
5 | UMLClass
6 |
7 | 540
8 | 300
9 | 200
10 | 30
11 |
12 | BFF.DataVirtulizingCollection
13 |
14 |
15 |
16 | UMLClass
17 |
18 | 540
19 | 220
20 | 200
21 | 30
22 |
23 | UI controls
24 |
25 |
26 |
27 | Relation
28 |
29 | 630
30 | 240
31 | 80
32 | 80
33 |
34 | lt=<->
35 | Binding
36 | 10.0;10.0;10.0;60.0
37 |
38 |
39 | UMLClass
40 |
41 | 540
42 | 390
43 | 200
44 | 30
45 |
46 | Backend
47 |
48 |
49 |
50 | Relation
51 |
52 | 630
53 | 320
54 | 120
55 | 90
56 |
57 | lt=<-
58 | Page &&
59 | Count Fechter
60 | 10.0;70.0;10.0;10.0
61 |
62 |
63 |
--------------------------------------------------------------------------------
/Documentation/Diagrams/DataVirtualizingCollection.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yeah69/BFF.DataVirtualizingCollection/6001b74136ffd77c7d54e4b4c7f161613dd88974/Documentation/Diagrams/DataVirtualizingCollection.png
--------------------------------------------------------------------------------
/Documentation/Diagrams/PublicInterfaceHierarchy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yeah69/BFF.DataVirtualizingCollection/6001b74136ffd77c7d54e4b4c7f161613dd88974/Documentation/Diagrams/PublicInterfaceHierarchy.png
--------------------------------------------------------------------------------
/Documentation/Diagrams/PublicInterfaceHierarchy.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
63 |
--------------------------------------------------------------------------------
/Documentation/Diagrams/SlidingWindow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yeah69/BFF.DataVirtualizingCollection/6001b74136ffd77c7d54e4b4c7f161613dd88974/Documentation/Diagrams/SlidingWindow.png
--------------------------------------------------------------------------------
/Icon/Yeah69Logo.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yeah69/BFF.DataVirtualizingCollection/6001b74136ffd77c7d54e4b4c7f161613dd88974/Icon/Yeah69Logo.ico
--------------------------------------------------------------------------------
/Icon/Yeah69Logo_256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yeah69/BFF.DataVirtualizingCollection/6001b74136ffd77c7d54e4b4c7f161613dd88974/Icon/Yeah69Logo_256.png
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | This is free and unencumbered software released into the public domain.
2 |
3 | Anyone is free to copy, modify, publish, use, compile, sell, or
4 | distribute this software, either in source code form or as a compiled
5 | binary, for any purpose, commercial or non-commercial, and by any
6 | means.
7 |
8 | In jurisdictions that recognize copyright laws, the author or authors
9 | of this software dedicate any and all copyright interest in the
10 | software to the public domain. We make this dedication for the benefit
11 | of the public at large and to the detriment of our heirs and
12 | successors. We intend this dedication to be an overt act of
13 | relinquishment in perpetuity of all present and future rights to this
14 | software under copyright law.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | For more information, please refer to
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # BFF.DataVirtualizingCollection
3 | This is an approach to data-virtualizing collections intended to be used in WPF projects.
4 |
5 | ## Nuget package
6 |
7 | Nuget is the easiest way to install it (see [nuget page](https://www.nuget.org/packages/BFF.DataVirtualizingCollection/)):
8 |
9 | ```
10 | > Install-Package BFF.DataVirtualizingCollection
11 | ```
12 |
13 | ## Wiki
14 |
15 | A detailed documentation is available in the [Wiki](https://github.com/Yeah69/BFF.DataVirtualizingCollection/wiki).
16 |
--------------------------------------------------------------------------------
/Sample.GettingStarted/App.xaml:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/Sample.GettingStarted/App.xaml.cs:
--------------------------------------------------------------------------------
1 | namespace BFF.DataVirtualizingCollection.Sample.GettingStarted;
2 |
3 | public partial class App
4 | {
5 | }
--------------------------------------------------------------------------------
/Sample.GettingStarted/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | [assembly: ThemeInfo(
4 | ResourceDictionaryLocation.None,
5 | ResourceDictionaryLocation.SourceAssembly
6 | )]
--------------------------------------------------------------------------------
/Sample.GettingStarted/MainWindow.xaml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/Sample.GettingStarted/MainWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Reactive.Concurrency;
4 | using System.Threading;
5 | using System.Windows;
6 | using System.Windows.Threading;
7 | using BFF.DataVirtualizingCollection.DataVirtualizingCollection;
8 |
9 | namespace BFF.DataVirtualizingCollection.Sample.GettingStarted;
10 |
11 | ///
12 | /// Interaction logic for MainWindow.xaml
13 | ///
14 | public partial class MainWindow
15 | {
16 | public MainWindow()
17 | {
18 | InitializeComponent();
19 |
20 | // Welcome to the "Getting Started" sample
21 |
22 | // It is intentionally held as minimalistic as possible. It should be a quick starting point.
23 | // In this sample we want to virtualize the numbers from 0 to 2,147,483,646 (int.MaxValue - 1)
24 |
25 | // At minimum three settings are required from the user (you) of the data vitualizing collection
26 | // The notification scheduler, the page fetcher and the count fetcher
27 |
28 | // For WPF applications the notification scheduler should be based on the current applications dispatcher
29 | // That'll lead to the notifications (INotifyPropertyChanged & INotifyCollectionChanged) to being marshalled to the UI thread
30 | var notificationScheduler =
31 | new SynchronizationContextScheduler(
32 | new DispatcherSynchronizationContext(
33 | Application.Current.Dispatcher));
34 |
35 | // Because we would like to virtualize the numbers from 0 to 2,147,483,646 (int.MaxValue - 1),
36 | // … the page fetcher will generate an array of consecutive numbers starting with the offset and of same count as the requested page size and
37 | Func pageFetcher = (offset, pageSize, _) =>
38 | Enumerable.Range(offset, pageSize).ToArray();
39 | // … the count just needs to return int.MaxValue
40 | Func countFetcher = _ =>
41 | int.MaxValue;
42 |
43 | // Given those pre-requirements, we'll create a data virtualizing collections with the most basic options (like hoarding)
44 | var dataVirtualizingCollection = DataVirtualizingCollectionBuilder
45 | .Build(notificationScheduler)
46 | .NonPreloading() // neighboring pages are not preloaded
47 | .Hoarding() // once loaded pages are not removed for the lifetime of the data virtualizing collection
48 | .NonTaskBasedFetchers(pageFetcher, countFetcher) // synchronous fetchers
49 | .SyncIndexAccess(); // synchronous indexer (of IReadOnlyList) access (i.e. UI will block for not yet loaded pages)
50 |
51 | // And assign it to the ItemsSource dependency property of a ListBox
52 | // (in an MVVM application you actually would bind it to the property)
53 | List.ItemsSource = dataVirtualizingCollection;
54 |
55 | // That's it. We've virtualized some numbers (up until the billions, that is :D)
56 |
57 | // Please see this as a starting point. Depending of what you would like to do with the data virtualizing collection you can go different ways from here on
58 |
59 | // For example, you could replace virtualizing consecutive number with an other source, for example a database.
60 |
61 | // For beginners it might be of interest to dive deeper into this minimalistic example. You could put a breakpoint to the page and count fetchers code in order to explore when and how often they get called
62 |
63 | // You could also explore options deviating from these basic options (like preloading or least recently used page removal).
64 | // In that case, I'll also recommend to have a look into the UI of the sample project "Sample.View". It is like a playground for exploring the different options
65 |
66 | // This project has a very different kind of virtualization called the "sliding window".
67 | // It is meant for the special use case where a range of element of rather fixed size slides through the virtualized data.
68 | // If that matches your use case, then the sliding window might be right for you.
69 | }
70 | }
--------------------------------------------------------------------------------
/Sample.GettingStarted/Sample.GettingStarted.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | WinExe
5 | net6.0-windows
6 | enable
7 | true
8 | BFF.DataVirtualizingCollection.Sample.GettingStarted
9 | BFF.DataVirtualizingCollection.Sample.GettingStarted
10 | 10
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/Sample.Model/AutofacModule.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using Autofac;
3 | using Module = Autofac.Module;
4 |
5 | namespace BFF.DataVirtualizingCollection.Sample.Model
6 | {
7 | public class AutofacModule : Module
8 | {
9 | protected override void Load(ContainerBuilder builder)
10 | {
11 | var assemblies = new[]
12 | {
13 | Assembly.GetExecutingAssembly()
14 | };
15 |
16 | builder.RegisterAssemblyTypes(assemblies)
17 | .AsImplementedInterfaces()
18 | .AsSelf();
19 | }
20 | }
21 | }
--------------------------------------------------------------------------------
/Sample.Model/BackendAccesses/AllNumbersFakeBackendAccess.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 |
4 | namespace BFF.DataVirtualizingCollection.Sample.Model.BackendAccesses
5 | {
6 | public interface IAllNumbersFakeBackendAccess : IBackendAccess
7 | {
8 | }
9 |
10 | internal class AllNumbersFakeBackendAccess : IAllNumbersFakeBackendAccess
11 | {
12 | public string Name => "All Positive Numbers";
13 |
14 | public int[] PageFetch(int pageOffset, int pageSize) => Enumerable.Range(pageOffset, pageSize).ToArray();
15 |
16 | public int PlaceholderFetch(int _, int __)
17 | {
18 | return -11;
19 | }
20 |
21 | public int PreloadingPlaceholderFetch(int _, int __)
22 | {
23 | return -21;
24 | }
25 |
26 | public int CountFetch()
27 | {
28 | return int.MaxValue;
29 | }
30 | }
31 | }
--------------------------------------------------------------------------------
/Sample.Model/BackendAccesses/HighWorkloadFakeBackendAccess.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using BFF.DataVirtualizingCollection.Sample.Model.Models;
4 |
5 | namespace BFF.DataVirtualizingCollection.Sample.Model.BackendAccesses
6 | {
7 | public interface IHighWorkloadFakeBackendAccess : IBackendAccess
8 | {
9 | }
10 |
11 | internal class HighWorkloadFakeBackendAccess : IHighWorkloadFakeBackendAccess
12 | {
13 | private ISomeWorkloadObject Placeholder { get; } = (ISomeWorkloadObject) new LowWorkloadObject(-11);
14 |
15 | private ISomeWorkloadObject PreloadingPlaceholder { get; } = (ISomeWorkloadObject) new LowWorkloadObject(-11);
16 |
17 | public string Name => "High Workload Simulation";
18 |
19 | public ISomeWorkloadObject[] PageFetch(int pageOffset, int pageSize) =>
20 | Enumerable
21 | .Range(pageOffset, pageSize)
22 | .Select(i => (ISomeWorkloadObject) new HighWorkloadObject(i))
23 | .ToArray();
24 |
25 | public ISomeWorkloadObject PlaceholderFetch(int _, int __)
26 | {
27 | return Placeholder;
28 | }
29 |
30 | public ISomeWorkloadObject PreloadingPlaceholderFetch(int _, int __)
31 | {
32 | return PreloadingPlaceholder;
33 | }
34 |
35 | public int CountFetch()
36 | {
37 | return int.MaxValue;
38 | }
39 | }
40 | }
--------------------------------------------------------------------------------
/Sample.Model/BackendAccesses/IBackendAccess.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Threading.Tasks;
3 |
4 | namespace BFF.DataVirtualizingCollection.Sample.Model.BackendAccesses
5 | {
6 | public interface IBackendAccess
7 | {
8 | string Name { get; }
9 |
10 | T[] PageFetch(int pageOffset, int pageSize);
11 |
12 | async IAsyncEnumerable AsyncEnumerablePageFetch(int pageOffset, int pageSize)
13 | {
14 | foreach (var item in PageFetch(pageOffset, pageSize))
15 | {
16 | await Task.Delay(1);
17 | yield return item;
18 | }
19 | }
20 |
21 | T PlaceholderFetch(int pageOffset, int indexInsidePage);
22 |
23 | T PreloadingPlaceholderFetch(int pageOffset, int indexInsidePage);
24 |
25 | int CountFetch();
26 | }
27 | }
--------------------------------------------------------------------------------
/Sample.Model/BackendAccesses/MillionNumbersBackendAccess.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using BFF.DataVirtualizingCollection.Sample.Model.PersistenceLink;
3 |
4 | namespace BFF.DataVirtualizingCollection.Sample.Model.BackendAccesses
5 | {
6 | public interface IMillionNumbersBackendAccess : IBackendAccess
7 | {
8 | }
9 |
10 | internal class MillionNumbersBackendAccess : IMillionNumbersBackendAccess
11 | {
12 | private readonly IFetchMillionNumbersFromBackend _fetchMillionNumbersFromBackend;
13 |
14 | public MillionNumbersBackendAccess(
15 | IFetchMillionNumbersFromBackend fetchMillionNumbersFromBackend)
16 | {
17 | _fetchMillionNumbersFromBackend = fetchMillionNumbersFromBackend;
18 | }
19 |
20 | public string Name => "A Million Numbers Accessed Through Sqlite";
21 |
22 | public long[] PageFetch(int pageOffset, int pageSize) => _fetchMillionNumbersFromBackend.FetchPage(pageOffset, pageSize);
23 |
24 | public long PlaceholderFetch(int _, int __)
25 | {
26 | return -11L;
27 | }
28 |
29 | public long PreloadingPlaceholderFetch(int _, int __)
30 | {
31 | return -21L;
32 | }
33 |
34 | public int CountFetch()
35 | {
36 | return _fetchMillionNumbersFromBackend.CountFetch();
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/Sample.Model/BackendAccesses/ProfilesFakeBackendAccess.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using BFF.DataVirtualizingCollection.Sample.Model.Models;
4 |
5 | namespace BFF.DataVirtualizingCollection.Sample.Model.BackendAccesses
6 | {
7 | public interface IProfilesFakeBackendAccess : IBackendAccess
8 | {
9 | }
10 |
11 | internal class ProfilesFakeBackendAccess : IProfilesFakeBackendAccess
12 | {
13 |
14 | public string Name => "Profiles";
15 |
16 | public IProfile[] PageFetch(int pageOffset, int pageSize) =>
17 | Enumerable
18 | .Range(pageOffset, pageSize)
19 | .Select(i => ProfileStatic.ProfilePool[i % ProfileStatic.ProfilePool.Count])
20 | .ToArray();
21 |
22 | public IProfile PlaceholderFetch(int _, int __)
23 | {
24 | return ProfileStatic.Empty;
25 | }
26 |
27 | public IProfile PreloadingPlaceholderFetch(int pageOffset, int indexInsidePage)
28 | {
29 | return ProfileStatic.Preloading;
30 | }
31 |
32 | public int CountFetch()
33 | {
34 | return 420420;
35 | }
36 | }
37 | }
--------------------------------------------------------------------------------
/Sample.Model/Models/HighWorkloadObject.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace BFF.DataVirtualizingCollection.Sample.Model.Models
4 | {
5 | public interface IHighWorkloadObject : ISomeWorkloadObject
6 | {
7 | }
8 |
9 | internal class HighWorkloadObject : SomeWorkloadObject, IHighWorkloadObject, IDisposable
10 | {
11 | // Simulates workload
12 | // ReSharper disable once UnusedMember.Local
13 | private readonly byte[] _workload = new byte[12500];
14 |
15 | public HighWorkloadObject(int number) : base(number)
16 | {
17 | }
18 |
19 | public void Dispose()
20 | {
21 | Console.WriteLine("Disposed");
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/Sample.Model/Models/LowWorkloadObject.cs:
--------------------------------------------------------------------------------
1 | namespace BFF.DataVirtualizingCollection.Sample.Model.Models
2 | {
3 | public interface ILowWorkloadObject : ISomeWorkloadObject
4 | {
5 | }
6 |
7 | internal class LowWorkloadObject : SomeWorkloadObject, ILowWorkloadObject
8 | {
9 | public LowWorkloadObject(int number) : base(number)
10 | {
11 | }
12 | }
13 | }
--------------------------------------------------------------------------------
/Sample.Model/Models/SomeWorkloadObject.cs:
--------------------------------------------------------------------------------
1 | namespace BFF.DataVirtualizingCollection.Sample.Model.Models
2 | {
3 | public interface ISomeWorkloadObject
4 | {
5 | int Number { get; }
6 | }
7 |
8 | internal abstract class SomeWorkloadObject : ISomeWorkloadObject
9 | {
10 | public SomeWorkloadObject(int number)
11 | {
12 | Number = number;
13 | }
14 |
15 | public int Number { get; }
16 | }
17 | }
--------------------------------------------------------------------------------
/Sample.Model/PersistenceLink/IFetchMillionNumbersFromBackend.cs:
--------------------------------------------------------------------------------
1 | namespace BFF.DataVirtualizingCollection.Sample.Model.PersistenceLink
2 | {
3 | public interface IFetchMillionNumbersFromBackend
4 | {
5 | long[] FetchPage(int pageOffset, int pageSize);
6 |
7 | int CountFetch();
8 | }
9 | }
--------------------------------------------------------------------------------
/Sample.Model/Sample.Model.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.1
5 | BFF.DataVirtualizingCollection.Sample.Model
6 | BFF.DataVirtualizingCollection.Sample.Model
7 |
8 |
9 |
10 |
11 |
12 | all
13 | runtime; build; native; contentfiles; analyzers; buildtransitive
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/Sample.Persistence.Proxy/AutofacModule.cs:
--------------------------------------------------------------------------------
1 | using Autofac;
2 | using Module = Autofac.Module;
3 |
4 | namespace BFF.DataVirtualizingCollection.Sample.Persistence.Proxy
5 | {
6 | public class AutofacModule : Module
7 | {
8 | protected override void Load(ContainerBuilder builder)
9 | {
10 | builder.RegisterModule(new BFF.DataVirtualizingCollection.Sample.Persistence.AutofacModule());
11 | }
12 | }
13 | }
--------------------------------------------------------------------------------
/Sample.Persistence.Proxy/Sample.Persistence.Proxy.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.1
5 | BFF.DataVirtualizingCollection.Sample.Persistence.Proxy
6 | BFF.DataVirtualizingCollection.Sample.Persistence.Proxy
7 |
8 |
9 |
10 |
11 |
12 | all
13 | runtime; build; native; contentfiles; analyzers; buildtransitive
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/Sample.Persistence/AutofacModule.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using Autofac;
3 | using Module = Autofac.Module;
4 |
5 | namespace BFF.DataVirtualizingCollection.Sample.Persistence
6 | {
7 | public class AutofacModule : Module
8 | {
9 | protected override void Load(ContainerBuilder builder)
10 | {
11 | var assemblies = new[]
12 | {
13 | Assembly.GetExecutingAssembly()
14 | };
15 |
16 | builder.RegisterAssemblyTypes(assemblies)
17 | .AsImplementedInterfaces()
18 | .AsSelf();
19 | }
20 | }
21 | }
--------------------------------------------------------------------------------
/Sample.Persistence/Databases/BFF.DataVirtualizingCollection.MillionNumbers.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yeah69/BFF.DataVirtualizingCollection/6001b74136ffd77c7d54e4b4c7f161613dd88974/Sample.Persistence/Databases/BFF.DataVirtualizingCollection.MillionNumbers.sqlite
--------------------------------------------------------------------------------
/Sample.Persistence/PersistenceLink/FetchMillionNumbersFromBackend.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.IO;
3 | using System.Linq;
4 | using BFF.DataVirtualizingCollection.Sample.Model.PersistenceLink;
5 | using Microsoft.Data.Sqlite;
6 |
7 | namespace BFF.DataVirtualizingCollection.Sample.Persistence.PersistenceLink
8 | {
9 | internal class FetchMillionNumbersFromBackend : IFetchMillionNumbersFromBackend
10 | {
11 | private static readonly string PathToSimpleTestDb = $"{System.Reflection.Assembly.GetEntryAssembly()?.Location.Remove(System.Reflection.Assembly.GetEntryAssembly()?.Location.LastIndexOf(Path.DirectorySeparatorChar) ?? 0)}{Path.DirectorySeparatorChar}Databases{Path.DirectorySeparatorChar}BFF.DataVirtualizingCollection.MillionNumbers.sqlite";
12 | public long[] FetchPage(int pageOffset, int pageSize)
13 | {
14 | using var sqliteConnection =
15 | new SqliteConnection(
16 | new SqliteConnectionStringBuilder
17 | {
18 | DataSource = PathToSimpleTestDb
19 | }
20 | .ToString());
21 | sqliteConnection.Open();
22 | sqliteConnection.BeginTransaction();
23 | var sqliteCommand = sqliteConnection.CreateCommand();
24 | sqliteCommand.CommandText = $"SELECT Number FROM Numbers LIMIT {pageSize} OFFSET {pageOffset};";
25 | var sqliteDataReader = sqliteCommand.ExecuteReader();
26 | IList ret = new List();
27 | while (sqliteDataReader.Read())
28 | ret.Add((long)sqliteDataReader["Number"]);
29 | return ret.ToArray();
30 | }
31 |
32 | public int CountFetch()
33 | {
34 | using var sqliteConnection =
35 | new SqliteConnection(
36 | new SqliteConnectionStringBuilder
37 | {
38 | DataSource = PathToSimpleTestDb
39 | }
40 | .ToString());
41 | sqliteConnection.Open();
42 | var sqliteCommand = sqliteConnection.CreateCommand();
43 | sqliteCommand.CommandText = "SELECT Count(*) FROM Numbers;";
44 | return (int)(long)sqliteCommand.ExecuteScalar();
45 | }
46 | }
47 | }
--------------------------------------------------------------------------------
/Sample.Persistence/Sample.Persistence.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.1
5 | BFF.DataVirtualizingCollection.Sample.Persistence
6 | BFF.DataVirtualizingCollection.Sample.Persistence
7 |
8 |
9 |
10 |
11 |
12 |
13 | all
14 | runtime; build; native; contentfiles; analyzers; buildtransitive
15 |
16 |
17 |
18 |
19 |
20 | Always
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/Sample.View/App.xaml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Sample.View/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | namespace BFF.DataVirtualizingCollection.Sample.View
4 | {
5 | public partial class App
6 | {
7 | public App()
8 | {
9 | var mainWindowView = AutofacModule.Start();
10 | mainWindowView.Show();
11 | }
12 |
13 | public static Visibility IsDebug
14 | {
15 | #if DEBUG
16 | get { return Visibility.Visible; }
17 | #else
18 | get { return Visibility.Collapsed; }
19 | #endif
20 | }
21 | }
22 | }
--------------------------------------------------------------------------------
/Sample.View/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | [assembly: ThemeInfo(
4 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
5 | //(used if a resource is not found in the page,
6 | // or application resource dictionaries)
7 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
8 | //(used if a resource is not found in the page,
9 | // app, or any theme specific resource dictionaries)
10 | )]
--------------------------------------------------------------------------------
/Sample.View/AutofacModule.cs:
--------------------------------------------------------------------------------
1 | using System.Reactive.Disposables;
2 | using System.Reflection;
3 | using Autofac;
4 | using BFF.DataVirtualizingCollection.Sample.View.ViewModelInterfaceImplementations;
5 | using BFF.DataVirtualizingCollection.Sample.View.Views;
6 | using Module = Autofac.Module;
7 |
8 | namespace BFF.DataVirtualizingCollection.Sample.View
9 | {
10 | public class AutofacModule : Module
11 | {
12 | public static MainWindowView Start()
13 | {
14 | var builder = new ContainerBuilder();
15 | builder.RegisterModule(new AutofacModule());
16 | return builder
17 | .Build()
18 | .BeginLifetimeScope()
19 | .Resolve();
20 | }
21 |
22 | private AutofacModule()
23 | {}
24 |
25 | protected override void Load(ContainerBuilder builder)
26 | {
27 | var assemblies = new[]
28 | {
29 | Assembly.GetExecutingAssembly()
30 | };
31 |
32 | builder.RegisterAssemblyTypes(assemblies)
33 | .AsImplementedInterfaces()
34 | .AsSelf();
35 |
36 | builder.RegisterType()
37 | .AsImplementedInterfaces()
38 | .AsSelf()
39 | .SingleInstance();
40 |
41 | builder.RegisterType()
42 | .AsSelf()
43 | .UsingConstructor(() => new CompositeDisposable())
44 | .InstancePerLifetimeScope();
45 |
46 | builder.RegisterModule(new BFF.DataVirtualizingCollection.Sample.ViewModel.AutofacModule());
47 |
48 | builder.RegisterModule(new BFF.DataVirtualizingCollection.Sample.Persistence.Proxy.AutofacModule());
49 | }
50 | }
51 | }
--------------------------------------------------------------------------------
/Sample.View/ProfilePics/00_Wide.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yeah69/BFF.DataVirtualizingCollection/6001b74136ffd77c7d54e4b4c7f161613dd88974/Sample.View/ProfilePics/00_Wide.png
--------------------------------------------------------------------------------
/Sample.View/ProfilePics/01_Paria.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yeah69/BFF.DataVirtualizingCollection/6001b74136ffd77c7d54e4b4c7f161613dd88974/Sample.View/ProfilePics/01_Paria.png
--------------------------------------------------------------------------------
/Sample.View/ProfilePics/02_Morexandra.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yeah69/BFF.DataVirtualizingCollection/6001b74136ffd77c7d54e4b4c7f161613dd88974/Sample.View/ProfilePics/02_Morexandra.png
--------------------------------------------------------------------------------
/Sample.View/ProfilePics/03_Smennifer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yeah69/BFF.DataVirtualizingCollection/6001b74136ffd77c7d54e4b4c7f161613dd88974/Sample.View/ProfilePics/03_Smennifer.png
--------------------------------------------------------------------------------
/Sample.View/ProfilePics/04_Anyetlana.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yeah69/BFF.DataVirtualizingCollection/6001b74136ffd77c7d54e4b4c7f161613dd88974/Sample.View/ProfilePics/04_Anyetlana.png
--------------------------------------------------------------------------------
/Sample.View/ProfilePics/05_Korko.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yeah69/BFF.DataVirtualizingCollection/6001b74136ffd77c7d54e4b4c7f161613dd88974/Sample.View/ProfilePics/05_Korko.png
--------------------------------------------------------------------------------
/Sample.View/ProfilePics/06_Kowel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yeah69/BFF.DataVirtualizingCollection/6001b74136ffd77c7d54e4b4c7f161613dd88974/Sample.View/ProfilePics/06_Kowel.png
--------------------------------------------------------------------------------
/Sample.View/ProfilePics/07_Sinia.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yeah69/BFF.DataVirtualizingCollection/6001b74136ffd77c7d54e4b4c7f161613dd88974/Sample.View/ProfilePics/07_Sinia.png
--------------------------------------------------------------------------------
/Sample.View/ProfilePics/08_Wonathan.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yeah69/BFF.DataVirtualizingCollection/6001b74136ffd77c7d54e4b4c7f161613dd88974/Sample.View/ProfilePics/08_Wonathan.png
--------------------------------------------------------------------------------
/Sample.View/ProfilePics/09_Matban.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yeah69/BFF.DataVirtualizingCollection/6001b74136ffd77c7d54e4b4c7f161613dd88974/Sample.View/ProfilePics/09_Matban.png
--------------------------------------------------------------------------------
/Sample.View/ProfilePics/10_Surgiana.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yeah69/BFF.DataVirtualizingCollection/6001b74136ffd77c7d54e4b4c7f161613dd88974/Sample.View/ProfilePics/10_Surgiana.png
--------------------------------------------------------------------------------
/Sample.View/ProfilePics/11_Jogory.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yeah69/BFF.DataVirtualizingCollection/6001b74136ffd77c7d54e4b4c7f161613dd88974/Sample.View/ProfilePics/11_Jogory.png
--------------------------------------------------------------------------------
/Sample.View/Sample.View.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | WinExe
5 | netcoreapp3.1
6 | true
7 | BFF.DataVirtualizingCollection.Sample.View
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | all
23 | runtime; build; native; contentfiles; analyzers; buildtransitive
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | Always
32 |
33 |
34 | Always
35 |
36 |
37 | Always
38 |
39 |
40 | Always
41 |
42 |
43 | Always
44 |
45 |
46 | Always
47 |
48 |
49 | Always
50 |
51 |
52 | Always
53 |
54 |
55 | Always
56 |
57 |
58 | Always
59 |
60 |
61 | Always
62 |
63 |
64 | Always
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 | GeneralOptionsView.xaml
86 |
87 |
88 | SlidingWindowOptionsViewModel.xaml
89 |
90 |
91 | GeneralFunctionsView.xaml
92 |
93 |
94 | SpecificFunctionsView.xaml
95 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/Sample.View/Utilities/Converters.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 | using System.Windows.Data;
3 |
4 | namespace BFF.DataVirtualizingCollection.Sample.View.Utilities
5 | {
6 | public static class Converters
7 | {
8 | public static IValueConverter BoolToVisibility =
9 | LambdaConverters.ValueConverter.Create(
10 | e => e.Value ? Visibility.Visible : Visibility.Collapsed);
11 |
12 | public static IValueConverter ValueEqualsToParameter =
13 | LambdaConverters.ValueConverter.Create