/* * Virtual groups - Classes and interfaces needed to implement virtual groups * * Author: Phillip Piper * Date: 28/08/2009 11:10am * * Change log: * 2011-02-21 JPP - Correctly honor group comparer and collapsible groups settings * v2.3 * 2009-08-28 JPP - Initial version * * To do: * * Copyright (C) 2009-2014 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.Generic; using System.Windows.Forms; using System.Runtime.InteropServices; namespace BrightIdeasSoftware { /// /// A IVirtualGroups is the interface that a virtual list must implement to support virtual groups /// public interface IVirtualGroups { /// /// Return the list of groups that should be shown according to the given parameters /// /// /// IList GetGroups(GroupingParameters parameters); /// /// Return the index of the item that appears at the given position within the given group. /// /// /// /// int GetGroupMember(OLVGroup group, int indexWithinGroup); /// /// Return the index of the group to which the given item belongs /// /// /// int GetGroup(int itemIndex); /// /// Return the index at which the given item is shown in the given group /// /// /// /// int GetIndexWithinGroup(OLVGroup group, int itemIndex); /// /// A hint that the given range of items are going to be required /// /// /// /// /// void CacheHint(int fromGroupIndex, int fromIndex, int toGroupIndex, int toIndex); } /// /// This is a safe, do nothing implementation of a grouping strategy /// public class AbstractVirtualGroups : IVirtualGroups { /// /// Return the list of groups that should be shown according to the given parameters /// /// /// public virtual IList GetGroups(GroupingParameters parameters) { return new List(); } /// /// Return the index of the item that appears at the given position within the given group. /// /// /// /// public virtual int GetGroupMember(OLVGroup group, int indexWithinGroup) { return -1; } /// /// Return the index of the group to which the given item belongs /// /// /// public virtual int GetGroup(int itemIndex) { return -1; } /// /// Return the index at which the given item is shown in the given group /// /// /// /// public virtual int GetIndexWithinGroup(OLVGroup group, int itemIndex) { return -1; } /// /// A hint that the given range of items are going to be required /// /// /// /// /// public virtual void CacheHint(int fromGroupIndex, int fromIndex, int toGroupIndex, int toIndex) { } } /// /// Provides grouping functionality to a FastObjectListView /// public class FastListGroupingStrategy : AbstractVirtualGroups { /// /// Create groups for FastListView /// /// /// public override IList GetGroups(GroupingParameters parmameters) { // There is a lot of overlap between this method and ObjectListView.MakeGroups() // Any changes made here may need to be reflected there // This strategy can only be used on FastObjectListViews FastObjectListView folv = (FastObjectListView)parmameters.ListView; // Separate the list view items into groups, using the group key as the descrimanent int objectCount = 0; NullableDictionary> map = new NullableDictionary>(); foreach (object model in folv.FilteredObjects) { object key = parmameters.GroupByColumn.GetGroupKey(model); if (!map.ContainsKey(key)) map[key] = new List(); map[key].Add(model); objectCount++; } // Sort the items within each group // TODO: Give parameters a ModelComparer property OLVColumn primarySortColumn = parmameters.SortItemsByPrimaryColumn ? parmameters.ListView.GetColumn(0) : parmameters.PrimarySort; ModelObjectComparer sorter = new ModelObjectComparer(primarySortColumn, parmameters.PrimarySortOrder, parmameters.SecondarySort, parmameters.SecondarySortOrder); foreach (object key in map.Keys) { map[key].Sort(sorter); } // Make a list of the required groups List groups = new List(); foreach (object key in map.Keys) { string title = parmameters.GroupByColumn.ConvertGroupKeyToTitle(key); if (!String.IsNullOrEmpty(parmameters.TitleFormat)) { int count = map[key].Count; string format = (count == 1 ? parmameters.TitleSingularFormat : parmameters.TitleFormat); try { title = String.Format(format, title, count); } catch (FormatException) { title = "Invalid group format: " + format; } } OLVGroup lvg = new OLVGroup(title); lvg.Collapsible = folv.HasCollapsibleGroups; lvg.Key = key; lvg.SortValue = key as IComparable; lvg.Contents = map[key].ConvertAll(delegate(object x) { return folv.IndexOf(x); }); lvg.VirtualItemCount = map[key].Count; if (parmameters.GroupByColumn.GroupFormatter != null) parmameters.GroupByColumn.GroupFormatter(lvg, parmameters); groups.Add(lvg); } // Sort the groups if (parmameters.GroupByOrder != SortOrder.None) groups.Sort(parmameters.GroupComparer ?? new OLVGroupComparer(parmameters.GroupByOrder)); // Build an array that remembers which group each item belongs to. this.indexToGroupMap = new List(objectCount); this.indexToGroupMap.AddRange(new int[objectCount]); for (int i = 0; i < groups.Count; i++) { OLVGroup group = groups[i]; List members = (List)group.Contents; foreach (int j in members) this.indexToGroupMap[j] = i; } return groups; } private List indexToGroupMap; /// /// /// /// /// /// public override int GetGroupMember(OLVGroup group, int indexWithinGroup) { return (int)group.Contents[indexWithinGroup]; } /// /// /// /// /// public override int GetGroup(int itemIndex) { return this.indexToGroupMap[itemIndex]; } /// /// /// /// /// /// public override int GetIndexWithinGroup(OLVGroup group, int itemIndex) { return group.Contents.IndexOf(itemIndex); } } /// /// This is the COM interface that a ListView must be given in order for groups in virtual lists to work. /// /// /// This interface is NOT documented by MS. It was found on Greg Chapell's site. This means that there is /// no guarantee that it will work on future versions of Windows, nor continue to work on current ones. /// [ComImport(), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("44C09D56-8D3B-419D-A462-7B956B105B47")] internal interface IOwnerDataCallback { /// /// Not sure what this does /// /// /// void GetItemPosition(int i, out NativeMethods.POINT pt); /// /// Not sure what this does /// /// /// void SetItemPosition(int t, NativeMethods.POINT pt); /// /// Get the index of the item that occurs at the n'th position of the indicated group. /// /// Index of the group /// Index within the group /// Index of the item within the whole list void GetItemInGroup(int groupIndex, int n, out int itemIndex); /// /// Get the index of the group to which the given item belongs /// /// Index of the item within the whole list /// Which occurences of the item is wanted /// Index of the group void GetItemGroup(int itemIndex, int occurrenceCount, out int groupIndex); /// /// Get the number of groups that contain the given item /// /// Index of the item within the whole list /// How many groups does it occur within void GetItemGroupCount(int itemIndex, out int occurrenceCount); /// /// A hint to prepare any cache for the given range of requests /// /// /// void OnCacheHint(NativeMethods.LVITEMINDEX i, NativeMethods.LVITEMINDEX j); } /// /// A default implementation of the IOwnerDataCallback interface /// [Guid("6FC61F50-80E8-49b4-B200-3F38D3865ABD")] internal class OwnerDataCallbackImpl : IOwnerDataCallback { public OwnerDataCallbackImpl(VirtualObjectListView olv) { this.olv = olv; } VirtualObjectListView olv; #region IOwnerDataCallback Members public void GetItemPosition(int i, out NativeMethods.POINT pt) { //System.Diagnostics.Debug.WriteLine("GetItemPosition"); throw new NotSupportedException(); } public void SetItemPosition(int t, NativeMethods.POINT pt) { //System.Diagnostics.Debug.WriteLine("SetItemPosition"); throw new NotSupportedException(); } public void GetItemInGroup(int groupIndex, int n, out int itemIndex) { //System.Diagnostics.Debug.WriteLine(String.Format("-> GetItemInGroup({0}, {1})", groupIndex, n)); itemIndex = this.olv.GroupingStrategy.GetGroupMember(this.olv.OLVGroups[groupIndex], n); //System.Diagnostics.Debug.WriteLine(String.Format("<- {0}", itemIndex)); } public void GetItemGroup(int itemIndex, int occurrenceCount, out int groupIndex) { //System.Diagnostics.Debug.WriteLine(String.Format("GetItemGroup({0}, {1})", itemIndex, occurrenceCount)); groupIndex = this.olv.GroupingStrategy.GetGroup(itemIndex); //System.Diagnostics.Debug.WriteLine(String.Format("<- {0}", groupIndex)); } public void GetItemGroupCount(int itemIndex, out int occurrenceCount) { //System.Diagnostics.Debug.WriteLine(String.Format("GetItemGroupCount({0})", itemIndex)); occurrenceCount = 1; } public void OnCacheHint(NativeMethods.LVITEMINDEX from, NativeMethods.LVITEMINDEX to) { //System.Diagnostics.Debug.WriteLine(String.Format("OnCacheHint({0}, {1}, {2}, {3})", from.iGroup, from.iItem, to.iGroup, to.iItem)); this.olv.GroupingStrategy.CacheHint(from.iGroup, from.iItem, to.iGroup, to.iItem); } #endregion } }