├── .gitignore ├── .nuget ├── NuGet.Config ├── NuGet.targets └── nuget.exe ├── License.txt ├── VirtualCollection.Web ├── Properties │ └── AssemblyInfo.cs ├── Silverlight.js ├── VirtualCollection.Web.csproj ├── VirtualCollectionTestPage.aspx ├── VirtualCollectionTestPage.html ├── Web.Debug.config ├── Web.Release.config └── Web.config ├── VirtualCollection.sln └── VirtualCollection ├── App.xaml ├── App.xaml.cs ├── BusynessIndicator.xaml ├── BusynessIndicator.xaml.cs ├── Demo ├── MainViewModel.cs ├── NorthwindProductsSource.cs ├── Schedulers.cs └── StringToImageConverter.cs ├── Framework ├── Behaviors │ └── NotifyViewModelOfLifeCycleEvents.cs ├── Converters │ ├── StringValueConversion.cs │ ├── StringValueConversionCollection.cs │ └── StringValueToObjectConverter.cs ├── Extensions │ ├── INotifyPropertyChangedExtensions.cs │ └── SymbolExtensions.cs └── MVVM │ ├── ActionCommand.cs │ ├── CommandBase.cs │ ├── CommandHolder.cs │ ├── IViewModel.cs │ └── ViewModel.cs ├── MainPage.xaml ├── MainPage.xaml.cs ├── Properties ├── AppManifest.xml └── AssemblyInfo.cs ├── Service References └── Northwind │ ├── Reference.cs │ ├── Reference.datasvcmap │ └── service.edmx ├── VirtualCollection.csproj ├── VirtualCollection ├── DeferredActionInvoker.cs ├── Disposer.cs ├── IEnquireAboutItemVisibility.cs ├── INotifyBusyness.cs ├── IVirtualCollectionSource.cs ├── ItemsRealizedEventArgs.cs ├── MostRecentUsedList.cs ├── ProvideVisibleItemRangeFromDataGridBehavior.cs ├── ProvideVisibleItemRangeFromItemsControlBehavior.cs ├── RefreshMode.cs ├── SparseList.cs ├── VirtualCollection.cs ├── VirtualCollectionSource.cs ├── VirtualItem.cs ├── VirtualizingWrapPanel.cs ├── WeakCollectionViewWrapper.cs └── WeakEventListener.cs └── packages.config /.gitignore: -------------------------------------------------------------------------------- 1 | _ReSharper* 2 | VirtualCollection/Bin/ 3 | *.user 4 | VirtualCollection/obj/ 5 | *.suo 6 | [Bb]in 7 | [Oo]bj 8 | ClientBin 9 | packages/ 10 | -------------------------------------------------------------------------------- /.nuget/NuGet.Config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.nuget/NuGet.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(MSBuildProjectDirectory)\..\ 5 | 6 | 7 | false 8 | 9 | 10 | false 11 | 12 | 13 | false 14 | 15 | 16 | 17 | 18 | 22 | 23 | 24 | 25 | 26 | $([System.IO.Path]::Combine($(SolutionDir), ".nuget")) 27 | $([System.IO.Path]::Combine($(ProjectDir), "packages.config")) 28 | $([System.IO.Path]::Combine($(SolutionDir), "packages")) 29 | 30 | 31 | 32 | 33 | $(SolutionDir).nuget 34 | packages.config 35 | $(SolutionDir)packages 36 | 37 | 38 | 39 | 40 | $(NuGetToolsPath)\nuget.exe 41 | @(PackageSource) 42 | 43 | "$(NuGetExePath)" 44 | mono --runtime=v4.0.30319 $(NuGetExePath) 45 | 46 | $(TargetDir.Trim('\\')) 47 | 48 | 49 | $(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" -o "$(PackagesDir)" 50 | $(NuGetCommand) pack "$(ProjectPath)" -p Configuration=$(Configuration) -o "$(PackageOutputDir)" -symbols 51 | 52 | 53 | 54 | RestorePackages; 55 | $(BuildDependsOn); 56 | 57 | 58 | 59 | 60 | $(BuildDependsOn); 61 | BuildPackage; 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | 82 | 84 | 85 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 136 | 137 | 138 | 139 | -------------------------------------------------------------------------------- /.nuget/nuget.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samueldjack/VirtualCollection/bbce670015b1a0af6332ea3ecfd20b28384573c9/.nuget/nuget.exe -------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /VirtualCollection.Web/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("VirtualCollection.Web")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("VirtualCollection.Web")] 13 | [assembly: AssemblyCopyright("Copyright © 2012")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("730913a8-a15b-421f-af04-1f76837fcf13")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Revision and Build Numbers 33 | // by using the '*' as shown below: 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /VirtualCollection.Web/Silverlight.js: -------------------------------------------------------------------------------- 1 | //v2.0.30511.0 2 | if(!window.Silverlight)window.Silverlight={};Silverlight._silverlightCount=0;Silverlight.__onSilverlightInstalledCalled=false;Silverlight.fwlinkRoot="http://go2.microsoft.com/fwlink/?LinkID=";Silverlight.__installationEventFired=false;Silverlight.onGetSilverlight=null;Silverlight.onSilverlightInstalled=function(){window.location.reload(false)};Silverlight.isInstalled=function(b){if(b==undefined)b=null;var a=false,m=null;try{var i=null,j=false;if(window.ActiveXObject)try{i=new ActiveXObject("AgControl.AgControl");if(b===null)a=true;else if(i.IsVersionSupported(b))a=true;i=null}catch(l){j=true}else j=true;if(j){var k=navigator.plugins["Silverlight Plug-In"];if(k)if(b===null)a=true;else{var h=k.description;if(h==="1.0.30226.2")h="2.0.30226.2";var c=h.split(".");while(c.length>3)c.pop();while(c.length<4)c.push(0);var e=b.split(".");while(e.length>4)e.pop();var d,g,f=0;do{d=parseInt(e[f]);g=parseInt(c[f]);f++}while(f");delete a.id;delete a.width;delete a.height;for(var c in a)if(a[c])b.push('');b.push("");return b.join("")};Silverlight.createObjectEx=function(b){var a=b,c=Silverlight.createObject(a.source,a.parentElement,a.id,a.properties,a.events,a.initParams,a.context);if(a.parentElement==null)return c};Silverlight.buildPromptHTML=function(b){var a="",d=Silverlight.fwlinkRoot,c=b.version;if(b.alt)a=b.alt;else{if(!c)c="";a="Get Microsoft Silverlight";a=a.replace("{1}",c);a=a.replace("{2}",d+"108181")}return a};Silverlight.getSilverlight=function(e){if(Silverlight.onGetSilverlight)Silverlight.onGetSilverlight();var b="",a=String(e).split(".");if(a.length>1){var c=parseInt(a[0]);if(isNaN(c)||c<2)b="1.0";else b=a[0]+"."+a[1]}var d="";if(b.match(/^\d+\056\d+$/))d="&v="+b;Silverlight.followFWLink("149156"+d)};Silverlight.followFWLink=function(a){top.location=Silverlight.fwlinkRoot+String(a)};Silverlight.HtmlAttributeEncode=function(c){var a,b="";if(c==null)return null;for(var d=0;d96&&a<123||a>64&&a<91||a>43&&a<58&&a!=47||a==95)b=b+String.fromCharCode(a);else b=b+"&#"+a+";"}return b};Silverlight.default_error_handler=function(e,b){var d,c=b.ErrorType;d=b.ErrorCode;var a="\nSilverlight error message \n";a+="ErrorCode: "+d+"\n";a+="ErrorType: "+c+" \n";a+="Message: "+b.ErrorMessage+" \n";if(c=="ParserError"){a+="XamlFile: "+b.xamlFile+" \n";a+="Line: "+b.lineNumber+" \n";a+="Position: "+b.charPosition+" \n"}else if(c=="RuntimeError"){if(b.lineNumber!=0){a+="Line: "+b.lineNumber+" \n";a+="Position: "+b.charPosition+" \n"}a+="MethodName: "+b.methodName+" \n"}alert(a)};Silverlight.__cleanup=function(){for(var a=Silverlight._silverlightCount-1;a>=0;a--)window["__slEvent"+a]=null;Silverlight._silverlightCount=0;if(window.removeEventListener)window.removeEventListener("unload",Silverlight.__cleanup,false);else window.detachEvent("onunload",Silverlight.__cleanup)};Silverlight.__getHandlerName=function(b){var a="";if(typeof b=="string")a=b;else if(typeof b=="function"){if(Silverlight._silverlightCount==0)if(window.addEventListener)window.addEventListener("onunload",Silverlight.__cleanup,false);else window.attachEvent("onunload",Silverlight.__cleanup);var c=Silverlight._silverlightCount++;a="__slEvent"+c;window[a]=b}else a=null;return a};Silverlight.onRequiredVersionAvailable=function(){};Silverlight.onRestartRequired=function(){};Silverlight.onUpgradeRequired=function(){};Silverlight.onInstallRequired=function(){};Silverlight.IsVersionAvailableOnError=function(d,a){var b=false;try{if(a.ErrorCode==8001&&!Silverlight.__installationEventFired){Silverlight.onUpgradeRequired();Silverlight.__installationEventFired=true}else if(a.ErrorCode==8002&&!Silverlight.__installationEventFired){Silverlight.onRestartRequired();Silverlight.__installationEventFired=true}else if(a.ErrorCode==5014||a.ErrorCode==2106){if(Silverlight.__verifySilverlight2UpgradeSuccess(a.getHost()))b=true}else b=true}catch(c){}return b};Silverlight.IsVersionAvailableOnLoad=function(b){var a=false;try{if(Silverlight.__verifySilverlight2UpgradeSuccess(b.getHost()))a=true}catch(c){}return a};Silverlight.__verifySilverlight2UpgradeSuccess=function(d){var c=false,b="2.0.31005",a=null;try{if(d.IsVersionSupported(b+".99")){a=Silverlight.onRequiredVersionAvailable;c=true}else if(d.IsVersionSupported(b+".0"))a=Silverlight.onRestartRequired;else a=Silverlight.onUpgradeRequired;if(a&&!Silverlight.__installationEventFired){a();Silverlight.__installationEventFired=true}}catch(e){}return c} -------------------------------------------------------------------------------- /VirtualCollection.Web/VirtualCollection.Web.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | 8 | 9 | 2.0 10 | {E8E63401-D8B0-42C5-8D15-9D68CE8641BB} 11 | {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} 12 | Library 13 | Properties 14 | VirtualCollection.Web 15 | VirtualCollection.Web 16 | v4.0 17 | false 18 | {19D96D76-77F4-456C-A04B-44F5FF9172E3}|..\VirtualCollection\VirtualCollection.csproj|ClientBin|False 19 | 20 | 21 | 22 | 23 | 4.0 24 | 25 | 26 | true 27 | full 28 | false 29 | bin\ 30 | DEBUG;TRACE 31 | prompt 32 | 4 33 | 34 | 35 | pdbonly 36 | true 37 | bin\ 38 | TRACE 39 | prompt 40 | 4 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | Web.config 68 | 69 | 70 | Web.config 71 | 72 | 73 | 74 | 75 | 76 | 77 | 10.0 78 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | False 88 | True 89 | 50505 90 | / 91 | 92 | 93 | False 94 | False 95 | 96 | 97 | False 98 | 99 | 100 | 101 | 102 | 109 | -------------------------------------------------------------------------------- /VirtualCollection.Web/VirtualCollectionTestPage.aspx: -------------------------------------------------------------------------------- 1 | <%@ Page Language="c#" AutoEventWireup="true" %> 2 | 3 | 4 | 5 | 6 | VirtualCollection 7 | 21 | 22 | 58 | 59 | 60 |
61 |
62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | Get Microsoft Silverlight 70 | 71 |
72 |
73 | 74 | 75 | -------------------------------------------------------------------------------- /VirtualCollection.Web/VirtualCollectionTestPage.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | VirtualCollection 6 | 20 | 21 | 57 | 58 | 59 |
60 |
61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | Get Microsoft Silverlight 69 | 70 |
71 |
72 | 73 | 74 | -------------------------------------------------------------------------------- /VirtualCollection.Web/Web.Debug.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 17 | 18 | 29 | 30 | -------------------------------------------------------------------------------- /VirtualCollection.Web/Web.Release.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 17 | 18 | 19 | 30 | 31 | -------------------------------------------------------------------------------- /VirtualCollection.Web/Web.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /VirtualCollection.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2012 4 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{60BC5A25-0D86-4E1C-A9AD-39655475B12B}" 5 | ProjectSection(SolutionItems) = preProject 6 | .nuget\NuGet.Config = .nuget\NuGet.Config 7 | .nuget\nuget.exe = .nuget\nuget.exe 8 | .nuget\NuGet.targets = .nuget\NuGet.targets 9 | EndProjectSection 10 | EndProject 11 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VirtualCollection", "VirtualCollection\VirtualCollection.csproj", "{19D96D76-77F4-456C-A04B-44F5FF9172E3}" 12 | EndProject 13 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VirtualCollection.Web", "VirtualCollection.Web\VirtualCollection.Web.csproj", "{E8E63401-D8B0-42C5-8D15-9D68CE8641BB}" 14 | EndProject 15 | Global 16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 17 | Debug|Any CPU = Debug|Any CPU 18 | Release|Any CPU = Release|Any CPU 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {19D96D76-77F4-456C-A04B-44F5FF9172E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {19D96D76-77F4-456C-A04B-44F5FF9172E3}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {19D96D76-77F4-456C-A04B-44F5FF9172E3}.Release|Any CPU.ActiveCfg = Release|Any CPU 24 | {19D96D76-77F4-456C-A04B-44F5FF9172E3}.Release|Any CPU.Build.0 = Release|Any CPU 25 | {E8E63401-D8B0-42C5-8D15-9D68CE8641BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {E8E63401-D8B0-42C5-8D15-9D68CE8641BB}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {E8E63401-D8B0-42C5-8D15-9D68CE8641BB}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {E8E63401-D8B0-42C5-8D15-9D68CE8641BB}.Release|Any CPU.Build.0 = Release|Any CPU 29 | EndGlobalSection 30 | GlobalSection(SolutionProperties) = preSolution 31 | HideSolutionNode = FALSE 32 | EndGlobalSection 33 | EndGlobal 34 | -------------------------------------------------------------------------------- /VirtualCollection/App.xaml: -------------------------------------------------------------------------------- 1 |  5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /VirtualCollection/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Threading.Tasks; 6 | using System.Windows; 7 | using System.Windows.Controls; 8 | using System.Windows.Documents; 9 | using System.Windows.Input; 10 | using System.Windows.Media; 11 | using System.Windows.Media.Animation; 12 | using System.Windows.Shapes; 13 | using VirtualCollection.Demo; 14 | 15 | namespace VirtualCollection 16 | { 17 | public partial class App : Application 18 | { 19 | 20 | public App() 21 | { 22 | this.Startup += this.Application_Startup; 23 | this.Exit += this.Application_Exit; 24 | this.UnhandledException += this.Application_UnhandledException; 25 | 26 | InitializeComponent(); 27 | } 28 | 29 | private void Application_Startup(object sender, StartupEventArgs e) 30 | { 31 | Schedulers.UIThread = TaskScheduler.FromCurrentSynchronizationContext(); 32 | 33 | this.RootVisual = new MainPage(); 34 | } 35 | 36 | private void Application_Exit(object sender, EventArgs e) 37 | { 38 | 39 | } 40 | 41 | private void Application_UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e) 42 | { 43 | // If the app is running outside of the debugger then report the exception using 44 | // the browser's exception mechanism. On IE this will display it a yellow alert 45 | // icon in the status bar and Firefox will display a script error. 46 | if (!System.Diagnostics.Debugger.IsAttached) 47 | { 48 | 49 | // NOTE: This will allow the application to continue running after an exception has been thrown 50 | // but not handled. 51 | // For production applications this error handling should be replaced with something that will 52 | // report the error to the website and stop the application. 53 | e.Handled = true; 54 | Deployment.Current.Dispatcher.BeginInvoke(delegate { ReportErrorToDOM(e); }); 55 | } 56 | } 57 | 58 | private void ReportErrorToDOM(ApplicationUnhandledExceptionEventArgs e) 59 | { 60 | try 61 | { 62 | string errorMsg = e.ExceptionObject.Message + e.ExceptionObject.StackTrace; 63 | errorMsg = errorMsg.Replace('"', '\'').Replace("\r\n", @"\n"); 64 | 65 | System.Windows.Browser.HtmlPage.Window.Eval("throw new Error(\"Unhandled Error in Silverlight Application " + errorMsg + "\");"); 66 | } 67 | catch (Exception) 68 | { 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /VirtualCollection/BusynessIndicator.xaml: -------------------------------------------------------------------------------- 1 |  9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 22 | 23 | 24 | 25 | 26 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /VirtualCollection/BusynessIndicator.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows; 3 | using System.Windows.Controls; 4 | using VirtualCollection.VirtualCollection; 5 | 6 | namespace VirtualCollection 7 | { 8 | public partial class BusynessIndicator : UserControl 9 | { 10 | public static readonly DependencyProperty BusyBodyProperty = 11 | DependencyProperty.Register("BusyBody", typeof(INotifyBusyness), typeof(BusynessIndicator), new PropertyMetadata(default(INotifyBusyness), HandleSourceChanged)); 12 | 13 | private static void HandleSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 14 | { 15 | var notifier = d as BusynessIndicator; 16 | 17 | if (e.OldValue != null) 18 | { 19 | (e.OldValue as INotifyBusyness).IsBusyChanged -= notifier.IsBusyChanged; 20 | } 21 | 22 | if (e.NewValue != null) 23 | { 24 | (e.NewValue as INotifyBusyness).IsBusyChanged += notifier.IsBusyChanged; 25 | } 26 | } 27 | 28 | private void IsBusyChanged(object sender, EventArgs e) 29 | { 30 | if (Dispatcher.CheckAccess()) 31 | { 32 | UpdateState(); 33 | } 34 | else 35 | { 36 | Dispatcher.BeginInvoke(UpdateState); 37 | } 38 | } 39 | 40 | private void UpdateState() 41 | { 42 | if (BusyBody != null && BusyBody.IsBusy) 43 | { 44 | VisualStateManager.GoToState(this, "Busy", true); 45 | } 46 | else 47 | { 48 | VisualStateManager.GoToState(this, "Idle", true); 49 | } 50 | } 51 | 52 | public INotifyBusyness BusyBody 53 | { 54 | get { return (INotifyBusyness)GetValue(BusyBodyProperty); } 55 | set { SetValue(BusyBodyProperty, value); } 56 | } 57 | 58 | public BusynessIndicator() 59 | { 60 | InitializeComponent(); 61 | 62 | VisualStateManager.GoToState(this, "Idle", true); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /VirtualCollection/Demo/MainViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data.Services.Client; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | using System.Windows.Controls; 9 | using System.Windows.Documents; 10 | using System.Windows.Ink; 11 | using System.Windows.Input; 12 | using System.Windows.Media; 13 | using System.Windows.Media.Animation; 14 | using System.Windows.Shapes; 15 | using VirtualCollection.Framework.MVVM; 16 | using VirtualCollection.Northwind; 17 | using VirtualCollection.VirtualCollection; 18 | using VirtualCollection.Framework.Extensions; 19 | using System.Reactive.Linq; 20 | 21 | namespace VirtualCollection.Demo 22 | { 23 | public class MainViewModel : ViewModel 24 | { 25 | private string _search; 26 | private NorthwindProductsSource _source; 27 | private string _displayStyle; 28 | 29 | public string Search 30 | { 31 | get { return _search; } 32 | set 33 | { 34 | _search = value; 35 | RaisePropertyChanged(() => Search); 36 | } 37 | } 38 | 39 | public string DisplayStyle 40 | { 41 | get { return _displayStyle; } 42 | set 43 | { 44 | _displayStyle = value; 45 | RaisePropertyChanged(() => DisplayStyle); 46 | } 47 | } 48 | 49 | public IList DisplayStyles { get { return new[] {"Card", "Details"}; } } 50 | 51 | public VirtualCollection Items { get; private set; } 52 | 53 | public MainViewModel() 54 | { 55 | _source = new NorthwindProductsSource(); 56 | Items = new VirtualCollection(_source, pageSize: 20, cachedPages: 5); 57 | 58 | this.ObservePropertyChanged(() => Search) 59 | .Throttle(TimeSpan.FromSeconds(0.25)) 60 | .ObserveOnDispatcher() 61 | .Subscribe(_ => _source.Search = Search); 62 | 63 | DisplayStyle = "Details"; 64 | } 65 | 66 | protected override void OnViewLoaded() 67 | { 68 | Items.Refresh(); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /VirtualCollection/Demo/NorthwindProductsSource.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Data.Services.Client; 5 | using System.Linq; 6 | using System.Net; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using System.Windows; 10 | using System.Windows.Controls; 11 | using System.Windows.Documents; 12 | using System.Windows.Ink; 13 | using System.Windows.Input; 14 | using System.Windows.Media; 15 | using System.Windows.Media.Animation; 16 | using System.Windows.Shapes; 17 | using VirtualCollection.Northwind; 18 | using VirtualCollection.VirtualCollection; 19 | 20 | namespace VirtualCollection.Demo 21 | { 22 | public class NorthwindProductsSource : VirtualCollectionSource 23 | { 24 | private string _search; 25 | 26 | public string Search 27 | { 28 | get { return _search; } 29 | set 30 | { 31 | _search = value; 32 | Refresh(RefreshMode.ClearStaleData); 33 | } 34 | } 35 | 36 | protected override Task> GetPageAsyncOverride(int start, int pageSize, IList sortDescriptions) 37 | { 38 | return GetQueryResults(start, pageSize, sortDescriptions) 39 | .ContinueWith(t => 40 | { 41 | SetCount((int)t.Result.TotalCount); 42 | return (IList)((IEnumerable)t.Result).ToList(); 43 | 44 | }, TaskContinuationOptions.ExecuteSynchronously); 45 | } 46 | 47 | private Task> GetQueryResults(int start, int pageSize, IList sortDescriptions) 48 | { 49 | var context = new NorthwindEntities(new Uri("http://services.odata.org/Northwind/Northwind.svc/")); 50 | 51 | var orderByString = CreateOrderByString(sortDescriptions); 52 | var query = context.Products 53 | .AddQueryOption("$skip", start) 54 | .AddQueryOption("$top", pageSize) 55 | .IncludeTotalCount(); 56 | 57 | if (!string.IsNullOrEmpty(Search)) 58 | { 59 | query = query.AddQueryOption("$filter", "(substringof('" + Search + "',ProductName) eq true)"); 60 | } 61 | 62 | if (orderByString.Length > 0) 63 | { 64 | query = query.AddQueryOption("$orderby", orderByString); 65 | } 66 | 67 | return Task.Factory.FromAsync>(query.BeginExecute, query.EndExecute, null) 68 | .ContinueWith(t => (QueryOperationResponse)t.Result, TaskContinuationOptions.ExecuteSynchronously); 69 | } 70 | 71 | private string CreateOrderByString(IList sortDescriptions) 72 | { 73 | var sb = new StringBuilder(); 74 | 75 | if (sortDescriptions != null) 76 | { 77 | foreach (var sortDescription in sortDescriptions) 78 | { 79 | if (sb.Length > 0) 80 | { 81 | sb.Append(","); 82 | } 83 | 84 | sb.Append(sortDescription.PropertyName + " " + 85 | (sortDescription.Direction == ListSortDirection.Ascending ? "asc" : "desc")); 86 | } 87 | } 88 | 89 | return sb.ToString(); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /VirtualCollection/Demo/Schedulers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Threading.Tasks; 4 | using System.Windows; 5 | using System.Windows.Controls; 6 | using System.Windows.Documents; 7 | using System.Windows.Ink; 8 | using System.Windows.Input; 9 | using System.Windows.Media; 10 | using System.Windows.Media.Animation; 11 | using System.Windows.Shapes; 12 | 13 | namespace VirtualCollection.Demo 14 | { 15 | public static class Schedulers 16 | { 17 | public static TaskScheduler UIThread { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /VirtualCollection/Demo/StringToImageConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Net; 4 | using System.Windows; 5 | using System.Windows.Controls; 6 | using System.Windows.Data; 7 | using System.Windows.Documents; 8 | using System.Windows.Ink; 9 | using System.Windows.Input; 10 | using System.Windows.Media; 11 | using System.Windows.Media.Animation; 12 | using System.Windows.Media.Imaging; 13 | using System.Windows.Shapes; 14 | 15 | namespace VirtualCollection.Demo 16 | { 17 | public class StringToImageConverter : IValueConverter 18 | { 19 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 20 | { 21 | var url = value as string; 22 | if (string.IsNullOrEmpty(url)) 23 | { 24 | return null; 25 | } 26 | 27 | try 28 | { 29 | var uri = new Uri(url); 30 | var bitmap = new BitmapImage(uri) { CreateOptions = BitmapCreateOptions.DelayCreation}; 31 | return bitmap; 32 | } 33 | catch (UriFormatException) 34 | { 35 | return null; 36 | } 37 | } 38 | 39 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 40 | { 41 | throw new NotImplementedException(); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /VirtualCollection/Framework/Behaviors/NotifyViewModelOfLifeCycleEvents.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Interactivity; 3 | using VirtualCollection.Framework.MVVM; 4 | 5 | namespace VirtualCollection.Framework.Behaviors 6 | { 7 | public class NotifyViewModelOfLifeCycleEvents : Behavior 8 | { 9 | protected override void OnAttached() 10 | { 11 | base.OnAttached(); 12 | 13 | AssociatedObject.Loaded += HandleLoaded; 14 | AssociatedObject.Unloaded += HandleUnloaded; 15 | } 16 | 17 | protected override void OnDetaching() 18 | { 19 | base.OnDetaching(); 20 | 21 | AssociatedObject.Loaded -= HandleLoaded; 22 | AssociatedObject.Unloaded -= HandleUnloaded; 23 | } 24 | 25 | private void HandleLoaded(object sender, RoutedEventArgs e) 26 | { 27 | if (ViewModel != null) 28 | { 29 | ViewModel.NotifyLoaded(); 30 | } 31 | } 32 | 33 | private void HandleUnloaded(object sender, RoutedEventArgs e) 34 | { 35 | if (ViewModel != null) 36 | { 37 | ViewModel.NotifyUnloaded(); 38 | } 39 | } 40 | 41 | private IViewModel ViewModel 42 | { 43 | get { return AssociatedObject.DataContext as IViewModel; } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /VirtualCollection/Framework/Converters/StringValueConversion.cs: -------------------------------------------------------------------------------- 1 | namespace VirtualCollection.Framework.Converters 2 | { 3 | public class StringValueConversion 4 | { 5 | public string When { get; set; } 6 | 7 | public object Then { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /VirtualCollection/Framework/Converters/StringValueConversionCollection.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.ObjectModel; 2 | 3 | namespace VirtualCollection.Framework.Converters 4 | { 5 | public class StringValueConversionCollection : Collection 6 | { 7 | 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /VirtualCollection/Framework/Converters/StringValueToObjectConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows.Data; 4 | using System.Linq; 5 | 6 | namespace VirtualCollection.Framework.Converters 7 | { 8 | public class StringValueToObjectConverter : IValueConverter 9 | { 10 | private StringValueConversionCollection conversions; 11 | 12 | public StringValueConversionCollection Conversions 13 | { 14 | get { return conversions ?? (conversions = new StringValueConversionCollection()); } 15 | set { conversions = value; } 16 | } 17 | 18 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 19 | { 20 | if (value == null) 21 | { 22 | return null; 23 | } 24 | 25 | var selector = value.ToString(); 26 | 27 | var conversion = Conversions.FirstOrDefault(c => c.When == selector); 28 | 29 | return conversion != null ? conversion.Then : null; 30 | } 31 | 32 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 33 | { 34 | throw new NotImplementedException(); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /VirtualCollection/Framework/Extensions/INotifyPropertyChangedExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Linq.Expressions; 4 | using System.Net; 5 | using System.Reactive; 6 | using System.Reactive.Linq; 7 | using System.Windows; 8 | using System.Windows.Controls; 9 | using System.Windows.Documents; 10 | using System.Windows.Ink; 11 | using System.Windows.Input; 12 | using System.Windows.Media; 13 | using System.Windows.Media.Animation; 14 | using System.Windows.Shapes; 15 | 16 | namespace VirtualCollection.Framework.Extensions 17 | { 18 | public static class INotifyPropertyChangedExtensions 19 | { 20 | public static IObservable ObservePropertyChanged(this INotifyPropertyChanged source, Expression> propertyExpression) 21 | { 22 | return ObservePropertyChanged(source, propertyExpression.GetPropertyName()); 23 | } 24 | 25 | public static IObservable ObservePropertyChanged(this INotifyPropertyChanged source, string property) 26 | { 27 | return Observable.FromEvent( 28 | h => (sender, e) => h(e), 29 | h => source.PropertyChanged += h, 30 | h => source.PropertyChanged -= h) 31 | .Where(e => e.PropertyName == property) 32 | .Select(_ => Unit.Default); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /VirtualCollection/Framework/Extensions/SymbolExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using System.Reflection; 4 | 5 | namespace VirtualCollection.Framework.Extensions 6 | { 7 | public static class SymbolExtensions 8 | { 9 | /// 10 | /// Gets the PropertyInfo for the last property access in an expression 11 | /// 12 | /// 13 | /// 14 | /// 15 | public static PropertyInfo GetPropertyInfo(this Expression> expression) 16 | { 17 | var memberExpression = expression.Body as MemberExpression; 18 | if (memberExpression == null) 19 | { 20 | throw new InvalidOperationException("expression must consist of a property access"); 21 | } 22 | 23 | var propertyInfo = memberExpression.Member as PropertyInfo; 24 | if (propertyInfo == null) 25 | { 26 | throw new InvalidOperationException("expression must consist of a property access"); 27 | } 28 | 29 | return propertyInfo; 30 | } 31 | 32 | public static string GetPropertyName(this Expression> expression) 33 | { 34 | return GetPropertyInfo(expression).Name; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /VirtualCollection/Framework/MVVM/ActionCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace VirtualCollection.Framework.MVVM 4 | { 5 | public class ActionCommand : CommandBase 6 | { 7 | private readonly Action _executeAction; 8 | 9 | public ActionCommand(Action executeAction) 10 | { 11 | _executeAction = executeAction; 12 | } 13 | 14 | public override void Execute(object parameter) 15 | { 16 | _executeAction(parameter); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /VirtualCollection/Framework/MVVM/CommandBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Input; 3 | 4 | namespace VirtualCollection.Framework.MVVM 5 | { 6 | public class CommandBase : ICommand 7 | { 8 | public event EventHandler CanExecuteChanged; 9 | 10 | public CommandBase() 11 | { 12 | } 13 | 14 | public virtual void Execute(object parameter) 15 | { 16 | } 17 | 18 | public virtual bool CanExecute(object parameter) 19 | { 20 | return true; 21 | } 22 | 23 | protected void OnCanExecuteChanged(EventArgs e) 24 | { 25 | EventHandler handler = CanExecuteChanged; 26 | if (handler != null) handler(this, e); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /VirtualCollection/Framework/MVVM/CommandHolder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | using System.Windows.Input; 5 | using VirtualCollection.Framework.Extensions; 6 | 7 | namespace VirtualCollection.Framework.MVVM 8 | { 9 | public class CommandHolder 10 | { 11 | Dictionary _commands = new Dictionary(); 12 | 13 | public ICommand GetOrCreateCommand(Expression> commandPropertyExpression, Action executeAction) 14 | { 15 | var name = commandPropertyExpression.GetPropertyName(); 16 | ICommand command; 17 | if (!_commands.TryGetValue(name, out command)) 18 | { 19 | command = new ActionCommand(_ => executeAction()); 20 | _commands.Add(name, command); 21 | } 22 | 23 | return command; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /VirtualCollection/Framework/MVVM/IViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace VirtualCollection.Framework.MVVM 2 | { 3 | interface IViewModel 4 | { 5 | void NotifyLoaded(); 6 | void NotifyUnloaded(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /VirtualCollection/Framework/MVVM/ViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Linq.Expressions; 4 | using VirtualCollection.Framework.Extensions; 5 | 6 | namespace VirtualCollection.Framework.MVVM 7 | { 8 | public abstract class ViewModel : INotifyPropertyChanged, IViewModel 9 | { 10 | private CommandHolder _commandHolder; 11 | 12 | public event PropertyChangedEventHandler PropertyChanged; 13 | 14 | protected CommandHolder Commands 15 | { 16 | get { return _commandHolder ?? (_commandHolder = new CommandHolder()); } 17 | } 18 | 19 | protected void RaiseAllPropertiesChanged() 20 | { 21 | RaisePropertyChanged(new PropertyChangedEventArgs(string.Empty)); 22 | } 23 | 24 | protected void RaisePropertyChanged(Expression> propertyExpression) 25 | { 26 | RaisePropertyChanged(new PropertyChangedEventArgs(propertyExpression.GetPropertyName())); 27 | } 28 | 29 | protected void RaisePropertyChanged(PropertyChangedEventArgs e) 30 | { 31 | var handler = PropertyChanged; 32 | if (handler != null) handler(this, e); 33 | } 34 | 35 | public void NotifyLoaded() 36 | { 37 | OnViewLoaded(); 38 | } 39 | 40 | public void NotifyUnloaded() 41 | { 42 | OnViewUnloaded(); 43 | } 44 | 45 | protected virtual void OnViewLoaded() 46 | { 47 | 48 | } 49 | 50 | protected virtual void OnViewUnloaded() 51 | { 52 | 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /VirtualCollection/MainPage.xaml: -------------------------------------------------------------------------------- 1 |  14 | 15 | 16 | 17 | 18 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 38 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 100 | 101 | 102 | 103 | 104 | 110 | 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /VirtualCollection/MainPage.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Windows; 6 | using System.Windows.Controls; 7 | using System.Windows.Documents; 8 | using System.Windows.Input; 9 | using System.Windows.Media; 10 | using System.Windows.Media.Animation; 11 | using System.Windows.Shapes; 12 | 13 | namespace VirtualCollection 14 | { 15 | public partial class MainPage : UserControl 16 | { 17 | public MainPage() 18 | { 19 | InitializeComponent(); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /VirtualCollection/Properties/AppManifest.xml: -------------------------------------------------------------------------------- 1 |  4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /VirtualCollection/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("VirtualCollection")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("VirtualCollection")] 13 | [assembly: AssemblyCopyright("Copyright © 2012")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("26f536be-b8ec-45b3-97c6-a36b82b980b9")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Revision and Build Numbers 33 | // by using the '*' as shown below: 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /VirtualCollection/Service References/Northwind/Reference.datasvcmap: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /VirtualCollection/Service References/Northwind/service.edmx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | -------------------------------------------------------------------------------- /VirtualCollection/VirtualCollection.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 8.0.50727 7 | 2.0 8 | {19D96D76-77F4-456C-A04B-44F5FF9172E3} 9 | {A1591282-1198-4647-A2B1-27E5FF5F6F3B};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} 10 | Library 11 | Properties 12 | VirtualCollection 13 | VirtualCollection 14 | Silverlight 15 | v5.0 16 | $(TargetFrameworkVersion) 17 | true 18 | 19 | 20 | true 21 | true 22 | VirtualCollection.xap 23 | Properties\AppManifest.xml 24 | VirtualCollection.App 25 | VirtualCollectionTestPage.html 26 | true 27 | true 28 | false 29 | Properties\OutOfBrowserSettings.xml 30 | false 31 | true 32 | 33 | 34 | ..\ 35 | true 36 | 37 | 40 | 41 | v3.5 42 | 43 | 44 | true 45 | full 46 | false 47 | Bin\Debug 48 | DEBUG;TRACE;SILVERLIGHT 49 | true 50 | true 51 | prompt 52 | 4 53 | 54 | 55 | pdbonly 56 | true 57 | Bin\Release 58 | TRACE;SILVERLIGHT 59 | true 60 | true 61 | prompt 62 | 4 63 | 64 | 65 | 66 | ..\packages\Microsoft.Data.Edm.5.3.0\lib\sl4\Microsoft.Data.Edm.SL.dll 67 | 68 | 69 | ..\packages\Microsoft.Data.OData.5.3.0\lib\sl4\Microsoft.Data.OData.SL.dll 70 | 71 | 72 | ..\packages\Microsoft.Data.Services.Client.5.3.0\lib\sl4\Microsoft.Data.Services.Client.SL.dll 73 | 74 | 75 | ..\packages\Blend.Interactivity.Silverlight.2.0.20520.0\lib\sl\Microsoft.Expression.Interactions.dll 76 | 77 | 78 | ..\packages\Microsoft.Bcl.Async.1.0.16\lib\sl4\Microsoft.Threading.Tasks.dll 79 | 80 | 81 | ..\packages\Microsoft.Bcl.Async.1.0.16\lib\sl4\Microsoft.Threading.Tasks.Extensions.dll 82 | 83 | 84 | ..\packages\Microsoft.Bcl.Async.1.0.16\lib\sl4\Microsoft.Threading.Tasks.Extensions.Silverlight.dll 85 | 86 | 87 | 88 | ..\packages\Rx-Core.2.1.30214.0\lib\SL5\System.Reactive.Core.dll 89 | 90 | 91 | ..\packages\Rx-Interfaces.2.1.30214.0\lib\SL5\System.Reactive.Interfaces.dll 92 | 93 | 94 | ..\packages\Rx-Linq.2.1.30214.0\lib\SL5\System.Reactive.Linq.dll 95 | 96 | 97 | ..\packages\Rx-PlatformServices.2.1.30214.0\lib\SL5\System.Reactive.PlatformServices.dll 98 | 99 | 100 | ..\packages\Rx-Xaml.2.1.30214.0\lib\SL5\System.Reactive.Windows.Threading.dll 101 | 102 | 103 | ..\packages\Microsoft.Bcl.1.0.19\lib\sl5\System.Runtime.dll 104 | 105 | 106 | 107 | 108 | ..\packages\System.Spatial.5.3.0\lib\sl4\System.Spatial.SL.dll 109 | 110 | 111 | ..\packages\Microsoft.Bcl.1.0.19\lib\sl5\System.Threading.Tasks.dll 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | ..\packages\SilverlightToolkit-Core.4.2010.4\lib\sl4\System.Windows.Controls.Toolkit.dll 121 | 122 | 123 | ..\packages\Blend.Interactivity.Silverlight.2.0.20520.0\lib\sl\System.Windows.Interactivity.dll 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | App.xaml 132 | 133 | 134 | BusynessIndicator.xaml 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | MainPage.xaml 153 | 154 | 155 | 156 | True 157 | True 158 | Reference.datasvcmap 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | Designer 181 | MSBuild:Compile 182 | 183 | 184 | MSBuild:Compile 185 | Designer 186 | 187 | 188 | Designer 189 | MSBuild:Compile 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | datasvcmap 203 | 204 | 205 | 206 | 207 | DataServicesCoreClientGenerator 208 | Reference.cs 209 | 210 | 211 | 212 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | -------------------------------------------------------------------------------- /VirtualCollection/VirtualCollection/DeferredActionInvoker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Windows; 4 | using System.Windows.Controls; 5 | using System.Windows.Documents; 6 | using System.Windows.Ink; 7 | using System.Windows.Input; 8 | using System.Windows.Media; 9 | using System.Windows.Media.Animation; 10 | using System.Windows.Shapes; 11 | using System.Windows.Threading; 12 | 13 | namespace VirtualCollection.VirtualCollection 14 | { 15 | public class DeferredActionInvoker 16 | { 17 | private readonly Action _action; 18 | private readonly TimeSpan _interval; 19 | private DispatcherTimer _timer; 20 | 21 | public DeferredActionInvoker(Action action, TimeSpan interval) 22 | { 23 | _action = action; 24 | _interval = interval; 25 | } 26 | 27 | public void Request() 28 | { 29 | if (_timer == null) 30 | { 31 | _timer = new DispatcherTimer() { Interval = _interval }; 32 | _timer.Tick += delegate 33 | { 34 | _timer.Stop(); 35 | _action(); 36 | }; 37 | } 38 | 39 | _timer.Stop(); 40 | _timer.Start(); 41 | } 42 | 43 | public void Cancel() 44 | { 45 | if (_timer != null) 46 | { 47 | _timer.Stop(); 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /VirtualCollection/VirtualCollection/Disposer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Windows; 4 | using System.Windows.Controls; 5 | using System.Windows.Documents; 6 | using System.Windows.Ink; 7 | using System.Windows.Input; 8 | using System.Windows.Media; 9 | using System.Windows.Media.Animation; 10 | using System.Windows.Shapes; 11 | 12 | namespace VirtualCollection.VirtualCollection 13 | { 14 | public class Disposer : IDisposable 15 | { 16 | private readonly Action _action; 17 | 18 | public Disposer(Action action) 19 | { 20 | _action = action; 21 | } 22 | 23 | public void Dispose() 24 | { 25 | _action(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /VirtualCollection/VirtualCollection/IEnquireAboutItemVisibility.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace VirtualCollection.VirtualCollection 4 | { 5 | public interface IEnquireAboutItemVisibility 6 | { 7 | event EventHandler QueryItemVisibility; 8 | } 9 | 10 | public class QueryItemVisibilityEventArgs : EventArgs 11 | { 12 | public QueryItemVisibilityEventArgs() 13 | { 14 | } 15 | 16 | public int? FirstVisibleIndex { get; private set; } 17 | 18 | public int? LastVisibleIndex { get; private set; } 19 | 20 | public void SetVisibleRange(int firstVisibleIndex, int lastVisibleIndex) 21 | { 22 | FirstVisibleIndex = FirstVisibleIndex.HasValue ? Math.Min(firstVisibleIndex, FirstVisibleIndex.Value) : firstVisibleIndex; 23 | LastVisibleIndex = LastVisibleIndex.HasValue ? Math.Max(lastVisibleIndex, LastVisibleIndex.Value) : lastVisibleIndex; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /VirtualCollection/VirtualCollection/INotifyBusyness.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace VirtualCollection.VirtualCollection 4 | { 5 | public interface INotifyBusyness 6 | { 7 | event EventHandler IsBusyChanged; 8 | bool IsBusy { get; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /VirtualCollection/VirtualCollection/IVirtualCollectionSource.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Threading.Tasks; 5 | 6 | namespace VirtualCollection.VirtualCollection 7 | { 8 | public interface IVirtualCollectionSource 9 | { 10 | event EventHandler CollectionChanged; 11 | event EventHandler CountChanged; 12 | 13 | int? Count { get; } 14 | void Refresh(RefreshMode mode); 15 | Task> GetPageAsync(int start, int pageSize, IList sortDescriptions); 16 | } 17 | 18 | public class VirtualCollectionSourceChangedEventArgs : EventArgs 19 | { 20 | public ChangeType ChangeType { get; private set; } 21 | 22 | public VirtualCollectionSourceChangedEventArgs(ChangeType changeType) 23 | { 24 | ChangeType = changeType; 25 | } 26 | } 27 | 28 | public enum ChangeType 29 | { 30 | /// 31 | /// Current data is invalid and should be cleared 32 | /// 33 | Reset, 34 | /// 35 | /// Current data may still be valid, and can be shown whilst refreshing 36 | /// 37 | Refresh, 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /VirtualCollection/VirtualCollection/ItemsRealizedEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace VirtualCollection.VirtualCollection 4 | { 5 | public class ItemsRealizedEventArgs : EventArgs 6 | { 7 | public ItemsRealizedEventArgs(int startIndex, int count) 8 | { 9 | StartingIndex = startIndex; 10 | Count = count; 11 | } 12 | 13 | public int StartingIndex { get; private set; } 14 | public int Count { get; private set; } 15 | } 16 | } -------------------------------------------------------------------------------- /VirtualCollection/VirtualCollection/MostRecentUsedList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace VirtualCollection.VirtualCollection 6 | { 7 | public class MostRecentUsedList : IEnumerable 8 | { 9 | public event EventHandler> ItemEvicted; 10 | 11 | private int _size; 12 | private LinkedList _list = new LinkedList(); 13 | 14 | public MostRecentUsedList(int size) 15 | { 16 | _size = size; 17 | } 18 | 19 | public int Size 20 | { 21 | get { return _size; } 22 | set 23 | { 24 | _size = value; 25 | Trim(); 26 | } 27 | } 28 | 29 | public void Add(T item) 30 | { 31 | if (_list.Contains(item)) 32 | { 33 | _list.Remove(item); 34 | } 35 | 36 | _list.AddFirst(item); 37 | 38 | Trim(); 39 | } 40 | 41 | private void Trim() 42 | { 43 | while (_list.Count > Size) 44 | { 45 | var item = _list.Last; 46 | _list.RemoveLast(); 47 | OnItemEvicted(new ItemEvictedEventArgs(item.Value)); 48 | } 49 | } 50 | 51 | public IEnumerator GetEnumerator() 52 | { 53 | return _list.GetEnumerator(); 54 | } 55 | 56 | IEnumerator IEnumerable.GetEnumerator() 57 | { 58 | return GetEnumerator(); 59 | } 60 | 61 | public void Clear() 62 | { 63 | _list.Clear(); 64 | } 65 | 66 | protected void OnItemEvicted(ItemEvictedEventArgs e) 67 | { 68 | EventHandler> handler = ItemEvicted; 69 | if (handler != null) handler(this, e); 70 | } 71 | 72 | public void AddRange(IEnumerable items) 73 | { 74 | foreach (var item in items) 75 | { 76 | Add(item); 77 | } 78 | } 79 | } 80 | 81 | public class ItemEvictedEventArgs : EventArgs 82 | { 83 | public T Item { get; private set; } 84 | 85 | public ItemEvictedEventArgs(T item) 86 | { 87 | Item = item; 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /VirtualCollection/VirtualCollection/ProvideVisibleItemRangeFromDataGridBehavior.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Windows; 4 | using System.Windows.Controls; 5 | using System.Windows.Data; 6 | using System.Windows.Interactivity; 7 | 8 | namespace VirtualCollection.VirtualCollection 9 | { 10 | public class ProvideVisibleItemRangeFromDataGridBehavior : Behavior 11 | { 12 | private static readonly DependencyProperty ItemsSourceProperty = 13 | DependencyProperty.Register("ItemsSource", typeof(object), typeof(ProvideVisibleItemRangeFromDataGridBehavior), new PropertyMetadata(default(object), HandleItemsSourceChanged)); 14 | 15 | private IEnquireAboutItemVisibility _cachedEnquirer; 16 | private bool _isLoaded; 17 | private HashSet _loadedRows = new HashSet(); 18 | 19 | private static void HandleItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 20 | { 21 | var behavior = d as ProvideVisibleItemRangeFromDataGridBehavior; 22 | 23 | behavior.HandleItemsSourceChanged(); 24 | } 25 | 26 | private void HandleItemsSourceChanged() 27 | { 28 | if (_isLoaded) 29 | { 30 | DetachFromCachedEnquirer(); 31 | AttachToEnquirer(); 32 | } 33 | } 34 | 35 | private void HandleQueryItemVisibility(object sender, QueryItemVisibilityEventArgs e) 36 | { 37 | if (_loadedRows.Count > 0) 38 | { 39 | e.SetVisibleRange(_loadedRows.Min(), _loadedRows.Max()); 40 | } 41 | } 42 | 43 | protected override void OnAttached() 44 | { 45 | base.OnAttached(); 46 | 47 | BindingOperations.SetBinding(this, ItemsSourceProperty, 48 | new Binding("ItemsSource") {Source = AssociatedObject}); 49 | 50 | AssociatedObject.Loaded += HandleLoaded; 51 | AssociatedObject.Unloaded += HandleUnloaded; 52 | AssociatedObject.LoadingRow += HandleLoadingRow; 53 | AssociatedObject.UnloadingRow += HandleUnloadingRow; 54 | } 55 | 56 | private void HandleUnloadingRow(object sender, DataGridRowEventArgs e) 57 | { 58 | _loadedRows.Remove(e.Row.GetIndex()); 59 | } 60 | 61 | private void HandleLoadingRow(object sender, DataGridRowEventArgs e) 62 | { 63 | _loadedRows.Add(e.Row.GetIndex()); 64 | } 65 | 66 | private void HandleLoaded(object sender, RoutedEventArgs e) 67 | { 68 | AttachToEnquirer(); 69 | _isLoaded = true; 70 | } 71 | 72 | private void AttachToEnquirer() 73 | { 74 | _cachedEnquirer = GetValue(ItemsSourceProperty) as IEnquireAboutItemVisibility; 75 | if (_cachedEnquirer != null) 76 | { 77 | _cachedEnquirer.QueryItemVisibility += HandleQueryItemVisibility; 78 | } 79 | } 80 | 81 | private void HandleUnloaded(object sender, RoutedEventArgs e) 82 | { 83 | DetachFromCachedEnquirer(); 84 | _isLoaded = false; 85 | } 86 | 87 | protected override void OnDetaching() 88 | { 89 | base.OnDetaching(); 90 | 91 | ClearValue(ItemsSourceProperty); 92 | 93 | AssociatedObject.Loaded -= HandleLoaded; 94 | AssociatedObject.Unloaded -= HandleUnloaded; 95 | AssociatedObject.LoadingRow -= HandleLoadingRow; 96 | AssociatedObject.UnloadingRow -= HandleUnloadingRow; 97 | 98 | DetachFromCachedEnquirer(); 99 | } 100 | 101 | private void DetachFromCachedEnquirer() 102 | { 103 | if (_cachedEnquirer != null) 104 | { 105 | _cachedEnquirer.QueryItemVisibility -= HandleQueryItemVisibility; 106 | } 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /VirtualCollection/VirtualCollection/ProvideVisibleItemRangeFromItemsControlBehavior.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Controls; 3 | using System.Windows.Data; 4 | using System.Windows.Interactivity; 5 | 6 | namespace VirtualCollection.VirtualCollection 7 | { 8 | public class ProvideVisibleItemRangeFromItemsControlBehavior : Behavior 9 | { 10 | private static readonly DependencyProperty ItemsSourceProperty = 11 | DependencyProperty.Register("ItemsSource", typeof (object), typeof (ProvideVisibleItemRangeFromItemsControlBehavior), new PropertyMetadata(default(object), HandleItemsSourceChanged)); 12 | 13 | private IEnquireAboutItemVisibility _cachedEnquirer; 14 | private bool _isLoaded; 15 | 16 | private static void HandleItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 17 | { 18 | var behavior = d as ProvideVisibleItemRangeFromItemsControlBehavior; 19 | 20 | behavior.HandleItemsSourceChanged(); 21 | } 22 | 23 | private void HandleItemsSourceChanged() 24 | { 25 | if (_isLoaded) 26 | { 27 | DetachFromCachedEnquirer(); 28 | AttachToEnquirer(); 29 | } 30 | } 31 | 32 | private void HandleQueryItemVisibility(object sender, QueryItemVisibilityEventArgs e) 33 | { 34 | var wrapPanel = AssociatedObject.GetItemsHost() as VirtualizingWrapPanel; 35 | if (wrapPanel != null) 36 | { 37 | var range = wrapPanel.GetVisibleItemsRange(); 38 | e.SetVisibleRange(range.FirstRealizedItemIndex, range.LastRealizedItemIndex); 39 | } 40 | } 41 | 42 | protected override void OnAttached() 43 | { 44 | base.OnAttached(); 45 | 46 | BindingOperations.SetBinding(this, ItemsSourceProperty, 47 | new Binding("ItemsSource") {Source = AssociatedObject}); 48 | 49 | AssociatedObject.Loaded += HandleLoaded; 50 | AssociatedObject.Unloaded += HandleUnloaded; 51 | } 52 | 53 | private void HandleLoaded(object sender, RoutedEventArgs e) 54 | { 55 | AttachToEnquirer(); 56 | _isLoaded = true; 57 | } 58 | 59 | private void AttachToEnquirer() 60 | { 61 | _cachedEnquirer = GetValue(ItemsSourceProperty) as IEnquireAboutItemVisibility; 62 | if (_cachedEnquirer != null) 63 | { 64 | _cachedEnquirer.QueryItemVisibility += HandleQueryItemVisibility; 65 | } 66 | } 67 | 68 | private void HandleUnloaded(object sender, RoutedEventArgs e) 69 | { 70 | DetachFromCachedEnquirer(); 71 | _isLoaded = false; 72 | } 73 | 74 | protected override void OnDetaching() 75 | { 76 | base.OnDetaching(); 77 | 78 | ClearValue(ItemsSourceProperty); 79 | 80 | AssociatedObject.Loaded -= HandleLoaded; 81 | AssociatedObject.Unloaded -= HandleUnloaded; 82 | 83 | DetachFromCachedEnquirer(); 84 | } 85 | 86 | private void DetachFromCachedEnquirer() 87 | { 88 | if (_cachedEnquirer != null) 89 | { 90 | _cachedEnquirer.QueryItemVisibility -= HandleQueryItemVisibility; 91 | } 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /VirtualCollection/VirtualCollection/RefreshMode.cs: -------------------------------------------------------------------------------- 1 | namespace VirtualCollection.VirtualCollection 2 | { 3 | public enum RefreshMode 4 | { 5 | PermitStaleDataWhilstRefreshing, 6 | ClearStaleData, 7 | } 8 | } -------------------------------------------------------------------------------- /VirtualCollection/VirtualCollection/SparseList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.ObjectModel; 3 | 4 | namespace VirtualCollection.VirtualCollection 5 | { 6 | /// 7 | /// A list which can support a huge virtual item count 8 | /// by assuming that many items are never accessed. Space for items is allocated 9 | /// in pages. 10 | /// 11 | /// 12 | public class SparseList where T:class 13 | { 14 | private readonly int _pageSize; 15 | private readonly PageList _allocatedPages; 16 | private Page _currentPage; 17 | 18 | public SparseList(int pageSize) 19 | { 20 | _pageSize = pageSize; 21 | _allocatedPages = new PageList(_pageSize); 22 | } 23 | 24 | /// This method is optimised for sequential access. I.e. it performs 25 | /// best when getting and setting indicies in the same locality 26 | public T this[int index] 27 | { 28 | get 29 | { 30 | var pageAndSubIndex = EnsureCurrentPage(index); 31 | 32 | return _currentPage[pageAndSubIndex.SubIndex]; 33 | } 34 | set 35 | { 36 | var pageAndSubIndex = EnsureCurrentPage(index); 37 | 38 | _currentPage[pageAndSubIndex.SubIndex] = value; 39 | } 40 | } 41 | 42 | private PageAndSubIndex EnsureCurrentPage(int index) 43 | { 44 | var pageAndSubIndex = new PageAndSubIndex(index / _pageSize, index % _pageSize); 45 | 46 | if (_currentPage == null || _currentPage.PageIndex != pageAndSubIndex.PageIndex) 47 | { 48 | _currentPage = _allocatedPages.GetOrCreatePage(pageAndSubIndex.PageIndex); 49 | } 50 | 51 | return pageAndSubIndex; 52 | } 53 | 54 | public void RemoveRange(int firstIndex, int count) 55 | { 56 | var firstItem = new PageAndSubIndex(firstIndex / _pageSize, firstIndex % _pageSize); 57 | if (firstItem.SubIndex + count > _pageSize) 58 | { 59 | throw new NotImplementedException("RemoveRange is only implemented to work within page boundaries"); 60 | } 61 | 62 | if (_allocatedPages.Contains(firstItem.PageIndex)) 63 | { 64 | if (_allocatedPages[firstItem.PageIndex].Trim(firstItem.SubIndex, count)) 65 | { 66 | _allocatedPages.Remove(firstItem.PageIndex); 67 | } 68 | } 69 | } 70 | 71 | private struct PageAndSubIndex 72 | { 73 | private readonly int _pageIndex; 74 | private readonly int _subIndex; 75 | 76 | public PageAndSubIndex(int pageIndex, int subIndex) 77 | { 78 | _pageIndex = pageIndex; 79 | _subIndex = subIndex; 80 | } 81 | 82 | public int PageIndex 83 | { 84 | get { return _pageIndex; } 85 | } 86 | 87 | public int SubIndex 88 | { 89 | get { return _subIndex; } 90 | } 91 | } 92 | 93 | private class Page 94 | { 95 | private readonly int _pageIndex; 96 | private readonly T[] _items; 97 | 98 | public Page(int pageIndex, int pageSize) 99 | { 100 | _pageIndex = pageIndex; 101 | _items = new T[pageSize]; 102 | } 103 | 104 | public int PageIndex 105 | { 106 | get { return _pageIndex; } 107 | } 108 | 109 | public T this[int index] 110 | { 111 | get 112 | { 113 | return _items[index]; 114 | } 115 | set 116 | { 117 | _items[index] = value; 118 | } 119 | } 120 | 121 | public bool Trim(int firstIndex, int count) 122 | { 123 | for (int i = firstIndex; i < firstIndex + count; i++) 124 | { 125 | _items[i] = default(T); 126 | } 127 | 128 | for (int i = 0; i < _items.Length; i++) 129 | { 130 | if (_items[i] != null) 131 | { 132 | return false; 133 | } 134 | } 135 | 136 | return true; 137 | } 138 | } 139 | 140 | private class PageList : KeyedCollection 141 | { 142 | private readonly int _pageSize; 143 | 144 | public PageList(int pageSize) 145 | { 146 | _pageSize = pageSize; 147 | } 148 | 149 | protected override int GetKeyForItem(Page item) 150 | { 151 | return item.PageIndex; 152 | } 153 | 154 | public Page GetOrCreatePage(int pageIndex) 155 | { 156 | if (!Contains(pageIndex)) 157 | { 158 | Add(new Page(pageIndex, _pageSize)); 159 | } 160 | 161 | return this[pageIndex]; 162 | } 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /VirtualCollection/VirtualCollection/VirtualCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Collections.ObjectModel; 5 | using System.Collections.Specialized; 6 | using System.ComponentModel; 7 | using System.Globalization; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | 11 | namespace VirtualCollection.VirtualCollection 12 | { 13 | /// 14 | /// Implements a collection that loads its items by pages only when requested 15 | /// 16 | /// 17 | /// The trick to ensuring that the silverlight datagrid doesn't attempt to enumerate all 18 | /// items from its DataSource in one shot is to implement both IList and ICollectionView. 19 | public class VirtualCollection : IList>, IList, ICollectionView, INotifyPropertyChanged, 20 | IEnquireAboutItemVisibility where T : class 21 | { 22 | private const int IndividualItemNotificationLimit = 100; 23 | private const int MaxConcurrentPageRequests = 4; 24 | 25 | public event NotifyCollectionChangedEventHandler CollectionChanged; 26 | 27 | public event PropertyChangedEventHandler PropertyChanged; 28 | public event EventHandler QueryItemVisibility; 29 | public event EventHandler ItemsRealized; 30 | public event CurrentChangingEventHandler CurrentChanging; 31 | public event EventHandler CurrentChanged; 32 | private readonly IVirtualCollectionSource _source; 33 | private readonly int _pageSize; 34 | private readonly IEqualityComparer _equalityComparer; 35 | 36 | private uint _state; // used to ensure that data-requests are not stale 37 | 38 | private readonly SparseList> _virtualItems; 39 | private readonly HashSet _fetchedPages = new HashSet(); 40 | private readonly HashSet _requestedPages = new HashSet(); 41 | 42 | private readonly MostRecentUsedList _mostRecentlyRequestedPages; 43 | private int _itemCount; 44 | private readonly TaskScheduler _synchronizationContextScheduler; 45 | private bool _isRefreshDeferred; 46 | private int _currentItem; 47 | 48 | private int _inProcessPageRequests; 49 | private Stack _pendingPageRequests = new Stack(); 50 | 51 | private readonly SortDescriptionCollection _sortDescriptions = new SortDescriptionCollection(); 52 | 53 | public VirtualCollection(IVirtualCollectionSource source, int pageSize, int cachedPages) 54 | : this(source, pageSize, cachedPages, EqualityComparer.Default) 55 | { 56 | 57 | } 58 | 59 | public VirtualCollection(IVirtualCollectionSource source, int pageSize, int cachedPages, 60 | IEqualityComparer equalityComparer) 61 | { 62 | if (pageSize < 1) 63 | throw new ArgumentException("pageSize must be bigger than 0"); 64 | 65 | if (equalityComparer == null) 66 | throw new ArgumentNullException("equalityComparer"); 67 | 68 | _source = source; 69 | _source.CollectionChanged += HandleSourceCollectionChanged; 70 | _source.CountChanged += HandleCountChanged; 71 | _pageSize = pageSize; 72 | _equalityComparer = equalityComparer; 73 | _virtualItems = CreateItemsCache(pageSize); 74 | _currentItem = -1; 75 | _synchronizationContextScheduler = TaskScheduler.FromCurrentSynchronizationContext(); 76 | _mostRecentlyRequestedPages = new MostRecentUsedList(cachedPages); 77 | _mostRecentlyRequestedPages.ItemEvicted += HandlePageEvicted; 78 | 79 | (_sortDescriptions as INotifyCollectionChanged).CollectionChanged += HandleSortDescriptionsChanged; 80 | } 81 | 82 | 83 | 84 | public IVirtualCollectionSource Source 85 | { 86 | get { return _source; } 87 | } 88 | public CultureInfo Culture { get; set; } 89 | 90 | public IEnumerable SourceCollection 91 | { 92 | get { return this; } 93 | } 94 | 95 | public Predicate Filter 96 | { 97 | get { throw new NotImplementedException(); } 98 | set { throw new NotImplementedException(); } 99 | } 100 | 101 | public bool CanFilter 102 | { 103 | get { return false; } 104 | } 105 | 106 | public SortDescriptionCollection SortDescriptions 107 | { 108 | get { return _sortDescriptions; } 109 | } 110 | 111 | public bool CanSort 112 | { 113 | get { return true; } 114 | } 115 | 116 | public bool CanGroup 117 | { 118 | get { return false; } 119 | } 120 | 121 | public ObservableCollection GroupDescriptions 122 | { 123 | get { throw new NotImplementedException(); } 124 | } 125 | 126 | public ReadOnlyObservableCollection Groups 127 | { 128 | get { throw new NotImplementedException(); } 129 | } 130 | 131 | public bool IsEmpty 132 | { 133 | get { return _itemCount == 0; } 134 | } 135 | 136 | public object CurrentItem 137 | { 138 | get { return 0 < CurrentPosition && CurrentPosition < _itemCount ? this[CurrentPosition] : null; } 139 | } 140 | 141 | public int CurrentPosition 142 | { 143 | get { return _currentItem; } 144 | private set 145 | { 146 | _currentItem = value; 147 | OnCurrentChanged(EventArgs.Empty); 148 | } 149 | } 150 | public bool IsCurrentAfterLast 151 | { 152 | get { return CurrentPosition >= _itemCount; } 153 | } 154 | 155 | public bool IsCurrentBeforeFirst 156 | { 157 | get { return CurrentPosition < 0; } 158 | } 159 | public int Count 160 | { 161 | get { return _itemCount; } 162 | } 163 | 164 | object ICollection.SyncRoot 165 | { 166 | get { throw new NotImplementedException(); } 167 | } 168 | 169 | public bool IsSynchronized 170 | { 171 | get { return false; } 172 | } 173 | 174 | public bool IsReadOnly 175 | { 176 | get { return true; } 177 | } 178 | 179 | bool IList.IsFixedSize 180 | { 181 | get { return false; } 182 | } 183 | 184 | protected uint State 185 | { 186 | get { return _state; } 187 | } 188 | 189 | public void RealizeItemRequested(int index) 190 | { 191 | var page = index / _pageSize; 192 | BeginGetPage(page); 193 | } 194 | 195 | public void Refresh() 196 | { 197 | Refresh(RefreshMode.PermitStaleDataWhilstRefreshing); 198 | } 199 | 200 | public void Refresh(RefreshMode mode) 201 | { 202 | if (!_isRefreshDeferred) 203 | _source.Refresh(mode); 204 | } 205 | 206 | private void HandlePageEvicted(object sender, ItemEvictedEventArgs e) 207 | { 208 | _requestedPages.Remove(e.Item); 209 | _fetchedPages.Remove(e.Item); 210 | _virtualItems.RemoveRange(e.Item * _pageSize, _pageSize); 211 | } 212 | 213 | private SparseList> CreateItemsCache(int fetchPageSize) 214 | { 215 | // we don't want the sparse list to have pages that are too small, 216 | // because that will harm performance by fragmenting the list across memory, 217 | // but too big, and we'll be wasting lots of space 218 | const int TargetSparseListPageSize = 100; 219 | 220 | var pageSize = fetchPageSize; 221 | 222 | if (pageSize < TargetSparseListPageSize) 223 | { 224 | // make pageSize the smallest multiple of fetchPageSize that is bigger than TargetSparseListPageSize 225 | pageSize = (int)Math.Ceiling((double)TargetSparseListPageSize / pageSize) * pageSize; 226 | } 227 | 228 | return new SparseList>(pageSize); 229 | } 230 | 231 | private void HandleSortDescriptionsChanged(object sender, NotifyCollectionChangedEventArgs e) 232 | { 233 | Refresh(); 234 | } 235 | 236 | private void HandleCountChanged(object sender, EventArgs e) 237 | { 238 | Task.Factory.StartNew(UpdateCount, CancellationToken.None, TaskCreationOptions.None, 239 | _synchronizationContextScheduler); 240 | } 241 | 242 | private void HandleSourceCollectionChanged(object sender, VirtualCollectionSourceChangedEventArgs e) 243 | { 244 | if (e.ChangeType == ChangeType.Refresh) 245 | { 246 | Task.Factory.StartNew(UpdateData, CancellationToken.None, 247 | TaskCreationOptions.None, _synchronizationContextScheduler); 248 | } 249 | else if (e.ChangeType == ChangeType.Reset) 250 | { 251 | Task.Factory.StartNew(Reset, CancellationToken.None, 252 | TaskCreationOptions.None, _synchronizationContextScheduler); 253 | } 254 | } 255 | 256 | private void MarkExistingItemsAsStale() 257 | { 258 | foreach (var page in _fetchedPages) 259 | { 260 | var startIndex = page * _pageSize; 261 | var endIndex = (page + 1) * _pageSize; 262 | 263 | for (int i = startIndex; i < endIndex; i++) 264 | { 265 | if (_virtualItems[i] != null) 266 | _virtualItems[i].IsStale = true; 267 | } 268 | } 269 | } 270 | 271 | private void BeginGetPage(int page) 272 | { 273 | if (IsPageAlreadyRequested(page)) 274 | return; 275 | 276 | _mostRecentlyRequestedPages.Add(page); 277 | _requestedPages.Add(page); 278 | 279 | _pendingPageRequests.Push(new PageRequest(page, State)); 280 | 281 | ProcessPageRequests(); 282 | } 283 | 284 | private void ProcessPageRequests() 285 | { 286 | while (_inProcessPageRequests < MaxConcurrentPageRequests && _pendingPageRequests.Count > 0) 287 | { 288 | var request = _pendingPageRequests.Pop(); 289 | 290 | // if we encounter a requested posted for an early collection state, 291 | // we can ignore it, and all that came before it 292 | if (State != request.StateWhenRequested) 293 | { 294 | _pendingPageRequests.Clear(); 295 | break; 296 | } 297 | 298 | // check that the page is still requested (the user might have scrolled, causing the 299 | // page to be ejected from the cache 300 | if (!_requestedPages.Contains(request.Page)) 301 | break; 302 | 303 | _inProcessPageRequests++; 304 | 305 | _source.GetPageAsync(request.Page * _pageSize, _pageSize, _sortDescriptions).ContinueWith( 306 | t => 307 | { 308 | if (!t.IsFaulted) 309 | UpdatePage(request.Page, t.Result, request.StateWhenRequested); 310 | else 311 | MarkPageAsError(request.Page, request.StateWhenRequested); 312 | 313 | // fire off any further requests 314 | _inProcessPageRequests--; 315 | ProcessPageRequests(); 316 | }, 317 | _synchronizationContextScheduler); 318 | } 319 | } 320 | 321 | private void MarkPageAsError(int page, uint stateWhenRequestInitiated) 322 | { 323 | if (stateWhenRequestInitiated != State) 324 | return; 325 | 326 | var stillRelevant = _requestedPages.Remove(page); 327 | if (!stillRelevant) 328 | return; 329 | 330 | var startIndex = page * _pageSize; 331 | 332 | for (int i = 0; i < _pageSize; i++) 333 | { 334 | var index = startIndex + i; 335 | var virtualItem = _virtualItems[index]; 336 | if (virtualItem != null) 337 | virtualItem.ErrorFetchingValue(); 338 | } 339 | } 340 | 341 | private bool IsPageAlreadyRequested(int page) 342 | { 343 | return _fetchedPages.Contains(page) || _requestedPages.Contains(page); 344 | } 345 | 346 | private void UpdatePage(int page, IList results, uint stateWhenRequested) 347 | { 348 | if (stateWhenRequested != State) 349 | { 350 | // this request may contain out-of-date data, so ignore it 351 | return; 352 | } 353 | 354 | bool stillRelevant = _requestedPages.Remove(page); 355 | if (!stillRelevant) 356 | return; 357 | 358 | _fetchedPages.Add(page); 359 | 360 | var startIndex = page * _pageSize; 361 | 362 | // guard against rogue collection sources returning too many results 363 | var count = Math.Min(results.Count, _pageSize); 364 | 365 | for (int i = 0; i < count; i++) 366 | { 367 | var index = startIndex + i; 368 | var virtualItem = _virtualItems[index] ?? (_virtualItems[index] = new VirtualItem(this, index)); 369 | if (virtualItem.Item == null || results[i] == null || !_equalityComparer.Equals(virtualItem.Item, results[i])) 370 | virtualItem.SupplyValue(results[i]); 371 | } 372 | 373 | if (count > 0) 374 | OnItemsRealized(new ItemsRealizedEventArgs(startIndex, count)); 375 | } 376 | 377 | protected void UpdateData() 378 | { 379 | IncrementState(); 380 | 381 | MarkExistingItemsAsStale(); 382 | 383 | _fetchedPages.Clear(); 384 | _requestedPages.Clear(); 385 | 386 | UpdateCount(); 387 | 388 | var queryItemVisibilityArgs = new QueryItemVisibilityEventArgs(); 389 | OnQueryItemVisibility(queryItemVisibilityArgs); 390 | 391 | if (queryItemVisibilityArgs.FirstVisibleIndex.HasValue) 392 | { 393 | var firstVisiblePage = queryItemVisibilityArgs.FirstVisibleIndex.Value / _pageSize; 394 | var lastVisiblePage = queryItemVisibilityArgs.LastVisibleIndex.Value / _pageSize; 395 | 396 | int numberOfVisiblePages = lastVisiblePage - firstVisiblePage + 1; 397 | EnsurePageCacheSize(numberOfVisiblePages); 398 | 399 | for (int i = firstVisiblePage; i <= lastVisiblePage; i++) 400 | { 401 | BeginGetPage(i); 402 | } 403 | } 404 | else 405 | { 406 | // in this case we have no way of knowing which items are currently visible, 407 | // so we signal a collection reset, and wait to see which pages are requested by the UI 408 | OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); 409 | } 410 | } 411 | 412 | private void IncrementState() 413 | { 414 | _state++; 415 | } 416 | 417 | private void EnsurePageCacheSize(int numberOfPages) 418 | { 419 | if (_mostRecentlyRequestedPages.Size < numberOfPages) 420 | _mostRecentlyRequestedPages.Size = numberOfPages; 421 | } 422 | 423 | private void Reset() 424 | { 425 | IncrementState(); 426 | 427 | foreach (var page in _fetchedPages) 428 | { 429 | var startIndex = page * _pageSize; 430 | var endIndex = (page + 1) * _pageSize; 431 | 432 | for (int i = startIndex; i < endIndex; i++) 433 | { 434 | if (_virtualItems[i] != null) 435 | _virtualItems[i].ClearValue(); 436 | } 437 | } 438 | 439 | _fetchedPages.Clear(); 440 | _requestedPages.Clear(); 441 | 442 | UpdateCount(0); 443 | UpdateCount(); 444 | } 445 | 446 | private void UpdateCount() 447 | { 448 | if (_source.Count.HasValue) 449 | { 450 | UpdateCount(_source.Count.Value); 451 | } 452 | else 453 | { 454 | // if the Count is null, that indicates that 455 | // the VirtualCollectionSource needs us to fetch a page before it will know how many elements there are 456 | BeginGetPage(0); 457 | } 458 | } 459 | 460 | private void UpdateCount(int count) 461 | { 462 | if (_itemCount == count) 463 | return; 464 | 465 | var wasCurrentBeyondLast = IsCurrentAfterLast; 466 | 467 | var originalItemCount = _itemCount; 468 | var delta = count - originalItemCount; 469 | _itemCount = count; 470 | 471 | if (IsCurrentAfterLast && !wasCurrentBeyondLast) 472 | UpdateCurrentPosition(_itemCount - 1, allowCancel: false); 473 | 474 | OnPropertyChanged(new PropertyChangedEventArgs("Count")); 475 | 476 | if (Math.Abs(delta) > IndividualItemNotificationLimit || _itemCount == 0) 477 | OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); 478 | else if (delta > 0) 479 | { 480 | for (int i = 0; i < delta; i++) 481 | { 482 | OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, null, 483 | originalItemCount + i)); 484 | } 485 | } 486 | else if (delta < 0) 487 | { 488 | for (int i = 1; i <= Math.Abs(delta); i++) 489 | { 490 | OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, 491 | _virtualItems[originalItemCount - i], 492 | originalItemCount - i)); 493 | } 494 | } 495 | } 496 | 497 | public int IndexOf(VirtualItem item) 498 | { 499 | return item.Index; 500 | } 501 | 502 | public bool Contains(object item) 503 | { 504 | return item is VirtualItem && Contains(item as VirtualItem); 505 | } 506 | 507 | object IList.this[int index] 508 | { 509 | get { return this[index]; } 510 | set { throw new NotImplementedException(); } 511 | } 512 | 513 | public VirtualItem this[int index] 514 | { 515 | get 516 | { 517 | if (index >= Count) 518 | { 519 | throw new ArgumentOutOfRangeException("index"); 520 | } 521 | 522 | RealizeItemRequested(index); 523 | return _virtualItems[index] ?? (_virtualItems[index] = new VirtualItem(this, index)); 524 | } 525 | set { throw new NotImplementedException(); } 526 | } 527 | 528 | public IDisposable DeferRefresh() 529 | { 530 | _isRefreshDeferred = true; 531 | 532 | return new Disposer(() => 533 | { 534 | _isRefreshDeferred = false; 535 | Refresh(); 536 | }); 537 | } 538 | 539 | public bool MoveCurrentToFirst() 540 | { 541 | return UpdateCurrentPosition(0); 542 | } 543 | 544 | public bool MoveCurrentToLast() 545 | { 546 | return UpdateCurrentPosition(_itemCount - 1); 547 | } 548 | 549 | public bool MoveCurrentToNext() 550 | { 551 | return UpdateCurrentPosition(CurrentPosition + 1); 552 | } 553 | 554 | public bool MoveCurrentToPrevious() 555 | { 556 | return UpdateCurrentPosition(CurrentPosition - 1); 557 | } 558 | 559 | public bool MoveCurrentTo(object item) 560 | { 561 | return MoveCurrentToPosition(((IList)this).IndexOf(item)); 562 | } 563 | 564 | public bool MoveCurrentToPosition(int position) 565 | { 566 | return UpdateCurrentPosition(position); 567 | } 568 | 569 | private bool UpdateCurrentPosition(int newCurrentPosition, bool allowCancel = true) 570 | { 571 | var changingEventArgs = new CurrentChangingEventArgs(allowCancel); 572 | 573 | OnCurrentChanging(changingEventArgs); 574 | 575 | if (!changingEventArgs.Cancel) 576 | { 577 | CurrentPosition = newCurrentPosition; 578 | } 579 | 580 | return !IsCurrentBeforeFirst && !IsCurrentAfterLast; 581 | } 582 | 583 | protected void OnCurrentChanging(CurrentChangingEventArgs e) 584 | { 585 | var handler = CurrentChanging; 586 | if (handler != null) handler(this, e); 587 | } 588 | 589 | 590 | protected void OnCurrentChanged(EventArgs e) 591 | { 592 | var handler = CurrentChanged; 593 | if (handler != null) handler(this, e); 594 | } 595 | 596 | protected void OnCollectionChanged(NotifyCollectionChangedEventArgs e) 597 | { 598 | var handler = CollectionChanged; 599 | if (handler != null) handler(this, e); 600 | } 601 | 602 | protected void OnPropertyChanged(PropertyChangedEventArgs e) 603 | { 604 | var handler = PropertyChanged; 605 | if (handler != null) handler(this, e); 606 | } 607 | 608 | protected void OnItemsRealized(ItemsRealizedEventArgs e) 609 | { 610 | var handler = ItemsRealized; 611 | if (handler != null) handler(this, e); 612 | } 613 | 614 | protected void OnQueryItemVisibility(QueryItemVisibilityEventArgs e) 615 | { 616 | var handler = QueryItemVisibility; 617 | if (handler != null) handler(this, e); 618 | } 619 | 620 | public IEnumerator> GetEnumerator() 621 | { 622 | for (var i = 0; i < _itemCount; i++) 623 | { 624 | yield return this[i]; 625 | } 626 | } 627 | 628 | IEnumerator IEnumerable.GetEnumerator() 629 | { 630 | return GetEnumerator(); 631 | } 632 | 633 | public bool Contains(VirtualItem item) 634 | { 635 | return item.Parent == this; 636 | } 637 | 638 | public void CopyTo(VirtualItem[] array, int arrayIndex) 639 | { 640 | throw new NotImplementedException(); 641 | } 642 | 643 | void ICollection.CopyTo(Array array, int index) 644 | { 645 | throw new NotImplementedException(); 646 | } 647 | 648 | public void Add(VirtualItem item) 649 | { 650 | throw new NotImplementedException(); 651 | } 652 | 653 | int IList.Add(object value) 654 | { 655 | throw new NotImplementedException(); 656 | } 657 | 658 | bool IList.Contains(object value) 659 | { 660 | return value is VirtualItem && Contains(value as VirtualItem); 661 | } 662 | 663 | public void Clear() 664 | { 665 | throw new NotImplementedException(); 666 | } 667 | 668 | int IList.IndexOf(object value) 669 | { 670 | var virtualItem = value as VirtualItem; 671 | return virtualItem == null ? -1 : virtualItem.Index; 672 | } 673 | 674 | void IList.Insert(int index, object value) 675 | { 676 | throw new NotImplementedException(); 677 | } 678 | 679 | void IList.Remove(object value) 680 | { 681 | throw new NotImplementedException(); 682 | } 683 | 684 | public bool Remove(VirtualItem item) 685 | { 686 | throw new NotImplementedException(); 687 | } 688 | 689 | public void Insert(int index, VirtualItem item) 690 | { 691 | throw new NotImplementedException(); 692 | } 693 | 694 | public void RemoveAt(int index) 695 | { 696 | throw new NotImplementedException(); 697 | } 698 | 699 | private struct PageRequest 700 | { 701 | public readonly int Page; 702 | public readonly uint StateWhenRequested; 703 | 704 | public PageRequest(int page, uint state) 705 | { 706 | Page = page; 707 | StateWhenRequested = state; 708 | } 709 | } 710 | } 711 | } 712 | -------------------------------------------------------------------------------- /VirtualCollection/VirtualCollection/VirtualCollectionSource.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace VirtualCollection.VirtualCollection 8 | { 9 | public abstract class VirtualCollectionSource : IVirtualCollectionSource, INotifyBusyness 10 | { 11 | private readonly object lockObject = new object(); 12 | public event EventHandler CollectionChanged; 13 | public event EventHandler CountChanged; 14 | public event EventHandler IsBusyChanged; 15 | 16 | private int? _count; 17 | private bool _isBusy; 18 | private int _outstandingTasks; 19 | 20 | public virtual int? Count 21 | { 22 | get 23 | { 24 | lock (lockObject) 25 | { 26 | return _count; 27 | } 28 | } 29 | } 30 | 31 | public bool IsBusy 32 | { 33 | get 34 | { 35 | lock (lockObject) 36 | { 37 | return _isBusy; 38 | } 39 | } 40 | private set 41 | { 42 | bool hasChanged; 43 | 44 | lock (lockObject) 45 | { 46 | hasChanged = (_isBusy != value); 47 | _isBusy = value; 48 | } 49 | 50 | if (hasChanged) 51 | { 52 | OnIsBusyChanged(EventArgs.Empty); 53 | } 54 | } 55 | } 56 | 57 | public Task> GetPageAsync(int start, int pageSize, IList sortDescriptions) 58 | { 59 | IncrementOutstandingTasks(); 60 | 61 | return GetPageAsyncOverride(start, pageSize, sortDescriptions) 62 | .ContinueWith(t => 63 | { 64 | DecrementOutstandingTasks(); 65 | return t.Result; 66 | }, TaskContinuationOptions.ExecuteSynchronously); 67 | } 68 | 69 | protected abstract Task> GetPageAsyncOverride(int start, int pageSize, 70 | IList sortDescriptions); 71 | 72 | protected void IncrementOutstandingTasks() 73 | { 74 | IsBusy = Interlocked.Increment(ref _outstandingTasks) > 0; 75 | } 76 | 77 | protected void DecrementOutstandingTasks() 78 | { 79 | IsBusy = Interlocked.Decrement(ref _outstandingTasks) > 0; 80 | } 81 | 82 | public void Refresh(RefreshMode mode) 83 | { 84 | InvalidateCount(); 85 | if (mode == RefreshMode.ClearStaleData) 86 | { 87 | OnCollectionChanged(new VirtualCollectionSourceChangedEventArgs(ChangeType.Reset)); 88 | } 89 | else 90 | { 91 | OnCollectionChanged(new VirtualCollectionSourceChangedEventArgs(ChangeType.Refresh)); 92 | } 93 | } 94 | 95 | protected void OnCollectionChanged(VirtualCollectionSourceChangedEventArgs e) 96 | { 97 | var handler = CollectionChanged; 98 | if (handler != null) handler(this, e); 99 | } 100 | 101 | protected void OnIsBusyChanged(EventArgs e) 102 | { 103 | var handler = IsBusyChanged; 104 | if (handler != null) handler(this, e); 105 | } 106 | 107 | protected void SetCount(int newCount) 108 | { 109 | bool fileCountChanged; 110 | 111 | lock (lockObject) 112 | { 113 | fileCountChanged = newCount != _count; 114 | _count = newCount; 115 | } 116 | 117 | if (fileCountChanged) 118 | { 119 | OnCountChanged(EventArgs.Empty); 120 | } 121 | } 122 | 123 | protected void InvalidateCount() 124 | { 125 | lock (lockObject) 126 | { 127 | _count = null; 128 | } 129 | } 130 | 131 | protected void OnCountChanged(EventArgs e) 132 | { 133 | EventHandler handler = CountChanged; 134 | if (handler != null) handler(this, e); 135 | } 136 | 137 | } 138 | } -------------------------------------------------------------------------------- /VirtualCollection/VirtualCollection/VirtualItem.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace VirtualCollection.VirtualCollection 4 | { 5 | public class VirtualItem : INotifyPropertyChanged where T : class 6 | { 7 | private readonly VirtualCollection _parent; 8 | private readonly int _index; 9 | private T _item; 10 | private bool _isStale; 11 | private bool dataFetchError; 12 | 13 | public event PropertyChangedEventHandler PropertyChanged; 14 | 15 | public VirtualItem(VirtualCollection parent, int index) 16 | { 17 | _parent = parent; 18 | _index = index; 19 | } 20 | 21 | public T Item 22 | { 23 | get 24 | { 25 | if (!IsRealized && !DataFetchError) 26 | { 27 | _parent.RealizeItemRequested(Index); 28 | } 29 | return _item; 30 | } 31 | private set 32 | { 33 | _item = value; 34 | OnPropertyChanged(new PropertyChangedEventArgs("Item")); 35 | OnPropertyChanged(new PropertyChangedEventArgs("IsRealized")); 36 | IsStale = false; 37 | } 38 | } 39 | 40 | public bool IsStale 41 | { 42 | get { return _isStale; } 43 | set 44 | { 45 | _isStale = value; 46 | OnPropertyChanged(new PropertyChangedEventArgs("IsStale")); 47 | } 48 | } 49 | 50 | public void SupplyValue(T value) 51 | { 52 | DataFetchError = false; 53 | Item = value; 54 | } 55 | 56 | public void ClearValue() 57 | { 58 | DataFetchError = false; 59 | Item = null; 60 | } 61 | 62 | public void ErrorFetchingValue() 63 | { 64 | Item = null; 65 | DataFetchError = true; 66 | } 67 | 68 | public bool DataFetchError 69 | { 70 | get { return dataFetchError; } 71 | private set 72 | { 73 | dataFetchError = value; 74 | OnPropertyChanged(new PropertyChangedEventArgs("DataFetchError")); 75 | } 76 | } 77 | 78 | public bool IsRealized { get { return _item != null; } } 79 | 80 | public int Index 81 | { 82 | get { return _index; } 83 | } 84 | 85 | public VirtualCollection Parent 86 | { 87 | get { return _parent; } 88 | } 89 | 90 | protected void OnPropertyChanged(PropertyChangedEventArgs e) 91 | { 92 | PropertyChangedEventHandler handler = PropertyChanged; 93 | if (handler != null) handler(this, e); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /VirtualCollection/VirtualCollection/VirtualizingWrapPanel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Windows; 5 | using System.Windows.Controls; 6 | using System.Windows.Controls.Primitives; 7 | 8 | namespace VirtualCollection.VirtualCollection 9 | { 10 | public class VirtualizingWrapPanel : VirtualizingPanel, IScrollInfo 11 | { 12 | private const double ScrollLineAmount = 16.0; 13 | 14 | private Size _extentSize; 15 | private Size _viewportSize; 16 | private Point _offset; 17 | private ItemsControl _itemsControl; 18 | private readonly Dictionary _childLayouts = new Dictionary(); 19 | 20 | public static readonly DependencyProperty ItemWidthProperty = 21 | DependencyProperty.Register("ItemWidth", typeof(double), typeof(VirtualizingWrapPanel), new PropertyMetadata(1.0, HandleItemDimensionChanged)); 22 | 23 | public static readonly DependencyProperty ItemHeightProperty = 24 | DependencyProperty.Register("ItemHeight", typeof(double), typeof(VirtualizingWrapPanel), new PropertyMetadata(1.0, HandleItemDimensionChanged)); 25 | 26 | private static readonly DependencyProperty VirtualItemIndexProperty = 27 | DependencyProperty.RegisterAttached("VirtualItemIndex", typeof(int), typeof(VirtualizingWrapPanel), new PropertyMetadata(-1)); 28 | private IRecyclingItemContainerGenerator _itemsGenerator; 29 | 30 | private bool _isInMeasure; 31 | 32 | private static int GetVirtualItemIndex(DependencyObject obj) 33 | { 34 | return (int)obj.GetValue(VirtualItemIndexProperty); 35 | } 36 | 37 | private static void SetVirtualItemIndex(DependencyObject obj, int value) 38 | { 39 | obj.SetValue(VirtualItemIndexProperty, value); 40 | } 41 | 42 | public double ItemHeight 43 | { 44 | get { return (double)GetValue(ItemHeightProperty); } 45 | set { SetValue(ItemHeightProperty, value); } 46 | } 47 | 48 | public double ItemWidth 49 | { 50 | get { return (double)GetValue(ItemWidthProperty); } 51 | set { SetValue(ItemWidthProperty, value); } 52 | } 53 | 54 | public VirtualizingWrapPanel() 55 | { 56 | if (!DesignerProperties.IsInDesignTool) 57 | { 58 | Dispatcher.BeginInvoke(Initialize); 59 | } 60 | } 61 | 62 | private void Initialize() 63 | { 64 | _itemsControl = ItemsControl.GetItemsOwner(this); 65 | _itemsGenerator = (IRecyclingItemContainerGenerator)ItemContainerGenerator; 66 | 67 | InvalidateMeasure(); 68 | } 69 | 70 | protected override void OnItemsChanged(object sender, ItemsChangedEventArgs args) 71 | { 72 | base.OnItemsChanged(sender, args); 73 | 74 | InvalidateMeasure(); 75 | } 76 | 77 | protected override Size MeasureOverride(Size availableSize) 78 | { 79 | if (_itemsControl == null) 80 | { 81 | return new Size(double.IsInfinity(availableSize.Width) ? 0 : availableSize.Width, 82 | double.IsInfinity(availableSize.Height) ? 0 : availableSize.Height); 83 | } 84 | 85 | _isInMeasure = true; 86 | _childLayouts.Clear(); 87 | 88 | var extentInfo = GetExtentInfo(availableSize, ItemHeight); 89 | 90 | EnsureScrollOffsetIsWithinConstrains(extentInfo); 91 | 92 | var layoutInfo = GetLayoutInfo(availableSize, ItemHeight, extentInfo); 93 | 94 | RecycleItems(layoutInfo); 95 | 96 | // Determine where the first item is in relation to previously realized items 97 | var generatorStartPosition = _itemsGenerator.GeneratorPositionFromIndex(layoutInfo.FirstRealizedItemIndex); 98 | 99 | var visualIndex = 0; 100 | 101 | var currentX = layoutInfo.FirstRealizedItemLeft; 102 | var currentY = layoutInfo.FirstRealizedLineTop; 103 | 104 | using (_itemsGenerator.StartAt(generatorStartPosition, GeneratorDirection.Forward, true)) 105 | { 106 | for (var itemIndex = layoutInfo.FirstRealizedItemIndex; itemIndex <= layoutInfo.LastRealizedItemIndex; itemIndex++, visualIndex++) 107 | { 108 | bool newlyRealized; 109 | 110 | var child = (UIElement)_itemsGenerator.GenerateNext(out newlyRealized); 111 | SetVirtualItemIndex(child, itemIndex); 112 | 113 | if (newlyRealized) 114 | { 115 | InsertInternalChild(visualIndex, child); 116 | } 117 | else 118 | { 119 | // check if item needs to be moved into a new position in the Children collection 120 | if (visualIndex < Children.Count) 121 | { 122 | if (Children[visualIndex] != child) 123 | { 124 | var childCurrentIndex = Children.IndexOf(child); 125 | 126 | if (childCurrentIndex >= 0) 127 | { 128 | RemoveInternalChildRange(childCurrentIndex, 1); 129 | } 130 | 131 | InsertInternalChild(visualIndex, child); 132 | } 133 | } 134 | else 135 | { 136 | // we know that the child can't already be in the children collection 137 | // because we've been inserting children in correct visualIndex order, 138 | // and this child has a visualIndex greater than the Children.Count 139 | AddInternalChild(child); 140 | } 141 | } 142 | 143 | // only prepare the item once it has been added to the visual tree 144 | _itemsGenerator.PrepareItemContainer(child); 145 | 146 | child.Measure(new Size(ItemWidth, ItemHeight)); 147 | 148 | _childLayouts.Add(child, new Rect(currentX, currentY, ItemWidth, ItemHeight)); 149 | 150 | if (currentX + ItemWidth * 2 >= availableSize.Width) 151 | { 152 | // wrap to a new line 153 | currentY += ItemHeight; 154 | currentX = 0; 155 | } 156 | else 157 | { 158 | currentX += ItemWidth; 159 | } 160 | } 161 | } 162 | 163 | RemoveRedundantChildren(); 164 | UpdateScrollInfo(availableSize, extentInfo); 165 | 166 | var desiredSize = new Size(double.IsInfinity(availableSize.Width) ? 0 : availableSize.Width, 167 | double.IsInfinity(availableSize.Height) ? 0 : availableSize.Height); 168 | 169 | _isInMeasure = false; 170 | 171 | return desiredSize; 172 | } 173 | 174 | private void EnsureScrollOffsetIsWithinConstrains(ExtentInfo extentInfo) 175 | { 176 | _offset.Y = Clamp(_offset.Y, 0, extentInfo.MaxVerticalOffset); 177 | } 178 | 179 | private void RecycleItems(ItemLayoutInfo layoutInfo) 180 | { 181 | foreach (var child in Children) 182 | { 183 | var virtualItemIndex = GetVirtualItemIndex(child); 184 | 185 | if (virtualItemIndex < layoutInfo.FirstRealizedItemIndex || virtualItemIndex > layoutInfo.LastRealizedItemIndex) 186 | { 187 | var generatorPosition = _itemsGenerator.GeneratorPositionFromIndex(virtualItemIndex); 188 | if (generatorPosition.Index >= 0) 189 | { 190 | _itemsGenerator.Recycle(generatorPosition, 1); 191 | } 192 | } 193 | 194 | SetVirtualItemIndex(child, -1); 195 | } 196 | } 197 | 198 | protected override Size ArrangeOverride(Size finalSize) 199 | { 200 | foreach (var child in Children) 201 | { 202 | child.Arrange(_childLayouts[child]); 203 | } 204 | 205 | return finalSize; 206 | } 207 | 208 | private void UpdateScrollInfo(Size availableSize, ExtentInfo extentInfo) 209 | { 210 | _viewportSize = availableSize; 211 | _extentSize = new Size(availableSize.Width, extentInfo.ExtentHeight); 212 | 213 | InvalidateScrollInfo(); 214 | } 215 | 216 | private void RemoveRedundantChildren() 217 | { 218 | // iterate backwards through the child collection because we're going to be 219 | // removing items from it 220 | for (var i = Children.Count - 1; i >= 0; i--) 221 | { 222 | var child = Children[i]; 223 | 224 | // if the virtual item index is -1, this indicates 225 | // it is a recycled item that hasn't been reused this time round 226 | if (GetVirtualItemIndex(child) == -1) 227 | { 228 | RemoveInternalChildRange(i, 1); 229 | } 230 | } 231 | } 232 | 233 | private ItemLayoutInfo GetLayoutInfo(Size availableSize, double itemHeight, ExtentInfo extentInfo) 234 | { 235 | if (_itemsControl == null) 236 | { 237 | return new ItemLayoutInfo(); 238 | } 239 | 240 | // we need to ensure that there is one realized item prior to the first visible item, and one after the last visible item, 241 | // so that keyboard navigation works properly. For example, when focus is on the first visible item, and the user 242 | // navigates up, the ListBox selects the previous item, and the scrolls that into view - and this triggers the loading of the rest of the items 243 | // in that row 244 | 245 | var firstVisibleLine = (int)Math.Floor(VerticalOffset / itemHeight); 246 | 247 | var firstRealizedIndex = Math.Max(extentInfo.ItemsPerLine * firstVisibleLine - 1, 0); 248 | var firstRealizedItemLeft = firstRealizedIndex % extentInfo.ItemsPerLine * ItemWidth - HorizontalOffset; 249 | var firstRealizedItemTop = (firstRealizedIndex / extentInfo.ItemsPerLine) * itemHeight - VerticalOffset; 250 | 251 | var firstCompleteLineTop = (firstVisibleLine == 0 ? firstRealizedItemTop : firstRealizedItemTop + ItemHeight); 252 | var completeRealizedLines = (int)Math.Ceiling((availableSize.Height - firstCompleteLineTop) / itemHeight); 253 | 254 | var lastRealizedIndex = Math.Min(firstRealizedIndex + completeRealizedLines * extentInfo.ItemsPerLine + 2, _itemsControl.Items.Count - 1); 255 | 256 | return new ItemLayoutInfo 257 | { 258 | FirstRealizedItemIndex = firstRealizedIndex, 259 | FirstRealizedItemLeft = firstRealizedItemLeft, 260 | FirstRealizedLineTop = firstRealizedItemTop, 261 | LastRealizedItemIndex = lastRealizedIndex, 262 | }; 263 | } 264 | 265 | private ExtentInfo GetExtentInfo(Size viewPortSize, double itemHeight) 266 | { 267 | if (_itemsControl == null) 268 | { 269 | return new ExtentInfo(); 270 | } 271 | 272 | var itemsPerLine = Math.Max((int)Math.Floor(viewPortSize.Width / ItemWidth), 1); 273 | var totalLines = (int)Math.Ceiling((double)_itemsControl.Items.Count / itemsPerLine); 274 | var extentHeight = Math.Max(totalLines * ItemHeight, viewPortSize.Height); 275 | 276 | return new ExtentInfo() 277 | { 278 | ItemsPerLine = itemsPerLine, 279 | TotalLines = totalLines, 280 | ExtentHeight = extentHeight, 281 | MaxVerticalOffset = extentHeight - viewPortSize.Height, 282 | }; 283 | } 284 | 285 | public void LineUp() 286 | { 287 | SetVerticalOffset(VerticalOffset - ScrollLineAmount); 288 | } 289 | 290 | public void LineDown() 291 | { 292 | SetVerticalOffset(VerticalOffset + ScrollLineAmount); 293 | } 294 | 295 | public void LineLeft() 296 | { 297 | SetHorizontalOffset(HorizontalOffset + ScrollLineAmount); 298 | } 299 | 300 | public void LineRight() 301 | { 302 | SetHorizontalOffset(HorizontalOffset - ScrollLineAmount); 303 | } 304 | 305 | public void PageUp() 306 | { 307 | SetVerticalOffset(VerticalOffset - ViewportHeight); 308 | } 309 | 310 | public void PageDown() 311 | { 312 | SetVerticalOffset(VerticalOffset + ViewportHeight); 313 | } 314 | 315 | public void PageLeft() 316 | { 317 | SetHorizontalOffset(HorizontalOffset + ItemWidth); 318 | } 319 | 320 | public void PageRight() 321 | { 322 | SetHorizontalOffset(HorizontalOffset - ItemWidth); 323 | } 324 | 325 | public void MouseWheelUp() 326 | { 327 | SetVerticalOffset(VerticalOffset - ScrollLineAmount * SystemParameters.WheelScrollLines); 328 | } 329 | 330 | public void MouseWheelDown() 331 | { 332 | SetVerticalOffset(VerticalOffset + ScrollLineAmount * SystemParameters.WheelScrollLines); 333 | } 334 | 335 | public void MouseWheelLeft() 336 | { 337 | SetHorizontalOffset(HorizontalOffset - ScrollLineAmount * SystemParameters.WheelScrollLines); 338 | } 339 | 340 | public void MouseWheelRight() 341 | { 342 | SetHorizontalOffset(HorizontalOffset + ScrollLineAmount * SystemParameters.WheelScrollLines); 343 | } 344 | 345 | public void SetHorizontalOffset(double offset) 346 | { 347 | if (_isInMeasure) 348 | { 349 | return; 350 | } 351 | 352 | offset = Clamp(offset, 0, ExtentWidth - ViewportWidth); 353 | _offset = new Point(offset, _offset.Y); 354 | 355 | InvalidateScrollInfo(); 356 | InvalidateMeasure(); 357 | } 358 | 359 | public void SetVerticalOffset(double offset) 360 | { 361 | if (_isInMeasure) 362 | { 363 | return; 364 | } 365 | 366 | offset = Clamp(offset, 0, ExtentHeight - ViewportHeight); 367 | _offset = new Point(_offset.X, offset); 368 | 369 | InvalidateScrollInfo(); 370 | InvalidateMeasure(); 371 | } 372 | 373 | public Rect MakeVisible(UIElement visual, Rect rectangle) 374 | { 375 | return new Rect(); 376 | } 377 | 378 | public ItemLayoutInfo GetVisibleItemsRange() 379 | { 380 | return GetLayoutInfo(_viewportSize, ItemHeight, GetExtentInfo(_viewportSize, ItemHeight)); 381 | } 382 | 383 | public bool CanVerticallyScroll 384 | { 385 | get; 386 | set; 387 | } 388 | 389 | public bool CanHorizontallyScroll 390 | { 391 | get; 392 | set; 393 | } 394 | 395 | public double ExtentWidth 396 | { 397 | get { return _extentSize.Width; } 398 | } 399 | 400 | public double ExtentHeight 401 | { 402 | get { return _extentSize.Height; } 403 | } 404 | 405 | public double ViewportWidth 406 | { 407 | get { return _viewportSize.Width; } 408 | } 409 | 410 | public double ViewportHeight 411 | { 412 | get { return _viewportSize.Height; } 413 | } 414 | 415 | public double HorizontalOffset 416 | { 417 | get { return _offset.X; } 418 | } 419 | 420 | public double VerticalOffset 421 | { 422 | get { return _offset.Y; } 423 | } 424 | 425 | public ScrollViewer ScrollOwner 426 | { 427 | get; 428 | set; 429 | } 430 | 431 | private void InvalidateScrollInfo() 432 | { 433 | if (ScrollOwner != null) 434 | { 435 | ScrollOwner.InvalidateScrollInfo(); 436 | } 437 | } 438 | 439 | private static void HandleItemDimensionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 440 | { 441 | var wrapPanel = (d as VirtualizingWrapPanel); 442 | 443 | wrapPanel.InvalidateMeasure(); 444 | } 445 | 446 | 447 | private double Clamp(double value, double min, double max) 448 | { 449 | return Math.Min(Math.Max(value, min), max); 450 | } 451 | 452 | internal class ExtentInfo 453 | { 454 | public int ItemsPerLine; 455 | public int TotalLines; 456 | public double ExtentHeight; 457 | public double MaxVerticalOffset; 458 | } 459 | } 460 | 461 | public class ItemLayoutInfo 462 | { 463 | public int FirstRealizedItemIndex; 464 | public double FirstRealizedLineTop; 465 | public double FirstRealizedItemLeft; 466 | public int LastRealizedItemIndex; 467 | } 468 | } 469 | -------------------------------------------------------------------------------- /VirtualCollection/VirtualCollection/WeakCollectionViewWrapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.ObjectModel; 4 | using System.Collections.Specialized; 5 | using System.ComponentModel; 6 | using System.Globalization; 7 | 8 | namespace VirtualCollection.VirtualCollection 9 | { 10 | /// 11 | /// This class exists because of a bug/oversight in Silverlight, where ListBox does not unsubscribe to 12 | /// CurrentChanged events of its ItemsSource. The solution used here is to give the ListBox a wrapper around the actual VirtualCollection 13 | /// that subscribes using weak events. 14 | /// 15 | /// 16 | public class WeakCollectionViewWrapper : IList, ICollectionView, IEnquireAboutItemVisibility where T : IList, ICollectionView 17 | { 18 | private readonly T innerCollection; 19 | 20 | public WeakCollectionViewWrapper(T innerCollection) 21 | { 22 | this.innerCollection = innerCollection; 23 | 24 | var currentChangedListener = new WeakEventListener, object, EventArgs>(this) 25 | { 26 | OnEventAction = 27 | (instance, source, eventArgs) => 28 | instance.OnCurrentChanged(eventArgs), 29 | OnDetachAction = 30 | listener => innerCollection.CurrentChanged -= listener.OnEvent 31 | }; 32 | 33 | innerCollection.CurrentChanged += currentChangedListener.OnEvent; 34 | 35 | var currentChangingListener = new WeakEventListener, object, CurrentChangingEventArgs>(this) 36 | { 37 | OnEventAction = 38 | (instance, source, eventArgs) => 39 | instance.OnCurrentChanging(eventArgs), 40 | OnDetachAction = 41 | listener => innerCollection.CurrentChanging -= listener.OnEvent 42 | }; 43 | 44 | innerCollection.CurrentChanging += currentChangingListener.OnEvent; 45 | 46 | var propertyChangedListener = new WeakEventListener, object, NotifyCollectionChangedEventArgs>(this) 47 | { 48 | OnEventAction = 49 | (instance, source, eventArgs) => 50 | instance.OnCollectionChanged(eventArgs), 51 | OnDetachAction = 52 | listener => innerCollection.CollectionChanged -= listener.OnEvent 53 | }; 54 | 55 | innerCollection.CollectionChanged += propertyChangedListener.OnEvent; 56 | 57 | var enquireAboutItemVisibility = innerCollection as IEnquireAboutItemVisibility; 58 | if (enquireAboutItemVisibility != null) 59 | { 60 | var queryItemVisibilityListener = 61 | new WeakEventListener, object, QueryItemVisibilityEventArgs>(this) 62 | { 63 | OnEventAction = 64 | (instance, source, eventArgs) => 65 | instance.OnQueryItemVisibility(eventArgs), 66 | OnDetachAction = 67 | listener => enquireAboutItemVisibility.QueryItemVisibility -= listener.OnEvent 68 | }; 69 | 70 | enquireAboutItemVisibility.QueryItemVisibility += queryItemVisibilityListener.OnEvent; 71 | } 72 | } 73 | 74 | public IEnumerator GetEnumerator() 75 | { 76 | return ((IEnumerable)innerCollection).GetEnumerator(); 77 | } 78 | 79 | public void CopyTo(Array array, int index) 80 | { 81 | ((ICollection)innerCollection).CopyTo(array, index); 82 | } 83 | 84 | public object SyncRoot 85 | { 86 | get { return ((ICollection)innerCollection).SyncRoot; } 87 | } 88 | 89 | public bool IsSynchronized 90 | { 91 | get { return innerCollection.IsSynchronized; } 92 | } 93 | 94 | public int Add(object value) 95 | { 96 | return ((IList)innerCollection).Add(value); 97 | } 98 | 99 | bool ICollectionView.Contains(object value) 100 | { 101 | return ((IList)innerCollection).Contains(value); 102 | } 103 | 104 | public int IndexOf(object value) 105 | { 106 | return ((IList)innerCollection).IndexOf(value); 107 | } 108 | 109 | public void Insert(int index, object value) 110 | { 111 | ((IList)innerCollection).Insert(index, value); 112 | } 113 | 114 | public void Remove(object value) 115 | { 116 | ((IList)innerCollection).Remove(value); 117 | } 118 | 119 | object IList.this[int index] 120 | { 121 | get { return innerCollection[index]; } 122 | set { ((IList)innerCollection)[index] = value; } 123 | } 124 | 125 | public bool IsFixedSize 126 | { 127 | get { return ((IList)innerCollection).IsFixedSize; } 128 | } 129 | 130 | public event NotifyCollectionChangedEventHandler CollectionChanged; 131 | 132 | private void OnCollectionChanged(NotifyCollectionChangedEventArgs e) 133 | { 134 | NotifyCollectionChangedEventHandler handler = CollectionChanged; 135 | if (handler != null) handler(this, e); 136 | } 137 | 138 | public bool Contains(object item) 139 | { 140 | return ((ICollectionView)innerCollection).Contains(item); 141 | } 142 | 143 | public void Refresh() 144 | { 145 | innerCollection.Refresh(); 146 | } 147 | 148 | public IDisposable DeferRefresh() 149 | { 150 | return innerCollection.DeferRefresh(); 151 | } 152 | 153 | public bool MoveCurrentToFirst() 154 | { 155 | return innerCollection.MoveCurrentToFirst(); 156 | } 157 | 158 | public bool MoveCurrentToLast() 159 | { 160 | return innerCollection.MoveCurrentToLast(); 161 | } 162 | 163 | public bool MoveCurrentToNext() 164 | { 165 | return innerCollection.MoveCurrentToNext(); 166 | } 167 | 168 | public bool MoveCurrentToPrevious() 169 | { 170 | return innerCollection.MoveCurrentToPrevious(); 171 | } 172 | 173 | public bool MoveCurrentTo(object item) 174 | { 175 | return innerCollection.MoveCurrentTo(item); 176 | } 177 | 178 | public bool MoveCurrentToPosition(int position) 179 | { 180 | return innerCollection.MoveCurrentToPosition(position); 181 | } 182 | 183 | public CultureInfo Culture 184 | { 185 | get { return innerCollection.Culture; } 186 | set { innerCollection.Culture = value; } 187 | } 188 | public IEnumerable SourceCollection 189 | { 190 | get { return innerCollection.SourceCollection; } 191 | } 192 | public Predicate Filter 193 | { 194 | get { return innerCollection.Filter; } 195 | set { innerCollection.Filter = value; } 196 | } 197 | public bool CanFilter 198 | { 199 | get { return innerCollection.CanFilter; } 200 | } 201 | public SortDescriptionCollection SortDescriptions 202 | { 203 | get { return innerCollection.SortDescriptions; } 204 | } 205 | public bool CanSort 206 | { 207 | get { return innerCollection.CanSort; } 208 | } 209 | public bool CanGroup 210 | { 211 | get { return innerCollection.CanGroup; } 212 | } 213 | public ObservableCollection GroupDescriptions 214 | { 215 | get { return innerCollection.GroupDescriptions; } 216 | } 217 | public ReadOnlyObservableCollection Groups 218 | { 219 | get { return innerCollection.Groups; } 220 | } 221 | public bool IsEmpty 222 | { 223 | get { return innerCollection.IsEmpty; } 224 | } 225 | public object CurrentItem 226 | { 227 | get { return innerCollection.CurrentItem; } 228 | } 229 | public int CurrentPosition 230 | { 231 | get { return innerCollection.CurrentPosition; } 232 | } 233 | public bool IsCurrentAfterLast 234 | { 235 | get { return innerCollection.IsCurrentAfterLast; } 236 | } 237 | public bool IsCurrentBeforeFirst 238 | { 239 | get { return innerCollection.IsCurrentBeforeFirst; } 240 | } 241 | 242 | public event CurrentChangingEventHandler CurrentChanging; 243 | 244 | private void OnCurrentChanging(CurrentChangingEventArgs e) 245 | { 246 | CurrentChangingEventHandler handler = CurrentChanging; 247 | if (handler != null) handler(this, e); 248 | } 249 | 250 | public event EventHandler CurrentChanged; 251 | 252 | private void OnCurrentChanged(EventArgs e) 253 | { 254 | EventHandler handler = CurrentChanged; 255 | if (handler != null) handler(this, e); 256 | } 257 | 258 | public int Count 259 | { 260 | get { return innerCollection.Count; } 261 | } 262 | 263 | public bool IsReadOnly 264 | { 265 | get { return innerCollection.IsReadOnly; } 266 | } 267 | 268 | public void Clear() 269 | { 270 | innerCollection.Clear(); 271 | } 272 | 273 | public void RemoveAt(int index) 274 | { 275 | innerCollection.RemoveAt(index); 276 | } 277 | 278 | public event EventHandler QueryItemVisibility; 279 | 280 | protected void OnQueryItemVisibility(QueryItemVisibilityEventArgs e) 281 | { 282 | EventHandler handler = QueryItemVisibility; 283 | if (handler != null) handler(this, e); 284 | } 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /VirtualCollection/VirtualCollection/WeakEventListener.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // (c) Copyright Microsoft Corporation. 4 | // This source is subject to the Microsoft Public License (Ms-PL). 5 | // Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. 6 | // All other rights reserved. 7 | // 8 | //----------------------------------------------------------------------- 9 | 10 | using System; 11 | 12 | namespace VirtualCollection.VirtualCollection 13 | { 14 | /// 15 | /// Implements a weak event listener that allows the owner to be garbage 16 | /// collected if its only remaining link is an event handler. 17 | /// 18 | /// Type of instance listening for the event. 19 | /// Type of source for the event. 20 | /// Type of event arguments for the event. 21 | public class WeakEventListener : IWeakEventListener where TInstance : class 22 | { 23 | /// 24 | /// WeakReference to the instance listening for the event. 25 | /// 26 | private WeakReference _weakInstance; 27 | 28 | /// 29 | /// Gets or sets the method to call when the event fires. 30 | /// 31 | public Action OnEventAction { get; set; } 32 | 33 | /// 34 | /// Gets or sets the method to call when detaching from the event. 35 | /// 36 | public Action> OnDetachAction { get; set; } 37 | 38 | /// 39 | /// Initializes a new instances of the WeakEventListener class. 40 | /// 41 | /// Instance subscribing to the event. 42 | public WeakEventListener(TInstance instance) 43 | { 44 | if (instance == null) 45 | { 46 | throw new ArgumentNullException("instance"); 47 | } 48 | 49 | this._weakInstance = new WeakReference(instance); 50 | } 51 | 52 | /// 53 | /// Handler for the subscribed event calls OnEventAction to handle it. 54 | /// 55 | /// Event source. 56 | /// Event arguments. 57 | public void OnEvent(TSource source, TEventArgs eventArgs) 58 | { 59 | TInstance target = _weakInstance.Target as TInstance; 60 | 61 | if (target != null) 62 | { 63 | // Call the registered action. 64 | if (this.OnEventAction != null) 65 | { 66 | this.OnEventAction(target, source, eventArgs); 67 | } 68 | } 69 | else 70 | { 71 | // Detach from the event. 72 | this.Detach(); 73 | } 74 | } 75 | 76 | /// 77 | /// Detaches from the subscribed event. 78 | /// 79 | public void Detach() 80 | { 81 | if (this.OnDetachAction != null) 82 | { 83 | this.OnDetachAction(this); 84 | this.OnDetachAction = null; 85 | } 86 | } 87 | } 88 | 89 | public interface IWeakEventListener 90 | { 91 | void Detach(); 92 | } 93 | } -------------------------------------------------------------------------------- /VirtualCollection/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | --------------------------------------------------------------------------------