context: /*
   * ObjectListView - A listview to show various aspects of a collection of objects
   *
   * Author: Phillip Piper
   * Date: 9/10/2006 11:15 AM
   *
   * Change log:
   * 2008-07-23  JPP  - Consistently use copy-on-write semantics with Add/RemoveObject methods
   * 2008-07-10  JPP  - Enable validation on cell editors through a CellEditValidating event.
   *                    (thanks to Artiom Chilaru for the initial suggestion and implementation).
   * 2008-07-09  JPP  - Added HeaderControl.Handle to allow OLV to be used within UserControls.
   *                    (thanks to Michael Coffey for tracking this down).
   * 2008-06-23  JPP  - Split the more generally useful CopyObjectsToClipboard() method
   *                    out of CopySelectionToClipboard()
   * 2008-06-22  JPP  - Added AlwaysGroupByColumn and AlwaysGroupBySortOrder, which
   *                    force the list view to always be grouped by a particular column.
   * 2008-05-31  JPP  - Allow check boxes on FastObjectListViews
   *                  - Added CheckedObject and CheckedObjects properties
   * 2008-05-11  JPP  - Allow selection foreground and background colors to be changed.
   *                    Windows doesn't allow this, so we can only make it happen when owner
   *                    drawing. Set the HighlightForegroundColor and  HighlightBackgroundColor
   *                    properties and then call EnableCustomSelectionColors().
   * v1.12
   * 2008-05-08  JPP  - Fixed bug where the column select menu would not appear if the
   *                    ObjectListView has a context menu installed.
   * 2008-05-05  JPP  - Non detail views can now be owner drawn. The renderer installed for
   *                    primary column is given the chance to render the whole item.
   *                    See BusinessCardRenderer in the demo for an example.
   *                  - BREAKING CHANGE: RenderDelegate now returns a bool to indicate if default
   *                    rendering should be done. Previously returned void. Only important if your
   *                    code used RendererDelegate directly. Renderers derived from BaseRenderer
   *                    are unchanged.
   * 2008-05-03  JPP  - Changed cell editing to use values directly when the values are Strings.
   *                    Previously, values were always handed to the AspectToStringConverter.
   *                  - When editing a cell, tabbing no longer tries to edit the next subitem
   *                    when not in details view!
   * 2008-05-02  JPP  - MappedImageRenderer can now handle a Aspects that return a collection
   *                    of values. Each value will be drawn as its own image.
   *                  - Made AddObjects() and RemoveObjects() work for all flavours (or at least not crash)
   *                  - Fixed bug with clearing virtual lists that has been scrolled vertically
   *                  - Made TopItemIndex work with virtual lists.
   * 2008-05-01  JPP  - Added AddObjects() and RemoveObjects() to allow faster mods to the list
   *                  - Reorganised public properties. Now alphabetical.
   *                  - Made the class ObjectListViewState internal, as it always should have been.
   * v1.11
   * 2008-04-29  JPP  - Preserve scroll position when building the list or changing columns.
   *                  - Added TopItemIndex property. Due to problems with the underlying control, this
   *                    property is not always reliable. See property docs for info.
   * 2008-04-27  JPP  - Added SelectedIndex property.
   *                  - Use a different, more general strategy to handle Invoke(). Removed all delegates
   *                    that were only declared to support Invoke().
   *                  - Check all native structures for 64-bit correctness.
   * 2008-04-25  JPP  - Released on SourceForge.
   * 2008-04-13  JPP  - Added ColumnRightClick event.
   *                  - Made the assembly CLS-compliant. To do this, our cell editors were made internal, and
   *                    the constraint on FlagRenderer template parameter was removed (the type must still
   *                    be an IConvertible, but if it isn't, the error will be caught at runtime, not compile time).
   * 2008-04-12  JPP  - Changed HandleHeaderRightClick() to have a columnIndex parameter, which tells
   *                    exactly which column was right-clicked.
   * 2008-03-31  JPP  - Added SaveState() and RestoreState()
   *                  - When cell editing, scrolling with a mouse wheel now ends the edit operation.
   * v1.10
   * 2008-03-25  JPP  - Added space filling columns. See OLVColumn.FreeSpaceProportion property for details.
   *                    A space filling columns fills all (or a portion) of the width unoccupied by other columns.
   * 2008-03-23  JPP  - Finished tinkering with support for Mono. Compile with conditional compilation symbol 'MONO'
   *                    to enable. On Windows, current problems with Mono:
   *                    - grid lines on virtual lists crashes
   *                    - when grouped, items sometimes are not drawn when any item is scrolled out of view
   *                    - i can't seem to get owner drawing to work
   *                    - when editing cell values, the editing controls always appear behind the listview,
   *                      where they function fine -- the user just can't see them :-)
   * 2008-03-16  JPP  - Added some methods suggested by Chris Marlowe (thanks for the suggestions Chris)
   *                    - ClearObjects()
   *                    - GetCheckedObject(), GetCheckedObjects()
   *                    - GetItemAt() variation that gets both the item and the column under a point
   * 2008-02-28  JPP  - Fixed bug with subitem colors when using OwnerDrawn lists and a RowFormatter.
   * v1.9.1
   * 2008-01-29  JPP  - Fixed bug that caused owner-drawn virtual lists to use 100% CPU
   *                  - Added FlagRenderer to help draw bitwise-OR'ed flag values
   * 2008-01-23  JPP  - Fixed bug (introduced in v1.9) that made alternate row colour with groups not quite right
   *                  - Ensure that DesignerSerializationVisibility.Hidden is set on all non-browsable properties
   *                  - Make sure that sort indicators are shown after changing which columns are visible
   * 2008-01-21  JPP  - Added FastObjectListView
   * v1.9
   * 2008-01-18  JPP  - Added IncrementalUpdate()
   * 2008-01-16  JPP  - Right clicking on column header will allow the user to choose which columns are visible.
   *                    Set SelectColumnsOnRightClick to false to prevent this behaviour.
   *                  - Added ImagesRenderer to draw more than one images in a column
   *                  - Changed the positioning of the empty list msg to use all the client area. Thanks to Matze.
   * 2007-12-13  JPP  - Added CopySelectionToClipboard(). Ctrl-C invokes this method. Supports text
   *                    and HTML formats.
   * 2007-12-12  JPP  - Added support for checkboxes via CheckStateGetter and CheckStatePutter properties.
   *                  - Made ObjectListView and OLVColumn into partial classes so that others can extend them.
   * 2007-12-09  JPP  - Added ability to have hidden columns, i.e. columns that the ObjectListView knows
   *                    about but that are not visible to the user. Controlled by OLVColumn.IsVisible.
   *                    Added ColumnSelectionForm to the project to show how it could be used in an application.
   *
   * v1.8
   * 2007-11-26  JPP  - Cell editing fully functional
   * 2007-11-21  JPP  - Added SelectionChanged event. This event is triggered once when the
   *                    selection changes, no matter how many items are selected or deselected (in
   *                    contrast to SelectedIndexChanged which is called once for every row that
   *                    is selected or deselected). Thanks to lupokehl42 (Daniel) for his suggestions and
   *                    improvements on this idea.
   * 2007-11-19  JPP  - First take at cell editing
   * 2007-11-17  JPP  - Changed so that items within a group are not sorted if lastSortOrder == None
   *                  - Only call MakeSortIndicatorImages() if we haven't already made the sort indicators
   *                    (Corrected misspelling in the name of the method too)
   * 2007-11-06  JPP  - Added ability to have secondary sort criteria when sorting
   *                    (SecondarySortColumn and SecondarySortOrder properties)
   *                  - Added SortGroupItemsByPrimaryColumn to allow group items to be sorted by the
   *                    primary column. Previous default was to sort by the grouping column.
   * v1.7
   * No big changes to this version but made to work with ListViewPrinter and released with it.
   *
   * 2007-11-05  JPP  - Changed BaseRenderer to use DrawString() rather than TextRenderer, since TextRenderer
   *                    does not work when printing.
   * v1.6
   * 2007-11-03  JPP  - Fixed some bugs in the rebuilding of DataListView.
   * 2007-10-31  JPP  - Changed to use builtin sort indicators on XP and later. This also avoids alignment
   *                    problems on Vista. (thanks to gravybod for the suggestion and example implementation)
   * 2007-10-21  JPP  - Added MinimumWidth and MaximumWidth properties to OLVColumn.
   *                  - Added ability for BuildList() to preserve selection. Calling BuildList() directly
   *                    tries to preserve selection; calling SetObjects() does not.
   *                  - Added SelectAll() and DeselectAll() methods. Useful for working with large lists.
   * 2007-10-08  JPP  - Added GetNextItem() and GetPreviousItem(), which walk sequentially through the
   *                    listview items, even when the view is grouped.
   *                  - Added SelectedItem property
   * 2007-09-28  JPP  - Optimized aspect-to-string conversion. BuildList() 15% faster.
   *                  - Added empty implementation of RefreshObjects() to VirtualObjectListView since
   *                    RefreshObjects() cannot work on virtual lists.
   * 2007-09-13  JPP  - Corrected bug with custom sorter in VirtualObjectListView (thanks for mpgjunky)
   * 2007-09-07  JPP  - Corrected image scaling bug in DrawAlignedImage() (thanks to krita970)
   * 2007-08-29  JPP  - Allow item count labels on groups to be set per column (thanks to cmarlow for idea)
   * 2007-08-14  JPP  - Major rework of DataListView based on Ian Griffiths's great work
   * 2007-08-11  JPP  - When empty, the control can now draw a "List Empty" message
   *                  - Added GetColumn() and GetItem() methods
   * v1.5
   * 2007-08-03  JPP  - Support animated GIFs in ImageRenderer
   *                  - Allow height of rows to be specified - EXPERIMENTAL!
   * 2007-07-26  JPP  - Optimised redrawing of owner-drawn lists by remembering the update rect
   *                  - Allow sort indicators to be turned off
   * 2007-06-30  JPP  - Added RowFormatter delegate
   *                  - Allow a different label when there is only one item in a group (thanks to cmarlow)
   * v1.4
   * 2007-04-12  JPP  - Allow owner drawn on steriods!
   *                  - Column headers now display sort indicators
   *                  - ImageGetter delegates can now return ints, strings or Images
   *                    (Images are only visible if the list is owner drawn)
   *                  - Added OLVColumn.MakeGroupies to help with group partitioning
   *                  - All normal listview views are now supported
   *                  - Allow dotted aspect names, e.g. Owner.Workgroup.Name (thanks to OlafD)
   *                  - Added SelectedObject and SelectedObjects properties
   * v1.3
   * 2007-03-01  JPP  - Added DataListView
   *                  - Added VirtualObjectListView
   * 					- Added Freeze/Unfreeze capabilities
   *                  - Allowed sort handler to be installed
   *                  - Simplified sort comparisons: handles 95% of cases with only 6 lines of code!
   *                  - Fixed bug with alternative line colors on unsorted lists (thanks to cmarlow)
   * 2007-01-13  JPP  - Fixed bug with lastSortOrder (thanks to Kwan Fu Sit)
   *                  - Non-OLVColumns are no longer allowed
   * 2007-01-04  JPP  - Clear sorter before rebuilding list. 10x faster! (thanks to aaberg)
   *                  - Include GetField in GetAspectByName() so field values can be Invoked too.
   * 					- Fixed subtle bug in RefreshItem() that erased background colors.
   * 2006-11-01  JPP  - Added alternate line colouring
   * 2006-10-20  JPP  - Refactored all sorting comparisons and made it extendable. See ComparerManager.
   *                  - Improved IDE integration
   *                  - Made control DoubleBuffered
   *                  - Added object selection methods
   * 2006-10-13  JPP  Implemented grouping and column sorting
   * 2006-10-09  JPP  Initial version
   *
   * TO DO:
   * - Extend MappedImageRender to be able to draw more than image if its Aspect returns an ICollection.
   *
   * Copyright (C) 2006-2008 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 <http://www.gnu.org/licenses/>.
   *
   * 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.Data;
  using System.Diagnostics;
  using System.Drawing;
  using System.Drawing.Design;
  using System.Drawing.Drawing2D;
  using System.Drawing.Imaging;
  using System.Globalization;
  using System.IO;
  using System.Reflection;
  using System.Runtime.InteropServices;
  using System.Text;
  using System.Threading;
  using System.Windows.Forms;
  using System.Runtime.Serialization.Formatters.Binary;
  
  namespace CDBurnerXP.Controls
  {
      /// <summary>
      /// An object list displays 'aspects' of a collection of objects in a listview control.
      /// </summary>
      /// <remarks>
      /// <para>
      /// The intelligence for this control is in the columns. OLVColumns are
      /// extended so they understand how to fetch an 'aspect' from each row
      /// object. They also understand how to sort by their aspect, and
      /// how to group them.
      /// </para>
      /// <para>
      /// Aspects are extracted by giving the name of a method to be called or a
      /// property to be fetched. These names can be simple names or they can be dotted
      /// to chain property access e.g. "Owner.Address.Postcode".
      /// Aspects can also be extracted by installing a delegate.
      /// </para>
      /// <para>
      /// Sorting by column clicking and grouping by column are handled automatically.
      /// </para>
      /// <para>
      /// Right clicking on the column header should present a popup menu that allows the user to
      /// choose which columns will be visible in the list. This behaviour can be disabled by
      /// setting SelectColumnsOnRightClick to false.
      /// </para>
      /// <para>
      /// This list puts sort indicators in the column headers to show the column sorting direction.
      /// On Windows XP and later, the system standard images are used.
      /// If you wish to replace the standard images with your own images, put entries in the small image list
      /// with the key values "sort-indicator-up" and "sort-indicator-down".
      /// </para>
      /// <para>
      /// For these classes to build correctly, the project must have references to these assemblies:
      /// <list>
      /// <item>System.Data</item>
      /// <item>System.Design</item>
      /// <item>System.Drawing</item>
      /// <item>System.Windows.Forms (obviously)</item>
      /// </list>
      /// </para>
      /// </remarks>
      public partial class ObjectListView : ListView, ISupportInitialize
      {
          /// <summary>
          /// Create an ObjectListView
          /// </summary>
          public ObjectListView()
              : base()
          {
              this.ColumnClick += new ColumnClickEventHandler(this.HandleColumnClick);
              this.ItemCheck += new ItemCheckEventHandler(this.HandleItemCheck);
              this.Layout += new LayoutEventHandler(this.HandleLayout);
              this.ColumnWidthChanging += new ColumnWidthChangingEventHandler(this.HandleColumnWidthChanging);
              this.ColumnWidthChanged += new ColumnWidthChangedEventHandler(this.HandleColumnWidthChanged);
  
              base.View = View.Details;
              this.DoubleBuffered = true; // kill nasty flickers. hiss... me hates 'em
              this.AlternateRowBackColor = Color.Empty;
              this.ShowSortIndicators = true;
              this.isOwnerOfObjects = false;
          }
  
          #region Public properties
  
          /// <summary>
          /// Get or set all the columns that this control knows about.
          /// Only those columns where IsVisible is true will be seen by the user.
          /// </summary>
          /// <remarks>If you want to add new columns programmatically, add them to
          /// AllColumns and then call RebuildColumns(). Normally, you do not have to
          /// deal with this property directly. Just use the IDE.</remarks>
          [Browsable(false),
          DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
          public List<OLVColumn> AllColumns
          {
              get
              {
                  // If someone has wiped out the columns, put the list back
                  if (allColumns == null)
                      allColumns = new List<OLVColumn>();
  
                  // If we don't know the columns, use the columns from the control.
                  // This handles legacy cases
                  if (allColumns.Count == 0 && this.Columns.Count > 0) {
                      for (int i = 0; i < this.Columns.Count; i++) {
                          this.allColumns.Add(this.GetColumn(i));
                      }
                  }
                  return allColumns;
              }
              set { allColumns = value; }
          }
          private List<OLVColumn> allColumns = new List<OLVColumn>();
  
          /// <summary>
          /// If every second row has a background different to the control, what color should it be?
          /// </summary>
          [Category("Appearance"),
           Description("If using alternate colors, what color should alterate rows be?"),
           DefaultValue(typeof(Color), "Empty")]
          public Color AlternateRowBackColor
          {
              get { return alternateRowBackColor; }
              set { alternateRowBackColor = value; }
          }
          private Color alternateRowBackColor = Color.Empty;
  
          /// <summary>
          /// Return the alternate row background color that has been set, or the default color
          /// </summary>
          [Browsable(false)]
          public Color AlternateRowBackColorOrDefault
          {
              get
              {
                  if (alternateRowBackColor == Color.Empty)
                      return Color.LemonChiffon;
                  else
                      return alternateRowBackColor;
              }
          }
  
          /// <summary>
          /// This property forces the ObjectListView to always group items by the given column.
          /// </summary>
          public OLVColumn AlwaysGroupByColumn
          {
              get { return alwaysGroupByColumn; }
              set { alwaysGroupByColumn = value; }
          }
          private OLVColumn alwaysGroupByColumn;
  
          /// <summary>
          /// If AlwaysGroupByColumn is not null, this property will be used to decide how
          /// those groups are sorted. If this property has the value SortOrder.None, then
          /// the sort order will toggle according to the users last header click.
          /// </summary>
          public SortOrder AlwaysGroupBySortOrder
          {
              get { return alwaysGroupBySortOrder; }
              set { alwaysGroupBySortOrder = value; }
          }
          private SortOrder alwaysGroupBySortOrder = SortOrder.None;
  
          /// <summary>
          /// Give access to the image list that is actually being used by the control
          /// </summary>
          [Browsable(false)]
          public ImageList BaseSmallImageList
          {
              get { return base.SmallImageList; }
          }
  
          /// <summary>
          /// How does a user indicate that they want to edit cells?
          /// </summary>
          public enum CellEditActivateMode
          {
              /// <summary>
              /// This list cannot be edited. F2 does nothing.
              /// </summary>
              None = 0,
  
              /// <summary>
              /// A single click on  a <strong>subitem</strong> will edit the value. Single clicking the primary column,
              /// selects the row just like normal. The user must press F2 to edit the primary column.
              /// </summary>
              SingleClick = 1,
  
              /// <summary>
              /// Double clicking a subitem or the primary column will edit that cell.
              /// F2 will edit the primary column.
              /// </summary>
              DoubleClick = 2,
  
              /// <summary>
              /// Pressing F2 is the only way to edit the cells. Once the primary column is being edited,
              /// the other cells in the row can be edited by pressing Tab.
              /// </summary>
              F2Only = 3
          }
  
          /// <summary>
          /// How does the user indicate that they want to edit a cell?
          /// None means that the listview cannot be edited.
          /// </summary>
          /// <remarks>Columns can also be marked as editable.</remarks>
          [Category("Behavior"),
          Description("How does the user indicate that they want to edit a cell?"),
          DefaultValue(CellEditActivateMode.None)]
          public CellEditActivateMode CellEditActivation
          {
              get { return cellEditActivation; }
              set { cellEditActivation = value; }
          }
          private CellEditActivateMode cellEditActivation = CellEditActivateMode.None;
  
          /// <summary>
          /// Return the model object of the row that is checked or null if no row is checked
          /// or more than one row is checked
          /// </summary>
          public Object CheckedObject
          {
              get {
                  ArrayList checkedObjects = this.GetCheckedObjects();
                  if (checkedObjects.Count == 1)
                      return checkedObjects[0];
                  else
                      return null;
              }
          }
  
          /// <summary>
          /// Get or set the collection of model objects that are checked.
          /// When setting this property, any row whose model object isn't
          /// in the given collection will be unchecked. Setting to null is
          /// equivilent to unchecking all.
          /// </summary>
          /// <remarks>
          /// <para>
          /// This property returns a simple collection. Changes made to the returned
          /// collection do NOT affect the list. This is different to the behaviour of
          /// CheckedIndicies collection.
          /// </para>
          /// <para>
          /// The CheckedItems property is not helpful. It is just a short-hand for
          /// iterating through the list looking for items that are checked.
          /// </para>
          /// <para>
          /// The performance of this method is O(n). Be careful on long lists.
          /// </para>
          /// </remarks>
          [Browsable(false),
           DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
          public IList CheckedObjects
          {
              get {
                  ArrayList objects = new ArrayList();
                  if (this.CheckBoxes) {
                      for (int i = 0; i < this.GetItemCount(); i++) {
                          OLVListItem olvi = this.GetItem(i);
                          if (olvi.Checked)
                              objects.Add(olvi.RowObject);
                      }
                  }
                  return objects;
              }
              set {
                  if (this.CheckBoxes) {
                      if (value == null)
                          value = new ArrayList();
  
                      for (int i = 0; i < this.GetItemCount(); i++) {
                          OLVListItem olvi = this.GetItem(i);
                          bool newValue = value.Contains(olvi.RowObject);
                          if (olvi.Checked != newValue)
                              this.ChangeCheckItem(olvi, olvi.Checked, newValue);
                      }
                  }
              }
          }
  
          /// <summary>
          /// Get/set the list of columns that should be used when the list switches to tile view.
          /// </summary>
          /// <remarks>If no list of columns has been installed, this value will default to the
          /// first column plus any column where IsTileViewColumn is true.</remarks>
          [Browsable(false),
          Obsolete("Use GetFilteredColumns() and OLVColumn.IsTileViewColumn instead"),
          DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
          public List<OLVColumn> ColumnsForTileView
          {
              get { return this.GetFilteredColumns(View.Tile); }
          }
  
          /// <summary>
          /// Return the visible columns in the order they are displayed to the user
          /// </summary>
          [Browsable(false)]
          public List<OLVColumn> ColumnsInDisplayOrder
          {
              get
              {
                  List<OLVColumn> columnsInDisplayOrder = new List<OLVColumn>(this.Columns.Count);
                  for (int i = 0; i < this.Columns.Count; i++)
                      columnsInDisplayOrder.Add(null);
                  for (int i = 0; i < this.Columns.Count; i++) {
                      OLVColumn col = this.GetColumn(i);
                      columnsInDisplayOrder[col.DisplayIndex] = col;
                  }
                  return columnsInDisplayOrder;
              }
          }
  
          /// <summary>
          /// If there are no items in this list view, what message should be drawn onto the control?
          /// </summary>
          [Category("Appearance"),
           Description("When the list has no items, show this message in the control"),
           DefaultValue("")]
          public String EmptyListMsg
          {
              get { return emptyListMsg; }
              set
              {
                  if (emptyListMsg != value) {
                      emptyListMsg = value;
                      this.Invalidate();
                  }
              }
          }
          private String emptyListMsg = "";
  
          /// <summary>
          /// What font should the 'list empty' message be drawn in?
          /// </summary>
          [Category("Appearance"),
          Description("What font should the 'list empty' message be drawn in?"),
          DefaultValue(null)]
          public Font EmptyListMsgFont
          {
              get { return emptyListMsgFont; }
              set { emptyListMsgFont = value; }
          }
          private Font emptyListMsgFont;
  
          /// <summary>
          /// Return the font for the 'list empty' message or a default
          /// </summary>
          [Browsable(false)]
          public Font EmptyListMsgFontOrDefault
          {
              get
              {
                  if (this.EmptyListMsgFont == null)
                      return new Font("Tahoma", 14);
                  else
                      return this.EmptyListMsgFont;
              }
          }
  
          /// <summary>
          /// Get or set whether or not the listview is frozen. When the listview is
          /// frozen, it will not update itself.
          /// </summary>
          /// <remarks><para>The Frozen property is similar to the methods Freeze()/Unfreeze()
          /// except that changes to the Frozen property do not nest.</para></remarks>
          /// <example>objectListView1.Frozen = false; // unfreeze the control regardless of the number of Freeze() calls
          /// </example>
          [Browsable(false),
           DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
          public bool Frozen
          {
              get { return freezeCount > 0; }
              set
              {
                  if (value)
                      Freeze();
                  else if (freezeCount > 0) {
                      freezeCount = 1;
                      Unfreeze();
                  }
              }
          }
          private int freezeCount = 0;
  
          /// <summary>
          /// When a group title has an item count, how should the lable be formatted?
          /// </summary>
          /// <remarks>
          /// The given format string can/should have two placeholders:
          /// <list type="bullet">
          /// <item>{0} - the original group title</item>
          /// <item>{1} - the number of items in the group</item>
          /// </list>
          /// </remarks>
          /// <example>"{0} [{1} items]"</example>
          [Category("Behavior"),
           Description("The format to use when suffixing item counts to group titles"),
           DefaultValue(null)]
          public string GroupWithItemCountFormat
          {
              get { return groupWithItemCountFormat; }
              set { groupWithItemCountFormat = value; }
          }
          private string groupWithItemCountFormat;
  
          /// <summary>
          /// Return this.GroupWithItemCountFormat or a reasonable default
          /// </summary>
          [Browsable(false)]
          public string GroupWithItemCountFormatOrDefault
          {
              get
              {
                  if (String.IsNullOrEmpty(this.GroupWithItemCountFormat))
                      return "{0} [{1} items]";
                  else
                      return this.GroupWithItemCountFormat;
              }
          }
  
          /// <summary>
          /// When a group title has an item count, how should the lable be formatted if
          /// there is only one item in the group?
          /// </summary>
          /// <remarks>
          /// The given format string can/should have two placeholders:
          /// <list type="bullet">
          /// <item>{0} - the original group title</item>
          /// <item>{1} - the number of items in the group (always 1)</item>
          /// </list>
          /// </remarks>
          /// <example>"{0} [{1} item]"</example>
          [Category("Behavior"),
           Description("The format to use when suffixing item counts to group titles"),
           DefaultValue(null)]
          public string GroupWithItemCountSingularFormat
          {
              get { return groupWithItemCountSingularFormat; }
              set { groupWithItemCountSingularFormat = value; }
          }
          private string groupWithItemCountSingularFormat;
  
          /// <summary>
          /// Return this.GroupWithItemCountSingularFormat or a reasonable default
          /// </summary>
          [Browsable(false)]
          public string GroupWithItemCountSingularFormatOrDefault
          {
              get
              {
                  if (String.IsNullOrEmpty(this.GroupWithItemCountSingularFormat))
                      return "{0} [{1} item]";
                  else
                      return this.GroupWithItemCountSingularFormat;
              }
          }
  
          /// <summary>
          /// Does this listview have a message that should be drawn when the list is empty?
          /// </summary>
          [Browsable(false)]
          public bool HasEmptyListMsg
          {
              get { return !String.IsNullOrEmpty(this.EmptyListMsg); }
          }
  
          /// <summary>
          /// What color should be used for the background of selected rows?
          /// </summary>
          /// <remarks>Windows does not give the option of changing the selection background.
          /// So this color is only used when control is owner drawn and when columns have a
          /// renderer installed -- a basic new BaseRenderer() will suffice.</remarks>
          [Category("Appearance"),
           Description("The background color of selected rows when the control is owner drawn"),
           DefaultValue(typeof(Color), "Empty")]
          public Color HighlightBackgroundColor
          {
              get { return highlightBackgroundColor; }
              set { highlightBackgroundColor = value; }
          }
          private Color highlightBackgroundColor = Color.Empty;
  
          /// <summary>
          /// Return the color should be used for the background of selected rows or a reasonable default
          /// </summary>
          [Browsable(false)]
          public Color HighlightBackgroundColorOrDefault
          {
              get  {
                  if (this.HighlightBackgroundColor.IsEmpty)
                      return SystemColors.Highlight;
                  else
                      return this.HighlightBackgroundColor;
              }
          }
  
          /// <summary>
          /// Setup the list so it will draw selected rows using custom colours.
          /// </summary>
          /// <remarks>
          /// This method makes the list owner drawn, and ensures that all columns have at
          /// least a BaseRender installed.
          /// </remarks>
          public void EnableCustomSelectionColors()
          {
              this.OwnerDraw = true;
  
              foreach (OLVColumn column in this.AllColumns) {
                  if (column.RendererDelegate == null)
                      column.Renderer = new BaseRenderer();
              }
          }
          /// <summary>
          /// What color should be used for the foreground of selected rows?
          /// </summary>
          /// <remarks>Windows does not give the option of changing the selection foreground (text color).
          /// So this color is only used when control is owner drawn and when columns have a
          /// renderer installed -- a basic new BaseRenderer() will suffice.</remarks>
          [Category("Appearance"),
           Description("The foreground color of selected rows when the control is owner drawn"),
           DefaultValue(typeof(Color), "Empty")]
          public Color HighlightForegroundColor
          {
              get { return highlightForegroundColor; }
              set { highlightForegroundColor = value; }
          }
          private Color highlightForegroundColor = Color.Empty;
  
          /// <summary>
          /// Return the color should be used for the foreground of selected rows or a reasonable default
          /// </summary>
          public Color HighlightForegroundColorOrDefault
          {
              get {
                  if (this.HighlightForegroundColor.IsEmpty)
                      return SystemColors.HighlightText;
                  else
                      return this.HighlightForegroundColor;
              }
          }
  
          /// <summary>
          /// Return true if a cell edit operation is currently happening
          /// </summary>
          [Browsable(false)]
          public bool IsCellEditing
          {
              get { return this.cellEditor != null; }
          }
  
          /// <summary>
          /// Get/set the collection of objects that this list will show
          /// </summary>
          /// <remarks>The contents of the control will be updated immediately after setting this property</remarks>
          [Browsable(false),
           DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
          public IEnumerable Objects
          {
              get { return this.objects; }
              set { this.SetObjects(value); }
          }
          private IEnumerable objects;
  
          /// <summary>
          /// Take ownership of the 'objects' collection. This separats our collection from the source.
          /// </summary>
          /// <remarks>
          /// <para>
          /// This method
          /// separates the 'objects' instance variable from its source, so that any AddObject/RemoveObject
          /// calls will modify our collection and not the original colleciton.
          /// </para>
          /// <para>
          /// This method has the intentional side-effect of converting our list of objects to an ArrayList.
          /// </para>
          /// </remarks>
          virtual protected void TakeOwnershipOfObjects()
          {
              if (this.isOwnerOfObjects)
                  return;
  
              this.isOwnerOfObjects = true;
  
              if (this.objects == null)
                  this.objects = new ArrayList();
              else if (this.objects is ICollection)
                  this.objects = new ArrayList((ICollection)this.objects);
              else {
                  ArrayList newObjects = new ArrayList();
                  foreach (object x in this.objects)
                      newObjects.Add(x);
                  this.objects = newObjects;
              }
          }
  
          /// <summary>
          /// Specify the height of each row in the control in pixels.
          /// </summary>
          /// <remarks><para>The row height in a listview is normally determined by the font size and the small image list size.
          /// This setting allows that calculation to be overridden (within reason: you still cannot set the line height to be
          /// less than the line height of the font used in the control). </para>
          /// <para>Setting it to -1 means use the normal calculation method.</para>
          /// <para><bold>This feature is experiemental!</bold> Strange things may happen to your program,
          /// your spouse or your pet if you use it.</para>
          /// </remarks>
          [Category("Appearance"),
           DefaultValue(-1)]
          public int RowHeight
          {
              get { return rowHeight; }
              set
              {
                  if (value < 1)
                      rowHeight = -1;
                  else
                      rowHeight = value;
                  this.SetupExternalImageList();
              }
          }
          private int rowHeight = -1;
  
          /// <summary>
          /// Get/set the column that will be used to resolve comparisons that are equal when sorting.
          /// </summary>
          /// <remarks>There is no user interface for this setting. It must be set programmatically.
          /// The default is the first column.</remarks>
          [Browsable(false),
           DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
          public OLVColumn SecondarySortColumn
          {
              get
              {
                  if (this.secondarySortColumn == null) {
                      if (this.Columns.Count > 0)
                          return this.GetColumn(0);
                      else
                          return null;
                  } else
                      return this.secondarySortColumn;
              }
              set
              {
                  this.secondarySortColumn = value;
              }
          }
          private OLVColumn secondarySortColumn;
  
          /// <summary>
          /// When the SecondarySortColumn is used, in what order will it compare results?
          /// </summary>
          [Browsable(false),
          DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
          public SortOrder SecondarySortOrder
          {
              get { return this.secondarySortOrder; }
              set { this.secondarySortOrder = value; }
          }
          private SortOrder secondarySortOrder = SortOrder.Ascending;
  
          /// <summary>
          /// When the user right clicks on the column headers, should a menu be presented which will allow
          /// them to choose which columns will be shown in the view?
          /// </summary>
          [Category("Behavior"),
          Description("When the user right clicks on the column headers, should a menu be presented which will allow them to choose which columns will be shown in the view?"),
          DefaultValue(true)]
          public bool SelectColumnsOnRightClick
          {
              get { return selectColumnsOnRightClick; }
              set { selectColumnsOnRightClick = value; }
          }
          private bool selectColumnsOnRightClick = true;
  
          /// <summary>
          /// When the column select menu is open, should it stay open after an item is selected?
          /// Staying open allows the user to turn more than one column on or off at a time.
          /// </summary>
          [Category("Behavior"),
          Description("When the column select menu is open, should it stay open after an item is selected?"),
          DefaultValue(true)]
          public bool SelectColumnsMenuStaysOpen
          {
              get { return selectColumnsMenuStaysOpen; }
              set { selectColumnsMenuStaysOpen = value; }
          }
          private bool selectColumnsMenuStaysOpen = true;
  
          /// <summary>
          /// Return the index of the row that is currently selected. If no row is selected,
          /// or more than one is selected, return -1.
          /// </summary>
          [Browsable(false),
           DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
          public int SelectedIndex
          {
              get
              {
                  if (this.SelectedIndices.Count == 1)
                      return this.SelectedIndices[0];
                  else
                      return -1;
              }
              set
              {
                  this.SelectedIndices.Clear();
                  if (value >= 0 && value < this.Items.Count)
                      this.SelectedIndices.Add(value);
              }
          }
  
          /// <summary>
          /// Get the ListViewItem that is currently selected . If no row is selected, or more than one is selected, return null.
          /// </summary>
          [Browsable(false),
           DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
          public ListViewItem SelectedItem
          {
              get
              {
                  if (this.SelectedIndices.Count == 1)
                      return this.GetItem(this.SelectedIndices[0]);
                  else
                      return null;
              }
              set
              {
                  this.SelectedIndices.Clear();
                  if (value != null)
                      this.SelectedIndices.Add(value.Index);
              }
          }
  
          /// <summary>
          /// Get the model object from the currently selected row. If no row is selected, or more than one is selected, return null.
          /// Select the row that is displaying the given model object. All other rows are deselected.
          /// </summary>
          [Browsable(false),
           DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
          public Object SelectedObject
          {
              get { return this.GetSelectedObject(); }
              set { this.SelectObject(value); }
          }
  
          /// <summary>
          /// Get the model objects from the currently selected rows. If no row is selected, the returned List will be empty.
          /// When setting this value, select the rows that is displaying the given model objects. All other rows are deselected.
          /// </summary>
          [Browsable(false),
           DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
          public ArrayList SelectedObjects
          {
              get { return this.GetSelectedObjects(); }
              set { this.SelectObjects(value); }
          }
  
          /// <summary>
          /// Should the list view show a bitmap in the column header to show the sort direction?
          /// </summary>
          /// <remarks>
          /// The only reason for not wanting to have sort indicators is that, on pre-XP versions of
          /// Windows, having sort indicators required the ListView to have a small image list, and
          /// as soon as you give a ListView a SmallImageList, the text of column 0 is bumped 16
          /// pixels to the right, even if you never used an image.
          /// </remarks>
          [Category("Behavior"),
           Description("Should the list view show sort indicators in the column headers?"),
           DefaultValue(true)]
          public bool ShowSortIndicators
          {
              get { return showSortIndicators; }
              set { showSortIndicators = value; }
          }
          private bool showSortIndicators;
  
          /// <summary>
          /// Should the list view show images on subitems?
          /// </summary>
          /// <remarks>
          /// <para>Under Windows, this works by sending messages to the underlying
          /// Windows control. To make this work under Mono, we would have to owner drawing the items :-(</para></remarks>
          [Category("Behavior"),
           Description("Should the list view show images on subitems?"),
           DefaultValue(false)]
          public bool ShowImagesOnSubItems
          {
              get
              {
  #if MONO
                  return false;
  #else
                  return showImagesOnSubItems;
  #endif
              }
              set { showImagesOnSubItems = value; }
          }
          private bool showImagesOnSubItems;
  
          /// <summary>
          /// This property controls whether group labels will be suffixed with a count of items.
          /// </summary>
          /// <remarks>
          /// The format of the suffix is controlled by GroupWithItemCountFormat/GroupWithItemCountSingularFormat properties
          /// </remarks>
          [Category("Behavior"),
           Description("Will group titles be suffixed with a count of the items in the group?"),
           DefaultValue(false)]
          public bool ShowItemCountOnGroups
          {
              get { return showItemCountOnGroups; }
              set { showItemCountOnGroups = value; }
          }
          private bool showItemCountOnGroups;
  
          /// <summary>
          /// Override the SmallImageList property so we can correctly shadow its operations.
          /// </summary>
          /// <remarks><para>If you use the RowHeight property to specify the row height, the SmallImageList
          /// must be fully initialised before setting/changing the RowHeight. If you add new images to the image
          /// list after setting the RowHeight, you must assign the imagelist to the control again. Something as simple
          /// as this will work:
          /// <code>listView1.SmallImageList = listView1.SmallImageList;</code></para>
          /// </remarks>
          new public ImageList SmallImageList
          {
              get { return this.shadowedImageList; }
              set
              {
                  this.shadowedImageList = value;
                  this.SetupExternalImageList();
              }
          }
          private ImageList shadowedImageList = null;
  
          /// <summary>
          /// When the listview is grouped, should the items be sorted by the primary column?
          /// If this is false, the items will be sorted by the same column as they are grouped.
          /// </summary>
          [Category("Behavior"),
           Description("When the listview is grouped, should the items be sorted by the primary column? If this is false, the items will be sorted by the same column as they are grouped."),
           DefaultValue(true)]
          public bool SortGroupItemsByPrimaryColumn
          {
              get { return this.sortGroupItemsByPrimaryColumn; }
              set { this.sortGroupItemsByPrimaryColumn = value; }
          }
          private bool sortGroupItemsByPrimaryColumn = true;
  
          /// <summary>
          /// When resizing a column by dragging its divider, should any space filling columns be
          /// resized at each mouse move? If this is false, the filling columns will be
          /// updated when the mouse is released.
          /// </summary>
          /// <remarks>
          /// I think that this looks very ugly, but it does give more immediate feedback.
          /// It looks ugly because every
          /// column to the right of the divider being dragged gets updated twice: once when
          /// the column be resized changes size (this moves
          /// all the columns slightly to the right); then again when the filling columns are updated, but they will be shrunk
          /// so that the combined width is not more than the control, so everything jumps slightly back to the left again.
          /// </remarks>
          [Category("Behavior"),
          Description("When resizing a column by dragging its divider, should any space filling columns be resized at each mouse move?"),
          DefaultValue(false)]
          public bool UpdateSpaceFillingColumnsWhenDraggingColumnDivider
          {
              get { return updateSpaceFillingColumnsWhenDraggingColumnDivider; }
              set { updateSpaceFillingColumnsWhenDraggingColumnDivider = value; }
          }
          private bool updateSpaceFillingColumnsWhenDraggingColumnDivider = false;
  
          /// <summary>
          /// Should the list give a different background color to every second row?
          /// </summary>
          /// <remarks><para>The color of the alternate rows is given by AlternateRowBackColor.</para>
          /// <para>There is a "feature" in .NET for listviews in non-full-row-select mode, where
          /// selected rows are not drawn with their correct background color.</para></remarks>
          [Category("Appearance"),
           Description("Should the list view use a different backcolor to alternate rows?"),
           DefaultValue(false)]
          public bool UseAlternatingBackColors
          {
              get { return useAlternatingBackColors; }
              set { useAlternatingBackColors = value; }
          }
          private bool useAlternatingBackColors;
  
  
          /// <summary>
          /// Get/set the style of view that this listview is using
          /// </summary>
          /// <remarks>Switching to tile or details view installs the columns appropriate to that view.
          /// Confusingly, in tile view, every column is shown as a row of information.</remarks>
          new public View View
          {
              get { return base.View; }
              set
              {
                  if (base.View == value)
                      return;
  
                  if (this.Frozen) {
                      base.View = value;
                      return;
                  }
  
                  this.Freeze();
  
                  // If we are switching to a Detail or Tile view, setup the columns needed for that view
                  if (value == View.Details || value == View.Tile) {
                      this.ChangeToFilteredColumns(value);
  
                      if (value == View.Tile)
                          this.CalculateReasonableTileSize();
                  }
  
                  base.View = value;
                  this.Unfreeze();
              }
          }
  
          #endregion
  
          #region Callbacks
  
          /// <summary>
          /// This delegate can be used to sort the table in a custom fasion.
          /// </summary>
          /// <remarks>
          /// <para>
          /// What the delegate has to do depends on the type of <code>ObjectListView</code> it is sorting:
          /// </para>
          /// <list>
          /// <item>
          /// If it is sorting a normal ObjectListView, the delegate must install a ListViewItemSorter on the ObjectListView. This install ItemSorter will actually do the work of sorting the ListViewItems. See ColumnComparer in the code for an example of what an ItemSorter has to do.
          /// </item>
          /// <item>
          /// If the delegate is sorting a VirtualObjectListView or a FastObjectListView, the delegate must sort the model objects that are sourcing the list (remember, in a virtual list, the application holds the model objects and the list just askes for them as it needs them).
          /// </item>
          /// </list>
          /// </remarks>
          [Browsable(false),
           DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
          public SortDelegate CustomSorter
          {
              get { return customSorter; }
              set { customSorter = value; }
          }
          private SortDelegate customSorter;
  
          /// <summary>
          /// This delegate can be used to format a OLVListItem before it is added to the control.
          /// </summary>
          /// <remarks>
          /// <para>The model object for the row can be found through the RowObject property of the OLVListItem object.</para>
          /// <para>All subitems normally have the same style as list item, so setting the forecolor on one
          /// subitem changes the forecolor of all subitems.
          /// To allow subitems to have different attributes, do this:<code>myListViewItem.UseItemStyleForSubItems = false;</code>.
          /// </para>
          /// <para>If UseAlternatingBackColors is true, the backcolor of the listitem will be calculated
          /// by the control and cannot be controlled by the RowFormatter delegate. In general, trying to use a RowFormatter
          /// when UseAlternatingBackColors is true does not work well.</para></remarks>
          [Browsable(false),
           DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
          public RowFormatterDelegate RowFormatter
          {
              get { return rowFormatter; }
              set { rowFormatter = value; }
          }
          private RowFormatterDelegate rowFormatter;
  
          /// <summary>
          /// This delegate will be called whenever the ObjectListView needs to know the check state
          /// of the row associated with a given model object
          /// </summary>
          [Browsable(false),
           DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
          public CheckStateGetterDelegate CheckStateGetter
          {
              get { return checkStateGetter; }
              set { checkStateGetter = value; }
          }
          private CheckStateGetterDelegate checkStateGetter;
  
          /// <summary>
          /// This delegate will be called whenever the user tries to change the check state
          /// of a row. The delegate should return the value that the listview should actuall
          /// use, which may be different to the one given to the delegate.
          /// </summary>
          [Browsable(false),
           DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
          public CheckStatePutterDelegate CheckStatePutter
          {
              get { return checkStatePutter; }
              set { checkStatePutter = value; }
          }
          private CheckStatePutterDelegate checkStatePutter;
  
          #endregion
  
          #region List commands
  
          /// <summary>
          /// Set the collection of objects that will be shown in this list view.
          /// </summary>
          /// <remark>This method can safely be called from background threads.</remark>
          /// <remarks>The list is updated immediately</remarks>
          /// <param name="collection">The objects to be displayed</param>
          virtual public void SetObjects(IEnumerable collection)
          {
              if (this.InvokeRequired) {
                  this.Invoke((MethodInvoker)delegate { this.SetObjects(collection); });
                  return;
              }
              // If we own the current list and they change to another list, we don't own it anymore
              if (this.isOwnerOfObjects && this.objects != collection)
                  this.isOwnerOfObjects = false;
              this.objects = collection;
              this.BuildList(false);
          }
  
          /// <summary>
          /// Add the given model object to this control.
          /// </summary>
          /// <param name="modelObject">The model object to be displayed</param>
          /// <remarks>See AddObjects() for more details</remarks>
          public void AddObject(object modelObject)
          {
              this.AddObjects(new object[] { modelObject });
          }
  
          /// <summary>
          /// Add the given collection of model objects to this control.
          /// </summary>
          /// <param name="modelObjects">A collection of model objects</param>
          /// <remarks>
          /// <para>The added objects will appear in their correct sort position, if sorting
          /// is active. Otherwise, they will appear at the end of the list.</para>
          /// <para>No check is performed to see if any of the objects are already in the ListView.</para>
          /// <para>The method uses the ObjectsAsList property. See that property for a
          /// description of what is does.</para>
          /// <para>Null objects are silently ignored.</para>
          /// </remarks>
          virtual public void AddObjects(ICollection modelObjects)
          {
              if (modelObjects == null)
                  return;
  
              this.BeginUpdate();
  
              this.TakeOwnershipOfObjects();
              ArrayList ourObjects = (ArrayList)this.Objects;
              List<OLVListItem> itemList = new List<OLVListItem>();
              foreach (object modelObject in modelObjects) {
                  if (modelObject != null) {
                      ourObjects.Add(modelObject);
                      OLVListItem lvi = new OLVListItem(modelObject);
                      this.FillInValues(lvi, modelObject);
                      itemList.Add(lvi);
                  }
              }
              this.Items.AddRange(itemList.ToArray());
              this.Sort(this.lastSortColumn);
  
              foreach (OLVListItem lvi in itemList) {
                  this.SetSubItemImages(lvi.Index, lvi);
              }
  
              this.EndUpdate();
          }
  
          /// <summary>
          /// Remove the given model object from the ListView
          /// </summary>
          /// <param name="modelObject">The model to be removed</param>
          /// <remarks>See RemoveObjects() for more details</remarks>
          public void RemoveObject(object modelObject)
          {
              this.RemoveObjects(new object[] { modelObject });
          }
  
          /// <summary>
          /// Remove all of the given objects from the control
          /// </summary>
          /// <param name="modelObjects">Collection of objects to be removed</param>
          /// <remarks>
          /// <para>Nulls and model objects that are not in the ListView are silently ignored.</para>
          /// </remarks>
          virtual public void RemoveObjects(ICollection modelObjects)
          {
              if (modelObjects == null)
                  return;
  
              this.BeginUpdate();
  
              this.TakeOwnershipOfObjects();
              ArrayList ourObjects = (ArrayList)this.Objects;
              foreach (object modelObject in modelObjects) {
                  if (modelObject != null) {
                      ourObjects.Remove(modelObject);
                      int i = this.IndexOf(modelObject);
                      if (i >= 0)
                          this.Items.RemoveAt(i);
                  }
              }
  
              this.EndUpdate();
          }
  
          /// <summary>
          /// Update the list to reflect the contents of the given collection, without affecting
          /// the scrolling position, selection or sort order.
          /// </summary>
          /// <param name="collection">The objects to be displayed</param>
          /// <remarks>
          /// <para>This method is about twice as slow as SetObjects().</para>
          /// <para>This method is experimental -- it may disappear in later versions of the code.</para>
          /// <para>There has to be a better way to do this! JPP 15/1/2008</para>
          /// <para>In most situations, if you need this functionality, use a FastObjectListView instead. JPP 2/2/2008</para>
          /// </remarks>
          [Obsolete("Use a FastObjectListView instead of this method.", false)]
          virtual public void IncrementalUpdate(IEnumerable collection)
          {
              if (this.InvokeRequired) {
                  this.Invoke((MethodInvoker)delegate { this.IncrementalUpdate(collection); });
                  return;
              }
  
              this.BeginUpdate();
  
              this.ListViewItemSorter = null;
              ArrayList previousSelection = this.SelectedObjects;
  
              // Replace existing rows, creating new listviewitems if we get to the end of the list
              List<OLVListItem> newItems = new List<OLVListItem>();
              int rowIndex = 0;
              int itemCount = this.Items.Count;
              foreach (object model in collection) {
                  if (rowIndex < itemCount) {
                      OLVListItem lvi = this.GetItem(rowIndex);
                      lvi.RowObject = model;
                      this.RefreshItem(lvi);
                  } else {
                      OLVListItem lvi = new OLVListItem(model);
                      this.FillInValues(lvi, model);
                      newItems.Add(lvi);
                  }
                  rowIndex++;
              }
  
              // Delete any excess rows
              int numRowsToDelete = itemCount - rowIndex;
              for (int i = 0; i < numRowsToDelete; i++)
                  this.Items.RemoveAt(rowIndex);
  
              this.Items.AddRange(newItems.ToArray());
              this.Sort(this.lastSortColumn);
  
              SetAllSubItemImages();
  
              this.SelectedObjects = previousSelection;
  
              this.EndUpdate();
  
              this.objects = collection;
          }
  
          /// <summary>
          /// Remove all items from this list
          /// </summary>
          /// <remark>This method can safely be called from background threads.</remark>
          virtual public void ClearObjects()
          {
              if (this.InvokeRequired)
                  this.Invoke(new MethodInvoker(ClearObjects));
              else
                  this.Items.Clear();
          }
  
          /// <summary>
          /// Build/rebuild all the list view items in the list
          /// </summary>
          virtual public void BuildList()
          {
              this.BuildList(true);
          }
  
          /// <summary>
          /// Build/rebuild all the list view items in the list
          /// </summary>
          /// <param name="shouldPreserveState">If this is true, the control will try to preserve the selection
          /// and the scroll position (see Remarks)
          /// </param>
          /// <remarks>
          /// <para>
          /// Use this method in situations were the contents of the list is basically the same
          /// as previously.
          /// </para>
          /// <para>
          /// Due to limitations in .NET's ListView, the scroll position is only preserved if
          /// the control is in Details view AND it is not showing groups.
          /// </para>
          /// </remarks>
          virtual public void BuildList(bool shouldPreserveState)
          {
              if (this.Frozen)
                  return;
  
              ArrayList previousSelection = new ArrayList();
              int previousTopIndex = this.TopItemIndex;
              if (shouldPreserveState && this.objects != null)
                  previousSelection = this.SelectedObjects;
  
              this.BeginUpdate();
              this.Items.Clear();
              this.ListViewItemSorter = null;
  
              if (this.objects != null) {
                  // Build a list of all our items and then display them. (Building
                  // a list and then doing one AddRange is about 10-15% faster than individual adds)
                  List<OLVListItem> itemList = new List<OLVListItem>();
                  foreach (object rowObject in this.objects) {
                      OLVListItem lvi = new OLVListItem(rowObject);
                      this.FillInValues(lvi, rowObject);
                      itemList.Add(lvi);
                  }
                  this.Items.AddRange(itemList.ToArray());
                  this.SetAllSubItemImages();
                  this.Sort(this.lastSortColumn);
  
                  if (shouldPreserveState)
                      this.SelectedObjects = previousSelection;
              }
  
              this.EndUpdate();
  
              // We can only restore the scroll position after the EndUpdate() because
              // of caching that the ListView does internally during a BeginUpdate/EndUpdate pair.
              if (shouldPreserveState)
                  this.TopItemIndex = previousTopIndex;
          }
  
          /// <summary>
          /// Get or set the index of the top item of this listview
          /// </summary>
          /// <remarks>
          /// <para>
          /// This property only works when the listview is in Details view and not showing groups.
          /// </para>
          /// <para>
          /// The reason that it does not work when showing groups is that, when groups are enabled,
          /// the Windows message LVM_GETTOPINDEX always returns 0, regardless of the
          /// scroll position.
          /// </para>
          /// </remarks>
          [Browsable(false),
          DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
          public int TopItemIndex
          {
              get
              {
                  if (this.View == View.Details && this.TopItem != null)
                      return this.TopItem.Index;
                  else
                      return -1;
              }
              set
              {
                  int newTopIndex = Math.Min(value, this.GetItemCount() - 1);
                  if (this.View == View.Details && newTopIndex >= 0) {
                      this.TopItem = this.Items[newTopIndex];
  
                      // Setting the TopItem sometimes gives off by one errors,
                      // that (bizarrely) are correct on a second attempt
                      if (this.TopItem != null && this.TopItem.Index != newTopIndex)
                          this.TopItem = this.GetItem(newTopIndex);
                  }
              }
          }
  
          /// <summary>
          /// Sort the items by the last sort column
          /// </summary>
          new public void Sort()
          {
              this.Sort(this.lastSortColumn);
          }
  
          /// <summary>
          /// Organise the view items into groups, based on the last sort column or the first column
          /// if there is no last sort column
          /// </summary>
          public void BuildGroups()
          {
              this.BuildGroups(this.lastSortColumn);
          }
  
          /// <summary>
          /// Organise the view items into groups, based on the given column
          /// </summary>
          /// <remarks>If the AlwaysGroupByColumn property is not null,
          /// the list view items will be organisd by that column,
          /// and the 'column' parameter will be ignored.</remarks>
          /// <param name="column">The column whose values should be used for sorting.</param>
          virtual public void BuildGroups(OLVColumn column)
          {
              if (column == null)
                  column = this.GetColumn(0);
  
              // If a specific column has been given as the group by column, we always
              // group by that column, regardless of what the user just clicked.
              OLVColumn groupByColumn = this.AlwaysGroupByColumn;
              if (groupByColumn == null)
                  groupByColumn = column;
  
              SortOrder groupSortOrder = this.AlwaysGroupBySortOrder;
              if (groupSortOrder == SortOrder.None)
                  groupSortOrder = this.lastSortOrder;
  
              this.Groups.Clear();
  
              // Getting the Count forces any internal cache of the ListView to be flushed. Without
              // this, iterating over the Items will not work correctly if the ListView handle
              // has not yet been created.
              int dummy = this.Items.Count;
  
              // Separate the list view items into groups, using the group key as the descrimanent
              Dictionary<object, List<OLVListItem>> map = new Dictionary<object, List<OLVListItem>>();
              foreach (OLVListItem olvi in this.Items) {
                  object key = groupByColumn.GetGroupKey(olvi.RowObject);
                  if (key == null)
                      key = "{null}"; // null can't be used as the key for a dictionary
                  if (!map.ContainsKey(key))
                      map[key] = new List<OLVListItem>();
                  map[key].Add(olvi);
              }
  
              // Make a list of the required groups
              List<ListViewGroup> groups = new List<ListViewGroup>();
              foreach (object key in map.Keys) {
                  ListViewGroup lvg = new ListViewGroup(groupByColumn.ConvertGroupKeyToTitle(key));
                  lvg.Tag = key;
                  groups.Add(lvg);
              }
  
              // Sort the groups
              groups.Sort(new ListViewGroupComparer(groupSortOrder));
  
              // Put each group into the list view, and give each group its member items.
              // The order of statements is important here:
              // - the header must be calculate before the group is added to the list view,
              //   otherwise changing the header causes a nasty redraw (even in the middle of a BeginUpdate...EndUpdate pair)
              // - the group must be added before it is given items, otherwise an exception is thrown (is this documented?)
              string fmt = groupByColumn.GroupWithItemCountFormatOrDefault;
              string singularFmt = groupByColumn.GroupWithItemCountSingularFormatOrDefault;
              ColumnComparer itemSorter = new ColumnComparer((this.SortGroupItemsByPrimaryColumn ? this.GetColumn(0) : column),
                                                             this.lastSortOrder, this.SecondarySortColumn, this.SecondarySortOrder);
              foreach (ListViewGroup group in groups) {
                  if (this.ShowItemCountOnGroups) {
                      int count = map[group.Tag].Count;
                      group.Header = String.Format((count == 1 ? singularFmt : fmt), group.Header, count);
                  }
                  this.Groups.Add(group);
                  // If there is no sort order, don't sort since the sort isn't stable
                  if (this.lastSortOrder != SortOrder.None)
                      map[group.Tag].Sort(itemSorter);
                  group.Items.AddRange(map[group.Tag].ToArray());
              }
          }
  
          /// <summary>
          /// Pause (or unpause) all animations in the list
          /// </summary>
          /// <param name="isPause">true to pause, false to unpause</param>
          public void PauseAnimations(bool isPause)
          {
              for (int i = 0; i < this.Columns.Count; i++) {
                  OLVColumn col = this.GetColumn(i);
                  if (col.Renderer is ImageRenderer)
                      ((ImageRenderer)col.Renderer).Paused = isPause;
              }
          }
  
          /// <summary>
          /// Give the listview a reasonable size of its tiles, based on the number of lines of
          /// information that each tile is going to display.
          /// </summary>
          public void CalculateReasonableTileSize()
          {
              if (this.Columns.Count <= 0)
                  return;
  
              int imageHeight = (this.LargeImageList == null ? 16 : this.LargeImageList.ImageSize.Height);
              int dataHeight = (this.Font.Height + 1) * this.Columns.Count;
              int tileWidth = (this.TileSize.Width == 0 ? 200 : this.TileSize.Width);
              int tileHeight = Math.Max(this.TileSize.Height, Math.Max(imageHeight, dataHeight));
              this.TileSize = new Size(tileWidth, tileHeight);
          }
  
          /// <summary>
          /// Rebuild this list for the given view
          /// </summary>
          /// <param name="view"></param>
          internal void ChangeToFilteredColumns(View view)
          {
              // Store the state
              ArrayList previousSelection = this.SelectedObjects;
              int previousTopIndex = this.TopItemIndex;
  
              this.Freeze();
              this.Clear();
              List<OLVColumn> cols = this.GetFilteredColumns(view);
              this.Columns.AddRange(cols.ToArray());
              if (view == View.Details) {
                  foreach (OLVColumn x in cols) {
                      if (x.LastDisplayIndex == -1 || x.LastDisplayIndex > cols.Count - 1)
                          x.DisplayIndex = cols.Count - 1;
                      else
                          x.DisplayIndex = x.LastDisplayIndex;
                  }
                  this.ShowSortIndicator();
              }
              this.BuildList();
              this.Unfreeze();
  
              // Restore the state
              this.SelectedObjects = previousSelection;
              this.TopItemIndex = previousTopIndex;
          }
  
          /// <summary>
          /// Rebuild the columns based upon its current view and column visibility settings
          /// </summary>
          public void RebuildColumns()
          {
              this.ChangeToFilteredColumns(this.View);
          }
  
          /// <summary>
          /// Copy a text and html representation of the selected rows onto the clipboard.
          /// </summary>
          /// <remarks>Be careful when using this with virtual lists. If the user has selected
          /// 10,000,000 rows, this method will faithfully try to copy all of them to the clipboard.
          /// From the user's point of view, your program will appear to have hung.</remarks>
          public void CopySelectionToClipboard()
          {
              //THINK: Do we want to include something like this?
              //if (this.SelectedIndices.Count > 10000)
              //    return;
  
              this.CopyObjectsToClipboard(this.SelectedObjects);
          }
  
          /// <summary>
          /// Copy a text and html representation of the given objects onto the clipboard.
          /// </summary>
          public void CopyObjectsToClipboard(IList objectsToCopy)
          {
              if (objectsToCopy.Count == 0)
                  return;
  
              List<OLVColumn> columns = this.ColumnsInDisplayOrder;
  
              // Build text and html versions of the selection
              StringBuilder sbText = new StringBuilder();
              StringBuilder sbHtml = new StringBuilder("<table>");
  
              foreach (object modelObject in objectsToCopy) {
                  sbHtml.Append("<tr><td>");
                  foreach (OLVColumn col in columns) {
                      if (col != columns[0]) {
                          sbText.Append("\t");
                          sbHtml.Append("</td><td>");
                      }
                      string strValue = col.GetStringValue(modelObject);
                      sbText.Append(strValue);
                      sbHtml.Append(strValue); //TODO: Should encode the string value
                  }
                  sbText.AppendLine();
                  sbHtml.AppendLine("</td></tr>");
              }
              sbHtml.AppendLine("</table>");
  
              // Put both the text and html versions onto the clipboard
              DataObject dataObject = new DataObject();
              dataObject.SetText(sbText.ToString(), TextDataFormat.UnicodeText);
              dataObject.SetText(ConvertToHtmlFragment(sbHtml.ToString()), TextDataFormat.Html);
              Clipboard.SetDataObject(dataObject);
          }
  
  
  
          /// <summary>
          /// Convert the fragment of HTML into the Clipboards HTML format.
          /// </summary>
          /// <remarks>The HTML format is found here http://msdn2.microsoft.com/en-us/library/aa767917.aspx
          /// </remarks>
          /// <param name="fragment">The HTML to put onto the clipboard. It must be valid HTML!</param>
          /// <returns>A string that can be put onto the clipboard and will be recognized as HTML</returns>
          private string ConvertToHtmlFragment(string fragment)
          {
              // Minimal implementation of HTML clipboard format
              string source = "http://www.codeproject.com/KB/list/ObjectListView.aspx";
  
              const String MARKER_BLOCK =
                  "Version:1.0\r\n" +
                  "StartHTML:{0,8}\r\n" +
                  "EndHTML:{1,8}\r\n" +
                  "StartFragment:{2,8}\r\n" +
                  "EndFragment:{3,8}\r\n" +
                  "StartSelection:{2,8}\r\n" +
                  "EndSelection:{3,8}\r\n" +
                  "SourceURL:{4}\r\n" +
                  "{5}";
  
              int prefixLength = String.Format(MARKER_BLOCK, 0, 0, 0, 0, source, "").Length;
  
              const String DEFAULT_HTML_BODY =
                  "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">" +
                  "<HTML><HEAD></HEAD><BODY><!--StartFragment-->{0}<!--EndFragment--></BODY></HTML>";
  
              string html = String.Format(DEFAULT_HTML_BODY, fragment);
              int startFragment = prefixLength + html.IndexOf(fragment);
              int endFragment = startFragment + fragment.Length;
  
              return String.Format(MARKER_BLOCK, prefixLength, prefixLength + html.Length, startFragment, endFragment, source, html);
          }
  
          /// <summary>
          /// Return a byte array that represents the current state of the ObjectListView, such
          /// that the state can be restored by RestoreState()
          /// </summary>
          /// <remarks>
          /// <para>The state of an ObjectListView includes the attributes that the user can modify:
          /// <list>
          /// <item>current view (i.e. Details, Tile, Large Icon...)</item>
          /// <item>sort column and direction</item>
          /// <item>column order</item>
          /// <item>column widths</item>
          /// <item>column visibility</item>
          /// </list>
          /// </para>
          /// <para>
          /// It does not include selection or the scroll position.
          /// </para>
          /// </remarks>
          /// <returns>A byte array representing the state of the ObjectListView</returns>
          public byte[] SaveState()
          {
              ObjectListViewState olvState = new ObjectListViewState();
              olvState.VersionNumber = 1;
              olvState.NumberOfColumns = this.AllColumns.Count;
              olvState.CurrentView = this.View;
  
              // If we have a sort column, it is possible that it is not currently being shown, in which
              // case, it's Index will be -1. So we calculate its index directly. Technically, the sort
              // column does not even have to a member of AllColumns, in which case IndexOf will return -1,
              // which is works fine since we have no way of restoring such a column anyway.
              if (this.lastSortColumn != null)
                  olvState.SortColumn = this.AllColumns.IndexOf(this.lastSortColumn);
              olvState.LastSortOrder = this.lastSortOrder;
              olvState.IsShowingGroups = this.ShowGroups;
  
              if (this.AllColumns.Count > 0 && this.AllColumns[0].LastDisplayIndex == -1)
                  this.RememberDisplayIndicies();
  
              foreach (OLVColumn column in this.AllColumns) {
                  olvState.ColumnIsVisible.Add(column.IsVisible);
                  olvState.ColumnDisplayIndicies.Add(column.LastDisplayIndex);
                  olvState.ColumnWidths.Add(column.Width);
              }
  
              // Now that we have stored our state, convert it to a byte array
              MemoryStream ms = new MemoryStream();
              BinaryFormatter serializer = new BinaryFormatter();
              serializer.Serialize(ms, olvState);
  
              return ms.ToArray();
          }
  
          /// <summary>
          /// Restore the state of the control from the given string, which must have been
          /// produced by SaveState()
          /// </summary>
          /// <param name="state">A byte array returned from SaveState()</param>
          /// <returns>Returns true if the state was restored</returns>
          public bool RestoreState(byte[] state)
          {
              MemoryStream ms = new MemoryStream(state);
              BinaryFormatter deserializer = new BinaryFormatter();
              ObjectListViewState olvState;
              try {
                  olvState = deserializer.Deserialize(ms) as ObjectListViewState;
              } catch (System.Runtime.Serialization.SerializationException) {
                  return false;
              }
  
              // The number of columns has changed. We have no way to match old
              // columns to the new ones, so we just give up.
              if (olvState.NumberOfColumns != this.AllColumns.Count)
                  return false;
  
              if (olvState.SortColumn == -1) {
                  this.lastSortColumn = null;
                  this.lastSortOrder = SortOrder.None;
              } else {
                  this.lastSortColumn = this.AllColumns[olvState.SortColumn];
                  this.lastSortOrder = olvState.LastSortOrder;
              }
  
              for (int i = 0; i < olvState.NumberOfColumns; i++) {
                  OLVColumn column = this.AllColumns[i];
                  column.Width = (int)olvState.ColumnWidths[i];
                  column.IsVisible = (bool)olvState.ColumnIsVisible[i];
                  column.LastDisplayIndex = (int)olvState.ColumnDisplayIndicies[i];
              }
  
              if (olvState.IsShowingGroups != this.ShowGroups)
                  this.ShowGroups = olvState.IsShowingGroups;
  
              if (this.View == olvState.CurrentView)
                  this.RebuildColumns();
              else
                  this.View = olvState.CurrentView;
  
              return true;
          }
  
          /// <summary>
          /// Instances of this class are used to store the state of an ObjectListView.
          /// </summary>
          [Serializable]
          internal class ObjectListViewState
          {
              public int VersionNumber = 1;
              public int NumberOfColumns = 1;
              public View CurrentView;
              public int SortColumn = -1;
              public bool IsShowingGroups;
              public SortOrder LastSortOrder = SortOrder.None;
              public ArrayList ColumnIsVisible = new ArrayList();
              public ArrayList ColumnDisplayIndicies = new ArrayList();
              public ArrayList ColumnWidths = new ArrayList();
          }
  
          #endregion
  
          #region Event handlers
  
          /// <summary>
          /// Event handler for the column click event
          /// </summary>
          virtual protected void HandleColumnClick(object sender, ColumnClickEventArgs e)
          {
              if (!this.PossibleFinishCellEditing())
                  return;
  
              // Toggle the sorting direction on successive clicks on the same column
              if (this.lastSortColumn != null && e.Column == this.lastSortColumn.Index)
                  this.lastSortOrder = (this.lastSortOrder == SortOrder.Descending ? SortOrder.Ascending : SortOrder.Descending);
              else
                  this.lastSortOrder = SortOrder.Ascending;
  
              this.BeginUpdate();
              this.Sort(e.Column);
              this.EndUpdate();
          }
  
          /// <summary>
          /// Handle when a user checks/unchecks a row
          /// </summary>
          protected void HandleItemCheck(object sender, ItemCheckEventArgs e)
          {
              if (this.CheckStatePutter != null)
                  e.NewValue = this.CheckStatePutter(this.GetModelObject(e.Index), e.NewValue);
          }
  
          #endregion
  
          #region Low level Windows message handling
  
          /// <summary>
          /// Override the basic message pump for this control
          /// </summary>
          /// <param name="m"></param>
          protected override void WndProc(ref Message m)
          {
              switch (m.Msg)
              {
                  case 0x0F: // WM_PAINT
                      this.HandlePrePaint();
                      base.WndProc(ref m);
                      this.HandlePostPaint();
                      break;
                  case 0x4E: // WM_NOTIFY
                      if (!this.HandleNotify(ref m))
                          base.WndProc(ref m);
                      break;
                  case 0x114: // WM_HSCROLL:
                  case 0x115: // WM_VSCROLL:
                  case 0x201: // WM_LBUTTONDOWN:
                  case 0x20A: // WM_MOUSEWHEEL:
                  case 0x20E: // WM_MOUSEHWHEEL:
                      if (this.PossibleFinishCellEditing())
                          base.WndProc(ref m);
                      break;
                  case 0x7B: // WM_CONTEXTMENU
                      if (!this.HandleContextMenu(ref m))
                          base.WndProc(ref m);
                      break;
                  default:
                      base.WndProc(ref m);
                      break;
              }
          }
  
          #endregion
  
          #region Empty List Msg handling
  
          /// <summary>
          /// Perform any steps needed before painting the control
          /// </summary>
          protected void HandlePrePaint()
          {
              // When we get a WM_PAINT msg, remember the rectangle that is being updated.
              // We can't get this information later, since the BeginPaint call wipes it out.
              this.lastUpdateRectangle = NativeMethods.GetUpdateRect(this);
  
              // When the list is empty, we want to handle the drawing of the control by ourselves.
              // Unfortunately, there is no easy way to tell our superclass that we want to do this.
              // So we resort to guile and deception. We validate the list area of the control, which
              // effectively tells our superclass that this area does not need to be painted.
              // Our superclass will then not paint the control, leaving us free to do so ourselves.
              // Without doing this trickery, the superclass will draw the
              // list as empty, and then moments later, we will draw the empty msg, giving a nasty flicker
              if (this.GetItemCount() == 0 && this.HasEmptyListMsg)
                  NativeMethods.ValidateRect(this, this.ClientRectangle);
          }
  
          /// <summary>
          /// Perform any steps needed after painting the control
          /// </summary>
          protected void HandlePostPaint()
          {
              // If the list isn't empty or there isn't an emptyList msg, do nothing
              if (this.GetItemCount() != 0 || !this.HasEmptyListMsg)
                  return;
  
              // Draw the empty list msg centered in the client area of the control
              using (BufferedGraphics buffered = BufferedGraphicsManager.Current.Allocate(this.CreateGraphics(), this.ClientRectangle)) {
                  Graphics g = buffered.Graphics;
                  g.Clear(this.BackColor);
                  StringFormat sf = new StringFormat();
                  sf.Alignment = StringAlignment.Center;
                  sf.LineAlignment = StringAlignment.Center;
                  sf.Trimming = StringTrimming.EllipsisCharacter;
  
                  g.DrawString(this.EmptyListMsg, this.EmptyListMsgFontOrDefault, SystemBrushes.ControlDark, this.ClientRectangle, sf);
                  
                  buffered.Render();
              }
          }
  
          #endregion
  
          #region Column header clicking, column hiding and resizing
  
          /// <summary>
          /// When the control is created capture the messages for the header.
          /// </summary>
          protected override void OnCreateControl()
          {
              base.OnCreateControl();
  #if !MONO
              hdrCtrl = new HeaderControl(this);
  #endif
          }
  #if !MONO
          private HeaderControl hdrCtrl = null;
  
          /// <summary>
          /// Class used to capture window messages for the header of the list view
          /// control.
          /// </summary>
          /// <remarks>We only need this class in order to not change the cursor
          /// when the cursor is over the divider of a fixed width column. It
          /// really is a little too perfectionist even for me.</remarks>
          private class HeaderControl : NativeWindow
          {
              private ObjectListView parentListView = null;
  
              public HeaderControl(ObjectListView olv)
              {
                  this.parentListView = olv;
                  this.AssignHandle(NativeMethods.GetHeaderControl(olv));
              }
  
              /// <summary>
              /// Return the Windows handle behind this control
              /// </summary>
              /// <remarks>
              /// When an ObjectListView is initialized as part of a UserControl, the
              /// GetHeaderControl() method returns 0 until the UserControl is
              /// completely initialized. So the AssignHandle() call in the constructor
              /// doesn't work. So we override the Handle property so value is always
              /// current.
              /// </remarks>
              public new IntPtr Handle
              {
                  get { return NativeMethods.GetHeaderControl(this.parentListView); }
              }
  
              protected override void WndProc(ref Message message)
              {
                  const int WM_SETCURSOR = 0x0020;
  
                  switch (message.Msg) {
                      case WM_SETCURSOR:
                          if (IsCursorOverLockedDivider()) {
                              message.Result = (IntPtr)1;	// Don't change the cursor
                              return;
                          }
                          break;
                  }
  
                  base.WndProc(ref message);
              }
  
              private bool IsCursorOverLockedDivider()
              {
                  Point pt = this.parentListView.PointToClient(Cursor.Position);
                  pt.X += NativeMethods.GetScrollPosition(this.parentListView.Handle, true);
                  int dividerIndex = NativeMethods.GetDividerUnderPoint(this.Handle, pt);
                  if (dividerIndex >= 0 && dividerIndex < this.parentListView.Columns.Count) {
                      OLVColumn column = this.parentListView.GetColumn(dividerIndex);
                      return column.IsFixedWidth || column.FillsFreeSpace;
                  } else
                      return false;
              }
  
              /// <summary>
              /// Return the index of the column under the current cursor position,
              /// or -1 if the cursor is not over a column
              /// </summary>
              /// <returns>Index of the column under the cursor, or -1</returns>
              public int GetColumnIndexUnderCursor()
              {
                  Point pt = this.parentListView.PointToClient(Cursor.Position);
                  pt.X += NativeMethods.GetScrollPosition(this.parentListView.Handle, true);
                  return NativeMethods.GetColumnUnderPoint(this.Handle, pt);
              }
          }
  #endif
          /// <summary>
          /// The user wants to see the context menu.
          /// </summary>
          /// <param name="m">The windows message</param>
          /// <returns>A bool indicating if this message has been handled</returns>
          /// <remarks>
          /// We want to ignore context menu requests that are triggered by right clicks on the header
          /// </remarks>
          protected bool HandleContextMenu(ref Message m)
          {
              // Don't try to handle context menu commands at design time.
              if (this.DesignMode)
                  return false;
  
              // If the context menu command was generated by the keyboard, LParam will be -1.
              // We don't want to process these.
              if (((int)m.LParam) == -1)
                  return false;
  
              // If the context menu came from somewhere other than the header control,
              // we also don't want to ignore it
              if (m.WParam != this.hdrCtrl.Handle)
                  return false;
  
              // OK. Looks like a right click in the header
              if (!this.PossibleFinishCellEditing())
                  return true;
  
              int columnIndex = this.hdrCtrl.GetColumnIndexUnderCursor();
              return this.HandleHeaderRightClick(columnIndex);
          }
  
          /// <summary>
          /// In the notification messages, we handle attempts to change the width of our columns
          /// </summary>
          /// <param name="m">The msg to be processed</param>
          /// <returns>bool to indicate if the msg has been handled</returns>
          protected bool HandleNotify(ref Message m)
          {
              bool isMsgHandled = false;
  
              const int HDN_FIRST = (0 - 300);
              const int HDN_ITEMCHANGINGA = (HDN_FIRST - 0);
              const int HDN_ITEMCHANGINGW = (HDN_FIRST - 20);
              const int HDN_ITEMCLICKA = (HDN_FIRST - 2);
              const int HDN_ITEMCLICKW = (HDN_FIRST - 22);
              const int HDN_DIVIDERDBLCLICKA = (HDN_FIRST - 5);
              const int HDN_DIVIDERDBLCLICKW = (HDN_FIRST - 25);
              const int HDN_BEGINTRACKA = (HDN_FIRST - 6);
              const int HDN_BEGINTRACKW = (HDN_FIRST - 26);
              //const int HDN_ENDTRACKA = (HDN_FIRST - 7);
              //const int HDN_ENDTRACKW = (HDN_FIRST - 27);
              const int HDN_TRACKA = (HDN_FIRST - 8);
              const int HDN_TRACKW = (HDN_FIRST - 28);
  
              // Handle the notification, remembering to handle both ANSI and Unicode versions
              NativeMethods.NMHDR nmhdr = (NativeMethods.NMHDR)m.GetLParam(typeof(NativeMethods.NMHDR));
              //if (nmhdr.code < HDN_FIRST)
              //    System.Diagnostics.Debug.WriteLine(nmhdr.code);
  
              // In KB Article #183258, MS states that when a header control has the HDS_FULLDRAG style, it will receive
              // ITEMCHANGING events rather than TRACK events. Under XP SP2 (at least) this is not always true, which may be
              // why MS has withdrawn that particular KB article. It is true that the header is always given the HDS_FULLDRAG
              // style. But even while window style set, the control doesn't always received ITEMCHANGING events.
              // The controlling setting seems to be the Explorer option "Show Window Contents While Dragging"!
              // In the category of "truly bizarre side effects", if the this option is turned on, we will receive
              // ITEMCHANGING events instead of TRACK events. But if it is turned off, we receive lots of TRACK events and
              // only one ITEMCHANGING event at the very end of the process.
              // If we receive HDN_TRACK messages, it's harder to control the resizing process. If we return a result of 1, we
              // cancel the whole drag operation, not just that particular track event, which is clearly not what we want.
              // If we are willing to compile with unsafe code enabled, we can modify the size of the column in place, using the
              // commented out code below. But without unsafe code, the best we can do is allow the user to drag the column to
              // any width, and then spring it back to within bounds once they release the mouse button. UI-wise it's very ugly.
              NativeMethods.NMHEADER nmheader;
              switch (nmhdr.code) {
  
                  case HDN_ITEMCLICKA:
                  case HDN_ITEMCLICKW:
                      if (!this.PossibleFinishCellEditing()) {
                          m.Result = (IntPtr)1; // prevent the change from happening
                          isMsgHandled = true;
                      }
                      break;
  
                  case HDN_DIVIDERDBLCLICKA:
                  case HDN_DIVIDERDBLCLICKW:
                  case HDN_BEGINTRACKA:
                  case HDN_BEGINTRACKW:
                      if (!this.PossibleFinishCellEditing()) {
                          m.Result = (IntPtr)1; // prevent the change from happening
                          isMsgHandled = true;
                          break;
                      }
                      nmheader = (NativeMethods.NMHEADER)m.GetLParam(typeof(NativeMethods.NMHEADER));
                      if (nmheader.iItem >= 0 && nmheader.iItem < this.Columns.Count) {
                          OLVColumn column = this.GetColumn(nmheader.iItem);
                          // Space filling columns can't be dragged or double-click resized
                          if (column.FillsFreeSpace) {
                              m.Result = (IntPtr)1; // prevent the change from happening
                              isMsgHandled = true;
                          }
                      }
                      break;
  
                  case HDN_TRACKA:
                  case HDN_TRACKW:
                      nmheader = (NativeMethods.NMHEADER)m.GetLParam(typeof(NativeMethods.NMHEADER));
                      if (nmheader.iItem >= 0 && nmheader.iItem < this.Columns.Count) {
                          //    unsafe {
                          //        NativeMethods.HDITEM *hditem = (NativeMethods.HDITEM*)nmheader.pHDITEM;
                          //        OLVColumn column = this.GetColumn(nmheader.iItem);
                          //        if (hditem->cxy < column.MiniumWidth)
                          //            hditem->cxy = column.MiniumWidth;
                          //        else if (column.MaxiumWidth != -1 && hditem->cxy > column.MaxiumWidth)
                          //            hditem->cxy = column.MaxiumWidth;
                          //    }
                      }
                      break;
  
                  case HDN_ITEMCHANGINGA:
                  case HDN_ITEMCHANGINGW:
                      nmheader = (NativeMethods.NMHEADER)m.GetLParam(typeof(NativeMethods.NMHEADER));
                      if (nmheader.iItem >= 0 && nmheader.iItem < this.Columns.Count) {
                          NativeMethods.HDITEM hditem = (NativeMethods.HDITEM)Marshal.PtrToStructure(nmheader.pHDITEM, typeof(NativeMethods.HDITEM));
                          OLVColumn column = this.GetColumn(nmheader.iItem);
                          // Check the mask to see if the width field is valid, and if it is, make sure it's within range
                          if ((hditem.mask & 1) == 1) {
                              if (hditem.cxy < column.MinimumWidth ||
                                  (column.MaximumWidth != -1 && hditem.cxy > column.MaximumWidth)) {
                                  m.Result = (IntPtr)1; // prevent the change from happening
                                  isMsgHandled = true;
                              }
                          }
                      }
                      break;
  
                  default:
                      break;
              }
  
              return isMsgHandled;
          }
  
          /// <summary>
          /// The user has right clicked on the column headers. Do whatever is required
          /// </summary>
          /// <returns>Return true if this event has been handle</returns>
          virtual protected bool HandleHeaderRightClick(int columnIndex)
          {
              ColumnClickEventArgs eventArgs = new ColumnClickEventArgs(columnIndex);
              this.OnColumnRightClick(eventArgs);
  
              if (this.SelectColumnsOnRightClick)
                  this.ShowColumnSelectMenu(Cursor.Position);
  
              return this.SelectColumnsOnRightClick;
          }
  
          /// <summary>
          /// The user has right clicked on the column headers. Do whatever is required
          /// </summary>
          /// <returns>Return true if this event has been handle</returns>
          [Obsolete("Use HandleHeaderRightClick(int) instead")]
          virtual protected bool HandleHeaderRightClick()
          {
              return false;
          }
  
          /// <summary>
          /// Tell the world when a cell is about to finish being edited.
          /// </summary>
          protected virtual void OnColumnRightClick(ColumnClickEventArgs e)
          {
              if (this.ColumnRightClick != null)
                  this.ColumnRightClick(this, e);
          }
  
          /// <summary>
          /// The callbacks for RightColumnClick events
          /// </summary>
          public delegate void ColumnRightClickEventHandler(object sender, ColumnClickEventArgs e);
  
          /// <summary>
          /// Triggered when a column header is right clicked.
          /// </summary>
          [Category("Behavior")]
          public event ColumnRightClickEventHandler ColumnRightClick;
  
          /// <summary>
          /// Show a popup menu at the given point which will allow the user to choose which columns
          /// are visible on this listview
          /// </summary>
          /// <param name="pt">Where should the menu be placed</param>
          protected void ShowColumnSelectMenu(Point pt)
          {
              ContextMenuStrip m = this.MakeColumnSelectMenu(null);
              m.Show(pt);
          }
  
          /// <summary>
          /// Append the column selection menu items to the given menu strip.
          /// </summary>
          /// <param name="strip">The menu to which the items will be added. If this is null, a new
          /// ContextMenuStrip will be created</param>
          /// <returns>Return the menu to which the items were added</returns>
          public ContextMenuStrip MakeColumnSelectMenu(ContextMenuStrip strip)
          {
              if (strip == null)
                  strip = new ContextMenuStrip();
  
              strip.ItemClicked += new ToolStripItemClickedEventHandler(ColumnSelectMenu_ItemClicked);
              strip.Closing += new ToolStripDropDownClosingEventHandler(ColumnSelectMenu_Closing);
  
              List<OLVColumn> columns = new List<OLVColumn>(this.AllColumns);
              columns.Sort(delegate(OLVColumn x, OLVColumn y) { return String.Compare(x.Text, y.Text, true); });
              foreach (OLVColumn col in columns) {
                  ToolStripMenuItem mi = new ToolStripMenuItem(col.Text);
                  mi.Checked = col.IsVisible;
                  mi.Tag = col;
  
                  // The 'Index' property returns -1 when the column is not visible, so if the
                  // column isn't visible we have to enable the item. Also the first column can't be turned off
                  mi.Enabled = !col.IsVisible || (col.Index > 0);
                  strip.Items.Add(mi);
              }
  
              return strip;
          }
  
          private void ColumnSelectMenu_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
          {
              ToolStripMenuItem mi = (ToolStripMenuItem)e.ClickedItem;
              OLVColumn col = (OLVColumn)mi.Tag;
              mi.Checked = !mi.Checked;
              col.IsVisible = mi.Checked;
              this.BeginInvoke(new MethodInvoker(this.RebuildColumns));
          }
  
          private void ColumnSelectMenu_Closing(object sender, ToolStripDropDownClosingEventArgs e)
          {
              e.Cancel = (this.SelectColumnsMenuStaysOpen &&
                  e.CloseReason == ToolStripDropDownCloseReason.ItemClicked);
          }
  
          /// <summary>
          /// Override the OnColumnReordered method to do what we want
          /// </summary>
          /// <param name="e"></param>
          protected override void OnColumnReordered(ColumnReorderedEventArgs e)
          {
              base.OnColumnReordered(e);
  
              // The internal logic of the .NET code behind a ENDDRAG event means that,
              // at this point, the DisplayIndex's of the columns are not yet as they are
              // going to be. So we have to invoke a method to run later that will remember
              // what the real DisplayIndex's are.
              this.BeginInvoke(new MethodInvoker(this.RememberDisplayIndicies));
          }
  
          private void RememberDisplayIndicies()
          {
              // Remember the display indexes so we can put them back at a later date
              foreach (OLVColumn x in this.AllColumns)
                  x.LastDisplayIndex = x.DisplayIndex;
          }
  
          void HandleColumnWidthChanging(object sender, ColumnWidthChangingEventArgs e)
          {
              if (this.UpdateSpaceFillingColumnsWhenDraggingColumnDivider && !this.GetColumn(e.ColumnIndex).FillsFreeSpace)
                  this.ResizeFreeSpaceFillingColumns();
          }
  
          void HandleColumnWidthChanged(object sender, ColumnWidthChangedEventArgs e)
          {
              if (!this.GetColumn(e.ColumnIndex).FillsFreeSpace)
                  this.ResizeFreeSpaceFillingColumns();
          }
  
          void HandleLayout(object sender, LayoutEventArgs e)
          {
              if (IsHandleCreated)
              {
                  this.BeginInvoke((MethodInvoker)delegate()
                  {
                      this.ResizeFreeSpaceFillingColumns();
                  });
              }
          }
  
          /// <summary>
          /// Resize our space filling columns so they fill any unoccupied width in the control
          /// </summary>
          protected void ResizeFreeSpaceFillingColumns()
          {
              // It's too confusing to dynamically resize columns at design time.
              if (this.DesignMode)
                  return;
  
              // Calculate the free space available
              int freeSpace = this.ClientSize.Width - 2;
              int totalProportion = 0;
              List<OLVColumn> spaceFillingColumns = new List<OLVColumn>();
              for (int i = 0; i < this.Columns.Count; i++) {
                  OLVColumn col = this.GetColumn(i);
                  if (col.FillsFreeSpace) {
                      spaceFillingColumns.Add(col);
                      totalProportion += col.FreeSpaceProportion;
                  } else
                      freeSpace -= col.Width;
              }
              freeSpace = Math.Max(0, freeSpace);
  
              // Any space filling column that would hit it's Minimum or Maximum
              // width must be treated as a fixed column.
              foreach (OLVColumn col in spaceFillingColumns.ToArray()) {
                  int newWidth = (freeSpace * col.FreeSpaceProportion) / totalProportion;
  
                  if (col.MinimumWidth != -1 && newWidth < col.MinimumWidth)
                      newWidth = col.MinimumWidth;
                  else if (col.MaximumWidth != -1 && newWidth > col.MaximumWidth)
                      newWidth = col.MaximumWidth;
                  else
                      newWidth = 0;
  
                  if (newWidth > 0) {
                      col.Width = newWidth;
                      freeSpace -= newWidth;
                      totalProportion -= col.FreeSpaceProportion;
                      spaceFillingColumns.Remove(col);
                  }
              }
  
              // Distribute the free space between the columns
              foreach (OLVColumn col in spaceFillingColumns) {
                  col.Width = (freeSpace * col.FreeSpaceProportion) / totalProportion;
              }
          }
  
          #endregion
  
          #region Checkboxes
  
          /// <summary>
          /// Change the given item from the old check value to a new one
          /// </summary>
          /// <param name="olvi">The item to be change</param>
          /// <param name="oldValue">The old value of the check</param>
          /// <param name="newValue">The new value of the check</param>
          protected void ChangeCheckItem(OLVListItem olvi, bool oldValue, bool newValue)
          {
              this.ChangeCheckItem(olvi,
                  (oldValue ? CheckState.Checked : CheckState.Unchecked),
                  (newValue ? CheckState.Checked : CheckState.Unchecked));
          }
  
          /// <summary>
          /// Change the given item from the old check value to a new one
          /// </summary>
          /// <param name="olvi">The item to be change</param>
          /// <param name="oldValue">The old value of the check</param>
          /// <param name="newValue">The new value of the check</param>
          protected void ChangeCheckItem(OLVListItem olvi, CheckState oldValue, CheckState newValue)
          {
              olvi.Checked = (newValue == CheckState.Checked);
              if (this.CheckStatePutter != null)
                  this.CheckStatePutter(olvi.RowObject, newValue);
              this.RefreshItem(olvi);
          }
  
          /// <summary>
          /// Return true of the given object is checked
          /// </summary>
          /// <param name="modelObject">The model object whose checkedness is returned</param>
          /// <returns>Is the given object checked?</returns>
          /// <remarks>If the given object is not in the list, this method returns false.</remarks>
          public bool IsChecked(object modelObject)
          {
              OLVListItem olvi = this.ModelToItem(modelObject);
              if (olvi == null)
                  return false;
              else
                  return olvi.Checked;
          }
  
          /// <summary>
          /// Return the OLVListItem that displays the given model object
          /// </summary>
          /// <param name="modelObject">The modelObject whose item is to be found</param>
          /// <returns>The OLVListItem that displays the model, or null</returns>
          /// <remarks>This method has O(n) performance.</remarks>
          protected OLVListItem ModelToItem(object modelObject)
          {
              if (modelObject == null)
                  return null;
  
              OLVListItem olvi;
              foreach (ListViewItem lvi in this.Items) {
                  olvi = (OLVListItem)lvi;
                  if (olvi.RowObject == modelObject)
                      return olvi;
              }
              return null;
          }
  
          /// <summary>
          /// Toggle the checkedness of the given object
          /// </summary>
          /// <param name="modelObject">The model object to be checked</param>
          public void ToggleCheckObject(object modelObject)
          {
              OLVListItem olvi = this.ModelToItem(modelObject);
              if (olvi != null)
                  this.ChangeCheckItem(olvi, olvi.Checked, !olvi.Checked);
          }
  
          /// <summary>
          /// Mark the given object as checked in the list
          /// </summary>
          /// <param name="modelObject">The model object to be checked</param>
          public void CheckObject(object modelObject)
          {
              OLVListItem olvi = this.ModelToItem(modelObject);
              if (olvi != null && !olvi.Checked)
                  this.ChangeCheckItem(olvi, false, true);
          }
  
          /// <summary>
          /// Mark the given object as unchecked in the list
          /// </summary>
          /// <param name="modelObject">The model object to be unchecked</param>
          public void UncheckObject(object modelObject)
          {
              OLVListItem olvi = this.ModelToItem(modelObject);
              if (olvi != null && olvi.Checked)
                  this.ChangeCheckItem(olvi, true, false);
          }
  
          #endregion
  
          #region OLV accessing
  
          /// <summary>
          /// Return the column at the given index
          /// </summary>
          /// <param name="index">Index of the column to be returned</param>
          /// <returns>An OLVColumn</returns>
          public OLVColumn GetColumn(int index)
          {
              return (OLVColumn)this.Columns[index];
          }
  
          /// <summary>
          /// Return the column at the given title.
          /// </summary>
          /// <param name="name">Name of the column to be returned</param>
          /// <returns>An OLVColumn</returns>
          public OLVColumn GetColumn(string name)
          {
              foreach (ColumnHeader column in this.Columns) {
                  if (column.Text == name)
                      return (OLVColumn)column;
              }
              return null;
          }
  
          /// <summary>
          /// Return the number of items in the list
          /// </summary>
          /// <returns>the number of items in the list</returns>
          virtual public int GetItemCount()
          {
              return this.Items.Count;
          }
  
          /// <summary>
          /// Return the item at the given index
          /// </summary>
          /// <param name="index">Index of the item to be returned</param>
          /// <returns>An OLVListItem</returns>
          virtual public OLVListItem GetItem(int index)
          {
              if (index >= 0 && index < this.GetItemCount())
                  return (OLVListItem)this.Items[index];
              else
                  return null;
          }
  
          /// <summary>
          /// Return the model object at the given index
          /// </summary>
          /// <param name="index">Index of the model object to be returned</param>
          /// <returns>A model object</returns>
          virtual public object GetModelObject(int index)
          {
              return this.GetItem(index).RowObject;
          }
  
          /// <summary>
          /// Find the item and column that are under the given co-ords
          /// </summary>
          /// <param name="x">X co-ord</param>
          /// <param name="y">Y co-ord</param>
          /// <param name="selectedColumn">The column under the given point</param>
          /// <returns>The item under the given point. Can be null.</returns>
          public OLVListItem GetItemAt(int x, int y, out OLVColumn selectedColumn)
          {
              selectedColumn = null;
              ListViewHitTestInfo info = this.HitTest(x, y);
              if (info.Item == null)
                  return null;
  
              if (info.SubItem != null) {
                  int subItemIndex = info.Item.SubItems.IndexOf(info.SubItem);
                  selectedColumn = this.GetColumn(subItemIndex);
              }
  
              return (OLVListItem)info.Item;
          }
  
          #endregion
  
          #region Object manipulation
  
          /// <summary>
          /// Select all rows in the listview
          /// </summary>
          public void SelectAll()
          {
              NativeMethods.SelectAllItems(this);
          }
  
          /// <summary>
          /// Deselect all rows in the listview
          /// </summary>
          public void DeselectAll()
          {
              NativeMethods.DeselectAllItems(this);
          }
  
          /// <summary>
          /// Return the model object of the row that is selected or null if there is no selection or more than one selection
          /// </summary>
          /// <returns>Model object or null</returns>
          virtual public object GetSelectedObject()
          {
              if (this.SelectedIndices.Count == 1)
                  return this.GetModelObject(this.SelectedIndices[0]);
              else
                  return null;
          }
  
          /// <summary>
          /// Return the model objects of the rows that are selected or an empty collection if there is no selection
          /// </summary>
          /// <returns>ArrayList</returns>
          virtual public ArrayList GetSelectedObjects()
          {
              ArrayList objects = new ArrayList(this.SelectedIndices.Count);
              foreach (int index in this.SelectedIndices)
                  objects.Add(this.GetModelObject(index));
  
              return objects;
          }
  
          /// <summary>
          /// Return the model object of the row that is checked or null if no row is checked
          /// or more than one row is checked
          /// </summary>
          /// <returns>Model object or null</returns>
          /// <remarks>Use CheckedObject property instead of this method</remarks>
          virtual public object GetCheckedObject()
          {
              return this.CheckedObject;
          }
  
          /// <summary>
          /// Get the collection of model objects that are checked.
          /// </summary>
          /// <remarks>Use CheckedObjects property instead of this method</remarks>
          virtual public ArrayList GetCheckedObjects()
          {
              return (ArrayList)this.CheckedObjects;
          }
  
          /// <summary>
          /// Select the row that is displaying the given model object. All other rows are deselected.
          /// </summary>
          /// <param name="modelObject">The object to be selected or null to deselect all</param>
          virtual public void SelectObject(object modelObject)
          {
              // If the given model is already selected, don't do anything else (prevents an flicker)
              if (this.SelectedItems.Count == 1 && ((OLVListItem)this.SelectedItems[0]).RowObject == modelObject)
                  return;
  
              this.SelectedItems.Clear();
  
              OLVListItem olvi = this.ModelToItem(modelObject);
              if (olvi != null)
                  olvi.Selected = true;
          }
  
          /// <summary>
          /// Select the rows that is displaying any of the given model object. All other rows are deselected.
          /// </summary>
          /// <param name="modelObjects">A collection of model objects</param>
          virtual public void SelectObjects(IList modelObjects)
          {
              this.SelectedItems.Clear();
  
              foreach (object modelObject in modelObjects) {
                  OLVListItem olvi = this.ModelToItem(modelObject);
                  if (olvi != null)
                      olvi.Selected = true;
              }
          }
  
          /// <summary>
          /// Update the ListViewItem with the data from its associated model.
          /// </summary>
          /// <remarks>This method does not resort or regroup the view. It simply updates
          /// the displayed data of the given item</remarks>
          virtual public void RefreshItem(OLVListItem olvi)
          {
              // For some reason, clearing the subitems also wipes out the back color,
              // so we need to store it and then put it back again later
              Color c = olvi.BackColor;
              olvi.SubItems.Clear();
              this.FillInValues(olvi, olvi.RowObject);
              this.SetSubItemImages(olvi.Index, olvi, true);
              olvi.BackColor = c;
          }
  
          /// <summary>
          /// Update the rows that are showing the given objects
          /// </summary>
          /// <remarks>This method does not resort or regroup the view.</remarks>
          virtual public void RefreshObject(object modelObject)
          {
              this.RefreshObjects(new object[] { modelObject });
          }
  
          /// <summary>
          /// Update the rows that are showing the given objects
          /// </summary>
          /// <remarks>
          /// <para>This method does not resort or regroup the view.</para>
          /// <para>This method can safely be called from background threads.</para>
          /// </remarks>
          virtual public void RefreshObjects(IList modelObjects)
          {
              if (this.InvokeRequired) {
                  this.Invoke((MethodInvoker)delegate { this.RefreshObjects(modelObjects); });
                  return;
              }
              foreach (object modelObject in modelObjects) {
                  OLVListItem olvi = this.ModelToItem(modelObject);
                  if (olvi != null)
                      this.RefreshItem(olvi);
              }
          }
  
          /// <summary>
          /// Update the rows that are selected
          /// </summary>
          /// <remarks>This method does not resort or regroup the view.</remarks>
          public void RefreshSelectedObjects()
          {
              foreach (ListViewItem lvi in this.SelectedItems)
                  this.RefreshItem((OLVListItem)lvi);
          }
  
          /// <summary>
          /// Find the given model object within the listview and return its index
          /// </summary>
          /// <remarks>Technically, this method will work with virtual lists, but it will
          /// probably be very slow.</remarks>
          /// <param name="modelObject">The model object to be found</param>
          /// <returns>The index of the object. -1 means the object was not present</returns>
          public int IndexOf(Object modelObject)
          {
              for (int i = 0; i < this.GetItemCount(); i++) {
                  if (this.GetModelObject(i) == modelObject)
                      return i;
              }
              return -1;
          }
  
          /// <summary>
          /// Return the ListViewItem that appears immediately after the given item.
          /// If the given item is null, the first item in the list will be returned.
          /// Return null if the given item is the last item.
          /// </summary>
          /// <param name="itemToFind">The item that is before the item that is returned, or null</param>
          /// <returns>A ListViewItem</returns>
          public ListViewItem GetNextItem(ListViewItem itemToFind)
          {
              if (this.ShowGroups) {
                  bool isFound = (itemToFind == null);
                  foreach (ListViewGroup group in this.Groups) {
                      foreach (ListViewItem lvi in group.Items) {
                          if (isFound)
                              return lvi;
                          isFound = (lvi == itemToFind);
                      }
                  }
                  return null;
              } else {
                  if (this.GetItemCount() == 0)
                      return null;
  
gt:                 if (itemToFind == null)