/* * FastObjectListView - A listview that behaves like an ObjectListView but has the speed of a virtual list * * Author: Phillip Piper * Date: 27/09/2008 9:15 AM * * Change log: * 2011-04-25 JPP - Fixed problem with removing objects from filtered or sorted list * v2.4 * 2010-04-05 JPP - Added filtering * v2.3 * 2009-08-27 JPP - Added GroupingStrategy * - Added optimized Objects property * v2.2.1 * 2009-01-07 JPP - Made all public and protected methods virtual * 2008-09-27 JPP - Separated from ObjectListView.cs * * Copyright (C) 2006-2010 Phillip Piper * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you wish to use this code in a closed source application, please contact phillip_piper@bigfoot.com. */ using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Windows.Forms; namespace BrightIdeasSoftware { /// /// A FastObjectListView trades function for speed. /// /// /// On my mid-range laptop, this view builds a list of 10,000 objects in 0.1 seconds, /// as opposed to a normal ObjectListView which takes 10-15 seconds. Lists of up to 50,000 items should be /// able to be handled with sub-second response times even on low end machines. /// /// A FastObjectListView is implemented as a virtual list with many of the virtual modes limits (e.g. no sorting) /// fixed through coding. There are some functions that simply cannot be provided. Specifically, a FastObjectListView cannot: /// /// use Tile view /// show groups on XP /// /// /// public class FastObjectListView : VirtualObjectListView { /// /// Make a FastObjectListView /// public FastObjectListView() { this.VirtualListDataSource = new FastObjectListDataSource(this); this.GroupingStrategy = new FastListGroupingStrategy(); } /// /// Get/set the collection of objects that this list will show /// /// /// /// The contents of the control will be updated immediately after setting this property. /// /// This method preserves selection, if possible. Use SetObjects() if /// you do not want to preserve the selection. Preserving selection is the slowest part of this /// code and performance is O(n) where n is the number of selected rows. /// This method is not thread safe. /// [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public override IEnumerable Objects { get { // This is much faster than the base method return ((FastObjectListDataSource)this.VirtualListDataSource).ObjectList; } set { base.Objects = value; } } /// /// Remove any sorting and revert to the given order of the model objects /// public override void Unsort() { this.ShowGroups = false; this.PrimarySortColumn = null; this.PrimarySortOrder = SortOrder.None; this.SetObjects(this.Objects); } } /// /// Provide a data source for a FastObjectListView /// /// /// This class isn't intended to be used directly, but it is left as a public /// class just in case someone wants to subclass it. /// public class FastObjectListDataSource : AbstractVirtualListDataSource { /// /// Create a FastObjectListDataSource /// /// public FastObjectListDataSource(FastObjectListView listView) : base(listView) { } internal ArrayList ObjectList { get { return fullObjectList; } } internal ArrayList FilteredObjectList { get { return filteredObjectList; } } #region IVirtualListDataSource Members /// /// Get n'th object /// /// /// public override object GetNthObject(int n) { if (n >= 0 && n < this.filteredObjectList.Count) return this.filteredObjectList[n]; else return null; } /// /// How many items are in the data source /// /// public override int GetObjectCount() { return this.filteredObjectList.Count; } /// /// Get the index of the given model /// /// /// public override int GetObjectIndex(object model) { int index; if (model != null && this.objectsToIndexMap.TryGetValue(model, out index)) return index; else return -1; } /// /// /// /// /// /// /// /// public override int SearchText(string value, int first, int last, OLVColumn column) { return DefaultSearchText(value, first, last, column, this); } /// /// /// /// /// public override void Sort(OLVColumn column, SortOrder sortOrder) { if (sortOrder != SortOrder.None) { ModelObjectComparer comparer = new ModelObjectComparer(column, sortOrder, this.listView.SecondarySortColumn, this.listView.SecondarySortOrder); this.fullObjectList.Sort(comparer); this.filteredObjectList.Sort(comparer); } this.RebuildIndexMap(); } /// /// /// /// public override void AddObjects(ICollection modelObjects) { foreach (object modelObject in modelObjects) { if (modelObject != null) this.fullObjectList.Add(modelObject); } this.FilterObjects(); this.RebuildIndexMap(); } /// /// Remove the given collection of models from this source. /// /// public override void RemoveObjects(ICollection modelObjects) { List indicesToRemove = new List(); foreach (object modelObject in modelObjects) { int i = this.GetObjectIndex(modelObject); if (i >= 0) indicesToRemove.Add(i); // Remove the objects from the unfiltered list this.fullObjectList.Remove(modelObject); } // Sort the indices from highest to lowest so that we // remove latter ones before earlier ones. In this way, the // indices of the rows doesn't change after the deletes. indicesToRemove.Sort(); indicesToRemove.Reverse(); foreach (int i in indicesToRemove) this.listView.SelectedIndices.Remove(i); this.FilterObjects(); this.RebuildIndexMap(); } /// /// /// /// public override void SetObjects(IEnumerable collection) { ArrayList newObjects = ObjectListView.EnumerableToArray(collection, true); this.fullObjectList = newObjects; this.FilterObjects(); this.RebuildIndexMap(); } private ArrayList fullObjectList = new ArrayList(); private ArrayList filteredObjectList = new ArrayList(); private IModelFilter modelFilter; private IListFilter listFilter; #endregion #region IFilterableDataSource Members /// /// Apply the given filters to this data source. One or both may be null. /// /// /// public override void ApplyFilters(IModelFilter iModelFilter, IListFilter iListFilter) { this.modelFilter = iModelFilter; this.listFilter = iListFilter; this.SetObjects(this.fullObjectList); } #endregion #region Implementation /// /// Rebuild the map that remembers which model object is displayed at which line /// protected void RebuildIndexMap() { this.objectsToIndexMap.Clear(); for (int i = 0; i < this.filteredObjectList.Count; i++) this.objectsToIndexMap[this.filteredObjectList[i]] = i; } readonly Dictionary objectsToIndexMap = new Dictionary(); /// /// Build our filtered list from our full list. /// protected void FilterObjects() { if (!this.listView.UseFiltering || (this.modelFilter == null && this.listFilter == null)) { this.filteredObjectList = new ArrayList(this.fullObjectList); return; } IEnumerable objects = (this.listFilter == null) ? this.fullObjectList : this.listFilter.Filter(this.fullObjectList); // Apply the object filter if there is one if (this.modelFilter == null) { this.filteredObjectList = ObjectListView.EnumerableToArray(objects, false); } else { this.filteredObjectList = new ArrayList(); foreach (object model in objects) { if (this.modelFilter.Filter(model)) this.filteredObjectList.Add(model); } } } #endregion } }