/*
 * ObjectListView - A listview to show various aspects of a collection of objects
 *
 * Author: Phillip Piper
 * Date: 9/10/2006 11:15 AM
 *
 * Change log
 * v2.9.1
 * 2015-12-30  JPP  - Added CellRendererGetter to allow each cell to have a different renderer.
 *                  - Obsolete properties are no longer code-gen'ed.
 *
 * v2.9.0
 * 2015-08-22  JPP  - Allow selected row back/fore colours to be specified for each row
 *                  - Renamed properties related to selection colours:
 *                       - HighlightBackgroundColor -> SelectedBackColor
 *                       - HighlightForegroundColor -> SelectedForeColor
 *                       - UnfocusedHighlightBackgroundColor -> UnfocusedSelectedBackColor
 *                       - UnfocusedHighlightForegroundColor -> UnfocusedSelectedForeColor
 *                  - UseCustomSelectionColors is no longer used
 * 2015-08-03  JPP  - Added ObjectListView.CellEditFinished event
 *                  - Added EditorRegistry.Unregister()
 * 2015-07-08  JPP  - All ObjectListViews are now OwnerDrawn by default. This allows all the great features
 *                    of ObjectListView to work correctly at the slight cost of more processing at render time.
 *                    It also avoids the annoying "hot item background ignored in column 0" behaviour that native
 *                    ListView has. Programmers can still turn it back off if they wish.
 * 2015-06-27  JPP  - Yet another attempt to disable ListView's "shift click toggles checkboxes" behaviour.
 *                    The last strategy (fake right click) worked, but had nasty side effects. This one works
 *                    by intercepting a HITTEST message so that it fails. It no longer creates fake right mouse events.
 *                  - Trigger SelectionChanged when filter is changed
 * 2015-06-23  JPP  - [BIG] Added support for Buttons
 * 2015-06-22  JPP  - Added OLVColumn.SearchValueGetter to allow the text used when text filtering to be customised
 *                  - The default DefaultRenderer is now a HighlightTextRenderer, since that seems more generally useful
 * 2015-06-17  JPP  - Added FocusedObject property
 *                  - Hot item is now always applied to the row even if FullRowSelect is false
 * 2015-06-11  JPP  - Added DefaultHotItemStyle property
 * 2015-06-07  JPP  - Added HeaderMinimumHeight property
 *                  - Added ObjectListView.CellEditUsesWholeCell and OLVColumn.CellEditUsesWholeCell properties.
 * 2015-05-15  JPP  - Allow ImageGetter to return an Image (which I can't believe didn't work from the beginning!)
 * 2015-04-27  JPP  - Fix bug where setting View to LargeIcon in the designer was not persisted
 * 2015-04-07  JPP  - Ensure changes to row.Font in FormatRow are not wiped out by FormatCell (SF #141)
 * 
 * v2.8.1
 * 2014-10-15  JPP  - Added CellEditActivateMode.SingleClickAlways mode
 *                  - Fire Filter event event if ModelFilter and ListFilter are null (SF #126)
 *                  - Fixed issue where single-click editing didn't work (SF #128)
 * v2.8.0
 * 2014-10-11  JPP  - Fixed some XP-only flicker issues
 * 2014-09-26  JPP  - Fixed intricate bug involving checkboxes on non-owner-drawn virtual lists.
 *                  - Fixed long standing (but previously unreported) error on non-details virtual lists where
 *                    users could not click on checkboxes.
 * 2014-09-07  JPP  - (Major) Added ability to have checkboxes in headers
 *                  - CellOver events are raised when the mouse moves over the header. Set TriggerCellOverEventsWhenOverHeader
 *                    to false to disable this behaviour.
 *                  - Freeze/Unfreeze now use BeginUpdate/EndUpdate to disable Window level drawing
 *                  - Changed default value of ObjectListView.HeaderUsesThemes from true to false. Too many people were
 *                    being confused, trying to make something interesting appear in the header and nothing showing up
 * 2014-08-04  JPP  - Final attempt to fix the multiple hyperlink events being raised. This involves turning
 *                    a NM_CLICK notification into a NM_RCLICK.
 * 2014-05-21  JPP  - (Major) Added ability to disable rows. DisabledObjects, DisableObjects(), DisabledItemStyle.
 * 2014-04-25  JPP  - Fixed issue where virtual lists containing a single row didn't update hyperlinks on MouseOver
 *                  - Added sanity check before BuildGroups()
 * 2014-03-22  JPP  - Fixed some subtle bugs resulting from misuse of TryGetValue()
 * 2014-03-09  JPP  - Added CollapsedGroups property
 *                  - Several minor Resharper complaints quiesced.
 * v2.7
 * 2014-02-14  JPP  - Fixed issue with ShowHeaderInAllViews (another one!) where setting it to false caused the list to lose
 *                    its other extended styles, leading to nasty flickering and worse.
 * 2014-02-06  JPP  - Fix issue on virtual lists where the filter was not correctly reapplied after columns were added or removed.
 *                  - Made disposing of cell editors optional (defaults to true). This allows controls to be cached and reused.
 *                  - Bracketed column resizing with BeginUpdate/EndUpdate to smooth redraws (thanks to Davide)
 * 2014-02-01  JPP  - Added static property ObjectListView.GroupTitleDefault to allow the default group title to be localised.
 * 2013-09-24  JPP  - Fixed issue in RefreshObjects() when model objects overrode the Equals()/GetHashCode() methods.
 *                  - Made sure get state checker were used when they should have been
 * 2013-04-21  JPP  - Clicking on a non-groupable column header when showing groups will now sort
 *                    the group contents by that column.
 * v2.6
 * 2012-08-16  JPP  - Added ObjectListView.EditModel() -- a convenience method to start an edit operation on a model
 * 2012-08-10  JPP  - Don't trigger selection changed events during sorting/grouping or add/removing columns
 * 2012-08-06  JPP  - Don't start a cell edit operation when the user clicks on the background of a checkbox cell.
 *                  - Honor values from the BeforeSorting event when calling a CustomSorter
 * 2012-08-02  JPP  - Added CellVerticalAlignment and CellPadding properties.
 * 2012-07-04  JPP  - Fixed issue with cell editing where the cell editing didn't finish until the first idle event.
 *                    This meant that if you clicked and held on the scroll thumb to finish a cell edit, the editor
 *                    wouldn't be removed until the mouse was released.
 * 2012-07-03  JPP  - Fixed issue with SingleClick cell edit mode where the cell editing would not begin until the
 *                    mouse moved after the click.
 * 2012-06-25  JPP  - Fixed bug where removing a column from a LargeIcon or SmallIcon view would crash the control. 
 * 2012-06-15  JPP  - Added Reset() method, which definitively removes all rows *and* columns from an ObjectListView.
 * 2012-06-11  JPP  - Added FilteredObjects property which returns the collection of objects that survives any installed filters.
 * 2012-06-04  JPP  - [Big] Added UseNotifyPropertyChanged to allow OLV to listen for INotifyPropertyChanged events on models.
 * 2012-05-30  JPP  - Added static property ObjectListView.IgnoreMissingAspects. If this is set to true, all 
 *                    ObjectListViews will silently ignore missing aspect errors. Read the remarks to see why this would be useful.
 * 2012-05-23  JPP  - Setting UseFilterIndicator to true now sets HeaderUsesTheme to false. 
 *                    Also, changed default value of UseFilterIndicator to false. Previously, HeaderUsesTheme and UseFilterIndicator
 *                    defaulted to true, which was pointless since when the HeaderUsesTheme is true, UseFilterIndicator does nothing.  
 * v2.5.1
 * 2012-05-06  JPP  - Fix bug where collapsing the first group would cause decorations to stop being drawn (SR #3502608)
 * 2012-04-23  JPP  - Trigger GroupExpandingCollapsing event to allow the expand/collapse to be cancelled
 *                  - Fixed SetGroupSpacing() so it corrects updates the space between all groups.
 *                  - ResizeLastGroup() now does nothing since it was broken and I can't remember what it was
 *                    even supposed to do :)
 * 2012-04-18  JPP  - Upgraded hit testing to include hits on groups. 
 *                  - HotItemChanged is now correctly recalculated on each mouse move. Includes "hot" group information.
 * 2012-04-14  JPP  - Added GroupStateChanged event. Useful for knowing when a group is collapsed/expanded.
 *                  - Added AdditionalFilter property. This filter is combined with the Excel-like filtering that
 *                    the end user might enact at runtime.
 * 2012-04-10  JPP  - Added PersistentCheckBoxes property to allow primary checkboxes to remember their values
 *                    across list rebuilds.
 * 2012-04-05  JPP  - Reverted some code to .NET 2.0 standard.
 *                  - Tweaked some code
 * 2012-02-05  JPP  - Fixed bug when selecting a separator on a drop down menu
 * 2011-06-24  JPP  - Added CanUseApplicationIdle property to cover cases where Application.Idle events
 *                    are not triggered. For example, when used within VS (and probably Office) extensions
 *                    Application.Idle is never triggered. Set CanUseApplicationIdle to false to handle 
 *                    these cases.
 *                  - Handle cases where a second tool tip is installed onto the ObjectListView.
 *                  - Correctly recolour rows after an Insert or Move
 *                  - Removed m.LParam cast which could cause overflow issues on Win7/64 bit.
 * v2.5.0
 * 2011-05-31  JPP  - SelectObject() and SelectObjects() no longer deselect all other rows.
                      Set the SelectedObject or SelectedObjects property to do that.
 *                  - Added CheckedObjectsEnumerable
 *                  - Made setting CheckedObjects more efficient on large collections
 *                  - Deprecated GetSelectedObject() and GetSelectedObjects()
 * 2011-04-25  JPP  - Added SubItemChecking event
 *                  - Fixed bug in handling of NewValue on CellEditFinishing event
 * 2011-04-12  JPP  - Added UseFilterIndicator 
 *                  - Added some more localizable messages
 * 2011-04-10  JPP  - FormatCellEventArgs now has a CellValue property, which is the model value displayed
 *                    by the cell. For example, for the Birthday column, the CellValue might be 
 *                    DateTime(1980, 12, 31), whereas the cell's text might be 'Dec 31, 1980'.
 * 2011-04-04  JPP  - Tweaked UseTranslucentSelection and UseTranslucentHotItem to look (a little) more
 *                    like Vista/Win7.
 *                  - Alternate colours are now only applied in Details view (as they always should have been)
 *                  - Alternate colours are now correctly recalculated after removing objects
 * 2011-03-29  JPP  - Added SelectColumnsOnRightClickBehaviour to allow the selecting of columns mechanism 
 *                    to be changed. Can now be InlineMenu (the default), SubMenu, or ModelDialog.
 *                  - ColumnSelectionForm was moved from the demo into the ObjectListView project itself.
 *                  - Ctrl-C copying is now able to use the DragSource to create the data transfer object.
 * 2011-03-19  JPP  - All model object comparisons now use Equals rather than == (thanks to vulkanino)
 *                  - [Small Break] GetNextItem() and GetPreviousItem() now accept and return OLVListView
 *                    rather than ListViewItems.
 * 2011-03-07  JPP  - [Big] Added Excel-style filtering. Right click on a header to show a Filtering menu.
 *                  - Added CellEditKeyEngine to allow key handling when cell editing to be completely customised.
 *                    Add CellEditTabChangesRows and CellEditEnterChangesRows to show some of these abilities.
 * 2011-03-06  JPP  - Added OLVColumn.AutoCompleteEditorMode in preference to AutoCompleteEditor 
 *                    (which is now just a wrapper). Thanks to Clive Haskins 
 *                  - Added lots of docs to new classes
 * 2011-02-25  JPP  - Preserve word wrap settings on TreeListView
 *                  - Resize last group to keep it on screen (thanks to ?)
 * 2010-11-16  JPP  - Fixed (once and for all) DisplayIndex problem with Generator
 *                  - Changed the serializer used in SaveState()/RestoreState() so that it resolves on
 *                    class name alone.
 *                  - Fixed bug in GroupWithItemCountSingularFormatOrDefault
 *                  - Fixed strange flickering in grouped, owner drawn OLV's using RefreshObject()
 * v2.4.1
 * 2010-08-25  JPP  - Fixed bug where setting OLVColumn.CheckBoxes to false gave it a renderer
 *                    specialized for checkboxes. Oddly, this made Generator created owner drawn
 *                    lists appear to be completely empty.
 *                  - In IDE, all ObjectListView properties are now in a single "ObjectListView" category,
 *                    rather than splitting them between "Appearance" and "Behavior" categories.
 *                  - Added GroupingParameters.GroupComparer to allow groups to be sorted in a customizable fashion.
 *                  - Sorting of items within a group can be disabled by setting 
 *                    GroupingParameters.PrimarySortOrder to None.
 * 2010-08-24  JPP  - Added OLVColumn.IsHeaderVertical to make a column draw its header vertical.
 *                  - Added OLVColumn.HeaderTextAlign to control the alignment of a column's header text.
 *                  - Added HeaderMaximumHeight to limit how tall the header section can become
 * 2010-08-18  JPP  - Fixed long standing bug where having 0 columns caused a InvalidCast exception.
 *                  - Added IncludeAllColumnsInDataObject property
 *                  - Improved BuildList(bool) so that it preserves scroll position even when
 *                    the listview is grouped.
 * 2010-08-08  JPP  - Added OLVColumn.HeaderImageKey to allow column headers to have an image.
 *                  - CellEdit validation and finish events now have NewValue property.
 * 2010-08-03  JPP  - Subitem checkboxes improvements: obey IsEditable, can be hot, can be disabled.
 *                  - No more flickering of selection when tabbing between cells
 *                  - Added EditingCellBorderDecoration to make it clearer which cell is being edited.
 * 2010-08-01  JPP  - Added ObjectListView.SmoothingMode to control the smoothing of all graphics
 *                    operations
 *                  - Columns now cache their group item format strings so that they still work as 
 *                    grouping columns after they have been removed from the listview. This cached
 *                    value is only used when the column is not part of the listview.
 * 2010-07-25  JPP  - Correctly trigger a Click event when the mouse is clicked.
 * 2010-07-16  JPP  - Invalidate the control before and after cell editing to make sure it looks right
 * 2010-06-23  JPP  - Right mouse clicks on checkboxes no longer confuse them
 * 2010-06-21  JPP  - Avoid bug in underlying ListView control where virtual lists in SmallIcon view
 *                    generate GETTOOLINFO msgs with invalid item indices.
 *                  - Fixed bug where FastObjectListView would throw an exception when showing hyperlinks
 *                    in any view except Details.
 * 2010-06-15  JPP  - Fixed bug in ChangeToFilteredColumns() that resulted in column display order
 *                    being lost when a column was hidden.
 *                  - Renamed IsVista property to IsVistaOrLater which more accurately describes its function.
 * v2.4
 * 2010-04-14  JPP  - Prevent object disposed errors when mouse event handlers cause the
 *                    ObjectListView to be destroyed (e.g. closing a form during a 
 *                    double click event).
 *                  - Avoid checkbox munging bug in standard ListView when shift clicking on non-primary
 *                    columns when FullRowSelect is true.
 * 2010-04-12  JPP  - Fixed bug in group sorting (thanks Mike).
 * 2010-04-07  JPP  - Prevent hyperlink processing from triggering spurious MouseUp events.
 *                    This showed itself by launching the same url multiple times.
 * 2010-04-06  JPP  - Space filling columns correctly resize upon initial display
 *                  - ShowHeaderInAllViews is better but still not working reliably.
 *                    See comments on property for more details.
 * 2010-03-23  JPP  - Added ObjectListView.HeaderFormatStyle and OLVColumn.HeaderFormatStyle.
 *                    This makes HeaderFont and HeaderForeColor properties unnecessary -- 
 *                    they will be marked obsolete in the next version and removed after that.
 * 2010-03-16  JPP  - Changed object checking so that objects can be pre-checked before they
 *                    are added to the list. Normal ObjectListViews managed "checkedness" in
 *                    the ListViewItem, so this won't work for them, unless check state getters
 *                    and putters have been installed. It will work on on virtual lists (thus fast lists and
 *                    tree views) since they manage their own check state.
 * 2010-03-06  JPP  - Hide "Items" and "Groups" from the IDE properties grid since they shouldn't be set like that.
 *                    They can still be accessed through "Custom Commands" and there's nothing we can do
 *                    about that.
 * 2010-03-05  JPP  - Added filtering
 * 2010-01-18  JPP  - Overlays can be turned off. They also only work on 32-bit displays
 * v2.3
 * 2009-10-30  JPP  - Plugged possible resource leak by using using() with CreateGraphics()
 * 2009-10-28  JPP  - Fix bug when right clicking in the empty area of the header
 * 2009-10-20  JPP  - Redraw the control after setting EmptyListMsg property
 * v2.3
 * 2009-09-30  JPP  - Added Dispose() method to properly release resources
 * 2009-09-16  JPP  - Added OwnerDrawnHeader, which you can set to true if you want to owner draw
 *                    the header yourself.
 * 2009-09-15  JPP  - Added UseExplorerTheme, which allow complete visual compliance with Vista explorer.
 *                    But see property documentation for its many limitations.
 *                  - Added ShowHeaderInAllViews. To make this work, Columns are no longer
 *                    changed when switching to/from Tile view.
 * 2009-09-11  JPP  - Added OLVColumn.AutoCompleteEditor to allow the autocomplete of cell editors
 *                    to be disabled.
 * 2009-09-01  JPP  - Added ObjectListView.TextRenderingHint property which controls the
 *                    text rendering hint of all drawn text.
 * 2009-08-28  JPP  - [BIG] Added group formatting to supercharge what is possible with groups
 *                  - [BIG] Virtual groups now work
 *                  - Extended MakeGroupies() to handle more aspects of group creation
 * 2009-08-19  JPP  - Added ability to show basic column commands when header is right clicked
 *                  - Added SelectedRowDecoration, UseTranslucentSelection and UseTranslucentHotItem.
 *                  - Added PrimarySortColumn and PrimarySortOrder
 * 2009-08-15  JPP  - Correct problems with standard hit test and subitems
 * 2009-08-14  JPP  - [BIG] Support Decorations
 *                  - [BIG] Added header formatting capabilities: font, color, word wrap
 *                  - Gave ObjectListView its own designer to hide unwanted properties
 *                  - Separated design time stuff into separate file
 *                  - Added FormatRow and FormatCell events
 * 2009-08-09  JPP  - Get around bug in HitTest when not FullRowSelect
 *                  - Added OLVListItem.GetSubItemBounds() method which works correctly
 *                    for all columns including column 0
 * 2009-08-07  JPP  - Added Hot* properties that track where the mouse is
 *                  - Added HotItemChanged event
 *                  - Overrode TextAlign on columns so that column 0 can have something other
 *                    than just left alignment. This is only honored when owner drawn.
 * v2.2.1
 * 2009-08-03  JPP  - Subitem edit rectangles always allowed for an image in the cell, even if there was none.
 *                    Now they only allow for an image when there actually is one.
 *                  - Added Bounds property to OLVListItem which handles items being part of collapsed groups.
 * 2009-07-29  JPP  - Added GetSubItem() methods to ObjectListView and OLVListItem
 * 2009-07-26  JPP  - Avoided bug in .NET framework involving column 0 of owner drawn listviews not being
 *                    redrawn when the listview was scrolled horizontally (this was a LOT of work to track
 *                    down and fix!)
 *                  - The cell edit rectangle is now correctly calculated when the listview is scrolled
 *                    horizontally.
 * 2009-07-14  JPP  - If the user clicks/double clicks on a tree list cell, an edit operation will no longer begin
 *                    if the click was to the left of the expander. This is implemented in such a way that
 *                    other renderers can have similar "dead" zones.
 * 2009-07-11  JPP  - CalculateCellBounds() messed with the FullRowSelect property, which confused the
 *                    tooltip handling on the underlying control. It no longer does this.
 *                  - The cell edit rectangle is now correctly calculated for owner-drawn, non-Details views.
 * 2009-07-08  JPP  - Added Cell events (CellClicked, CellOver, CellRightClicked)
 *                  - Made BuildList(), AddObject() and RemoveObject() thread-safe
 * 2009-07-04  JPP  - Space bar now properly toggles checkedness of selected rows
 * 2009-07-02  JPP  - Fixed bug with tooltips when the underlying Windows control was destroyed.
 *                  - CellToolTipShowing events are now triggered in all views.
 * v2.2
 * 2009-06-02  JPP  - BeforeSortingEventArgs now has a Handled property to let event handlers do
 *                    the item sorting themselves.
 *                  - AlwaysGroupByColumn works again, as does SortGroupItemsByPrimaryColumn and all their
 *                    various permutations.
 *                  - SecondarySortOrder and SecondarySortColumn are now "null" by default
 * 2009-05-15  JPP  - Fixed bug so that KeyPress events are again triggered
 * 2009-05-10  JPP  - Removed all unsafe code
 * 2009-05-07  JPP  - Don't use glass panel for overlays when in design mode. It's too confusing.
 * 2009-05-05  JPP  - Added Scroll event (thanks to Christophe Hosten for the complete patch to implement this)
 *                  - Added Unfocused foreground and background colors (also thanks to Christophe Hosten)
 * 2009-04-29  JPP  - Added SelectedColumn property, which puts a slight tint on that column. Combine
 *                    this with TintSortColumn property and the sort column is automatically tinted.
 *                  - Use an overlay to implement "empty list" msg. Default empty list msg is now prettier.
 * 2009-04-28  JPP  - Fixed bug where DoubleClick events were not triggered when CheckBoxes was true
 * 2009-04-23  JPP  - Fixed various bugs under Vista.
 *                  - Made groups collapsible - Vista only. Thanks to Crustyapplesniffer.
 *                  - Forward events from DropSink to the control itself. This allows handlers to be defined
 *                    within the IDE for drop events
 * 2009-04-16  JPP  - Made several properties localizable.
 * 2009-04-11  JPP  - Correctly renderer checkboxes when RowHeight is non-standard
 * 2009-04-11  JPP  - Implemented overlay architecture, based on CustomDraw scheme.
 *                    This unified drag drop feedback, empty list msgs and overlay images.
 *                  - Added OverlayImage and friends, which allows an image to be drawn
 *                    transparently over the listview
 * 2009-04-10  JPP  - Fixed long-standing annoying flicker on owner drawn virtual lists!
 *                    This means, amongst other things, that grid lines no longer get confused,
 *                    and drag-select no longer flickers.
 * 2009-04-07  JPP  - Calculate edit rectangles more accurately
 * 2009-04-06  JPP  - Double-clicking no longer toggles the checkbox
 *                  - Double-clicking on a checkbox no longer confuses the checkbox
 * 2009-03-16  JPP  - Optimized the build of autocomplete lists
 * v2.1
 * 2009-02-24  JPP  - Fix bug where double-clicking VERY quickly on two different cells
 *                    could give two editors
 *                  - Maintain focused item when rebuilding list (SF #2547060)
 * 2009-02-22  JPP  - Reworked checkboxes so that events are triggered for virtual lists
 * 2009-02-15  JPP  - Added ObjectListView.ConfigureAutoComplete utility method
 * 2009-02-02  JPP  - Fixed bug with AlwaysGroupByColumn where column header clicks would not resort groups.
 * 2009-02-01  JPP  - OLVColumn.CheckBoxes and TriStateCheckBoxes now work.
 * 2009-01-28  JPP  - Complete overhaul of renderers!
 *                       - Use IRenderer
 *                       - Added ObjectListView.ItemRenderer to draw whole items
 * 2009-01-23  JPP  - Simple Checkboxes now work properly
 *                  - Added TriStateCheckBoxes property to control whether the user can
 *                    set the row checkbox to have the Indeterminate value
 *                  - CheckState property is now just a wrapper around the StateImageIndex property
 * 2009-01-20  JPP  - Changed to always draw columns when owner drawn, rather than falling back on DrawDefault.
 *                    This simplified several owner drawn problems
 *                  - Added DefaultRenderer property to help with the above
 *                  - HotItem background color is applied to all cells even when FullRowSelect is false
 *                  - Allow grouping by CheckedAspectName columns
 *                  - Commented out experimental animations. Still needs work.
 * 2009-01-17  JPP  - Added HotItemStyle and UseHotItem to highlight the row under the cursor
 *                  - Added UseCustomSelectionColors property
 *                  - Owner draw mode now honors ForeColor and BackColor settings on the list
 * 2009-01-16  JPP  - Changed to use EditorRegistry rather than hard coding cell editors
 * 2009-01-10  JPP  - Changed to use Equals() method rather than == to compare model objects.
 * v2.0.1
 * 2009-01-08  JPP  - Fixed long-standing "multiple columns generated" problem.
 *                    Thanks to pinkjones for his help with solving this one!
 *                  - Added EnsureGroupVisible()
 * 2009-01-07  JPP  - Made all public and protected methods virtual
 *                  - FinishCellEditing, PossibleFinishCellEditing and CancelCellEditing are now public
 * 2008-12-20  JPP  - Fixed bug with group comparisons when a group key was null (SF#2445761)
 * 2008-12-19  JPP  - Fixed bug with space filling columns and layout events
 *                  - Fixed RowHeight so that it only changes the row height, not the width of the images.
 * v2.0
 * 2008-12-10  JPP  - Handle Backspace key. Resets the search-by-typing state without delay
 *                  - Made some changes to the column collection editor to try and avoid
 *                    the multiple column generation problem.
 *                  - Updated some documentation
 * 2008-12-07  JPP  - Search-by-typing now works when showing groups
 *                  - Added BeforeSearching and AfterSearching events which are triggered when the user types
 *                    into the list.
 *                  - Added secondary sort information to Before/AfterSorting events
 *                  - Reorganized group sorting code. Now triggers Sorting events.
 *                  - Added GetItemIndexInDisplayOrder()
 *                  - Tweaked in the interaction of the column editor with the IDE so that we (normally)
 *                    don't rely on a hack to find the owning ObjectListView
 *                  - Changed all 'DefaultValue(typeof(Color), "Empty")' to 'DefaultValue(typeof(Color), "")'
 *                    since the first does not given Color.Empty as I thought, but the second does.
 * 2008-11-28  JPP  - Fixed long standing bug with horizontal scrollbar when shrinking the window.
 *                    (thanks to Bartosz Borowik)
 * 2008-11-25  JPP  - Added support for dynamic tooltips
 *                  - Split out comparers and header controls stuff into their own files
 * 2008-11-21  JPP  - Fixed bug where enabling grouping when there was not a sort column would not
 *                    produce a grouped list. Grouping column now defaults to column 0.
 *                  - Preserve selection on virtual lists when sorting
 * 2008-11-20  JPP  - Added ability to search by sort column to ObjectListView. Unified this with
 *                    ability that was already in VirtualObjectListView
 * 2008-11-19  JPP  - Fixed bug in ChangeToFilteredColumns() where DisplayOrder was not always restored correctly.
 * 2008-10-29  JPP  - Event argument blocks moved to directly within the namespace, rather than being
 *                    nested inside ObjectListView class.
 *                  - Removed OLVColumn.CellEditor since it was never used.
 *                  - Marked OLVColumn.AspectGetterAutoGenerated as obsolete (it has not been used for
 *                    several versions now).
 * 2008-10-28  JPP  - SelectedObjects is now an IList, rather than an ArrayList. This allows
 *                    it to accept generic list (eg List<File>).
 * 2008-10-09  JPP  - Support indeterminate checkbox values.
 *                    [BREAKING CHANGE] CheckStateGetter/CheckStatePutter now use CheckState types only.
 *                    BooleanCheckStateGetter and BooleanCheckStatePutter added to ease transition.
 * 2008-10-08  JPP  - Added setFocus parameter to SelectObject(), which allows focus to be set
 *                    at the same time as selecting.
 * 2008-09-27  JPP  - BIG CHANGE: Fissioned this file into separate files for each component
 * 2008-09-24  JPP  - Corrected bug with owner drawn lists where a column 0 with a renderer
 *                    would draw at column 0 even if column 0 was dragged to another position.
 *                  - Correctly handle space filling columns when columns are added/removed
 * 2008-09-16  JPP  - Consistently use try..finally for BeginUpdate()/EndUpdate() pairs
 * 2008-08-24  JPP  - If LastSortOrder is None when adding objects, don't force a resort.
 * 2008-08-22  JPP  - Catch and ignore some problems with setting TopIndex on FastObjectListViews.
 * 2008-08-05  JPP  - In the right-click column select menu, columns are now sorted by display order, rather than alphabetically
 * v1.13
 * 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 m 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" m
 *                  - 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:
 * - Support undocumented group features: subseted groups, group footer items
 *
 * Copyright (C) 2006-2016 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@gmail.com.
 */

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.Serialization.Formatters.Binary;
using System.Windows.Forms;
using System.Windows.Forms.VisualStyles;
using System.Runtime.Serialization.Formatters;
using System.Threading;

namespace BrightIdeasSoftware
{
    /// <summary>
    /// An ObjectListView is a much easier to use, and much more powerful, version of the ListView.
    /// </summary>
    /// <remarks>
    /// <para>
    /// An ObjectListView automatically populates a ListView control with information taken 
    /// from a given collection of objects. It can do this because each column is configured
    /// to know which bit of the model object (the "aspect") it should be displaying. Columns similarly
    /// understand how to sort the list based on their aspect, and how to construct groups
    /// using their aspect.
    /// </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>
    /// An ObjectListView can show a "this list is empty" message when there is nothing to show in the list, 
    /// so that the user knows the control is supposed to be empty.
    /// </para>
    /// <para>
    /// Right clicking on a column header should present a menu which can contain:
    /// commands (sort, group, ungroup); filtering; and column selection. Whether these
    /// parts of the menu appear is controlled by ShowCommandMenuOnRightClick, 
    /// ShowFilterMenuOnRightClick and SelectColumnsOnRightClick respectively.
    /// </para>
    /// <para>
    /// The groups created by an ObjectListView can be configured to include other formatting
    /// information, including a group icon, subtitle and task button. Using some undocumented
    /// interfaces, these groups can even on virtual lists.
    /// </para>
    /// <para>
    /// ObjectListView supports dragging rows to other places, including other application. 
    /// Special support is provide for drops from other ObjectListViews in the same application. 
    /// In many cases, an ObjectListView becomes a full drag source by setting <see cref="IsSimpleDragSource"/> to 
    /// true. Similarly, to accept drops, it is usually enough to set <see cref="IsSimpleDropSink"/> to true, 
    /// and then handle the <see cref="CanDrop"/>  and <see cref="Dropped"/>  events (or the <see cref="ModelCanDrop"/>  and 
    /// <see cref="ModelDropped"/> events, if you only want to handle drops from other ObjectListViews in your application).
    /// </para>
    /// <para>
    /// For these classes to build correctly, the project must have references to these assemblies:
    /// </para>
    /// <list type="bullet">
    /// <item><description>System</description></item>
    /// <item><description>System.Data</description></item>
    /// <item><description>System.Design</description></item>
    /// <item><description>System.Drawing</description></item>
    /// <item><description>System.Windows.Forms (obviously)</description></item>
    /// </list>
    /// </remarks>
    [Designer(typeof(BrightIdeasSoftware.Design.ObjectListViewDesigner))]   
    public partial class ObjectListView : ListView, ISupportInitialize {
        
        #region Life and death
        
        /// <summary>
        /// 创建一个 ObjectListView
        /// </summary>
        public ObjectListView() {
            this.ColumnClick += new ColumnClickEventHandler(this.HandleColumnClick);
            this.Layout += new LayoutEventHandler(this.HandleLayout);
            this.ColumnWidthChanging += new ColumnWidthChangingEventHandler(this.HandleColumnWidthChanging);
            this.ColumnWidthChanged += new ColumnWidthChangedEventHandler(this.HandleColumnWidthChanged);
            base.ShowGroups = false;
            base.View = View.Details;
            base.FullRowSelect = true;
            // Turn on owner draw so that we are responsible for our own fates (and isolated from bugs in the underlying ListView)
            this.OwnerDraw = true;

// ReSharper disable DoNotCallOverridableMethodsInConstructor
            this.DoubleBuffered = true; // kill nasty flickers. hiss... me hates 'em
            this.ShowSortIndicators = true;

            // Setup the overlays that will be controlled by the IDE settings
            this.InitializeStandardOverlays();
            this.InitializeEmptyListMsgOverlay();
// ReSharper restore DoNotCallOverridableMethodsInConstructor
        }

        /// <summary>
        /// Dispose of any resources this instance has been using
        /// </summary>
        /// <param name="disposing"></param>
        protected override void Dispose(bool disposing) {
            base.Dispose(disposing);

            if (!disposing) 
                return;

            foreach (GlassPanelForm glassPanel in this.glassPanels) {
                glassPanel.Unbind();
                glassPanel.Dispose();
            }
            this.glassPanels.Clear();

            this.UnsubscribeNotifications(null);
        }

        #endregion

        // TODO
        //public CheckBoxSettings CheckBoxSettings {
        //    get { return checkBoxSettings; }
        //    private set { checkBoxSettings = value; }
        //}

        #region Static properties

        /// <summary>
        /// 获取鼠标左键是否在此时按下。
        /// </summary>
        public static bool IsLeftMouseDown {
            get { return (Control.MouseButtons & MouseButtons.Left) == MouseButtons.Left; }
        }

        /// <summary>
        /// 获取该程序是否在Vista或更高版本上运行
        /// </summary>
        static public bool IsVistaOrLater {
            get {
                if (!ObjectListView.sIsVistaOrLater.HasValue)
                    ObjectListView.sIsVistaOrLater = Environment.OSVersion.Version.Major >= 6;
                return ObjectListView.sIsVistaOrLater.Value;
            }
        }
        static private bool? sIsVistaOrLater;

        /// <summary>
        /// 获取该程序是否在Win7或更高版本上运行
        /// </summary>
        static public bool IsWin7OrLater {
            get {
                if (!ObjectListView.sIsWin7OrLater.HasValue) {
                    // For some reason, Win7 is v6.1, not v7.0
                    Version version = Environment.OSVersion.Version;
                    ObjectListView.sIsWin7OrLater = version.Major > 6 || (version.Major == 6 && version.Minor > 0);
                }
                return ObjectListView.sIsWin7OrLater.Value;
            }
        }
        static private bool? sIsWin7OrLater;

        /// <summary>
        /// Gets or sets how what smoothing mode will be applied to graphic operations.
        /// </summary>
        static public System.Drawing.Drawing2D.SmoothingMode SmoothingMode {
            get { return ObjectListView.sSmoothingMode; }
            set { ObjectListView.sSmoothingMode = value; }
        }
        static private System.Drawing.Drawing2D.SmoothingMode sSmoothingMode =
            System.Drawing.Drawing2D.SmoothingMode.HighQuality;

        /// <summary>
        /// Gets or sets how should text be renderered.
        /// </summary>
        static public System.Drawing.Text.TextRenderingHint TextRenderingHint {
            get { return ObjectListView.sTextRendereringHint; }
            set { ObjectListView.sTextRendereringHint = value; }
        }
        static private System.Drawing.Text.TextRenderingHint sTextRendereringHint =
            System.Drawing.Text.TextRenderingHint.SystemDefault;

        /// <summary>
        /// 获取或设置当组Key为Null时将用于标题组的字符串。将其公开以使其可以本地化。
        /// </summary>
        static public string GroupTitleDefault {
            get { return ObjectListView.sGroupTitleDefault; }
            set { ObjectListView.sGroupTitleDefault = value ?? "{无分组}"; }
        }static private string sGroupTitleDefault = "{无分组}";

        /// <summary>
        /// Convert the given enumerable into an ArrayList as efficiently as possible
        /// </summary>
        /// <param name="collection">The source collection</param>
        /// <param name="alwaysCreate">If true, this method will always create a new
        /// collection.</param>
        /// <returns>An ArrayList with the same contents as the given collection.</returns>
        /// <remarks>
        /// <para>When we move to .NET 3.5, we can use LINQ and not need this method.</para>
        /// </remarks>
        public static ArrayList EnumerableToArray(IEnumerable collection, bool alwaysCreate) {
            if (collection == null)
                return new ArrayList();

            if (!alwaysCreate) {
                ArrayList array = collection as ArrayList;
                if (array != null)
                    return array;

                IList iList = collection as IList;
                if (iList != null)
                    return ArrayList.Adapter(iList);
            }

            ICollection iCollection = collection as ICollection;
            if (iCollection != null)
                return new ArrayList(iCollection);

            ArrayList newObjects = new ArrayList();
            foreach (object x in collection)
                newObjects.Add(x);
            return newObjects;
        }


        /// <summary>
        /// Return the count of items in the given enumerable
        /// </summary>
        /// <param name="collection"></param>
        /// <returns></returns>
        /// <remarks>When we move to .NET 3.5, we can use LINQ and not need this method.</remarks>
        public static int EnumerableCount(IEnumerable collection) {
            if (collection == null)
                return 0;

            ICollection iCollection = collection as ICollection;
            if (iCollection != null)
                return iCollection.Count;

            int i = 0;
// ReSharper disable once UnusedVariable
            foreach (object x in collection)
                i++;
            return i;
        }

        /// <summary>
        /// Return whether or not the given enumerable is empty. A string is regarded as 
        /// an empty collection.
        /// </summary>
        /// <param name="collection"></param>
        /// <returns>True if the given collection is null or empty</returns>
        /// <remarks>
        /// <para>When we move to .NET 3.5, we can use LINQ and not need this method.</para>
        /// </remarks>
        public static bool IsEnumerableEmpty(IEnumerable collection) {
            return collection == null || (collection is string) || !collection.GetEnumerator().MoveNext();
        }

        /// <summary>
        /// Gets or sets whether all ObjectListViews will silently ignore missing aspect errors.
        /// </summary>
        /// <remarks>
        /// <para>
        /// By default, if an ObjectListView is asked to display an aspect
        /// (i.e. a field/property/method)
        /// that does not exist from a model, it displays an error message in that cell, since that 
        /// condition is normally a programming error. There are some use cases where
        /// this is not an error -- in those cases, set this to true and ObjectListView will
        /// simply display an empty cell.
        /// </para>
        /// <para>Be warned: if you set this to true, it can be very difficult to track down
        /// typing mistakes or name changes in AspectNames.</para>
        /// </remarks>
        public static bool IgnoreMissingAspects {
            get { return Munger.IgnoreMissingAspects; }
            set { Munger.IgnoreMissingAspects = value; }
        }

        /// <summary>
        /// Gets or sets whether the control will draw a rectangle in each cell showing the cell padding.
        /// </summary>
        /// <remarks>
        /// <para>
        /// This can help with debugging display problems from cell padding.
        /// </para>
        /// <para>As with all cell padding, this setting only takes effect when the control is owner drawn.</para>
        /// </remarks>
        public static bool ShowCellPaddingBounds {
            get { return sShowCellPaddingBounds;  }
            set { sShowCellPaddingBounds = value;  }
        }
        private static bool sShowCellPaddingBounds;

        /// <summary>
        /// Gets the style that will be used by default to format disabled rows
        /// </summary>
        public static SimpleItemStyle DefaultDisabledItemStyle {
            get {
                if (sDefaultDisabledItemStyle == null) {
                    sDefaultDisabledItemStyle = new SimpleItemStyle();
                    sDefaultDisabledItemStyle.ForeColor = Color.DarkGray;
                }
                return sDefaultDisabledItemStyle;
            }
        }
        private static SimpleItemStyle sDefaultDisabledItemStyle;

        /// <summary>
        /// Gets the style that will be used by default to format hot rows
        /// </summary>
        public static HotItemStyle DefaultHotItemStyle {
            get {
                if (sDefaultHotItemStyle == null) {
                    sDefaultHotItemStyle = new HotItemStyle();
                    sDefaultHotItemStyle.BackColor = Color.FromArgb(224, 235, 253);
                }
                return sDefaultHotItemStyle;
            }
        }
        private static HotItemStyle sDefaultHotItemStyle;

        #endregion

        #region Public properties

        /// <summary>
        /// Gets or sets an model filter that is combined with any column filtering that the end-user specifies.
        /// </summary>
        /// <remarks>This is different from the ModelFilter property, since setting that will replace
        /// any column filtering, whereas setting this will combine this filter with the column filtering</remarks>
        [Browsable(false),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual IModelFilter AdditionalFilter
        {
            get { return this.additionalFilter; }
            set
            {
                if (this.additionalFilter == value)
                    return;
                this.additionalFilter = value;
                this.UpdateColumnFiltering();
            }
        }
        private IModelFilter additionalFilter;

        /// <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>
        /// <para>
        /// 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.
        /// </para>
        /// <para>If you do add or remove columns from the AllColumns collection,
        /// you have to call RebuildColumns() to make those changes take effect.</para>
        /// </remarks>
        [Browsable(false),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public virtual List<OLVColumn> AllColumns {
            get { return this.allColumns; }
            set { this.allColumns = value ?? new List<OLVColumn>(); }
        }
        private List<OLVColumn> allColumns = new List<OLVColumn>();

        /// <summary>
        /// 获取或设置每隔一行的背景色
        /// </summary>
        [Category("ObjectListView"),
         Description("如果使用交替颜色，更改行的背景应该是什么颜色?"),
         DefaultValue(typeof(Color), "")]
        public Color AlternateRowBackColor {
            get { return alternateRowBackColor; }
            set { alternateRowBackColor = value; }
        }
        private Color alternateRowBackColor = Color.Empty;

        /// <summary>
        ///获取已设置的交替行背景色或默认颜色
        /// </summary>
        [Browsable(false)]
        public virtual Color AlternateRowBackColorOrDefault {
            get {
                return this.alternateRowBackColor == Color.Empty ? Color.LemonChiffon : this.alternateRowBackColor;
            }
        }

        /// <summary>
        ///此属性强制ObjectListView始终按给定列对项进行分组。
        /// </summary>
        [Browsable(false),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual OLVColumn AlwaysGroupByColumn {
            get { return alwaysGroupByColumn; }
            set { alwaysGroupByColumn = value; }
        }
        private OLVColumn alwaysGroupByColumn;

        /// <summary>
        ///如果AlwaysGroupByColumn不为空，则此属性将用于确定如何对这些组进行排序。
        ///如果此属性的值为SortOrder.None，则排序顺序将根据用户上次单击的标题切换。
        /// </summary>
        [Browsable(false),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual 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>
        /// <remarks>
        /// Normally, it is preferable to use SmallImageList. Only use this property
        /// if you know exactly what you are doing.
        /// </remarks>
        [Browsable(false),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual ImageList BaseSmallImageList {
            get { return base.SmallImageList; }
            set { base.SmallImageList = value; }
        }

        /// <summary>
        ///获取或设置编辑单元格的方式
        /// </summary>
        /// <remarks>Columns can also be marked as editable.</remarks>
        [Category("ObjectListView"),
         Description("获取或设置编辑单元格的方式"),
         DefaultValue(CellEditActivateMode.None)]
        public virtual CellEditActivateMode CellEditActivation {
            get { return cellEditActivation; }
            set {
                cellEditActivation = value;
                if (this.Created)
                    this.Invalidate();
            }
        }
        private CellEditActivateMode cellEditActivation = CellEditActivateMode.None;

        /// <summary>
        ///编辑单元格时，是否应该使用整个单元格(减去复选框或图像使用的任何空间)？
        /// 默认值为 true.
        /// </summary>
        /// <remarks>
        /// <para>This is always treated as true when the control is NOT owner drawn.</para>
        /// <para>
        /// When this is false and the control is owner drawn, 
        /// ObjectListView will try to calculate the width of the cell's
        /// actual contents, and then size the editing control to be just the right width. If this is true,
        /// the whole width of the cell will be used, regardless of the cell's contents.
        /// </para>
        /// <para>Each column can have a different value for property. This value from the control is only
        /// used when a column is not specified one way or another.</para>
        /// <para>Regardless of this setting, developers can specify the exact size of the editing control
        /// by listening for the CellEditStarting event.</para>
        /// </remarks>
        [Category("ObjectListView"),
         Description("编辑单元格时，是否应该使用整个单元格？"),
         DefaultValue(true)]
        public virtual bool CellEditUseWholeCell {
            get { return cellEditUseWholeCell; }
            set { cellEditUseWholeCell = value; }
        }
        private bool cellEditUseWholeCell;

        /// <summary>
        /// Gets or sets the engine that will handle key presses during a cell edit operation.
        /// Settings this to null will reset it to default value.
        /// </summary>
        [Browsable(false),
         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public CellEditKeyEngine CellEditKeyEngine {
            get { return this.cellEditKeyEngine ?? (this.cellEditKeyEngine = new CellEditKeyEngine()); }
            set { this.cellEditKeyEngine = value; }
        }
        private CellEditKeyEngine cellEditKeyEngine;

        /// <summary>
        /// 获取当前用于编辑单元格的控件。
        /// </summary>
        /// <remarks>This will obviously be null if no cell is being edited.</remarks>
        [Browsable(false)]
        public Control CellEditor {
            get {
                return this.cellEditor;
            }
        }

        /// <summary>
        ///获取或设置编辑控件左边缘或右边缘的单元格时Tab键的行为。
        ///如果为False(默认值)，则按Tab键将换行到同一行的另一侧。
        ///如果为True，编辑最右边的单元格时按Tab键将前进到下一行，
        ///编辑最左边的单元格时按Shift-Tab组合键将更改为上一行。
        /// </summary>
        [Category("ObjectListView"),
        Description("单元格编辑时是否应使用Tab/Shift-Tab组合键更改行? "),
        DefaultValue(false)]
        public virtual bool CellEditTabChangesRows {
            get { return cellEditTabChangesRows; }
            set {
                cellEditTabChangesRows = value;
                if (cellEditTabChangesRows) {
                    this.CellEditKeyEngine.SetKeyBehaviour(Keys.Tab, CellEditCharacterBehaviour.ChangeColumnRight, CellEditAtEdgeBehaviour.ChangeRow);
                    this.CellEditKeyEngine.SetKeyBehaviour(Keys.Tab|Keys.Shift, CellEditCharacterBehaviour.ChangeColumnLeft, CellEditAtEdgeBehaviour.ChangeRow);
                } else {
                    this.CellEditKeyEngine.SetKeyBehaviour(Keys.Tab, CellEditCharacterBehaviour.ChangeColumnRight, CellEditAtEdgeBehaviour.Wrap);
                    this.CellEditKeyEngine.SetKeyBehaviour(Keys.Tab | Keys.Shift, CellEditCharacterBehaviour.ChangeColumnLeft, CellEditAtEdgeBehaviour.Wrap);
                }
            }
        }
        private bool cellEditTabChangesRows;

        /// <summary>
        /// 获取或设置编辑单元格时Enter键的行为。
        /// 如果为False(默认值)，则按Enter键将简单地完成编辑操作。
        /// 如果为True，则Enter将完成编辑操作，并在当前单元格下方的单元格上启动新的编辑操作，在位于底部单元格时换行到下一行的顶部。
        /// </summary>
        [Category("ObjectListView"),
        Description("获取或设置编辑单元格时Enter键的行为"),
        DefaultValue(false)]
        public virtual bool CellEditEnterChangesRows {
            get { return cellEditEnterChangesRows; }
            set {
                cellEditEnterChangesRows = value;
                if (cellEditEnterChangesRows) {
                    this.CellEditKeyEngine.SetKeyBehaviour(Keys.Enter, CellEditCharacterBehaviour.ChangeRowDown, CellEditAtEdgeBehaviour.ChangeColumn);
                    this.CellEditKeyEngine.SetKeyBehaviour(Keys.Enter | Keys.Shift, CellEditCharacterBehaviour.ChangeRowUp, CellEditAtEdgeBehaviour.ChangeColumn);
                } else {
                    this.CellEditKeyEngine.SetKeyBehaviour(Keys.Enter, CellEditCharacterBehaviour.EndEdit, CellEditAtEdgeBehaviour.EndEdit);
                    this.CellEditKeyEngine.SetKeyBehaviour(Keys.Enter | Keys.Shift, CellEditCharacterBehaviour.EndEdit, CellEditAtEdgeBehaviour.EndEdit);
                }
            }
        }
        private bool cellEditEnterChangesRows;

        /// <summary>
        ///获取显示单元格提示的工具提示控件
        /// </summary>
        [Browsable(false)]
        public ToolTipControl CellToolTip {
            get {
                if (this.cellToolTip == null) {
                    this.CreateCellToolTip();
                }
                return this.cellToolTip;
            }
        }
        private ToolTipControl cellToolTip;

        /// <summary>
        /// 获取或设置该项的每个单元格周围将留空的像素数。单元格内容在考虑填充后对齐。
        /// </summary>
        /// <remarks>
        /// <para>Each value of the given rectangle will be treated as an inset from
        /// the corresponding side. The width of the rectangle is the padding for the
        /// right cell edge. The height of the rectangle is the padding for the bottom
        /// cell edge.
        /// </para>
        /// <para>
        /// So, this.olv1.CellPadding = new Rectangle(1, 2, 3, 4); will leave one pixel
        /// of space to the left of the cell, 2 pixels at the top, 3 pixels of space
        /// on the right edge, and 4 pixels of space at the bottom of each cell.
        /// </para>
        /// <para>
        /// This setting only takes effect when the control is owner drawn.
        /// </para>
        /// <para>This setting only affects the contents of the cell. The background is
        /// not affected.</para>
        /// <para>If you set this to a foolish value, your control will appear to be empty.</para>
        /// </remarks>
        [Category("ObjectListView"),
         Description("获取或设置该项的每个单元格周围将留空的像素数。单元格内容在考虑填充后对齐。"),
         DefaultValue(null)]
        public Rectangle? CellPadding {
            get { return this.cellPadding; }
            set { this.cellPadding = value; }
        }
        private Rectangle? cellPadding;

        /// <summary>
        ///获取或设置默认情况下单元格垂直对齐的方式。
        /// </summary>
        /// <remarks>This setting only takes effect when the control is owner drawn. It will only be noticable
        /// when RowHeight has been set such that there is some vertical space in each row.</remarks>
        [Category("ObjectListView"),
         Description("获取或设置默认情况下单元格垂直对齐的方式。"),
         DefaultValue(StringAlignment.Center)]
        public virtual StringAlignment CellVerticalAlignment {
            get { return this.cellVerticalAlignment;  }
            set { this.cellVerticalAlignment = value;  }
        }
        private StringAlignment cellVerticalAlignment = StringAlignment.Center;

        /// <summary>
        /// Should this list show checkboxes?
        /// </summary>
        public new bool CheckBoxes {
            get { return base.CheckBoxes; }
            set {
                // Due to code in the base ListView class, turning off CheckBoxes on a virtual
                // list always throws an InvalidOperationException. We have to do some major hacking
                // to get around that
                if (this.VirtualMode) {
                    // Leave virtual mode
                    this.StateImageList = null;
                    this.VirtualListSize = 0;
                    this.VirtualMode = false;

                    // Change the CheckBox setting while not in virtual mode
                    base.CheckBoxes = value;

                    // Reinstate virtual mode
                    this.VirtualMode = true;

                    // Re-enact the bits that we lost by switching to virtual mode
                    this.ShowGroups = this.ShowGroups;
                    this.BuildList(true);
                } else {
                    base.CheckBoxes = value;
                    // Initialize the state image list so we can display indetermined values.
                    this.InitializeStateImageList();
                }
            }
        }

        /// <summary>
        /// 返回选中行的模型对象；如果未选中行或选中多行，则返回NULL
        /// </summary>
        [Browsable(false),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual Object CheckedObject {
            get {
                IList checkedObjects = this.CheckedObjects;
                return checkedObjects.Count == 1 ? checkedObjects[0] : null;
            }
            set {
                this.CheckedObjects = new ArrayList(new Object[] { value });
            }
        }

        /// <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>
        /// .NET's 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 the get method is O(n), where n is the number of items
        /// in the control. The performance of the set method is
        /// O(n + m) where m is the number of objects being checked. Be careful on long lists.
        /// </para>
        /// </remarks>
        [Browsable(false),
         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual IList CheckedObjects {
            get {
                ArrayList list = new ArrayList();
                if (this.CheckBoxes) {
                    for (int i = 0; i < this.GetItemCount(); i++) {
                        OLVListItem olvi = this.GetItem(i);
                        if (olvi.CheckState == CheckState.Checked)
                            list.Add(olvi.RowObject);
                    }
                }
                return list;
            }
            set {
                if (!this.CheckBoxes)
                    return;

                Stopwatch sw = Stopwatch.StartNew();

                // Set up an efficient way of testing for the presence of a particular model
                Hashtable table = new Hashtable(this.GetItemCount());
                if (value != null) {
                    foreach (object x in value)
                        table[x] = true;
                }

                this.BeginUpdate();
                foreach (Object x in this.Objects) {
                    this.SetObjectCheckedness(x, table.ContainsKey(x) ? CheckState.Checked : CheckState.Unchecked);
                }
                this.EndUpdate();

                Debug.WriteLine(String.Format("PERF - Setting CheckedObjects on {2} objects took {0}ms / {1} ticks", sw.ElapsedMilliseconds, sw.ElapsedTicks, this.GetItemCount()));

            }
        }

        /// <summary>
        ///获取或设置可枚举对象中的选中对象。
        /// </summary>
        /// <remarks>
        /// Useful for checking all objects in the list.
        /// </remarks>
        /// <example>
        /// this.olv1.CheckedObjectsEnumerable = this.olv1.Objects;
        /// </example>
        [Browsable(false),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual IEnumerable CheckedObjectsEnumerable {
            get {
                return this.CheckedObjects;
            }
            set {
                this.CheckedObjects = ObjectListView.EnumerableToArray(value, true);
            }
        }

        /// <summary>
        /// Gets Columns for this list. We hide the original so we can associate
        /// a specialised editor with it.
        /// </summary>
        [Editor("BrightIdeasSoftware.Design.OLVColumnCollectionEditor", "System.Drawing.Design.UITypeEditor")]
        new public ListView.ColumnHeaderCollection Columns {
            get {
                return base.Columns;
            }
        }

        /// <summary>
        ///获取/设置当列表切换到TileView(平铺视图)时应使用的列的列表。
        /// </summary>
        [Browsable(false),
        Obsolete("Use GetFilteredColumns() and OLVColumn.IsTileViewColumn instead"),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public List<OLVColumn> ColumnsForTileView {
            get { return this.GetFilteredColumns(View.Tile); }
        }

        /// <summary>
        ///按向用户显示的顺序返回可见列
        /// </summary>
        [Browsable(false)]
        public virtual List<OLVColumn> ColumnsInDisplayOrder {
            get {
                OLVColumn[] columnsInDisplayOrder = new OLVColumn[this.Columns.Count];
                foreach (OLVColumn col in this.Columns) {
                    columnsInDisplayOrder[col.DisplayIndex] = col;
                }
                return new List<OLVColumn>(columnsInDisplayOrder);
            }
        }


        /// <summary>
        /// 获取显示列表的控件区域，减去任何标题控件
        /// </summary>
        [Browsable(false)]
        public Rectangle ContentRectangle {
            get {
                Rectangle r = this.ClientRectangle;

                // If the listview has a header control, remove the header from the control area
                if ((this.View == View.Details || this.ShowHeaderInAllViews) && this.HeaderControl != null) {
                    Rectangle hdrBounds = new Rectangle();
                    NativeMethods.GetClientRect(this.HeaderControl.Handle, ref hdrBounds);
                    r.Y = hdrBounds.Height;
                    r.Height = r.Height - hdrBounds.Height;
                }

                return r;
            }
        }

        /// <summary>
        ///获取或设置当用户按Ctrl+C组合键时是否应将选定行复制到剪贴板
        /// </summary>
        [Category("ObjectListView"),
        Description("获取或设置当用户按Ctrl+C组合键时是否应将选定行复制到剪贴板"),
        DefaultValue(true)]
        public virtual bool CopySelectionOnControlC {
            get { return copySelectionOnControlC; }
            set { copySelectionOnControlC = value; }
        }
        private bool copySelectionOnControlC = true;


        /// <summary>
        /// 获取或设置按Ctrl+C键复制到剪贴板功能是否应使用安装的DragSource创建放置到剪贴板上的数据对象。
        /// </summary>
        /// <remarks>This is normally what is desired, unless a custom DragSource is installed 
        /// that does some very specialized drag-drop behaviour.</remarks>
        [Category("ObjectListView"),
        Description("获取或设置按Ctrl + C键复制到剪贴板功能是否应使用安装的DragSource创建放置到剪贴板上的数据对象。"),
        DefaultValue(true)]
        public bool CopySelectionOnControlCUsesDragSource {
            get { return this.copySelectionOnControlCUsesDragSource; }
            set { this.copySelectionOnControlCUsesDragSource = value; }
        }
        private bool copySelectionOnControlCUsesDragSource = true;

        /// <summary>
        /// Gets the list of decorations that will be drawn the ListView
        /// </summary>
        /// <remarks>
        /// <para>
        /// Do not modify the contents of this list directly. Use the AddDecoration() and RemoveDecoration() methods.
        /// </para>
        /// <para>
        /// A decoration scrolls with the list contents. An overlay is fixed in place.
        /// </para>
        /// </remarks>
        [Browsable(false)]
        protected IList<IDecoration> Decorations {
            get { return this.decorations; }
        }
        private readonly List<IDecoration> decorations = new List<IDecoration>();

        /// <summary>
        /// 所有者绘制时，此渲染器将绘制未指定特定渲染器的列
        /// </summary>
        /// <remarks>如果尝试将其设置为NULL，则它将恢复为HighlightTextRenende</remarks>
        [Browsable(false),
         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public IRenderer DefaultRenderer {
            get { return this.defaultRenderer; }
            set { this.defaultRenderer = value ?? new HighlightTextRenderer(); }
        }
        private IRenderer defaultRenderer = new HighlightTextRenderer();

        /// <summary>
        /// 获取用于绘制给定单元格的渲染器。
        /// </summary>
        /// <param name="model">The row model for the row</param>
        /// <param name="column">The column to be drawn</param>
        /// <returns>The renderer used for drawing a cell. Must not return null.</returns>
        public IRenderer GetCellRenderer(object model, OLVColumn column) {
            IRenderer renderer = this.CellRendererGetter == null ? null : this.CellRendererGetter(model, column);
            return renderer ?? column.Renderer ?? this.DefaultRenderer;
        }

        /// <summary>
        /// 获取或设置将应用于禁用项的样式。
        /// </summary>
        /// <remarks>If this is not set explicitly, <see cref="ObjectListView.DefaultDisabledItemStyle"/>  will be used.</remarks>
        [Category("ObjectListView"),
        Description("获取或设置将应用于禁用项的样式。"),
        DefaultValue(null)]
        public SimpleItemStyle DisabledItemStyle
        {
            get { return disabledItemStyle; }
            set { disabledItemStyle = value; }
        }
        private SimpleItemStyle disabledItemStyle;

        /// <summary>
        ///获取或设置已禁用的模型对象的列表。无法选择或激活已禁用的对象。
        /// </summary>
        [Browsable(false),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual IEnumerable DisabledObjects
        {
            get
            {
                return disabledObjects.Keys;
            }
            set
            {
                this.disabledObjects.Clear();
                DisableObjects(value);
            }
        }
        private readonly Hashtable disabledObjects = new Hashtable();

        /// <summary>
        /// 判断此给定模型对象是否已禁用
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        public bool IsDisabled(object model)
        {
            return model != null && this.disabledObjects.ContainsKey(model);
        }

        /// <summary>
        /// 禁用给定的模型对象。禁用的对象无法选择或激活。
        /// </summary>
        /// <param name="model">Must not be null</param>
        public void DisableObject(object model) {
            ArrayList list = new ArrayList();
            list.Add(model);
            this.DisableObjects(list);
        }

        /// <summary>
        ///禁用所有给定的模型对象
        /// </summary>
        /// <param name="models"></param>
        public void DisableObjects(IEnumerable models)
        {
            if (models == null)
                return;
            ArrayList list = ObjectListView.EnumerableToArray(models, false);
            foreach (object model in list)
            {
                if (model == null) 
                    continue;

                this.disabledObjects[model] = true;
                int modelIndex = this.IndexOf(model);
                if (modelIndex >= 0)
                    NativeMethods.DeselectOneItem(this, modelIndex);
            }
            this.RefreshObjects(list);
        }

        /// <summary>
        ///启用给定的模型对象，以便可以再次选择并激活它。
        /// </summary>
        /// <param name="model">Must not be null</param>
        public void EnableObject(object model)
        {
            this.disabledObjects.Remove(model);
            this.RefreshObject(model);
        }

        /// <summary>
        ///启用所有给定的模型对象
        /// </summary>
        /// <param name="models"></param>
        public void EnableObjects(IEnumerable models)
        {
            if (models == null)
                return;
            ArrayList list = ObjectListView.EnumerableToArray(models, false);
            foreach (object model in list)
            {
                if (model != null)
                    this.disabledObjects.Remove(model);
            }
            this.RefreshObjects(list);
        }

        /// <summary>
        ///忽略所有禁用的对象。这不会触发重绘或重建
        /// </summary>
        protected void ClearDisabledObjects()
        {
            this.disabledObjects.Clear();            
        }

        /// <summary>
        /// Gets or sets the object that controls how drags start from this control
        /// </summary>
        [Browsable(false),
         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public IDragSource DragSource {
            get { return this.dragSource; }
            set { this.dragSource = value; }
        }
        private IDragSource dragSource;

        /// <summary>
        /// Gets or sets the object that controls how drops are accepted and processed
        /// by this ListView.
        /// </summary>
        /// <remarks>
        /// <para>
        /// If the given sink is an instance of SimpleDropSink, then events from the drop sink
        /// will be automatically forwarded to the ObjectListView (which means that handlers
        /// for those event can be configured within the IDE).
        /// </para>
        /// <para>If this is set to null, the control will not accept drops.</para>
        /// </remarks>
        [Browsable(false),
         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public IDropSink DropSink {
            get { return this.dropSink; }
            set {
                if (this.dropSink == value)
                    return;

                // Stop listening for events on the old sink
                SimpleDropSink oldSink = this.dropSink as SimpleDropSink;
                if (oldSink != null) {
                    oldSink.CanDrop -= new EventHandler<OlvDropEventArgs>(this.DropSinkCanDrop);
                    oldSink.Dropped -= new EventHandler<OlvDropEventArgs>(this.DropSinkDropped);
                    oldSink.ModelCanDrop -= new EventHandler<ModelDropEventArgs>(this.DropSinkModelCanDrop);
                    oldSink.ModelDropped -= new EventHandler<ModelDropEventArgs>(this.DropSinkModelDropped);
                }

                this.dropSink = value;
                this.AllowDrop = (value != null);
                if (this.dropSink != null)
                    this.dropSink.ListView = this;

                // Start listening for events on the new sink
                SimpleDropSink newSink = value as SimpleDropSink;
                if (newSink != null) {
                    newSink.CanDrop += new EventHandler<OlvDropEventArgs>(this.DropSinkCanDrop);
                    newSink.Dropped += new EventHandler<OlvDropEventArgs>(this.DropSinkDropped);
                    newSink.ModelCanDrop += new EventHandler<ModelDropEventArgs>(this.DropSinkModelCanDrop);
                    newSink.ModelDropped += new EventHandler<ModelDropEventArgs>(this.DropSinkModelDropped);
                }
            }
        }
        private IDropSink dropSink;

        // Forward events from the drop sink to the control itself
        void DropSinkCanDrop(object sender, OlvDropEventArgs e) { this.OnCanDrop(e); }
        void DropSinkDropped(object sender, OlvDropEventArgs e) { this.OnDropped(e); }
        void DropSinkModelCanDrop(object sender, ModelDropEventArgs e) { this.OnModelCanDrop(e); }
        void DropSinkModelDropped(object sender, ModelDropEventArgs e) { this.OnModelDropped(e); }

        /// <summary>
        /// This registry decides what control should be used to edit what cells, based
        /// on the type of the value in the cell.
        /// </summary>
        /// <see cref="EditorRegistry"/>
        /// <remarks>All instances of ObjectListView share the same editor registry.</remarks>
// ReSharper disable FieldCanBeMadeReadOnly.Global
        static public EditorRegistry EditorRegistry = new EditorRegistry();
        // ReSharper restore FieldCanBeMadeReadOnly.Global

        /// <summary>
        /// 获取或设置在此列表视图中没有项时应显示的文本。
        /// </summary>
        /// <remarks>If the EmptyListMsgOverlay has been changed to something other than a TextOverlay,
        /// this property does nothing</remarks>
        [Category("ObjectListView"),
         Description("当列表没有项时，在控件中显示此消息"),
         DefaultValue(null),
         Localizable(true)]
        public virtual String EmptyListMsg {
            get {
                TextOverlay overlay = this.EmptyListMsgOverlay as TextOverlay;
                return overlay == null ? null : overlay.Text;
            }
            set {
                TextOverlay overlay = this.EmptyListMsgOverlay as TextOverlay;
                if (overlay != null) {
                    overlay.Text = value;
                    this.Invalidate();
                }
            }
        }

        /// <summary>
        /// 获取或设置绘制列表空消息时应使用的字体
        /// </summary>
        /// <remarks>If the EmptyListMsgOverlay has been changed to something other than a TextOverlay,
        /// this property does nothing</remarks>
        [Category("ObjectListView"),
        Description("获取或设置绘制列表空消息时应使用的字体"),
        DefaultValue(null)]
        public virtual Font EmptyListMsgFont {
            get {
                TextOverlay overlay = this.EmptyListMsgOverlay as TextOverlay;
                return overlay == null ? null : overlay.Font;
            }
            set {
                TextOverlay overlay = this.EmptyListMsgOverlay as TextOverlay;
                if (overlay != null)
                    overlay.Font = value;
            }
        }

        /// <summary>
        /// 返回“列表为空”消息的字体或合理的默认值
        /// </summary>
        [Browsable(false)]
        public virtual Font EmptyListMsgFontOrDefault {
            get {
               return this.EmptyListMsgFont ?? new Font("Tahoma", 14);
            }
        }

        /// <summary>
        /// Gets or sets the overlay responsible for drawing the List Empty msg.
        /// </summary>
        [Browsable(false),
         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual IOverlay EmptyListMsgOverlay {
            get { return this.emptyListMsgOverlay; }
            set {
                if (this.emptyListMsgOverlay != value) {
                    this.emptyListMsgOverlay = value;
                    this.Invalidate();
                }
            }
        }
        private IOverlay emptyListMsgOverlay;

        /// <summary>
        ///获取在筛选中幸存下来的对象的集合。
        /// </summary>
        /// <remarks>
        /// <para>
        /// This collection is the result of filtering the current list of objects. 
        /// It is not a snapshot of the filtered list that was last used to build the control. 
        /// </para>
        /// <para>
        /// Normal warnings apply when using this with virtual lists. It will work, but it
        /// may take a while.
        /// </para>
        /// </remarks>
        [Browsable(false),
         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        virtual public IEnumerable FilteredObjects {
            get {
                if (this.UseFiltering)
                    return this.FilterObjects(this.Objects, this.ModelFilter, this.ListFilter);
                
                return this.Objects;
            }
        }

        /// <summary>
        /// 获取或设置将用于生成筛选器菜单的策略对象
        /// </summary>
        /// <remarks>如果为NULL，则不会生成筛选器菜单。</remarks>
        [Browsable(false),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public FilterMenuBuilder FilterMenuBuildStrategy {
            get { return filterMenuBuilder; }
            set { filterMenuBuilder = value; }
        }
        private FilterMenuBuilder filterMenuBuilder = new FilterMenuBuilder();

        /// <summary>
        /// 获取或设置具有键盘焦点的行
        /// </summary>
        /// <remarks>
        /// <para>
        /// Setting an object to be focused does *not* select it. If you want to select and focus a row,
        /// use <see cref="SelectedObject"/>.
        /// </para>
        /// <para>
        /// This property is not generally used and is only useful in specialized situations.
        /// </para>
        /// </remarks>
        [Browsable(false),
         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual Object FocusedObject {
            get { return this.FocusedItem == null ? null : ((OLVListItem)this.FocusedItem).RowObject; }
            set {
                OLVListItem item = this.ModelToItem(value);
                if (item != null)
                    item.Focused = true;
            }
        }

        /// <summary>
        /// 隐藏Groups集合，使其在“属性”网格中不可见。
        /// </summary>
        [Browsable(false),
         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        new public ListViewGroupCollection Groups {
            get { return base.Groups; }
        }

        /// <summary>
        ///获取或设置分组使用的ImageList
        /// </summary>
        /// <remarks>如果未设置此选项，则分组标题将不会显示任何图像。</remarks>
        [Category("ObjectListView"),
         Description("获取或设置分组使用的ImageList"),
         DefaultValue(null)]
        public ImageList GroupImageList {
            get { return this.groupImageList; }
            set {
                this.groupImageList = value;
                if (this.Created) {
                    NativeMethods.SetGroupImageList(this, value);
                }
            }
        }
        private ImageList groupImageList;

        /// <summary>
        /// 获取在组为空或包含多个项目时应如何设置组标签的格式
        /// </summary>
        /// <remarks>
        /// 给定的格式字符串必须有两个占位符：
        /// <list type="bullet">
        /// <item><description>{0} - 原来的组标题</description></item>
        /// <item><description>{1} - 组中的项目数</description></item>
        /// </list>
        /// </remarks>
        /// <example>"[{0} [{1} 项]"</example>
        [Category("ObjectListView"),
         Description("获取在组为空或包含多个项目时应如何设置组标签的格式"),
         DefaultValue(null),
         Localizable(true)]
        public virtual string GroupWithItemCountFormat {
            get { return groupWithItemCountFormat; }
            set { groupWithItemCountFormat = value; }
        }
        private string groupWithItemCountFormat;

        /// <summary>
        /// 返回this.GroupWithItemCountFormat或合理的默认值
        /// </summary>
        [Browsable(false)]
        public virtual string GroupWithItemCountFormatOrDefault {
            get {
                return String.IsNullOrEmpty(this.GroupWithItemCountFormat) ? "{0} [{1} 项]" : this.GroupWithItemCountFormat;
            }
        }

        /// <summary>
        /// 获取在组仅包含单个项目时应如何设置组标签的格式
        /// </summary>
        /// <remarks>
        ///给定的格式字符串必须有两个占位符：
        /// <list type="bullet">
        /// <item><description>{0} - 原来的组标题</description></item>
        /// <item><description>{1} - 组中的项目数 (总是1)</description></item>
        /// </list>
        /// </remarks>
        /// <example>"[{0} [{1} 项]"</example>
        [Category("ObjectListView"),
         Description("获取在组仅包含单个项目时应如何设置组标签的格式"),
         DefaultValue(null),
         Localizable(true)]
        public virtual string GroupWithItemCountSingularFormat {
            get { return groupWithItemCountSingularFormat; }
            set { groupWithItemCountSingularFormat = value; }
        }
        private string groupWithItemCountSingularFormat;

        /// <summary>
        /// 获取GroupWithItemCountSingularFormat或合理的默认值
        /// </summary>
        [Browsable(false)]
        public virtual string GroupWithItemCountSingularFormatOrDefault {
            get {
                return String.IsNullOrEmpty(this.GroupWithItemCountSingularFormat) ? "{0} [{1} 项]" : this.GroupWithItemCountSingularFormat;
            }
        }

        /// <summary>
        ///获取或设置此ObjectListView中的组是否应可折叠。
        /// </summary>
        /// <remarks>
        /// 此特性只支持Vista或之后的系统.
        /// </remarks>
        [Browsable(true),
         Category("ObjectListView"),
         Description("获取或设置此ObjectListView中的组是否应可折叠 (Vista或之后的系统才支持)."),
         DefaultValue(true)]
        public bool HasCollapsibleGroups {
            get { return hasCollapsibleGroups; }
            set { hasCollapsibleGroups = value; }
        }
        private bool hasCollapsibleGroups = true;

        /// <summary>
        /// 获取当前列表是否存在当列表为空时显示的文本
        /// </summary>
        [Browsable(false)]
        public virtual bool HasEmptyListMsg {
            get { return !String.IsNullOrEmpty(this.EmptyListMsg); }
        }

        /// <summary>
        /// 获取是否有要绘制的叠加层
        /// </summary>
        [Browsable(false)]
        public bool HasOverlays {
            get {
                return (this.Overlays.Count > 2 ||
                    this.imageOverlay.Image != null ||
                    !String.IsNullOrEmpty(this.textOverlay.Text));
            }
        }

        /// <summary>
        /// 获取ListView的列头控件
        /// </summary>
        [Browsable(false)]
        public HeaderControl HeaderControl {
            get { return this.headerControl ?? (this.headerControl = new HeaderControl(this)); }
        }
        private HeaderControl headerControl;

        /// <summary>
        /// 获取或设置列标题文本的绘制字体
        /// </summary>
        /// <remarks>单个列可以通过其HeaderFormatStyle属性重写此设置。</remarks>
        [DefaultValue(null)]
        [Browsable(false)]
        [Obsolete("请改用HeaderFormatStyle", false)]
        public Font HeaderFont {
            get { return this.HeaderFormatStyle == null ? null : this.HeaderFormatStyle.Normal.Font; }
            set {
                if (value == null && this.HeaderFormatStyle == null)
                    return;

                if (this.HeaderFormatStyle == null)
                    this.HeaderFormatStyle = new HeaderFormatStyle();

                this.HeaderFormatStyle.SetFont(value);
            }
        }

        /// <summary>
        /// 获取或设置将用于绘制列表视图的列标题的样式
        /// </summary>
        /// <remarks>
        /// <para>
        /// 仅当HeaderUsesThemes为false时才使用此选项。
        /// </para>
        /// <para>
        /// 单个列可以通过其HeaderFormatStyle属性重写此设置。
        /// </para>
        /// </remarks>
        [Category("ObjectListView"),
         Description("获取或设置将用于绘制列表视图的列标题的样式"),
         DefaultValue(null)]
        public HeaderFormatStyle HeaderFormatStyle {
            get { return this.headerFormatStyle; }
            set { this.headerFormatStyle = value; }
        }
        private HeaderFormatStyle headerFormatStyle;

        /// <summary>
        /// 获取或设置标题的最大高度。-1表示没有最大值。
        /// </summary>
        [Category("ObjectListView"),
         Description("获取或设置标题的最大高度。-1表示没有最大值。"),
         DefaultValue(-1)]
        public int HeaderMaximumHeight
        {
            get { return headerMaximumHeight; }
            set { headerMaximumHeight = value; }
        }
        private int headerMaximumHeight = -1;

        /// <summary>
        /// 获取或设置标题的最小高度。-1表示没有最小值。
        /// </summary>
        [Category("ObjectListView"),
         Description("获取或设置标题的最小高度。-1表示没有最小值。"),
         DefaultValue(-1)]
        public int HeaderMinimumHeight
        {
            get { return headerMinimumHeight; }
            set { headerMinimumHeight = value; }
        }
        private int headerMinimumHeight = -1;

        /// <summary>
        /// 获取或设置是否严格按照操作系统的主题绘制标题。
        /// </summary>
        /// <remarks>
        /// <para>
        ///如果设置为true，则列头将完全由系统呈现，不需要ObjectListViews的任何特殊处理。
        ///页眉中将没有图像、没有过滤器指示符、没有文字换行、没有页眉样式、没有复选框。
        /// </para>
        /// <para>如果将其设置为False，ObjectListView将以其认为最好的方式呈现标题。
        /// 如果不需要特殊功能，则ObjectListView会将渲染委托给操作系统。
        /// 否则，ObjectListView将根据配置设置绘制标题。
        /// </para>
        /// <para>
        /// 没有主题的效果会因操作系统不同而不同。至少，分类指示器不会是标准的。
        /// </para>
        /// </remarks>
        [Category("ObjectListView"),
         Description("获取或设置是否严格按照操作系统的主题绘制标题"),
         DefaultValue(false)]
        public bool HeaderUsesThemes {
            get { return this.headerUsesThemes; }
            set { this.headerUsesThemes = value; }
        }
        private bool headerUsesThemes;

        /// <summary>
        /// 获取或设置列头标题中的文本是否自动换行。
        /// </summary>
        /// <remarks>
        /// <para>将在单词之间应用换行符。过长的单词仍将被省略。</para>
        /// <para>
        /// 与所有使列头看起来不同的设置一样，HeaderUsesThemes必须设置为false，否则操作系统将负责绘制列头，并且不允许自动换行文本。
        /// </para>
        /// </remarks>
        [Category("ObjectListView"),
         Description("获取或设置列头标题中的文本是否自动换行"),
         DefaultValue(false)]
        public bool HeaderWordWrap {
            get { return this.headerWordWrap; }
            set {
                this.headerWordWrap = value;
                if (this.headerControl != null)
                    this.headerControl.WordWrap = value;
            }
        }
        private bool headerWordWrap;

        /// <summary>
        /// 获取显示列标题提示的工具提示
        /// </summary>
        [Browsable(false)]
        public ToolTipControl HeaderToolTip {
            get {
                return this.HeaderControl.ToolTip;
            }
        }

        /// <summary>
        ///获取鼠标当前所在行的索引。
        /// </summary>
        [Browsable(false),
         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual int HotRowIndex {
            get { return this.hotRowIndex; }
            protected set { this.hotRowIndex = value; }
        }
        private int hotRowIndex;

        /// <summary>
        /// 获取鼠标当前所在列的索引
        /// </summary>
        [Browsable(false),
         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual int HotColumnIndex {
            get { return this.hotColumnIndex; }
            protected set { this.hotColumnIndex = value; }
        }
        private int hotColumnIndex;

        /// <summary>
        /// 获取鼠标当前所在的项/子项的一部分
        /// </summary>
        [Browsable(false),
         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual HitTestLocation HotCellHitLocation {
            get { return this.hotCellHitLocation; }
            protected set { this.hotCellHitLocation = value; }
        }
        private HitTestLocation hotCellHitLocation;

        /// <summary>
        /// Gets an extended indication of the part of item/subitem/group that the mouse is currently over
        /// </summary>
        [Browsable(false),
         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual HitTestLocationEx HotCellHitLocationEx
        {
            get { return this.hotCellHitLocationEx; }
            protected set { this.hotCellHitLocationEx = value; }
        }
        private HitTestLocationEx hotCellHitLocationEx;

        /// <summary>
        /// 获取鼠标所在的组。
        /// </summary>
        [Browsable(false),
         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public OLVGroup HotGroup
        {
            get { return hotGroup; }
            internal set { hotGroup = value; }
        }
        private OLVGroup hotGroup;

        /// <summary>
        ///处于“热”状态的项的索引，即在光标下方。-1表示没有项目。
        /// </summary>
        [Browsable(false),
         Obsolete("请使用 HotRowIndex 代替", false)]
        public virtual int HotItemIndex {
            get { return this.HotRowIndex; }
        }

        /// <summary>
        /// 获取和设置对游标下的行应用哪种格式
        /// </summary>
        /// <remarks>
        /// <para>
        ///这仅在UseHotItem为true时生效。
        /// </para>
        /// <para>如果样式具有叠加层，则必须对其进行设置。*之前* 将其分配给此属性。之后添加它将被忽略。 </para>
        /// </remarks>
        [Category("ObjectListView"),
         Description("获取和设置对游标下的行应用哪种格式"),
         DefaultValue(null)]
        public virtual HotItemStyle HotItemStyle {
            get { return this.hotItemStyle; }
            set {
                if (this.HotItemStyle != null)
                    this.RemoveOverlay(this.HotItemStyle.Overlay);
                this.hotItemStyle = value;
                if (this.HotItemStyle != null)
                    this.AddOverlay(this.HotItemStyle.Overlay);
            }
        }
        private HotItemStyle hotItemStyle;

        /// <summary>
        /// 获取已安装的热点项样式或合理的默认值。
        /// </summary>
        [Browsable(false),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual HotItemStyle HotItemStyleOrDefault {
            get { return this.HotItemStyle ?? ObjectListView.DefaultHotItemStyle; }
        }

        /// <summary>
        /// 获取和设置应将哪种格式应用于超链接
        /// </summary>
        [Category("ObjectListView"),
         Description("获取和设置应将哪种格式应用于超链接"),
         DefaultValue(null)]
        public virtual HyperlinkStyle HyperlinkStyle {
            get { return this.hyperlinkStyle; }
            set { this.hyperlinkStyle = value; }
        }
        private HyperlinkStyle hyperlinkStyle;

        /// <summary>
        /// 获取和设置所选行的背景应该使用什么颜色
        /// </summary>
        [Category("ObjectListView"),
         Description("获取和设置所选行的背景应该使用什么颜色"),
         DefaultValue(typeof(Color), "")]
        public virtual Color SelectedBackColor {
            get { return this.selectedBackColor; }
            set { this.selectedBackColor = value; }
        }
        private Color selectedBackColor = Color.Empty;

        /// <summary>
        /// 返回应用于选定行的背景颜色或合理的默认值
        /// </summary>
        [Browsable(false)]
        public virtual Color SelectedBackColorOrDefault {
            get {
                return this.SelectedBackColor.IsEmpty ? SystemColors.Highlight : this.SelectedBackColor;
            }
        }

        /// <summary>
        /// 获取和设置所选行的字体应该使用什么颜色
        /// </summary>
        [Category("ObjectListView"),
         Description("获取和设置所选行的字体应该使用什么颜色"),
         DefaultValue(typeof(Color), "")]
        public virtual Color SelectedForeColor {
            get { return this.selectedForeColor; }
            set { this.selectedForeColor = value; }
        }
        private Color selectedForeColor = Color.Empty;

        /// <summary>
        /// 返回应用于选定行的字体颜色或合理的默认值
        /// </summary>
        [Browsable(false)]
        public virtual Color SelectedForeColorOrDefault {
            get {
                return this.SelectedForeColor.IsEmpty ? SystemColors.HighlightText : this.SelectedForeColor;
            }
        }
        /// <summary>
        /// 使用 SelectedBackColor 代替
        /// </summary>
        [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        [Obsolete("使用 SelectedBackColor 代替")]
        public virtual Color HighlightBackgroundColor { get { return this.SelectedBackColor; } set { this.SelectedBackColor = value; } }
        /// <summary>
        /// 使用 SelectedBackColorOrDefault 代替
        /// </summary>
        [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        [Obsolete("使用 SelectedBackColorOrDefault 代替")]
        public virtual Color HighlightBackgroundColorOrDefault { get { return this.SelectedBackColorOrDefault; } }
        /// <summary>
        /// 使用 SelectedForeColor 代替
        /// </summary>
        [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        [Obsolete("使用 SelectedForeColor 代替")]
        public virtual Color HighlightForegroundColor { get { return this.SelectedForeColor; } set { this.SelectedForeColor = value; } }
        /// <summary>
        /// 使用 SelectedForeColorOrDefault 代替
        /// </summary>
        [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        [Obsolete("使用 SelectedForeColorOrDefault 代替")]
        public virtual Color HighlightForegroundColorOrDefault { get { return this.SelectedForeColorOrDefault; } }
        /// <summary>
        /// 使用 UnfocusedSelectedBackColor 代替
        /// </summary>
        [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        [Obsolete("使用 UnfocusedSelectedBackColor 代替")]
        public virtual Color UnfocusedHighlightBackgroundColor { get { return this.UnfocusedSelectedBackColor; } set { this.UnfocusedSelectedBackColor = value; } }
        /// <summary>
        /// 使用 UnfocusedSelectedBackColorOrDefault 代替
        /// </summary>
        [Obsolete("使用 UnfocusedSelectedBackColorOrDefault 代替")]
        public virtual Color UnfocusedHighlightBackgroundColorOrDefault { get { return this.UnfocusedSelectedBackColorOrDefault; } }
        /// <summary>
        /// 使用 UnfocusedSelectedForeColor 代替
        /// </summary>
        [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        [Obsolete("使用 UnfocusedSelectedForeColor 代替")]
        public virtual Color UnfocusedHighlightForegroundColor { get { return this.UnfocusedSelectedForeColor; } set { this.UnfocusedSelectedForeColor = value; } }
        /// <summary>
        /// 使用 UnfocusedSelectedForeColorOrDefault 代替
        /// </summary>
        [Obsolete("使用 UnfocusedSelectedForeColorOrDefault 代替")]
        public virtual Color UnfocusedHighlightForegroundColorOrDefault { get { return this.UnfocusedSelectedForeColorOrDefault; } }

        /// <summary>
        /// 获取或设置是否应将隐藏列包括在复制或拖动到其他应用程序的行的文本表示形式中。
        /// 如果为False(默认值)，则仅包括可见列。
        /// </summary>
        [Category("ObjectListView"),
        Description("获取或设置是否应将隐藏列包括在复制或拖动到其他应用程序的行的文本表示形式中。如果为False(默认值)，则仅包括可见列。"),
        DefaultValue(false)]
        public virtual bool IncludeHiddenColumnsInDataTransfer {
            get { return includeHiddenColumnsInDataTransfer; }
            set { includeHiddenColumnsInDataTransfer = value; }
        }
        private bool includeHiddenColumnsInDataTransfer;

        /// <summary>
        /// 获取或设置复制行时，列标题是否在文本中。
        /// 如果为False(默认值)，在文本中将不会显示列标题。
        /// </summary>
        [Category("ObjectListView"),
        Description("复制行时，列标题是否在文本中."),
        DefaultValue(false)]
        public virtual bool IncludeColumnHeadersInCopy
        {
            get { return includeColumnHeadersInCopy; }
            set { includeColumnHeadersInCopy = value; }
        }
        private bool includeColumnHeadersInCopy;

        /// <summary>
        ///如果当前正在进行单元格编辑操作，则返回TRUE
        /// </summary>
        [Browsable(false)]
        public virtual bool IsCellEditing {
            get { return this.cellEditor != null; }
        }

        /// <summary>
        /// 如果在开发环境中使用ObjectListView，则返回true。
        /// </summary>
        [Browsable(false)]
        public virtual bool IsDesignMode {
            get { return this.DesignMode; }
        }

        /// <summary>
        ///获取当前列表是否正在筛选其内容
        /// </summary>
        [Browsable(false),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        virtual public bool IsFiltering {
            get { return this.UseFiltering && (this.ModelFilter != null || this.ListFilter != null); }
        }

        /// <summary>
        ///当用户在列表中键入内容时，是否应该搜索当前排序列中的值以查找匹配项？
        ///如果为False，则无论排序列是什么，都将始终使用主列。
        /// </summary>
        /// <remarks>When this is true, the behavior is like that of ITunes.</remarks>
        [Category("ObjectListView"),
        Description("当用户在列表中键入内容时，是否应该搜索当前排序列中的值以查找匹配项？如果为False，则无论排序列是什么，都将始终使用主列。"),
        DefaultValue(true)]
        public virtual bool IsSearchOnSortColumn {
            get { return isSearchOnSortColumn; }
            set { isSearchOnSortColumn = value; }
        }
        private bool isSearchOnSortColumn = true;

        /// <summary>
        /// 获取或设置此控件是否将使用SimpleDropSink接收拖动
        /// </summary>
        /// <remarks>
        /// <para>
        /// 此设置将替换任何以前的DropSink。
        /// </para>
        /// <para>
        /// 将其设置为true后，SimpleDropSink仍需要配置为说明它何时可以接受拖动，
        /// 以及在拖动某些内容时应该执行什么操作。需要执行这些操作使此属性几乎毫无用处 :(
        /// </para>
        /// </remarks>
        [Category("ObjectListView"),
        Description("获取或设置此控件是否将使用SimpleDropSink接收拖动"),
        DefaultValue(false)]
        public virtual bool IsSimpleDropSink {
            get { return this.DropSink != null; }
            set {
                this.DropSink = value ? new SimpleDropSink() : null;
            }
        }

        /// <summary>
        /// 获取或设置此控件是否将使用SimpleDragSource来启动拖动
        /// </summary>
        /// <remarks>此设置将替换之前的任何DragSource</remarks>
        [Category("ObjectListView"),
        Description("获取或设置此控件是否将使用SimpleDragSource来启动拖动"),
        DefaultValue(false)]
        public virtual bool IsSimpleDragSource {
            get { return this.DragSource != null; }
            set {
                this.DragSource = value ? new SimpleDragSource() : null;
            }
        }

        /// <summary>
        /// 隐藏Items集合，使其在“属性”网格中不可见。
        /// </summary>
        [Browsable(false),
         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        new public ListViewItemCollection Items {
            get { return base.Items; }
        }

        /// <summary>
        /// 当列表中的项目处于非详细视图中时，此渲染器会绘制项目。在详细信息视图中，负责个体列的呈现器。
        /// </summary>
        [Category("ObjectListView"),
        Description("当列表中的项目处于非详细视图中时，此渲染器会绘制项目。在详细信息视图中，负责个体列的呈现器。"),
        DefaultValue(null)]
        public IRenderer ItemRenderer {
            get { return itemRenderer; }
            set { itemRenderer = value; }
        }
        private IRenderer itemRenderer;

        /// <summary>
        /// 获取或设置最后一次排序的列
        /// </summary>
        /// <remarks>这是PrimarySortColumn的别名</remarks>
        [Browsable(false),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual OLVColumn LastSortColumn {
            get { return this.PrimarySortColumn; }
            set { this.PrimarySortColumn = value; }
        }

        /// <summary>
        /// 获取或设置最后一次排序的方向
        /// </summary>
        /// <remarks>这是 PrimarySortOrder的别名</remarks>
        [Browsable(false),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual SortOrder LastSortOrder {
            get { return this.PrimarySortOrder; }
            set { this.PrimarySortOrder = value; }
        }

        /// <summary>
        /// 获取或设置应用于整个对象列表的筛选器。
        /// </summary>
        /// <remarks>
        /// 列表将立即更新以反映此筛选器。
        /// </remarks>
        [Browsable(false),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual IListFilter ListFilter {
            get { return listFilter; }
            set {
                listFilter = value;
                if (this.UseFiltering)
                    this.UpdateFiltering();
            }
        }
        private IListFilter listFilter;

        /// <summary>
        /// 获取或设置应用于列表中每个模型对象的筛选器
        /// </summary>
        /// <remarks>
        /// <para>您可能需要考虑使用 <see cref="AdditionalFilter"/> 而不是此属性，
        /// 因为AdditionalFilter在运行时与列筛选相结合。
        /// 设置此属性只是替换用户可能已给出的任何列筛选器。</para>
        /// <para>
        /// The list is updated immediately to reflect this filter. 
        /// </para>
        /// </remarks>
        [Browsable(false),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual IModelFilter ModelFilter {
            get { return modelFilter; }
            set {
                modelFilter = value;
                this.NotifyNewModelFilter();
                if (this.UseFiltering) {
                    this.UpdateFiltering();

                    // When the filter changes, it's likely/possible that the selection has also changed.
                    // It's expensive to see if the selection has actually changed (for large virtual lists),
                    // so we just fake a selection changed event, just in case. SF #144
                    this.OnSelectedIndexChanged(EventArgs.Empty);
                }
            }
        }
        private IModelFilter modelFilter;

        /// <summary>
        /// Gets the hit test info last time the mouse was moved.
        /// </summary>
        /// <remarks>Useful for hot item processing.</remarks>
        [Browsable(false),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual OlvListViewHitTestInfo MouseMoveHitTest {
            get { return mouseMoveHitTest; }
            private set { mouseMoveHitTest = value; }
        }
        private OlvListViewHitTestInfo mouseMoveHitTest;

        /// <summary>
        ///获取或设置列表显示的组列表。
        /// </summary>
        /// <remarks>
        /// 此属性与.NET Groups属性的工作方式不同，应将其视为只读属性。
        /// 对列表所做的更改不会反映在ListView本身中。
        /// 在此列表中添加或删除组是没有意义的，这样的修改不会起到任何作用。
        /// 要执行此类操作，必须侦听BeforeCreatingGroups或AboutToCreateGroups事件，
        /// 并更改这些事件中的组列表。
        /// </remarks>
        [Browsable(false),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public IList<OLVGroup> OLVGroups {
            get { return this.olvGroups; }
            set { this.olvGroups = value; }
        }
        private IList<OLVGroup> olvGroups;

        /// <summary>
        /// 获取或设置折叠的OLVGroups的集合。
        /// </summary>
        [Browsable(false),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public IEnumerable<OLVGroup> CollapsedGroups {
            get
            {
                if (this.OLVGroups != null)
                {
                    foreach (OLVGroup group in this.OLVGroups)
                    {
                        if (group.Collapsed)
                            yield return group;
                    }
                }
            }
            set
            {
                if (this.OLVGroups == null)
                    return;

                Hashtable shouldCollapse = new Hashtable();
                if (value != null)
                {
                    foreach (OLVGroup group in value)
                        shouldCollapse[group.Key] = true;
                }
                foreach (OLVGroup group in this.OLVGroups)
                {
                    group.Collapsed = shouldCollapse.ContainsKey(group.Key);
                }

            }
        }

        /// <summary>
        /// Gets or sets whether the user wants to owner draw the header control
        /// themselves. If this is false (the default), ObjectListView will use
        /// custom drawing to render the header, if needed.
        /// </summary>
        /// <remarks>
        /// If you listen for the DrawColumnHeader event, you need to set this to true,
        /// otherwise your event handler will not be called.
        /// </remarks>
        [Category("ObjectListView"),
         Description("Should the DrawColumnHeader event be triggered"),
         DefaultValue(false)]
        public bool OwnerDrawnHeader {
            get { return ownerDrawnHeader; }
            set { ownerDrawnHeader = value; }
        }
        private bool ownerDrawnHeader;

        /// <summary>
        ///获取/设置此列表将显示的对象集合
        /// </summary>
        /// <remarks>
        /// <para>
        /// 设置此属性后，控件的内容将立即更新。
        /// </para>
        /// <para>如果可能，此方法将保留所选内容。使用 <see cref="SetObjects(IEnumerable, bool)"/> if
        /// 如果您不想保留所选内容，请执行以下操作。
        ///保留选择是该代码中最慢的部分，性能为O(N)，其中n是选定的行数。</para>
        /// <para>此方法不是线程安全的。</para>
        /// <para>该属性确实适用于虚拟列表：设置是没有问题的，但如果您尝试获取它，
        /// 并且列表有1000万个对象，则可能需要一些时间才能返回。</para>
        /// <para>此集合未经过筛选。使用 <see cref="FilteredObjects"/> 只访问那些在任何已安装的筛选器中幸存下来的对象。</para>
        /// </remarks>
        [Browsable(false),
         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual IEnumerable Objects {
            get { return this.objects; }
            set { this.SetObjects(value, true); }
        }
        private IEnumerable objects;
        /// <summary>
        /// 获取当前控件所有对象组成的列表
        /// </summary>
        public virtual IList ObjectsList
        {
            get 
            {
                var enumer = this.objects.GetEnumerator();
                ArrayList list = new ArrayList();
                while(enumer.MoveNext())
                {
                    list.Add(enumer.Current);
                }
                return list;
            }
        }
        /// <summary>
        /// 获取创建群集时将考虑的对象集合。
        /// (用于生成类似Excel的列筛选器)
        /// </summary>
        [Browsable(false),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual IEnumerable ObjectsForClustering {
            get { return this.Objects; }
        }

        /// <summary>
        /// 获取或设置将在ListView顶部绘制的图像
        /// </summary>
        [Category("ObjectListView"),
         Description("获取或设置将在ListView顶部绘制的图像"),
         DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public ImageOverlay OverlayImage {
            get { return this.imageOverlay; }
            set {
                if (this.imageOverlay == value)
                    return;

                this.RemoveOverlay(this.imageOverlay);
                this.imageOverlay = value;
                this.AddOverlay(this.imageOverlay);
            }
        }
        private ImageOverlay imageOverlay;

        /// <summary>
        /// 获取或设置将在ListView顶部绘制的文本
        /// </summary>
        [Category("ObjectListView"),
         Description("获取或设置将在ListView顶部绘制的文本"),
         DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public TextOverlay OverlayText {
            get { return this.textOverlay; }
            set {
                if (this.textOverlay == value)
                    return;

                this.RemoveOverlay(this.textOverlay);
                this.textOverlay = value;
                this.AddOverlay(this.textOverlay);
            }
        }
        private TextOverlay textOverlay;

        /// <summary>
        /// 获取或设置所有覆盖图的透明度。0表示完全透明，255表示完全不透明。
        /// </summary>
        /// <remarks>
        ///这已经过时了，请在每个覆盖上使用Transparency。
        /// </remarks>
        [Browsable(false),
         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public int OverlayTransparency {
            get { return this.overlayTransparency; }
            set { this.overlayTransparency = Math.Min(255, Math.Max(0, value)); }
        }
        private int overlayTransparency = 128;

        /// <summary>
        /// 获取将在ListView顶部绘制的覆盖列表
        /// </summary>
        /// <remarks>
        /// You can add new overlays and remove overlays that you have added, but
        /// don't mess with the overlays that you didn't create.
        /// </remarks>
        [Browsable(false)]
        protected IList<IOverlay> Overlays {
            get { return this.overlays; }
        }
        private readonly List<IOverlay> overlays = new List<IOverlay>();

        /// <summary>
        /// Gets or sets whether the ObjectListView will be owner drawn. Defaults to true.
        /// </summary>
        /// <remarks>
        /// <para>
        /// When this is true, all of ObjectListView's neat features are available.
        /// </para>
        /// <para>We have to reimplement this property, even though we just call the base
        /// property, in order to change the [DefaultValue] to true.
        /// </para>
        /// </remarks>
        [Category("Appearance"),
         Description("Should the ListView do its own rendering"),
         DefaultValue(true)]
        public new bool OwnerDraw {
            get { return base.OwnerDraw; }
            set { base.OwnerDraw = value; }
        }

        /// <summary>
        /// 获取或设置主复选框是否在列表重新生成和筛选操作中保持其值。
        /// </summary>
        /// <remarks>
        /// <para>
        /// 此属性仅在未显式设置CheckStateGetter/Putter时才有用。
        /// 如果您使用CheckStateGetter/Putter，那么这些方法已经持久化了行的检查性。
        /// </para>
        /// <para>此默认值为true。如果为False，则在重新生成或筛选列表时，复选框将丢失其值。
        /// 如果在虚拟列表中将其设置为false，则必须安装CheckStateGetter/Putters。</para>
        /// </remarks>
        [Category("ObjectListView"),
         Description("获取或设置主复选框是否在列表重新生成和筛选操作中保持其值"),
         DefaultValue(true)]
        public virtual bool PersistentCheckBoxes {
            get { return persistentCheckBoxes; }
            set {
                if (persistentCheckBoxes == value)
                    return;
                persistentCheckBoxes = value;
                this.ClearPersistentCheckState();
            }
        }
        private bool persistentCheckBoxes = true;

        /// <summary>
        /// 获取或设置记住模型对象的复选框状态的字典
        /// </summary>
        /// <remarks>当PersistentCheckBoxs值为TRUE时，此选项用于虚拟列表.</remarks>
        protected Dictionary<Object, CheckState> CheckStateMap {
            get { return checkStateMap ?? (checkStateMap = new Dictionary<object, CheckState>()); }
            set { checkStateMap = value; }
        }
        private Dictionary<Object, CheckState> checkStateMap;

        /// <summary>
        /// 获取或设置最后一次排序的列
        /// </summary>
        [Browsable(false),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual OLVColumn PrimarySortColumn {
            get { return this.primarySortColumn; }
            set {
                this.primarySortColumn = value;
                if (this.TintSortColumn) 
                    this.SelectedColumn = value;
            }
        }
        private OLVColumn primarySortColumn;

        /// <summary>
        /// 获取或设置最后一次排序的方向
        /// </summary>
        [Browsable(false),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual SortOrder PrimarySortOrder {
            get { return primarySortOrder; }
            set { primarySortOrder = value; }
        }
        private SortOrder primarySortOrder;

        /// <summary>
        /// 获取或设置不可编辑复选框是否绘制为禁用。默认是false。
        /// </summary>
        /// <remarks>
        /// <para>这仅在所有者描述模式(owner drawn mode)下有效。</para>
        /// </remarks>
        [Category("ObjectListView"),
         Description("获取或设置不可编辑复选框是否绘制为禁用"),
         DefaultValue(false)]
        public virtual bool RenderNonEditableCheckboxesAsDisabled {
            get { return renderNonEditableCheckboxesAsDisabled; }
            set { renderNonEditableCheckboxesAsDisabled = value; }
        }
        private bool renderNonEditableCheckboxesAsDisabled;

        /// <summary>
        /// 以像素为单位指定控件中每行的高度。
        /// </summary>
        /// <remarks><para>列表视图中的行高通常由字体大小和小图像列表大小决定。
        /// 此设置允许覆盖该计算(原因是：您仍然不能将行高设置为小于控件中使用的字体的行高)。</para>
        /// <para>将其设置为-1表示使用正常计算方法。</para>
        /// <para><bold>此功能是体验功能！</bold>如果您使用此功能，您的程序可能会发生意想不到的事情。</para>
        /// </remarks>
        [Category("ObjectListView"),
         Description("以像素为单位指定控件中每行的高度"),
         DefaultValue(-1)]
        public virtual int RowHeight {
            get { return rowHeight; }
            set {
                if (value < 1)
                    rowHeight = -1;
                else
                    rowHeight = value;
                if (this.DesignMode)
                    return;
                this.SetupBaseImageList();
                if (this.CheckBoxes)
                    this.InitializeStateImageList();
            }
        }
        private int rowHeight = -1;

        /// <summary>
        /// 获取每行有多少像素高
        /// </summary>
        [Browsable(false)]
        public virtual int RowHeightEffective {
            get {
                switch (this.View) {
                    case View.List:
                    case View.SmallIcon:
                    case View.Details:
                        return Math.Max(this.SmallImageSize.Height, this.Font.Height);

                    case View.Tile:
                        return this.TileSize.Height;

                    case View.LargeIcon:
                        if (this.LargeImageList == null)
                            return this.Font.Height;
                        
                        return Math.Max(this.LargeImageList.ImageSize.Height, this.Font.Height);

                    default:
                        // This should never happen
                        return 0;
                }
            }
        }

        /// <summary>
        /// 获取此控件的每页上显示多少行
        /// </summary>
        [Browsable(false)]
        public virtual int RowsPerPage {
            get {
                return NativeMethods.GetCountPerPage(this);
            }
        }

        /// <summary>
        ///获取/设置将用于解析排序时相等的比较的列(第二排序列)。
        /// </summary>
        /// <remarks>此设置没有用户界面。它必须以编程方式设置。</remarks>
        [Browsable(false),
         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual OLVColumn SecondarySortColumn {
            get { return this.secondarySortColumn; }
            set { this.secondarySortColumn = value; }
        }
        private OLVColumn secondarySortColumn;

        /// <summary>
        /// 获取或设置当使用Second darySortColumn时，它将以什么顺序比较结果
        /// </summary>
        [Browsable(false),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual SortOrder SecondarySortOrder {
            get { return this.secondarySortOrder; }
            set { this.secondarySortOrder = value; }
        }
        private SortOrder secondarySortOrder = SortOrder.None;

        /// <summary>
        ///获取或设置当用户按Ctrl+A组合键时是否应选择所有行
        /// </summary>
        [Category("ObjectListView"),
        Description("获取或设置当用户按Ctrl+A组合键时是否应选择所有行"),
        DefaultValue(true)]
        public virtual bool SelectAllOnControlA {
            get { return selectAllOnControlA; }
            set { selectAllOnControlA = value; }
        }
        private bool selectAllOnControlA = true;

        /// <summary>
        /// 获取或设置当用户右键单击列标题时，是否应该显示一个菜单，允许用户选择视图中将显示哪些列
        /// </summary>
        /// <remarks>这只是SelectColumnsOnRightClickBehaviour的兼容性包装属性.</remarks>
        [Category("ObjectListView"),
        Description("获取或设置当用户右键单击列标题时，是否应该显示一个菜单，允许用户选择视图中将显示哪些列"),
        DefaultValue(true)]
        public virtual bool SelectColumnsOnRightClick {
            get { return this.SelectColumnsOnRightClickBehaviour != ColumnSelectBehaviour.None; }
            set {
                if (value) {
                    if (this.SelectColumnsOnRightClickBehaviour == ColumnSelectBehaviour.None)
                        this.SelectColumnsOnRightClickBehaviour = ColumnSelectBehaviour.InlineMenu;
                } else {
                    this.SelectColumnsOnRightClickBehaviour = ColumnSelectBehaviour.None;
                }
            }
        }

        /// <summary>
        /// 获取或设置当右键单击标题时用户如何能够选择列
        /// </summary>
        [Category("ObjectListView"),
        Description("获取或设置当右键单击标题时用户如何能够选择列"),
        DefaultValue(ColumnSelectBehaviour.InlineMenu)]
        public virtual ColumnSelectBehaviour SelectColumnsOnRightClickBehaviour {
            get { return selectColumnsOnRightClickBehaviour; }
            set { selectColumnsOnRightClickBehaviour = value; }
        }
        private ColumnSelectBehaviour selectColumnsOnRightClickBehaviour = ColumnSelectBehaviour.InlineMenu;

        /// <summary>
        /// 当列选择菜单打开时，是否应在选择项目后保持打开状态？保持打开状态允许用户一次打开或关闭多个列。
        /// </summary>
        /// <remarks>SelectColumnsOnRightClickBehaviour设置为InlineMenu时才有效。当行为设置为SubMenu菜单时，该选项不起作用。</remarks>
        [Category("ObjectListView"),
        Description("当列选择菜单打开时，是否应在选择项目后保持打开状态？保持打开状态允许用户一次打开或关闭多个列。"),
        DefaultValue(true)]
        public virtual bool SelectColumnsMenuStaysOpen {
            get { return selectColumnsMenuStaysOpen; }
            set { selectColumnsMenuStaysOpen = value; }
        }
        private bool selectColumnsMenuStaysOpen = true;

        /// <summary>
        ///获取或设置选定列
        /// </summary>
        /// <remarks>
        /// <para>
        /// 如果TintSortColumn为true，则排序列将自动成为选定列。
        /// </para>
        /// <para>
        /// 色调的颜色由SelectedColumnTint控制。
        /// </para>
        /// </remarks>
        [Browsable(false),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public OLVColumn SelectedColumn {
            get { return this.selectedColumn; }
            set {
                this.selectedColumn = value;
                if (value == null) {
                    this.RemoveDecoration(this.selectedColumnDecoration);
                } else {
                    if (!this.HasDecoration(this.selectedColumnDecoration))
                        this.AddDecoration(this.selectedColumnDecoration);
                }
            }
        }
        private OLVColumn selectedColumn;
        private readonly TintedColumnDecoration selectedColumnDecoration = new TintedColumnDecoration();

        /// <summary>
        /// Gets or sets the decoration that will be drawn on all selected rows
        /// </summary>
        [Browsable(false),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual IDecoration SelectedRowDecoration {
            get { return this.selectedRowDecoration; }
            set { this.selectedRowDecoration = value; }
        }
        private IDecoration selectedRowDecoration;

        /// <summary>
        /// 获取或设置应该使用什么颜色来给选定的列着色
        /// </summary>
        /// <remarks>
        /// 色调颜色必须是Alpha可混合的，因此如果给定的颜色是纯色(即Alpha=255)，则会将其更改为具有合理的Alpha值。
        /// </remarks>
        [Category("ObjectListView"),
         Description("获取或设置应该使用什么颜色来给选定的列着色"),
         DefaultValue(typeof(Color), "")]
        public virtual Color SelectedColumnTint {
            get { return selectedColumnTint; }
            set {
                this.selectedColumnTint = value.A == 255 ? Color.FromArgb(15, value) : value;
                this.selectedColumnDecoration.Tint = this.selectedColumnTint;
            }
        }
        private Color selectedColumnTint = Color.Empty;

        /// <summary>
        /// 获取或设置当前选定行的索引。
        /// 获取索引时，如果没有选择任何行，或者选择了多个行，则返回-1。
        /// </summary>
        [Browsable(false),
         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual int SelectedIndex {
            get { return this.SelectedIndices.Count == 1 ? this.SelectedIndices[0] : -1; }
            set {
                this.SelectedIndices.Clear();
                if (value >= 0 && value < this.Items.Count)
                    this.SelectedIndices.Add(value);
            }
        }

        /// <summary>
        ///获取或设置当前选定的ListViewItem。如果未选择任何行，或者选择了多个行，则返回NULL。
        /// </summary>
        [Browsable(false),
         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual OLVListItem SelectedItem {
            get {
                return this.SelectedIndices.Count == 1 ? this.GetItem(this.SelectedIndices[0]) : null;
            }
            set {
                this.SelectedIndices.Clear();
                if (value != null)
                    this.SelectedIndices.Add(value.Index);
            }
        }

        /// <summary>
        /// 如果只选择了一行，则从当前选定的行获取模型对象。
        /// 如果未选择任何行，或选择了多个行，则返回NULL。
        /// 设置时，这将选择显示给定模型对象的行，并将焦点放在该行上，取消选择所有其他行。
        /// </summary>
        [Browsable(false),
         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual Object SelectedObject {
            get {
                return this.SelectedIndices.Count == 1 ? this.GetModelObject(this.SelectedIndices[0]) : null;
            }
            set {
                // If the given model is already selected, don't do anything else (prevents an flicker)
                object selectedObject = this.SelectedObject;
                if (selectedObject != null && selectedObject.Equals(value))
                    return;

                this.SelectedIndices.Clear();
                this.SelectObject(value, true); 
            }
        }

        /// <summary>
        /// 从当前选定的行中获取模型对象。
        /// 如果未选中任何行，则返回的列表为空。
        /// 设置此值时，请选择显示给定模型对象的行，取消选择所有其他行。
        /// </summary>
        [Browsable(false),
         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual IList SelectedObjects {
            get {
                ArrayList list = new ArrayList();
                foreach (int index in this.SelectedIndices)
                  list.Add(this.GetModelObject(index));
                return list;
            }
            set {
                this.SelectedIndices.Clear();
                this.SelectObjects(value); 
            }
        }

        /// <summary>
        /// 获取或设置当用户右键单击列标题时，是否应该显示一个菜单，允许他们选择要在列表视图上执行的常见任务
        /// </summary>
        [Category("ObjectListView"),
        Description("获取或设置当用户右键单击列标题时，是否应该显示一个菜单，允许他们选择要在列表视图上执行的常见任务"),
        DefaultValue(false)]
        public virtual bool ShowCommandMenuOnRightClick {
            get { return showCommandMenuOnRightClick; }
            set { showCommandMenuOnRightClick = value; }
        }
        private bool showCommandMenuOnRightClick;

        /// <summary>
        /// 获取或设置当右键单击标题控件时，此ObjectListView是否像筛选菜单一样显示表格
        /// </summary>
        [Category("ObjectListView"),
         Description("如果为True，则右键单击列标题将显示筛选器菜单选项"),
         DefaultValue(true)]
        public bool ShowFilterMenuOnRightClick {
            get { return showFilterMenuOnRightClick; }
            set { showFilterMenuOnRightClick = value; }
        }
        private bool showFilterMenuOnRightClick = true;

        /// <summary>
        /// 获取或设置是否应按组显示其项目
        /// </summary>
        [Category("Appearance"),
         Description("获取或设置是否应按组显示其项目"),
         DefaultValue(false)]
        new public virtual bool ShowGroups {
            get { return base.ShowGroups; }
            set {
                this.GroupImageList = this.GroupImageList;
                base.ShowGroups = value; 
            }
        }

        /// <summary>
        /// 列表视图是否应该在列标题中显示位图以显示排序方向
        /// </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("ObjectListView"),
         Description("列表视图是否应该在列标题中显示位图以显示排序方向"),
         DefaultValue(true)]
        public virtual bool ShowSortIndicators {
            get { return showSortIndicators; }
            set { showSortIndicators = value; }
        }
        private bool showSortIndicators;

        /// <summary>
        /// 列表视图是否应该显示子项上的图像
        /// </summary>
        /// <remarks>
        /// <para>虚拟列表必须是所有者绘制的，才能在子项上显示图像</para>
        /// </remarks>
        [Category("ObjectListView"),
         Description("列表视图是否应该显示子项上的图像"),
         DefaultValue(false)]
        public virtual bool ShowImagesOnSubItems {
            get { return showImagesOnSubItems; }
            set {
                showImagesOnSubItems = value;
                if (this.Created)
                    this.ApplyExtendedStyles();
                if (value && this.VirtualMode)
                    this.OwnerDraw = true;
            }
        }
        private bool showImagesOnSubItems;

        /// <summary>
        /// 此属性控制分组标签是否显示该分组项目数量
        /// </summary>
        /// <remarks>
        ///后缀的格式由GroupWithItemCountFormat/GroupWithItemCountSingularFormat属性控制
        /// </remarks>
        [Category("ObjectListView"),
         Description("此属性控制分组标签是否显示该分组项目数量"),
         DefaultValue(false)]
        public virtual bool ShowItemCountOnGroups {
            get { return showItemCountOnGroups; }
            set { showItemCountOnGroups = value; }
        }
        private bool showItemCountOnGroups;

        /// <summary>
        /// 获取或设置控件是在所有视图中显示列标题(True)，还是仅在详细信息视图中显示列标题(False)
        /// </summary>
        /// <remarks>
        /// <para>
        /// 此属性工作不正常。JPP 2010/04/06。
        /// 如果它是在创建控件之前设置的，则可以正常工作。
        /// 但是，如果在创建控件后将其关闭，则控件将丢失其复选框(奇怪！)
        /// </para>
        /// <para>
        /// 要在创建控件后更改此设置，事情会很复杂。
        /// 如果它是关闭的，而我们想要打开它，我们必须更改视图，标题就会出现。
        /// 如果它当前处于打开状态，而我们想要将其关闭，则必须更改视图并重新创建控制柄。
        /// 重新创建句柄是一个问题，因为它会使我们的复选框样式消失。
        /// </para>
        /// <para>
        /// 该属性不能工作在 Win XP 系统.</para>
        /// </remarks>
        [Category("ObjectListView"),
        Description("获取或设置控件是在所有视图中显示列标题(True)，还是仅在详细信息视图中显示列标题(False)"),
        DefaultValue(true)]
        public bool ShowHeaderInAllViews {
            get { return ObjectListView.IsVistaOrLater && showHeaderInAllViews; }
            set {
                if (showHeaderInAllViews == value)
                    return;

                showHeaderInAllViews = value;

                // If the control isn't already created, everything is fine.
                if (!this.Created)
                    return;

                // If the header is being hidden, we have to recreate the control
                // to remove the style (not sure why this is)
                if (showHeaderInAllViews) 
                    this.ApplyExtendedStyles();
                else 
                    this.RecreateHandle();

                // Still more complications. The change doesn't become visible until the View is changed
                if (this.View != View.Details) {
                    View temp = this.View;
                    this.View = View.Details;
                    this.View = temp;
                }
            }
        }
        private bool showHeaderInAllViews = true;

        /// <summary>
        ///重写SmallImageList属性，以便我们可以正确地隐藏其操作。
        /// </summary>
        /// <remarks><para>如果使用RowHeight属性指定行高，则必须在设置/更改RowHeight之前完全初始化SmallImageList。
        /// 如果在设置行高之后将新图像添加到图像列表，则必须再次将图像列表分配给控件。像这样简单的事情会奏效的：
        /// <code>listView1.SmallImageList = listView1.SmallImageList;</code></para>
        /// </remarks>
        new public ImageList SmallImageList {
            get { return this.shadowedImageList; }
            set {
                this.shadowedImageList = value;
                if (this.UseSubItemCheckBoxes)
                    this.SetupSubItemCheckBoxes();
                this.SetupBaseImageList();
            }
        }
        private ImageList shadowedImageList;

        /// <summary>
        /// 返回小图像列表中图像的大小或合理的默认值
        /// </summary>
        [Browsable(false)]
        public virtual Size SmallImageSize {
            get {
                return this.BaseSmallImageList == null ? new Size(16, 16) : this.BaseSmallImageList.ImageSize;
            }
        }

        /// <summary>
        /// 当列表视图分组时，项是否应该按主列排序？如果为False，则项目将按分组时的同一列进行排序。
        /// </summary>
        [Category("ObjectListView"),
         Description("当列表视图分组时，项是否应该按主列排序？如果为False，则项目将按分组时的同一列进行排序。"),
         DefaultValue(true)]
        public virtual bool SortGroupItemsByPrimaryColumn {
            get { return this.sortGroupItemsByPrimaryColumn; }
            set { this.sortGroupItemsByPrimaryColumn = value; }
        }
        private bool sortGroupItemsByPrimaryColumn = true;

        /// <summary>
        ///当列表视图分组时，一个组的末尾和下一个组的开头之间应该有多少像素
        /// </summary>
        [Category("ObjectListView"),
         Description("当列表视图分组时，一个组的末尾和下一个组的开头之间应该有多少像素"),
         DefaultValue(0)]
        public virtual int SpaceBetweenGroups {
            get { return this.spaceBetweenGroups; }
            set {
                if (this.spaceBetweenGroups == value)
                    return;

                this.spaceBetweenGroups = value;
                this.SetGroupSpacing();
            }
        }
        private int spaceBetweenGroups;

        private void SetGroupSpacing() {
            if (!this.IsHandleCreated)
                return;

            NativeMethods.LVGROUPMETRICS metrics = new NativeMethods.LVGROUPMETRICS();
            metrics.cbSize = ((uint)Marshal.SizeOf(typeof(NativeMethods.LVGROUPMETRICS)));
            metrics.mask = (uint)GroupMetricsMask.LVGMF_BORDERSIZE;
            metrics.Bottom = (uint)this.SpaceBetweenGroups;
            NativeMethods.SetGroupMetrics(this, metrics);
        }

        /// <summary>
        /// 排序列是否应该显示轻微的色调？
        /// </summary>
        [Category("ObjectListView"),
         Description("排序列是否应该显示轻微的色调"),
         DefaultValue(false)]
        public virtual bool TintSortColumn {
            get { return this.tintSortColumn; }
            set {
                this.tintSortColumn = value;
                if (value && this.PrimarySortColumn != null)
                    this.SelectedColumn = this.PrimarySortColumn;
                else
                    this.SelectedColumn = null;
            }
        }
        private bool tintSortColumn;

        /// <summary>
        /// 获取或设置是否使用三态复选框？
        /// </summary>
        /// <remarks>
        /// 如果为True，用户可以选择第三种状态(通常是不确定的)。
        /// 否则，用户在选中和取消选中之间交替单击。
        /// 当此设置为False时，CheckStateGetter仍可以返回不确定。
        /// </remarks>
        [Category("ObjectListView"),
         Description("获取或设置是否使用三态复选框"),
         DefaultValue(false)]
        public virtual bool TriStateCheckBoxes {
            get { return triStateCheckBoxes; }
            set {
                triStateCheckBoxes = value;
                if (value && !this.CheckBoxes)
                    this.CheckBoxes = true;
                this.InitializeStateImageList();
            }
        }
        private bool triStateCheckBoxes;

        /// <summary>
        /// 获取或设置列表顶部项目的索引
        /// </summary>
        /// <remarks>
        /// <para>
        /// 此属性仅在列表视图处于详细信息视图中且不显示组时才起作用。
        /// </para>
        /// <para>
        /// 显示组时不起作用的原因是，当启用组时，无论滚动位置如何，
        /// Windows msg LVM_GETTOPINDEX总是返回0。
        /// </para>
        /// </remarks>
        [Browsable(false),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual int TopItemIndex {
            get {
                if (this.View == View.Details && this.IsHandleCreated)
                    return NativeMethods.GetTopIndex(this);
                
                return -1;
            }
            set {
                int newTopIndex = Math.Min(value, this.GetItemCount() - 1);
                if (this.View != View.Details || newTopIndex < 0) 
                    return;

                try {
                    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);
                }
                catch (NullReferenceException) {
                    // There is a bug in the .NET code where setting the TopItem
                    // will sometimes throw null reference exceptions
                    // There is nothing we can do to get around it.
                }
            }
        }

        /// <summary>
        /// 获取或设置将鼠标移到标题上是否会触发CellOver事件。
        /// 默认为true。
        /// </summary>
        /// <remarks>
        /// 将鼠标移动到标题上之前不会触发CellOver事件，因为Header被视为单独的控件。
        /// 如果此行为更改导致您的应用程序出现问题，请将其设置为False。
        /// 如果您想知道鼠标何时在标题上移动，请将此属性设置为true(默认值)。
        /// </remarks>
        [Category("ObjectListView"),
         Description("获取或设置将鼠标移到标题上是否会触发CellOver事件。"),
         DefaultValue(true)]
        public bool TriggerCellOverEventsWhenOverHeader
        {
            get { return triggerCellOverEventsWhenOverHeader; }
            set { triggerCellOverEventsWhenOverHeader = value; }
        }
        private bool triggerCellOverEventsWhenOverHeader = true;

        /// <summary>
        /// 通过拖动分隔线调整列的大小时，是否应在每次鼠标移动时调整填充空格的列的大小？
        /// 如果为False，则释放鼠标时将更新填充栏。
        /// </summary>
        /// <remarks>
        /// <para>
        /// 如果空间填充列位于正在调整大小的列的左侧，这看起来会很奇怪：该列的右边缘将被拖动，但由于空间填充列正在收缩，其<b>左边缘</b>将会移动。
        /// </para>
        /// <para>这是合乎逻辑的行为--只是看起来不对。
        /// </para>
        /// <para>
        /// 考虑到上述行为，如果空间填充列不是最右边的列，最好关闭该属性。</para>
        /// </remarks>
        [Category("ObjectListView"),
        Description("通过拖动分隔线调整列的大小时，是否应在每次鼠标移动时调整填充空格的列的大小"),
        DefaultValue(true)]
        public virtual bool UpdateSpaceFillingColumnsWhenDraggingColumnDivider {
            get { return updateSpaceFillingColumnsWhenDraggingColumnDivider; }
            set { updateSpaceFillingColumnsWhenDraggingColumnDivider = value; }
        }
        private bool updateSpaceFillingColumnsWhenDraggingColumnDivider = true;

        /// <summary>
        /// 当控件没有焦点时，应该使用什么颜色作为选定行的背景
        /// </summary>
        [Category("ObjectListView"),
         Description("当控件没有焦点时，应该使用什么颜色作为选定行的背景"),
         DefaultValue(typeof(Color), "")]
        public virtual Color UnfocusedSelectedBackColor {
            get { return this.unfocusedSelectedBackColor; }
            set { this.unfocusedSelectedBackColor = value; }
        }
        private Color unfocusedSelectedBackColor = Color.Empty;

        /// <summary>
        /// 当控件没有焦点或合理的默认值时，返回应用于选定行的背景的颜色
        /// </summary>
        [Browsable(false)]
        public virtual Color UnfocusedSelectedBackColorOrDefault {
            get {
                return this.UnfocusedSelectedBackColor.IsEmpty ? SystemColors.Control : this.UnfocusedSelectedBackColor;
            }
        }

        /// <summary>
        ///当控件没有焦点时，应该使用什么颜色作为选定行的字体颜色。
        /// </summary>
        [Category("ObjectListView"),
         Description("当控件没有焦点时，应该使用什么颜色作为选定行的字体颜色。"),
         DefaultValue(typeof(Color), "")]
        public virtual Color UnfocusedSelectedForeColor {
            get { return this.unfocusedSelectedForeColor; }
            set { this.unfocusedSelectedForeColor = value; }
        }
        private Color unfocusedSelectedForeColor = Color.Empty;

        /// <summary>
        /// 当控件没有焦点或合理的默认值时，返回应用于选定行的字体颜色
        /// </summary>
        [Browsable(false)]
        public virtual Color UnfocusedSelectedForeColorOrDefault {
            get {
                return this.UnfocusedSelectedForeColor.IsEmpty ? SystemColors.ControlText : this.UnfocusedSelectedForeColor;
            }
        }

        /// <summary>
        /// 获取或设置列表是否每隔一行提供不同的背景色？默认值为false。
        /// </summary>
        /// <remarks><para>交替行的颜色由AlternateRowBackColor提供。</para>
        /// <para>在.NET中，列表视图在非整行选择模式下有一个“功能”，在这种模式下，所选行不会使用正确的背景色绘制。</para></remarks>
        [Category("ObjectListView"),
         Description("获取或设置列表是否每隔一行提供不同的背景色？默认值为false。"),
         DefaultValue(false)]
        public virtual bool UseAlternatingBackColors {
            get { return useAlternatingBackColors; }
            set { useAlternatingBackColors = value; }
        }
        private bool useAlternatingBackColors;

        /// <summary>
        /// 是否应该为控件中的每个单元格调用FormatCell事件？
        /// </summary>
        /// <remarks>
        /// <para>在许多情况下，不执行单元格级格式设置。
        /// 如果除非需要，ObjectListView不会为每个单元格触发Format Cell事件，则它的运行速度会稍快一些。
        /// 因此，默认情况下，它不会为每个单元格引发事件。
        /// </para>
        /// <para>ObjectListView*确实*在每次重新生成行时引发FormatRow事件。各行可以决定是否对行中的每个单元格引发FormatCell事件。
        /// </para>
        /// <para>
        /// 无论此设置如何，只有当ObjectListView处于详细视图中时才会引发FormatCell事件。</para>
        /// </remarks>
        [Category("ObjectListView"),
         Description("是否应该为控件中的每个单元格调用FormatCell事件"),
         DefaultValue(false)]
        public bool UseCellFormatEvents {
            get { return useCellFormatEvents; }
            set { useCellFormatEvents = value; }
        }
        private bool useCellFormatEvents;

        /// <summary>
        /// 所选行是否应使用非标准前景色和背景色绘制？
        /// </summary>
        /// <remarks>V2.9不再需要此属性</remarks>
        [Category("ObjectListView"),
         Description("所选行是否应使用非标准前景色和背景色绘制"),
         DefaultValue(false)]
        public bool UseCustomSelectionColors {
            get { return false; }
            // ReSharper disable once ValueParameterNotUsed
            set { }
        }

        /// <summary>
        /// 获取或设置此ObjectListView是否将使用与Vista资源管理器相同的热项和选择机制。
        /// </summary>
        /// <remarks>
        /// <para>
        /// 此属性有许多缺陷：
        /// <list type="bullet">
        /// <item><description>这仅适用于Vista及更高版本</description></item>
        /// <item><description>它不能很好地与AlternateRowBackColors配合使用。</description></item>
        /// <item><description>它不能很好地与HotItemStyles配合使用。</description></item>
        /// <item><description>如果FullRowSelect为false，这看起来有点傻。</description></item>
        /// <item><description>当列表是所有者绘制的时，它根本不起作用(因为所有的绘制都是由渲染器完成的)。
        /// 因此，它不能与TreeListView一起工作，因为它们“必须”是所有者绘制的。你仍然可以设置它，但它不会生效。</description></item>
        /// </list>
        ///但如果你一定要看起来像Vista/Win7，这是你的权限。如果这项设置搞砸了其他事情，请不要抱怨。
        /// </para>
        /// <para>
        /// 当此属性设置为True时，ObjectListView将不是所有者描述的。
        /// 这将禁用ObjectListView的许多漂亮的基于绘图的功能。
        /// </para>
        /// </remarks>
        [Category("ObjectListView"),
         Description("获取或设置此ObjectListView是否将使用与Vista资源管理器相同的热项和选择机制"),
         DefaultValue(false)]
        public bool UseExplorerTheme {
            get { return useExplorerTheme; }
            set {
                useExplorerTheme = value;
                if (this.Created)
                    NativeMethods.SetWindowTheme(this.Handle, value ? "explorer" : "", null);
                
                this.OwnerDraw = !value;
            }
        }
        private bool useExplorerTheme;

        /// <summary>
        /// 获取或设置列表是否应启用筛选
        /// </summary>
        [Category("ObjectListView"),
        Description("获取或设置列表是否应启用筛选"),
        DefaultValue(false)]
        virtual public bool UseFiltering {
            get { return useFiltering; }
            set {
                if (useFiltering == value)
                    return;
                useFiltering = value;
                this.UpdateFiltering();
            }
        }
        private bool useFiltering;

        /// <summary>
        /// 获取或设置列表是否应将指示符放入列的标题中，以表明它正在筛选该列
        /// </summary>
        /// <remarks>如果将其设置为true，HeaderUsesThemes将自动设置为false，因为我们只能在不使用主题标题时绘制筛选器指示器。</remarks>
        [Category("ObjectListView"),
        Description("获取或设置列表是否应将指示符放入列的标题中，以表明它正在筛选该列"),
        DefaultValue(false)]
        virtual public bool UseFilterIndicator {
            get { return useFilterIndicator; }
            set {
                if (this.useFilterIndicator == value)
                    return;
                useFilterIndicator = value;
                if (this.useFilterIndicator)
                    this.HeaderUsesThemes = false;
                this.Invalidate();
            }
        }
        private bool useFilterIndicator;

        /// <summary>
        /// 鼠标下方的控件(复选框或按钮)是否应该被绘制为“Hot”
        /// </summary>
        /// <remarks>
        /// <para>如果为False，则当鼠标悬停在控件上时，控件的绘制方式不会有所不同。</para>
        /// <para>
        /// 如果为False，且UseHotItem和UseHyperLinks为False，则ObjectListView可以跳过鼠标移动的某些处理。这使得鼠标移动处理几乎不使用CPU。
        /// </para>
        /// </remarks>
        [Category("ObjectListView"),
         Description("鼠标下方的控件(复选框或按钮)是否应该被绘制为“Hot”"),
         DefaultValue(true)]
        public bool UseHotControls {
            get { return this.useHotControls; }
            set { this.useHotControls = value; }
        }
        private bool useHotControls = true;

        /// <summary>
        /// 光标下的项是否应以特殊方式设置格式？
        /// </summary>
        [Category("ObjectListView"),
         Description("光标下的项是否应以特殊方式设置格式"),
         DefaultValue(false)]
        public bool UseHotItem {
            get { return this.useHotItem; }
            set {
                this.useHotItem = value;
                if (value)
                    this.AddOverlay(this.HotItemStyleOrDefault.Overlay);
                else
                    this.RemoveOverlay(this.HotItemStyleOrDefault.Overlay);
            }
        }
        private bool useHotItem;

        /// <summary>
        /// 获取或设置此列表视图是否应在单元格中显示超链接。
        /// </summary>
        [Category("ObjectListView"),
         Description("获取或设置此列表视图是否应在单元格中显示超链接"),
         DefaultValue(false)]
        public bool UseHyperlinks {
            get { return this.useHyperlinks; }
            set {
                this.useHyperlinks = value;
                if (value && this.HyperlinkStyle == null)
                    this.HyperlinkStyle = new HyperlinkStyle();
            }
        }
        private bool useHyperlinks;

        /// <summary>
        /// 此控件是否应显示覆盖(Overlays)
        /// </summary>
        /// <remarks>覆盖(Overlays)在默认情况下是启用的，只有在它们在您的开发环境中造成问题时才需要禁用。</remarks>
        [Category("ObjectListView"),
         Description("此控件是否应显示覆盖(Overlays)"),
         DefaultValue(true)]
        public bool UseOverlays {
            get { return this.useOverlays; }
            set { this.useOverlays = value; }
        }
        private bool useOverlays = true;

        /// <summary>
        /// 是否应将此控件配置为在子项上显示复选框？
        /// </summary>
        /// <remarks>如果将其设置为True，则将为该控件提供一个SmallImageList(如果它还没有一个SmallImageList)。
        /// 此外，如果它是一个虚拟列表，它将被设置为所有者描述的，因为虚拟列表不能在没有所有者描述的情况下绘制复选框。</remarks>
        [Category("ObjectListView"),
         Description("是否应将此控件配置为在子项上显示复选框？"),
         DefaultValue(false)]
        public bool UseSubItemCheckBoxes {
            get { return this.useSubItemCheckBoxes; }
            set {
                this.useSubItemCheckBoxes = value;
                if (value)
                    this.SetupSubItemCheckBoxes();
            }
        }
        private bool useSubItemCheckBoxes;

        /// <summary>
        ///获取或设置ObjectListView是否将使用类似Vista的半透明选择机制。
        /// </summary>
        /// <remarks>
        /// <para>
        /// 与UseExplorerTheme不同的是，这个类似Vista的方案可以在XP上运行，并且既适用于所有者描述的列表，也适用于非所有者描述的列表。
        /// </para>
        /// <para>
        ///这将替换任何已安装的SelectedRowDecory。
        /// </para>
        /// <para>
        /// 如果您不喜欢用于选择的颜色，请忽略此属性，
        /// 只需创建您自己的RowBorderDecory并将其分配给SelectedRowDecory，
        /// 就像此属性设置器所做的那样。
        /// </para>
        /// </remarks>
        [Category("ObjectListView"),
         Description("获取或设置ObjectListView是否将使用类似Vista的半透明选择机制"),
         DefaultValue(false)]
        public bool UseTranslucentSelection {
            get { return useTranslucentSelection; }
            set {
                useTranslucentSelection = value;
                if (value) {
                    RowBorderDecoration rbd = new RowBorderDecoration();
                    rbd.BorderPen = new Pen(Color.FromArgb(154, 223, 251));
                    rbd.FillBrush = new SolidBrush(Color.FromArgb(48, 163, 217, 225));
                    rbd.BoundsPadding = new Size(0, 0);
                    rbd.CornerRounding = 6.0f;
                    this.SelectedRowDecoration = rbd;
                } else
                    this.SelectedRowDecoration = null;
            }
        }
        private bool useTranslucentSelection;

        /// <summary>
        /// 获取或设置ObjectListView是否将使用类似于Vista的半透明热行突出显示机制。
        /// </summary>
        /// <remarks>
        /// <para>
        /// 设置此项将替换任何已安装的HotItemStyle。
        /// </para>
        /// <para>
        /// 如果您不喜欢热项目使用的颜色，请忽略此属性，
        /// 只需创建您自己的HotItemStyle，填充所需的值，
        /// 并将其分配给HotItemStyle属性，就像此属性设置器所做的那样。
        /// </para>
        /// </remarks>
        [Category("ObjectListView"),
         Description("获取或设置ObjectListView是否将使用类似于Vista的半透明热行突出显示机制。"),
         DefaultValue(false)]
        public bool UseTranslucentHotItem {
            get { return useTranslucentHotItem; }
            set {
                useTranslucentHotItem = value;
                if (value) {
                    RowBorderDecoration rbd = new RowBorderDecoration();
                    rbd.BorderPen = new Pen(Color.FromArgb(154, 223, 251));
                    rbd.BoundsPadding = new Size(0, 0);
                    rbd.CornerRounding = 6.0f;
                    rbd.FillGradientFrom = Color.FromArgb(0, 255, 255, 255);
                    rbd.FillGradientTo = Color.FromArgb(64, 183, 237, 240);
                    HotItemStyle his = new HotItemStyle();
                    his.Decoration = rbd;
                    this.HotItemStyle = his;
                } else
                    this.HotItemStyle = null;
                this.UseHotItem = value;
            }
        }
        private bool useTranslucentHotItem;

        /// <summary>
        /// 获取/设置此列表视图正在使用的视图样式
        /// </summary>
        /// <remarks>切换到平铺(Tile)或详细信息(Details)视图会安装适合该视图的列。令人困惑的是，在平铺视图中，每列都显示为一行信息。</remarks>
        [Category("Appearance"),
         Description("获取/设置此列表视图正在使用的视图样式"),
         DefaultValue(null)]
        new public View View
        {
            get { return base.View; }
            set {
                if (base.View == value)
                    return;

                if (this.Frozen) {
                    base.View = value;
                    this.SetupBaseImageList();
                } else {
                    this.Freeze();

                    if (value == View.Tile)
                        this.CalculateReasonableTileSize();

                    base.View = value;
                    this.SetupBaseImageList();
                    this.Unfreeze();
                }
            }
        }

        #endregion

        #region Callbacks

        /// <summary>
        /// 此委托仅将对象的检查性作为布尔值获取。
        /// </summary>
        /// <remarks>如果您永远不想担心不确定状态(这是相当常见的)，请使用此选项。
        /// <para>
        /// 这是CheckStateGetter属性的便捷包装。
        /// </para>
        /// </remarks>
        [Browsable(false),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual BooleanCheckStateGetterDelegate BooleanCheckStateGetter {
            set {
                if (value == null)
                    this.CheckStateGetter = null;
                else
                    this.CheckStateGetter = delegate(Object x) {
                        return value(x) ? CheckState.Checked : CheckState.Unchecked;
                    };
            }
        }

        /// <summary>
        /// This delegate sets the checkedness of an object as a boolean only. It must return
        /// true or false indicating if the object was checked or not.
        /// </summary>
        /// <remarks>Use this if you never want to worry about the
        /// Indeterminate state (which is fairly common).
        /// <para>
        /// This is a convenience wrapper around the CheckStatePutter property.
        /// </para>
        /// </remarks>
        [Browsable(false),
         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual BooleanCheckStatePutterDelegate BooleanCheckStatePutter {
            set {
                if (value == null)
                    this.CheckStatePutter = null;
                else
                    this.CheckStatePutter = delegate(Object x, CheckState state) {
                        bool isChecked = (state == CheckState.Checked);
                        return value(x, isChecked) ? CheckState.Checked : CheckState.Unchecked;
                    };
            }
        }

        /// <summary>
        /// 获取此列表视图是否能够显示组
        /// </summary>
        [Browsable(false)]
        public virtual bool CanShowGroups {
            get {
                return true;
            }
        }

        /// <summary>
        /// 获取或设置ObjectListView是否可以依赖正在引发的Application.Idle事件。
        /// </summary>
        /// <remarks>在某些主机环境中(例如，在VisualStudio和Office中作为扩展运行时)，从不引发Application.Idle事件。
        /// 如果不会引发Idle事件，则将其设置为False，ObjectListView将自行引发这些事件。
        /// </remarks>
        [Browsable(false),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual bool CanUseApplicationIdle {
            get { return this.canUseApplicationIdle; }
            set { this.canUseApplicationIdle = value; }
        }
        private bool canUseApplicationIdle = true;

        /// <summary>
        /// This delegate fetches the renderer for a particular cell. 
        /// </summary>
        /// <remarks>
        /// <para>
        /// If this returns null (or is not installed), the renderer for the column will be used.
        /// If the column renderer is null, then <seealso cref="DefaultRenderer"/> will be used.
        /// </para>
        /// <para>
        /// This is called every time any cell is drawn. It must be efficient! 
        /// </para>
        /// </remarks>
        [Browsable(false),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual CellRendererGetterDelegate CellRendererGetter
        {
            get { return this.cellRendererGetter; }
            set { this.cellRendererGetter = value; }
        }
        private CellRendererGetterDelegate cellRendererGetter;

        /// <summary>
        /// This delegate is called when the list wants to show a tooltip for a particular cell.
        /// The delegate should return the text to display, or null to use the default behavior
        /// (which is to show the full text of truncated cell values).
        /// </summary>
        /// <remarks>
        /// Displaying the full text of truncated cell values only work for FullRowSelect listviews.
        /// This is MS's behavior, not mine. Don't complain to me :)
        /// </remarks>
        [Browsable(false),
         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual CellToolTipGetterDelegate CellToolTipGetter {
            get { return cellToolTipGetter; }
            set { cellToolTipGetter = value; }
        }
        private CellToolTipGetterDelegate cellToolTipGetter;

        /// <summary>
        /// 保存是否选中模型的属性(或字段)的名称。
        /// </summary>
        /// <remarks>
        /// <para>该属性是可修改的。它必须具有bool(或bool？)返回类型。如果TriStateCheckBooks为真)。</para>
        /// <para>设置此属性将替换任何已安装的CheckStateGetter或CheckStatePutter。
        /// 相反，稍后设置CheckStateGetter或CheckStatePutter属性将优先于此属性的行为。</para>
        /// </remarks>
        [Category("ObjectListView"),
         Description("保存模型的“检查性”的属性或字段的名称"),
         DefaultValue(null)]
        public virtual string CheckedAspectName {
            get { return checkedAspectName; }
            set {
                checkedAspectName = value;
                if (String.IsNullOrEmpty(checkedAspectName)) {
                    this.checkedAspectMunger = null;
                    this.CheckStateGetter = null;
                    this.CheckStatePutter = null;
                } else {
                    this.checkedAspectMunger = new Munger(checkedAspectName);
                    this.CheckStateGetter = delegate(Object modelObject) {
                        bool? result = this.checkedAspectMunger.GetValue(modelObject) as bool?;
                        if (result.HasValue)
                            return result.Value ? CheckState.Checked : CheckState.Unchecked;
                        return this.TriStateCheckBoxes ? CheckState.Indeterminate : CheckState.Unchecked;
                    };
                    this.CheckStatePutter = delegate(Object modelObject, CheckState newValue) {
                        if (this.TriStateCheckBoxes && newValue == CheckState.Indeterminate)
                            this.checkedAspectMunger.PutValue(modelObject, null);
                        else
                            this.checkedAspectMunger.PutValue(modelObject, newValue == CheckState.Checked);
                        return this.CheckStateGetter(modelObject);
                    };
                }
            }
        }
        private string checkedAspectName;
        private Munger checkedAspectMunger;

        /// <summary>
        /// 只要ObjectListView需要知道与给定模型对象关联的行的检查状态，就会调用此委托。
        /// </summary>
        /// <remarks>
        /// <para>.NET has no support for indeterminate values, but as of v2.0, this class allows
        /// indeterminate values.</para>
        /// </remarks>
        [Browsable(false),
         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual 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 state that was actually set, which may be different
        /// to the state given.
        /// </summary>
        [Browsable(false),
         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual CheckStatePutterDelegate CheckStatePutter {
            get { return checkStatePutter; }
            set { checkStatePutter = value; }
        }
        private CheckStatePutterDelegate checkStatePutter;

        /// <summary>
        /// 此委托可用于以自定义方式对表进行排序。
        /// </summary>
        /// <remarks>
        /// <para>
        ///委托必须在ObjectListView上安装ListViewItemSorter。
        ///安装ItemSorter会执行对ListViewItems进行排序的实际工作。
        ///有关ItemSorter必须执行的操作的示例，请参见代码中的ColumnCompeller。
        /// </para>
        /// <para>
        /// 请勿在VirtualObjectListView上安装CustomSorter。改为重写IVirtualListDataSource的SortObjects()方法。
        /// </para>
        /// </remarks>
        [Browsable(false),
         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual SortDelegate CustomSorter {
            get { return customSorter; }
            set { customSorter = value; }
        }
        private SortDelegate customSorter;

        /// <summary>
        /// This delegate is called when the list wants to show a tooltip for a particular header.
        /// The delegate should return the text to display, or null to use the default behavior
        /// (which is to not show any tooltip).
        /// </summary>
        /// <remarks>
        /// Installing a HeaderToolTipGetter takes precedence over any text in OLVColumn.ToolTipText.
        /// </remarks>
        [Browsable(false),
         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual HeaderToolTipGetterDelegate HeaderToolTipGetter {
            get { return headerToolTipGetter; }
            set { headerToolTipGetter = value; }
        }
        private HeaderToolTipGetterDelegate headerToolTipGetter;

        /// <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>
        /// <para>As it says in the summary, this is called <b>before</b> the item is added to the control.
        /// Many properties of the OLVListItem itself are not available at that point, including:
        /// Index, Selected, Focused, Bounds, Checked, DisplayIndex.</para>
        /// </remarks>
        [Browsable(false),
         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual RowFormatterDelegate RowFormatter {
            get { return rowFormatter; }
            set { rowFormatter = value; }
        }
        private RowFormatterDelegate rowFormatter;

        #endregion

        #region List commands

        /// <summary>
        /// 将给定的模型对象添加到此控件。
        /// </summary>
        /// <param name="modelObject">要显示的模型对象</param>
        /// <remarks>See AddObjects() for more details</remarks>
        public virtual void AddObject(object modelObject) {
            if (this.InvokeRequired)
                this.Invoke((MethodInvoker)delegate() { this.AddObject(modelObject); });
            else
                this.AddObjects(new object[] { modelObject });
        }

        /// <summary>
        /// 将给定的模型对象集合添加到此控件。
        /// </summary>
        /// <param name="modelObjects">要显示的模型对象集合</param>
        /// <remarks>
        /// <para>如果排序处于活动状态(即，如果PrimarySortColumn不为空)，则添加的对象将出现在其正确的排序位置。否则，它们将出现在列表的末尾。</para>
        /// <para>不执行任何检查以查看是否有任何对象已在ListView中。</para>
        /// <para>空对象将被静默忽略。</para>
        /// </remarks>
        public virtual void AddObjects(ICollection modelObjects) {
            if (this.InvokeRequired) {
                this.Invoke((MethodInvoker)delegate() { this.AddObjects(modelObjects); });
                return;
            }
            this.InsertObjects(ObjectListView.EnumerableCount(this.Objects), modelObjects);
            this.Sort(this.PrimarySortColumn, this.PrimarySortOrder);
        }

        /// <summary>
        /// 将列的大小调整到页眉宽度和数据的最大值。
        /// </summary>		
        public virtual void AutoResizeColumns() {
            foreach (OLVColumn c in this.Columns) {
                this.AutoResizeColumn(c.Index, ColumnHeaderAutoResizeStyle.HeaderSize);
            }
        }

        /// <summary>
        /// 设置任何自动初始化的列宽(宽度为0或-1的列将分别调整为其内容或标题的宽度)。
        /// </summary>
        /// <remarks>
        ///显然，这只会起作用一次。一旦运行，列宽将更改为其他值(不是0或-1)，因此第二次运行时不会执行任何操作。
        ///若要强制所有列更改其大小，请使用<see cref="AutoResizeColumns()"/> 。
        /// </remarks>
        public virtual void AutoSizeColumns() {
            // If we are supposed to resize to content, but if there is no content, 
            // resize to the header size instead.
            ColumnHeaderAutoResizeStyle resizeToContentStyle = this.GetItemCount() == 0 ?
                ColumnHeaderAutoResizeStyle.HeaderSize :
                ColumnHeaderAutoResizeStyle.ColumnContent;
            foreach (ColumnHeader column in this.Columns) {
                switch (column.Width) {
                    case 0:
                        this.AutoResizeColumn(column.Index, resizeToContentStyle);
                        break;
                    case -1:
                        this.AutoResizeColumn(column.Index, ColumnHeaderAutoResizeStyle.HeaderSize);
                        break;
                }
            }
        }

        /// <summary>
        /// 根据最后一个排序列或第一列(如果没有最后一个排序列)将视图项组织到组中
        /// </summary>
        public virtual void BuildGroups() {
            this.BuildGroups(this.PrimarySortColumn, this.PrimarySortOrder == SortOrder.None ? SortOrder.Ascending : this.PrimarySortOrder);
        }

        /// <summary>
        /// 根据给定列将视图项组织成组
        /// </summary>
        /// <remarks>
        /// <para>
        /// 如果AlwaysGroupByColumn属性不为Null，则列表视图项将按该列组织，并且将忽略‘Column’参数。
        /// </para>
        /// <para>此方法触发排序事件：BeforeSorting和AfterSorting。</para>
        /// </remarks>
        /// <param name="column">其值应用于排序的列。</param>
        /// <param name="order"></param>
        public virtual void BuildGroups(OLVColumn column, SortOrder order) {
            // Sanity
            if (this.GetItemCount() == 0 || this.Columns.Count == 0)
                return;

            BeforeSortingEventArgs args = this.BuildBeforeSortingEventArgs(column, order);
            this.OnBeforeSorting(args);
            if (args.Canceled)
                return;

            this.BuildGroups(args.ColumnToGroupBy, args.GroupByOrder,
                args.ColumnToSort, args.SortOrder, args.SecondaryColumnToSort, args.SecondarySortOrder);

            this.OnAfterSorting(new AfterSortingEventArgs(args));
        }

        private BeforeSortingEventArgs BuildBeforeSortingEventArgs(OLVColumn column, SortOrder order) {
            OLVColumn groupBy = this.AlwaysGroupByColumn ?? column ?? this.GetColumn(0);
            SortOrder groupByOrder = this.AlwaysGroupBySortOrder;
            if (order == SortOrder.None) {
                order = this.Sorting;
                if (order == SortOrder.None)
                    order = SortOrder.Ascending;
            }
            if (groupByOrder == SortOrder.None)
                groupByOrder = order;

            BeforeSortingEventArgs args = new BeforeSortingEventArgs(
                groupBy, groupByOrder,
                column, order,
                this.SecondarySortColumn ?? this.GetColumn(0),
                this.SecondarySortOrder == SortOrder.None ? order : this.SecondarySortOrder);
            if (column != null)
                args.Canceled = !column.Sortable;
            return args;
        }

        /// <summary>
        /// 根据给定列将视图项组织成组
        /// </summary>
        /// <param name="groupByColumn">将使用哪一列进行分组</param>
        /// <param name="groupByOrder">组将使用什么顺序</param>
        /// <param name="column">其值应用于排序的列。不能为空</param>
        /// <param name="order">列中值的排序顺序</param>
        /// <param name="secondaryColumn">当‘column’中的值相等时，请使用此列提供的值</param>
        /// <param name="secondaryOrder">次级值将如何排序</param>
        /// <remarks>此方法不会触发排序事件。使用BuildGroups()执行此操作</remarks>
        public virtual void BuildGroups(OLVColumn groupByColumn, SortOrder groupByOrder,
            OLVColumn column, SortOrder order, OLVColumn secondaryColumn, SortOrder secondaryOrder) {
            // Sanity checks
            if (groupByColumn == null)
                return;

            // 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.
#pragma warning disable 168
// ReSharper disable once UnusedVariable
            int dummy = this.Items.Count;
#pragma warning restore 168

            // Collect all the information that governs the creation of groups
            GroupingParameters parms = this.CollectGroupingParameters(groupByColumn, groupByOrder,
                column, order, secondaryColumn, secondaryOrder);

            // Trigger an event to let the world create groups if they want
            CreateGroupsEventArgs args = new CreateGroupsEventArgs(parms);
            if (parms.GroupByColumn != null)
                args.Canceled = !parms.GroupByColumn.Groupable;
            this.OnBeforeCreatingGroups(args);
            if (args.Canceled)
                return;

            // If the event didn't create them for us, use our default strategy
            if (args.Groups == null) 
                args.Groups = this.MakeGroups(parms);

            // Give the world a chance to munge the groups before they are created
            this.OnAboutToCreateGroups(args);
            if (args.Canceled)
                return;

            // Create the groups now
            this.OLVGroups = args.Groups;
            this.CreateGroups(args.Groups);

            // Tell the world that new groups have been created
            this.OnAfterCreatingGroups(args);
            lastGroupingParameters = args.Parameters;
        }
        private GroupingParameters lastGroupingParameters;

        /// <summary>
        /// 收集并返回影响组创建的所有变量
        /// </summary>
        /// <returns></returns>
        protected virtual GroupingParameters CollectGroupingParameters(OLVColumn groupByColumn, SortOrder groupByOrder,
            OLVColumn sortByColumn, SortOrder sortByOrder, OLVColumn secondaryColumn, SortOrder secondaryOrder) {
            
            // If the user tries to group by a non-groupable column, keep the current group by
            // settings, but use the non-groupable column for sorting
            if (!groupByColumn.Groupable && lastGroupingParameters != null) {
                sortByColumn = groupByColumn;
                sortByOrder = groupByOrder;
                groupByColumn = lastGroupingParameters.GroupByColumn;
                groupByOrder = lastGroupingParameters.GroupByOrder;
            }

            string titleFormat = this.ShowItemCountOnGroups ? groupByColumn.GroupWithItemCountFormatOrDefault : null;
            string titleSingularFormat = this.ShowItemCountOnGroups ? groupByColumn.GroupWithItemCountSingularFormatOrDefault : null;
            GroupingParameters parms = new GroupingParameters(this, groupByColumn, groupByOrder,
                sortByColumn, sortByOrder, secondaryColumn, secondaryOrder,
                titleFormat, titleSingularFormat, this.SortGroupItemsByPrimaryColumn);
            return parms;
        }

        /// <summary>
        /// Make a list of groups that should be shown according to the given parameters
        /// </summary>
        /// <param name="parms"></param>
        /// <returns>The list of groups to be created</returns>
        /// <remarks>This should not change the state of the control. It is possible that the
        /// groups created will not be used. They may simply be discarded.</remarks>
        protected virtual IList<OLVGroup> MakeGroups(GroupingParameters parms) {

            // There is a lot of overlap between this method and FastListGroupingStrategy.MakeGroups()
            // Any changes made here may need to be reflected there

            // Separate the list view items into groups, using the group key as the descrimanent
            NullableDictionary<object, List<OLVListItem>> map = new NullableDictionary<object, List<OLVListItem>>();
            foreach (OLVListItem olvi in parms.ListView.Items) {
                object key = parms.GroupByColumn.GetGroupKey(olvi.RowObject);
                if (!map.ContainsKey(key))
                    map[key] = new List<OLVListItem>();
                map[key].Add(olvi);
            }

            // Sort the items within each group (unless specifically turned off)
            OLVColumn sortColumn = parms.SortItemsByPrimaryColumn ? parms.ListView.GetColumn(0) : parms.PrimarySort;
            if (sortColumn != null && parms.PrimarySortOrder != SortOrder.None) {
                IComparer<OLVListItem> itemSorter = parms.ItemComparer ?? 
                    new ColumnComparer(sortColumn, parms.PrimarySortOrder, parms.SecondarySort, parms.SecondarySortOrder);
                foreach (object key in map.Keys) {
                    map[key].Sort(itemSorter);
                }
            }

            // Make a list of the required groups
            List<OLVGroup> groups = new List<OLVGroup>();
            foreach (object key in map.Keys) {
                string title = parms.GroupByColumn.ConvertGroupKeyToTitle(key);
                if (!String.IsNullOrEmpty(parms.TitleFormat)) {
                    int count = map[key].Count;
                    string format = (count == 1 ? parms.TitleSingularFormat : parms.TitleFormat);
                    try {
                        title = String.Format(format, title, count);
                    } catch (FormatException) {
                        title = "Invalid group format: " + format;
                    }
                }

                OLVGroup lvg = new OLVGroup(title);
                lvg.Collapsible = this.HasCollapsibleGroups;
                lvg.Key = key;
                lvg.SortValue = key as IComparable;
                lvg.Items = map[key];
                if (parms.GroupByColumn.GroupFormatter != null)
                    parms.GroupByColumn.GroupFormatter(lvg, parms);
                groups.Add(lvg);
            }

            // Sort the groups
            if (parms.GroupByOrder != SortOrder.None)
                groups.Sort(parms.GroupComparer ?? new OLVGroupComparer(parms.GroupByOrder));

            return groups;
        }

        /// <summary>
        /// 生成/重新生成列表中的所有列表视图项，尽可能多地保留状态
        /// </summary>
        public virtual void BuildList() {
            if (this.InvokeRequired)
                this.Invoke(new MethodInvoker(this.BuildList));
            else
                this.BuildList(true);
        }

        /// <summary>
        /// 生成/重新生成列表中的所有列表视图项
        /// </summary>
        /// <param name="shouldPreserveState">If this is true, the control will try to preserve the selection,
        /// focused item, 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>
        /// </remarks>
        public virtual void BuildList(bool shouldPreserveState) {
            if (this.Frozen)
                return;

            Stopwatch sw = Stopwatch.StartNew();

            this.ApplyExtendedStyles();
            this.ClearHotItem();
            int previousTopIndex = this.TopItemIndex;
            Point currentScrollPosition = this.LowLevelScrollPosition;

            IList previousSelection = new ArrayList();
            Object previousFocus = null;
            if (shouldPreserveState && this.objects != null) {
                previousSelection = this.SelectedObjects;
                OLVListItem focusedItem = this.FocusedItem as OLVListItem;
                if (focusedItem != null)
                    previousFocus = focusedItem.RowObject;
            }

            IEnumerable objectsToDisplay = this.FilteredObjects;

            this.BeginUpdate();
            try {
                this.Items.Clear();
                this.ListViewItemSorter = null;

                if (objectsToDisplay != 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<ListViewItem> itemList = new List<ListViewItem>(); // use ListViewItem to avoid co-variant conversion
                    foreach (object rowObject in objectsToDisplay) {
                        OLVListItem lvi = new OLVListItem(rowObject);
                        this.FillInValues(lvi, rowObject);
                        itemList.Add(lvi);
                    }
                    this.Items.AddRange(itemList.ToArray());
                    this.Sort();

                    if (shouldPreserveState) {
                        this.SelectedObjects = previousSelection;
                        this.FocusedItem = this.ModelToItem(previousFocus);
                    }
                }
            } finally {
                this.EndUpdate();
            }

            this.RefreshHotItem();

            // 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) {
                // Restore the scroll position. TopItemIndex is best, but doesn't work
                // when the control is grouped.
                if (this.ShowGroups)
                    this.LowLevelScroll(currentScrollPosition.X, currentScrollPosition.Y);
                else
                    this.TopItemIndex = previousTopIndex;
            }

            // System.Diagnostics.Debug.WriteLine(String.Format("PERF - Building list for {2} objects took {0}ms / {1} ticks", sw.ElapsedMilliseconds, sw.ElapsedTicks, this.GetItemCount()));
        }

        /// <summary>
        /// 清除此列表可能一直在使用的所有缓存信息
        /// </summary>
        public virtual void ClearCachedInfo()
        {
            // ObjectListView当前不缓存信息，但子类缓存(或可能缓存)
        }

        /// <summary>
        /// Apply all required extended styles to our control.
        /// </summary>
        /// <remarks>
        /// <para>
        /// Whenever .NET code sets an extended style, it erases all other extended styles
        /// that it doesn't use. So, we have to explicit reapply the styles that we have
        /// added.
        /// </para>
        /// <para>
        /// Normally, we would override CreateParms property and update
        /// the ExStyle member, but ListView seems to ignore all ExStyles that
        /// it doesn't already know about. Worse, when we set the LVS_EX_HEADERINALLVIEWS 
        /// value, bad things happen (the control crashes!).
        /// </para>
        /// </remarks>
        protected virtual void ApplyExtendedStyles() {
            const int LVS_EX_SUBITEMIMAGES = 0x00000002;
            //const int LVS_EX_TRANSPARENTBKGND = 0x00400000;
            const int LVS_EX_HEADERINALLVIEWS = 0x02000000;

            const int STYLE_MASK = LVS_EX_SUBITEMIMAGES | LVS_EX_HEADERINALLVIEWS;
            int style = 0;

            if (this.ShowImagesOnSubItems && !this.VirtualMode)
                style ^= LVS_EX_SUBITEMIMAGES;

            if (this.ShowHeaderInAllViews)
                style ^= LVS_EX_HEADERINALLVIEWS;

            NativeMethods.SetExtendedStyle(this, style, STYLE_MASK);
        }

        /// <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 virtual void CalculateReasonableTileSize() {
            if (this.Columns.Count <= 0)
                return;

            List<OLVColumn> columns = this.AllColumns.FindAll(delegate(OLVColumn x) {
                return (x.Index == 0) || x.IsTileViewColumn;
            });

            int imageHeight = (this.LargeImageList == null ? 16 : this.LargeImageList.ImageSize.Height);
            int dataHeight = (this.Font.Height + 1) * 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>
        public virtual void ChangeToFilteredColumns(View view) {
            // Store the state
            this.SuspendSelectionEvents();
            IList previousSelection = this.SelectedObjects;
            int previousTopIndex = this.TopItemIndex;

            this.Freeze();
            this.Clear();
            List<OLVColumn> columns = this.GetFilteredColumns(view);
            if (view == View.Details || this.ShowHeaderInAllViews) {
                // Make sure all columns have a reasonable LastDisplayIndex
                for (int index = 0; index < columns.Count; index++)
                {
                    if (columns[index].LastDisplayIndex == -1)
                        columns[index].LastDisplayIndex = index;
                }
                // ListView will ignore DisplayIndex FOR ALL COLUMNS if there are any errors, 
                // e.g. duplicates (two columns with the same DisplayIndex) or gaps. 
                // LastDisplayIndex isn't guaranteed to be unique, so we just sort the columns by
                // the last position they were displayed and use that to generate a sequence 
                // we can use for the DisplayIndex values.
                List<OLVColumn> columnsInDisplayOrder = new List<OLVColumn>(columns);
                columnsInDisplayOrder.Sort(delegate(OLVColumn x, OLVColumn y) { return (x.LastDisplayIndex - y.LastDisplayIndex); });
                int i = 0;
                foreach (OLVColumn col in columnsInDisplayOrder)
                    col.DisplayIndex = i++;
            }

// ReSharper disable once CoVariantArrayConversion
            this.Columns.AddRange(columns.ToArray());
            if (view == View.Details || this.ShowHeaderInAllViews)
                this.ShowSortIndicator();
            this.UpdateFiltering();
            this.Unfreeze();

            // Restore the state
            this.SelectedObjects = previousSelection;
            this.TopItemIndex = previousTopIndex;
            this.ResumeSelectionEvents();
        }

        /// <summary>
        /// Remove all items from this list
        /// </summary>
        /// <remark>This method can safely be called from background threads.</remark>
        public virtual void ClearObjects() {
            if (this.InvokeRequired)
                this.Invoke(new MethodInvoker(this.ClearObjects));
            else
                this.SetObjects(null);
        }

        /// <summary>
        /// 重置已访问其URL的内存
        /// </summary>
        public virtual void ClearUrlVisited() {
            this.visitedUrlMap = new Dictionary<string, bool>();
        }

        /// <summary>
        ///将选定行的文本和HTML表示复制到剪贴板上。
        /// </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 virtual void CopySelectionToClipboard() {
            IList selection = this.SelectedObjects;
            if (selection.Count == 0)
                return;

            // Use the DragSource object to create the data object, if so configured.
            // This relies on the assumption that DragSource will handle the selected objects only.
            // It is legal for StartDrag to return null.
            object data = null;
            if (this.CopySelectionOnControlCUsesDragSource && this.DragSource != null)
                data = this.DragSource.StartDrag(this, MouseButtons.Left, this.ModelToItem(selection[0]));

            Clipboard.SetDataObject(data ?? new OLVDataObject(this, selection));
        }

        /// <summary>
        /// 将给定对象的文本和HTML表示复制到剪贴板上。
        /// </summary>
        public virtual void CopyObjectsToClipboard(IList objectsToCopy) {
            if (objectsToCopy.Count == 0)
                return;

            // We don't know where these objects came from, so we can't use the DragSource to create
            // the data object, like we do with CopySelectionToClipboard() above.
            OLVDataObject dataObject = new OLVDataObject(this, objectsToCopy);
            dataObject.CreateTextFormats();
            Clipboard.SetDataObject(dataObject);
        }

        /// <summary>
        /// 返回给定对象的html表示形式
        /// </summary>
        public virtual string ObjectsToHtml(IList objectsToConvert) {
            if (objectsToConvert.Count == 0)
                return String.Empty;

            OLVExporter exporter = new OLVExporter(this, objectsToConvert);
            return exporter.ExportTo(OLVExporter.ExportFormat.HTML);
        }

        /// <summary>
        /// 取消选择列表视图中的所有行
        /// </summary>
        public virtual void DeselectAll() {
            NativeMethods.DeselectAllItems(this);
        }

        /// <summary>
        /// 返回紧跟在给定项后面的ListViewItem。
        ///如果给定项为空，则返回列表中的第一个项。
        ///如果给定项是最后一项，则返回NULL。
        /// </summary>
        /// <param name="itemToFind">The item that is before the item that is returned, or null</param>
        /// <returns>A ListViewItem</returns>
        public virtual OLVListItem GetNextItem(OLVListItem itemToFind) {
            if (this.ShowGroups) {
                bool isFound = (itemToFind == null);
                foreach (ListViewGroup group in this.Groups) {
                    foreach (OLVListItem olvi in group.Items) {
                        if (isFound)
                            return olvi;
                        isFound = (itemToFind == olvi);
                    }
                }
                return null;
            }
            if (this.GetItemCount() == 0)
                return null;
            if (itemToFind == null)
                return this.GetItem(0);
            if (itemToFind.Index == this.GetItemCount() - 1)
                return null;
            return this.GetItem(itemToFind.Index + 1);
        }

        /// <summary>
        /// 按向用户显示的顺序返回最后一项。
        ///如果控件未分组，则显示顺序与排序的列表顺序相同。
        ///但如果列表是分组的，则显示顺序不同。
        /// </summary>
        /// <returns></returns>
        public virtual OLVListItem GetLastItemInDisplayOrder() {
            if (!this.ShowGroups)
                return this.GetItem(this.GetItemCount() - 1);
            
            if (this.Groups.Count > 0) {
                ListViewGroup lastGroup = this.Groups[this.Groups.Count - 1];
                if (lastGroup.Items.Count > 0)
                    return (OLVListItem)lastGroup.Items[lastGroup.Items.Count - 1];
            }

            return null;
        }

        /// <summary>
        /// 按向用户显示的顺序返回第n项(从0开始)。
        ///如果控件未分组，则显示顺序与排序的列表顺序相同。
        ///但如果列表是分组的，则显示顺序不同。
        /// </summary>
        /// <param name="n"></param>
        /// <returns></returns>
        public virtual OLVListItem GetNthItemInDisplayOrder(int n) {
            if (!this.ShowGroups || this.Groups.Count == 0)
                return this.GetItem(n);

            foreach (ListViewGroup group in this.Groups) {
                if (n < group.Items.Count)
                    return (OLVListItem)group.Items[n];

                n -= group.Items.Count;
            }

            return null;
        }

        /// <summary>
        ///返回给定listviewitem索引的显示索引。
        ///如果控件未分组，则显示顺序与排序的列表顺序相同。
        ///但如果列表是分组的，则显示顺序不同。
        /// </summary>
        /// <param name="itemIndex"></param>
        /// <returns></returns>
        public virtual int GetDisplayOrderOfItemIndex(int itemIndex) {
            if (!this.ShowGroups || this.Groups.Count == 0)
                return itemIndex;

            // TODO: This could be optimized
            int i = 0;
            foreach (ListViewGroup lvg in this.Groups) {
                foreach (ListViewItem lvi in lvg.Items) {
                    if (lvi.Index == itemIndex)
                        return i;
                    i++;
                }
            }

            return -1;
        }

        /// <summary>
        /// 返回紧接在给定项之前出现的ListViewItem。
        ///如果给定项为空，则返回列表中的最后一项。
        ///如果给定项是第一项，则返回NULL。
        /// </summary>
        /// <param name="itemToFind">The item that is before the item that is returned</param>
        /// <returns>A ListViewItem</returns>
        public virtual OLVListItem GetPreviousItem(OLVListItem itemToFind) {
            if (this.ShowGroups) {
                OLVListItem previousItem = null;
                foreach (ListViewGroup group in this.Groups) {
                    foreach (OLVListItem lvi in group.Items) {
                        if (lvi == itemToFind)
                            return previousItem;
                        
                        previousItem = lvi;
                    }
                }
                return itemToFind == null ? previousItem : null;
            }
            if (this.GetItemCount() == 0)
                return null;
            if (itemToFind == null)
                return this.GetItem(this.GetItemCount() - 1);
            if (itemToFind.Index == 0)
                return null;
            return this.GetItem(itemToFind.Index - 1);
        }

        /// <summary>
        /// 在给定位置之前插入给定的对象集合
        /// </summary>
        /// <param name="index">Where to insert the objects</param>
        /// <param name="modelObjects">The objects to be inserted</param>
        /// <remarks>
        /// <para>
        /// This operation only makes sense of non-sorted, non-grouped
        /// lists, since any subsequent sort/group operation will rearrange
        /// the list.
        /// </para>
        /// <para>This method only works on ObjectListViews and FastObjectListViews.</para>
        ///</remarks>
        public virtual void InsertObjects(int index, ICollection modelObjects) {
            if (this.InvokeRequired) {
                this.Invoke((MethodInvoker)delegate() {
                    this.InsertObjects(index, modelObjects);
                });
                return;
            }
            if (modelObjects == null)
                return;

            this.BeginUpdate();
            try {
                // Give the world a chance to cancel or change the added objects
                ItemsAddingEventArgs args = new ItemsAddingEventArgs(modelObjects);
                this.OnItemsAdding(args);
                if (args.Canceled)
                    return;
                modelObjects = args.ObjectsToAdd;

                this.TakeOwnershipOfObjects();
                ArrayList ourObjects = ObjectListView.EnumerableToArray(this.Objects, false);

                // If we are filtering the list, there is no way to efficiently
                // insert the objects, so just put them into our collection and rebuild.
                if (this.IsFiltering) {
                    index = Math.Max(0, Math.Min(index, ourObjects.Count));
                    ourObjects.InsertRange(index, modelObjects);
                    this.BuildList(true);
                } else {
                    this.ListViewItemSorter = null;
                    index = Math.Max(0, Math.Min(index, this.GetItemCount()));
                    int i = index;
                    foreach (object modelObject in modelObjects) {
                        if (modelObject != null) {
                            ourObjects.Insert(i, modelObject);
                            OLVListItem lvi = new OLVListItem(modelObject);
                            this.FillInValues(lvi, modelObject);
                            this.Items.Insert(i, lvi);
                            i++;
                        }
                    }

                    for (i = index; i < this.GetItemCount(); i++) {
                        OLVListItem lvi = this.GetItem(i);
                        this.SetSubItemImages(lvi.Index, lvi);
                    }

                    this.PostProcessRows();
                }

                // Tell the world that the list has changed
                this.SubscribeNotifications(modelObjects);
                this.OnItemsChanged(new ItemsChangedEventArgs());
            } finally {
                this.EndUpdate();
            }
        }

        /// <summary>
        /// 如果选择了表示给定模型的行，则返回TRUE
        /// </summary>
        /// <param name="model">The model object to look for</param>
        /// <returns>Is the row selected</returns>
        public bool IsSelected(object model) {
            OLVListItem item = this.ModelToItem(model);
            return item != null && item.Selected;
        }

        /// <summary>
        /// 给定的URL是否已被访问
        /// </summary>
        /// <param name="url">The string to be consider</param>
        /// <returns>Has it been visited</returns>
        public virtual bool IsUrlVisited(string url) {
            return this.visitedUrlMap.ContainsKey(url);
        }

        /// <summary>
        /// 按给定的值增量滚动ListView。
        /// </summary>
        /// <param name="dx">Horizontal delta</param>
        /// <param name="dy">Vertical delta</param>
        public void LowLevelScroll(int dx, int dy) {
            NativeMethods.Scroll(this, dx, dy);
        }

        /// <summary>
        /// Return a point that represents the current horizontal and vertical scroll positions 
        /// </summary>
        [Browsable(false),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public Point LowLevelScrollPosition 
        {
            get {
                return new Point(NativeMethods.GetScrollPosition(this, true), NativeMethods.GetScrollPosition(this, false));
            }
        }

        /// <summary>
        ///记住给定的URL已被访问
        /// </summary>
        /// <param name="url">The url to be remembered</param>
        /// <remarks>This does not cause the control be redrawn</remarks>
        public virtual void MarkUrlVisited(string url) {
            this.visitedUrlMap[url] = true;
        }

        /// <summary>
        /// 将给定的对象集合移动到给定的索引。
        /// </summary>
        /// <remarks>此操作仅对未分组的ObjectListViews有意义。</remarks>
        /// <param name="index"></param>
        /// <param name="modelObjects"></param>
        public virtual void MoveObjects(int index, ICollection modelObjects) {

            // We are going to remove all the given objects from our list
            // and then insert them at the given location
            this.TakeOwnershipOfObjects();
            ArrayList ourObjects = ObjectListView.EnumerableToArray(this.Objects, false);

            List<int> indicesToRemove = new List<int>();
            foreach (object modelObject in modelObjects) {
                if (modelObject != null) {
                    int i = this.IndexOf(modelObject);
                    if (i >= 0) {
                        indicesToRemove.Add(i);
                        ourObjects.Remove(modelObject);
                        if (i <= index)
                            index--;
                    }
                }
            }

            // Remove the objects in reverse order so earlier
            // deletes don't change the index of later ones
            indicesToRemove.Sort();
            indicesToRemove.Reverse();
            try {
                this.BeginUpdate();
                foreach (int i in indicesToRemove) {
                    this.Items.RemoveAt(i);
                }
                this.InsertObjects(index, modelObjects);
            } finally {
                this.EndUpdate();
            }
        }

        /// <summary>
        ///计算在给定点下的项目是什么？
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <returns></returns>
        new public ListViewHitTestInfo HitTest(int x, int y) {
            // Everything costs something. Playing with the layout of the header can cause problems
            // with the hit testing. If the header shrinks, the underlying control can throw a tantrum.
            try {
                return base.HitTest(x, y);
            } catch (ArgumentOutOfRangeException) {
                return new ListViewHitTestInfo(null, null, ListViewHitTestLocations.None);
            }
        }

        /// <summary>
        /// 使用Windows控件的SUBITEMHITTEST消息执行点击测试。
        /// 这提供了标准ListView.HitTest()不提供的有关组点击的信息。
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <returns></returns>
        protected OlvListViewHitTestInfo LowLevelHitTest(int x, int y) {

            // If it's not even in the control, don't bother with anything else
            if (!this.ClientRectangle.Contains(x, y))
                return new OlvListViewHitTestInfo(null, null, 0, null, 0);

            // Is the point over the header?
            OlvListViewHitTestInfo.HeaderHitTestInfo headerHitTestInfo = this.HeaderControl.HitTest(x, y);
            if (headerHitTestInfo != null) 
                return new OlvListViewHitTestInfo(this, headerHitTestInfo.ColumnIndex, headerHitTestInfo.IsOverCheckBox, headerHitTestInfo.OverDividerIndex);

            // Call the native hit test method, which is a little confusing.
            NativeMethods.LVHITTESTINFO lParam = new NativeMethods.LVHITTESTINFO();
            lParam.pt_x = x;
            lParam.pt_y = y;
            int index = NativeMethods.HitTest(this, ref lParam);

            // Setup the various values we need to make our hit test structure
            bool isGroupHit = (lParam.flags & (int)HitTestLocationEx.LVHT_EX_GROUP) != 0;
            OLVListItem hitItem = isGroupHit || index == -1 ? null : this.GetItem(index);
            OLVListSubItem subItem = (this.View == View.Details && hitItem != null) ? hitItem.GetSubItem(lParam.iSubItem) : null;

            // Figure out which group is involved in the hit test. This is a little complicated:
            // If the list is virtual:
            //   - the returned value is list view item index
            //   - iGroup is the *index* of the hit group.
            // If the list is not virtual:
            //   - iGroup is always -1.
            //   - if the point is over a group, the returned value is the *id* of the hit group.
            //   - if the point is not over a group, the returned value is list view item index.
            OLVGroup group = null;
            if (this.ShowGroups && this.OLVGroups != null) {
                if (this.VirtualMode) {
                    group = lParam.iGroup >= 0 && lParam.iGroup < this.OLVGroups.Count ? this.OLVGroups[lParam.iGroup] : null;
                } else {
                    if (isGroupHit) {
                        foreach (OLVGroup olvGroup in this.OLVGroups) {
                            if (olvGroup.GroupId == index) {
                                group = olvGroup;
                                break;
                            }
                        }
                    }
                }
            }
            OlvListViewHitTestInfo olvListViewHitTest = new OlvListViewHitTestInfo(hitItem, subItem, lParam.flags, group, lParam.iSubItem);
            // System.Diagnostics.Debug.WriteLine(String.Format("HitTest({0}, {1})=>{2}", x, y, olvListViewHitTest));
            return olvListViewHitTest;
        }

        /// <summary>
        /// What is under the given point? This takes the various parts of a cell into accout, including
        /// any custom parts that a custom renderer might use
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <returns>An information block about what is under the point</returns>
        public virtual OlvListViewHitTestInfo OlvHitTest(int x, int y) {
            OlvListViewHitTestInfo hti = this.LowLevelHitTest(x, y);

            // There is a bug/"feature" of the ListView concerning hit testing.
            // If FullRowSelect is false and the point is over cell 0 but not on
            // the text or icon, HitTest will not register a hit. We could turn
            // FullRowSelect on, do the HitTest, and then turn it off again, but
            // toggling FullRowSelect in that way messes up the tooltip in the
            // underlying control. So we have to find another way.
            //
            // It's too hard to try to write the hit test from scratch. Grouping (for
            // example) makes it just too complicated. So, we have to use HitTest
            // but try to get around its limits.
            //
            // First step is to determine if the point was within column 0.
            // If it was, then we only have to determine if there is an actual row
            // under the point. If there is, then we know that the point is over cell 0.
            // So we try a Battleship-style approach: is there a subcell to the right
            // of cell 0? This will return a false negative if column 0 is the rightmost column,
            // so we also check for a subcell to the left. But if only column 0 is visible,
            // then that will fail too, so we check for something at the very left of the
            // control.
            //
            // This will still fail under pathological conditions. If column 0 fills
            // the whole listview and no part of the text column 0 is visible
            // (because it is horizontally scrolled offscreen), then the hit test will fail.

            // Are we in the buggy context? Details view, not full row select, and
            // failing to find anything
            if (hti.Item == null && !this.FullRowSelect && this.View == View.Details) {
                // Is the point within the column 0? If it is, maybe it should have been a hit.
                // Let's test slightly to the right and then to left of column 0. Hopefully one
                // of those will hit a subitem
                Point sides = NativeMethods.GetScrolledColumnSides(this, 0);
                if (x >= sides.X && x <= sides.Y) {
                    // We look for:
                    // - any subitem to the right of cell 0?
                    // - any subitem to the left of cell 0?
                    // - cell 0 at the left edge of the screen
                    hti = this.LowLevelHitTest(sides.Y + 4, y);
                    if (hti.Item == null)
                        hti = this.LowLevelHitTest(sides.X - 4, y);
                    if (hti.Item == null)
                        hti = this.LowLevelHitTest(4, y);

                    if (hti.Item != null) {
                        // We hit something! So, the original point must have been in cell 0
                        hti.ColumnIndex = 0;
                        hti.SubItem = hti.Item.GetSubItem(0);
                        hti.Location = ListViewHitTestLocations.None;
                        hti.HitTestLocation = HitTestLocation.InCell;
                    }
                }
            }

            if (this.OwnerDraw)
                this.CalculateOwnerDrawnHitTest(hti, x, y);
            else
                this.CalculateStandardHitTest(hti, x, y);

            return hti;
        }

        /// <summary>
        /// Perform a hit test when the control is not owner drawn
        /// </summary>
        /// <param name="hti"></param>
        /// <param name="x"></param>
        /// <param name="y"></param>
        protected virtual void CalculateStandardHitTest(OlvListViewHitTestInfo hti, int x, int y) {

            // Standard hit test works fine for the primary column
            if (this.View != View.Details || hti.ColumnIndex == 0 ||
                hti.SubItem == null || hti.Column == null)
                return;

            Rectangle cellBounds = hti.SubItem.Bounds;
            bool hasImage = (this.GetActualImageIndex(hti.SubItem.ImageSelector) != -1);

            // Unless we say otherwise, it was an general incell hit
            hti.HitTestLocation = HitTestLocation.InCell;

            // Check if the point is over where an image should be.
            // If there is a checkbox or image there, tag it and exit.
            Rectangle r = cellBounds;
            r.Width = this.SmallImageSize.Width;
            if (r.Contains(x, y)) {
                if (hti.Column.CheckBoxes) {
                    hti.HitTestLocation = HitTestLocation.CheckBox;
                    return;
                }
                if (hasImage) {
                    hti.HitTestLocation = HitTestLocation.Image;
                    return;
                }
            }

            // Figure out where the text actually is and if the point is in it
            // The standard HitTest assumes that any point inside a subitem is
            // a hit on Text -- which is clearly not true.
            Rectangle textBounds = cellBounds;
            textBounds.X += 4;
            if (hasImage)
                textBounds.X += this.SmallImageSize.Width;

            Size proposedSize = new Size(textBounds.Width, textBounds.Height);
            Size textSize = TextRenderer.MeasureText(hti.SubItem.Text, this.Font, proposedSize, TextFormatFlags.EndEllipsis | TextFormatFlags.SingleLine | TextFormatFlags.NoPrefix);
            textBounds.Width = textSize.Width;

            switch (hti.Column.TextAlign) {
                case HorizontalAlignment.Center:
                    textBounds.X += (cellBounds.Right - cellBounds.Left - textSize.Width) / 2;
                    break;
                case HorizontalAlignment.Right:
                    textBounds.X = cellBounds.Right - textSize.Width;
                    break;
            }
            if (textBounds.Contains(x, y)) {
                hti.HitTestLocation = HitTestLocation.Text;
            }
        }

        /// <summary>
        /// Perform a hit test when the control is owner drawn. This hands off responsibility
        /// to the renderer.
        /// </summary>
        /// <param name="hti"></param>
        /// <param name="x"></param>
        /// <param name="y"></param>
        protected virtual void CalculateOwnerDrawnHitTest(OlvListViewHitTestInfo hti, int x, int y) {
            // If the click wasn't on an item, give up
            if (hti.Item == null)
                return;

            // If the list is showing column, but they clicked outside the columns, also give up
            if (this.View == View.Details && hti.Column == null)
                return;

            // Which renderer was responsible for drawing that point
            IRenderer renderer = this.View == View.Details
                ? this.GetCellRenderer(hti.RowObject, hti.Column)
                : this.ItemRenderer;

            // We can't decide who was responsible. Give up
            if (renderer == null)
                return;

            // Ask the responsible renderer what is at that point
            renderer.HitTest(hti, x, y);
        }

        /// <summary>
        /// Pause (or unpause) all animations in the list
        /// </summary>
        /// <param name="isPause">true to pause, false to unpause</param>
        public virtual void PauseAnimations(bool isPause) {
            for (int i = 0; i < this.Columns.Count; i++) {
                OLVColumn col = this.GetColumn(i);
                ImageRenderer renderer = col.Renderer as ImageRenderer;
                if (renderer != null) 
                    renderer.Paused = isPause;
            }
        }

        /// <summary>
        /// Rebuild the columns based upon its current view and column visibility settings
        /// </summary>
        public virtual void RebuildColumns() {
            this.ChangeToFilteredColumns(this.View);
        }

        /// <summary>
        /// 从ListView中删除给定的模型对象
        /// </summary>
        /// <param name="modelObject">The model to be removed</param>
        /// <remarks>See RemoveObjects() for more details
        /// <para>This method is thread-safe.</para>
        /// </remarks>
        public virtual void RemoveObject(object modelObject) {
            if (this.InvokeRequired)
                this.Invoke((MethodInvoker)delegate() { this.RemoveObject(modelObject); });
            else
                this.RemoveObjects(new object[] { modelObject });
        }

        /// <summary>
        ///从控件中移除所有给定对象。
        /// </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>
        /// <para>This method is thread-safe.</para>
        /// </remarks>
        public virtual void RemoveObjects(ICollection modelObjects) {
            if (this.InvokeRequired) {
                this.Invoke((MethodInvoker)delegate() { this.RemoveObjects(modelObjects); });
                return;
            }
            if (modelObjects == null)
                return;

            this.BeginUpdate();
            try {
                // Give the world a chance to cancel or change the added objects
                ItemsRemovingEventArgs args = new ItemsRemovingEventArgs(modelObjects);
                this.OnItemsRemoving(args);
                if (args.Canceled)
                    return;
                modelObjects = args.ObjectsToRemove;

                this.TakeOwnershipOfObjects();
                ArrayList ourObjects = ObjectListView.EnumerableToArray(this.Objects, false);
                foreach (object modelObject in modelObjects) {
                    if (modelObject != null) {
// ReSharper disable PossibleMultipleEnumeration
                        int i = ourObjects.IndexOf(modelObject);
                        if (i >= 0)
                            ourObjects.RemoveAt(i);
// ReSharper restore PossibleMultipleEnumeration
                        i = this.IndexOf(modelObject);
                        if (i >= 0)
                            this.Items.RemoveAt(i);
                    }
                }
                this.PostProcessRows();

                // Tell the world that the list has changed
                this.UnsubscribeNotifications(modelObjects);
                this.OnItemsChanged(new ItemsChangedEventArgs());
            } finally {
                this.EndUpdate();
            }
        }

        /// <summary>
        /// 选择列表视图中的所有行
        /// </summary>
        public virtual void SelectAll() {
            NativeMethods.SelectAllItems(this);
        }

        /// <summary>
        ///在列表视图的右下角设置要固定的给定图像。
        ///当列表视图滚动时，此图像不会滚动。
        /// </summary>
        /// <remarks>
        /// <para>
        /// 此方法使用ListView的原生功能显示背景图像。
        /// 它有几个限制：
        /// </para>
        /// <list type="bullet">
        /// <item><description>它在owner drawn模式下不能很好地工作。在owner drawn绘制模式下，
        /// 每个单元格都会绘制自身，包括覆盖背景图像的背景。</description></item>
        /// <item><description>启用网格线时看起来不是很好，因为网格线是在图像上绘制的。</description></item>
        /// <item><description>它在XP上完全不起作用。</description></item>
        /// <item><description>显然，当启用交替行背景色时，效果不佳。</description></item>
        /// </list>
        /// <para>
        /// 如果你能忍受这些限制，原生水印就相当不错了。
        ///它们是真实的背景，而不是像OverlayImage使用的半透明覆盖。
        ///与覆盖相比，它们也有明显的优势，因为即使在MDI应用程序中，它们也可以正常工作。
        /// </para>
        /// <para>设置此选项将清除所有背景图像。</para>
        /// </remarks>
        /// <param name="image">The image to be drawn. If null, any existing image will be removed.</param>
        public void SetNativeBackgroundWatermark(Image image) {
            NativeMethods.SetBackgroundImage(this, image, true, false, 0, 0);
        }

        /// <summary>
        /// 将给定的图像设置为ListView的背景，以便它在列表中以给定的百分比偏移量显示。
        /// </summary>
        /// <remarks>
        /// <para>这具有与 <see cref="SetNativeBackgroundWatermark"/>中所述的相同限制. 
        /// 在使用该方法之前，请确保了解这些限制。</para>
        /// <para>这非常类似于 <see cref="System.Windows.Forms.Control.BackgroundImage"/> 属性，
        /// 但标准Backround Image不能正确处理具有透明区域的图像--它将透明区域呈现为黑色。这种方法没有这个问题。</para>
        /// <para>设置此选项将清除所有背景水印。</para>
        /// </remarks>
        /// <param name="image">The image to be drawn. If null, any existing image will be removed.</param>
        /// <param name="xOffset">The horizontal percentage where the image will be placed. 0 is absolute left, 100 is absolute right.</param>
        /// <param name="yOffset">The vertical percentage where the image will be placed.</param>
        public void SetNativeBackgroundImage(Image image, int xOffset, int yOffset) {
            NativeMethods.SetBackgroundImage(this, image, false, false, xOffset, yOffset);
        }

        /// <summary>
        /// 将给定图像设置为ListView的平铺背景。
        /// </summary>
        /// <remarks>
        /// <para>这具有与 <see cref="SetNativeBackgroundWatermark"/> 和 <see cref="SetNativeBackgroundImage"/> 相同的限制.
        /// 在使用该方法之前，请确保了解这些限制。</para>
        /// </remarks>
        /// <param name="image">要绘制的图像。如果为null，则将删除所有现有图像。</param>
        public void SetNativeBackgroundTiledImage(Image image) {
            NativeMethods.SetBackgroundImage(this, image, false, true, 0, 0);
        }

        /// <summary>
        /// 设置将在此列表视图中显示的对象集合。
        /// </summary>
        /// <remark>可以从后台线程安全地调用此方法。</remark>
        /// <remarks>该列表将立即更新</remarks>
        /// <param name="collection">The objects to be displayed</param>
        public virtual void SetObjects(IEnumerable collection) {
            this.SetObjects(collection, false);
        }

        /// <summary>
        /// 设置将在此列表视图中显示的对象集合。
        /// </summary>
        /// <remark>可以从后台线程安全地调用此方法.</remark>
        /// <remarks>该列表将立即更新</remarks>
        /// <param name="collection">The objects to be displayed</param>
        /// <param name="preserveState">是否应尽可能保留列表的状态。</param>
        public virtual void SetObjects(IEnumerable collection, bool preserveState) {
            if (this.InvokeRequired) {
                this.Invoke((MethodInvoker)delegate { this.SetObjects(collection, preserveState); });
                return;
            }

            // Give the world a chance to cancel or change the assigned collection
            ItemsChangingEventArgs args = new ItemsChangingEventArgs(this.objects, collection);
            this.OnItemsChanging(args);
            if (args.Canceled)
                return;
            collection = args.NewObjects;

            // If we own the current list and they change to another list, we don't own it anymore
            if (this.isOwnerOfObjects && !ReferenceEquals(this.objects, collection))
                this.isOwnerOfObjects = false;
            this.objects = collection;
            this.BuildList(preserveState);

            // Tell the world that the list has changed
            this.UpdateNotificationSubscriptions(this.objects);
            this.OnItemsChanged(new ItemsChangedEventArgs());
        }

        /// <summary>
        ///将给定的模型对象更新到ListView中。
        ///如果该模型尚不存在，则会添加该模型。
        /// </summary>
        /// <param name="modelObject">The model to be updated</param>
        /// <remarks>
        /// <para>
        /// See <see cref="RemoveObjects(ICollection)"/> for more details
        /// </para>
        /// <para>此方法是线程安全的。</para>
        /// <para>此方法将导致重新排序列表。</para>
        /// <para>此方法仅适用于ObjectListViews和FastObjectListViews。</para>
        /// </remarks>
        public virtual void UpdateObject(object modelObject) {
            if (this.InvokeRequired)
                this.Invoke((MethodInvoker)delegate() { this.UpdateObject(modelObject); });
            else
                this.UpdateObjects(new object[] { modelObject });
        }

        /// <summary>
        /// 更新与给定对象相等的预先存在的模型。
        ///如果控件中不存在任何模型，则会添加它们。
        /// </summary>
        /// <param name="modelObjects">Collection of objects to be updated/added</param>
        /// <remarks>
        /// <para>此方法将导致重新排序列表。</para>
        /// <para>null值将被静默忽略。</para>
        /// <para>此方法是线程安全的。</para>
        /// <para>此方法仅适用于ObjectListViews和FastObjectListViews。</para>
        /// </remarks>
        public virtual void UpdateObjects(ICollection modelObjects) {
            if (this.InvokeRequired) {
                this.Invoke((MethodInvoker)delegate() { this.UpdateObjects(modelObjects); });
                return;
            }
            if (modelObjects == null || modelObjects.Count == 0)
                return;

            this.BeginUpdate();
            try {
                this.UnsubscribeNotifications(modelObjects);

                ArrayList objectsToAdd = new ArrayList();

                this.TakeOwnershipOfObjects();
                ArrayList ourObjects = ObjectListView.EnumerableToArray(this.Objects, false);
                foreach (object modelObject in modelObjects) {
                    if (modelObject != null) {
                        int i = ourObjects.IndexOf(modelObject);
                        if (i < 0) 
                            objectsToAdd.Add(modelObject);
                        else {
                            ourObjects[i] = modelObject;
                            OLVListItem olvi = this.ModelToItem(modelObject);
                            if (olvi != null) {
                                olvi.RowObject = modelObject;
                                this.RefreshItem(olvi);
                            }
                        }
                    }
                }
                this.PostProcessRows();

                this.AddObjects(objectsToAdd);

                // Tell the world that the list has changed
                this.SubscribeNotifications(modelObjects);
                this.OnItemsChanged(new ItemsChangedEventArgs());
            }
            finally {
                this.EndUpdate();
            }
        }

        /// <summary>
        /// 更改当前模型对象上对INotifyPropertyChanged事件的任何订阅，
        /// 以便我们不再侦听旧模型上的事件，而侦听给定集合上的事件。
        /// </summary>
        /// <remarks>This does nothing if UseNotifyPropertyChanged is false.</remarks>
        /// <param name="collection"></param>
        protected virtual void UpdateNotificationSubscriptions(IEnumerable collection) {
            if (!this.UseNotifyPropertyChanged)
                return;

            // We could calculate a symmetric difference between the old models and the new models
            // except that we don't have the previous models at this point.

            this.UnsubscribeNotifications(null);
            this.SubscribeNotifications(collection ?? this.Objects);
        }

        /// <summary>
        /// 获取或设置ObjectListView是否应订阅它给定的模型对象上的INotifyPropertyChanged事件。
        /// </summary>
        /// <remarks>
        /// <para>
        /// 这应该在调用SetObjects()之前设置。如果将其设置为False，ObjectListView将取消订阅所有当前模型对象。
        /// </para>
        /// <para>如果在虚拟列表上将其设置为true，ObjectListView将遍历列表中尝试订阅更改通知的所有对象。
        /// 如果您的虚拟列表中有10,000,000个项目，这可能需要一些时间。</para>
        /// </remarks>
        [Category("ObjectListView"),
        Description("Should ObjectListView listen for property changed events on the model objects?"),
        DefaultValue(false)]
        public bool UseNotifyPropertyChanged {
            get { return this.useNotifyPropertyChanged; }
            set {
                if (this.useNotifyPropertyChanged == value)
                    return;
                this.useNotifyPropertyChanged = value;
                if (value)
                    this.SubscribeNotifications(this.Objects);
                else
                    this.UnsubscribeNotifications(null);
            }
        }
        private bool useNotifyPropertyChanged;

        /// <summary>
        /// Subscribe to INotifyPropertyChanges on the given collection of objects.
        /// </summary>
        /// <param name="models"></param>
        protected void SubscribeNotifications(IEnumerable models) {
            if (!this.UseNotifyPropertyChanged || models == null)
                return;
            foreach (object x in models) {
                INotifyPropertyChanged notifier = x as INotifyPropertyChanged;
                if (notifier != null && !subscribedModels.ContainsKey(notifier)) {
                    notifier.PropertyChanged += HandleModelOnPropertyChanged;
                    subscribedModels[notifier] = notifier;
                }
            }
        }

        /// <summary>
        /// Unsubscribe from INotifyPropertyChanges on the given collection of objects.
        /// If the given collection is null, unsubscribe from all current subscriptions
        /// </summary>
        /// <param name="models"></param>
        protected void UnsubscribeNotifications(IEnumerable models) {
            if (models == null) {
                foreach (INotifyPropertyChanged notifier in this.subscribedModels.Keys) {
                    notifier.PropertyChanged -= HandleModelOnPropertyChanged;
                }
                subscribedModels = new Hashtable();
            } else {
                foreach (object x in models) {
                    INotifyPropertyChanged notifier = x as INotifyPropertyChanged;
                    if (notifier != null) {
                        notifier.PropertyChanged -= HandleModelOnPropertyChanged;
                        subscribedModels.Remove(notifier);
                    }
                }
            }
        }

        private void HandleModelOnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs) {
            // System.Diagnostics.Debug.WriteLine(String.Format("PropertyChanged: '{0}' on '{1}", propertyChangedEventArgs.PropertyName, sender));
            this.RefreshObject(sender);
        }

        private Hashtable subscribedModels = new Hashtable();

        #endregion

        #region Save/Restore State

        /// <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 type="bullet">
        /// <item><description>current view (i.e. Details, Tile, Large Icon...)</description></item>
        /// <item><description>sort column and direction</description></item>
        /// <item><description>column order</description></item>
        /// <item><description>column widths</description></item>
        /// <item><description>column visibility</description></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 virtual 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.PrimarySortColumn != null)
                olvState.SortColumn = this.AllColumns.IndexOf(this.PrimarySortColumn);
            olvState.LastSortOrder = this.PrimarySortOrder;
            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
            using (MemoryStream ms = new MemoryStream()) {
                BinaryFormatter serializer = new BinaryFormatter();
                serializer.AssemblyFormat = FormatterAssemblyStyle.Simple;
                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 virtual bool RestoreState(byte[] state) {
            using (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 == null || olvState.NumberOfColumns != this.AllColumns.Count)
                    return false;
                if (olvState.SortColumn == -1) {
                    this.PrimarySortColumn = null;
                    this.PrimarySortOrder = SortOrder.None;
                } else {
                    this.PrimarySortColumn = this.AllColumns[olvState.SortColumn];
                    this.PrimarySortOrder = 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];
                }
// ReSharper disable RedundantCheckBeforeAssignment
                if (olvState.IsShowingGroups != this.ShowGroups)
// ReSharper restore RedundantCheckBeforeAssignment
                    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
        {
// ReSharper disable NotAccessedField.Global
            public int VersionNumber = 1;
// ReSharper restore NotAccessedField.Global
            public int NumberOfColumns = 1;
            public View CurrentView;
            public int SortColumn = -1;
            public bool IsShowingGroups;
            public SortOrder LastSortOrder = SortOrder.None;
// ReSharper disable FieldCanBeMadeReadOnly.Global
            public ArrayList ColumnIsVisible = new ArrayList();
            public ArrayList ColumnDisplayIndicies = new ArrayList();
            public ArrayList ColumnWidths = new ArrayList();
// ReSharper restore FieldCanBeMadeReadOnly.Global
        }

        #endregion

        #region Event handlers

        /// <summary>
        /// The application is idle. Trigger a SelectionChanged event.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected virtual void HandleApplicationIdle(object sender, EventArgs e) {
            // Remove the handler before triggering the event
            Application.Idle -= new EventHandler(HandleApplicationIdle);
            this.hasIdleHandler = false;

            this.OnSelectionChanged(new EventArgs());
        }

        /// <summary>
        /// The application is idle. Handle the column resizing event.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected virtual void HandleApplicationIdleResizeColumns(object sender, EventArgs e) {
            // Remove the handler before triggering the event
            Application.Idle -= new EventHandler(this.HandleApplicationIdleResizeColumns);
            this.hasResizeColumnsHandler = false;

            this.ResizeFreeSpaceFillingColumns();
        }

        /// <summary>
        /// Handle the BeginScroll listview notification
        /// </summary>
        /// <param name="m"></param>
        /// <returns>True if the event was completely handled</returns>
        protected virtual bool HandleBeginScroll(ref Message m) {
            //System.Diagnostics.Debug.WriteLine("LVN_BEGINSCROLL");

            NativeMethods.NMLVSCROLL nmlvscroll = (NativeMethods.NMLVSCROLL)m.GetLParam(typeof(NativeMethods.NMLVSCROLL));
            if (nmlvscroll.dx != 0) {
                int scrollPositionH = NativeMethods.GetScrollPosition(this, true);
                ScrollEventArgs args = new ScrollEventArgs(ScrollEventType.EndScroll, scrollPositionH - nmlvscroll.dx, scrollPositionH, ScrollOrientation.HorizontalScroll);
                this.OnScroll(args);

                // Force any empty list msg to redraw when the list is scrolled horizontally
                if (this.GetItemCount() == 0)
                    this.Invalidate();
            }
            if (nmlvscroll.dy != 0) {
                int scrollPositionV = NativeMethods.GetScrollPosition(this, false);
                ScrollEventArgs args = new ScrollEventArgs(ScrollEventType.EndScroll, scrollPositionV - nmlvscroll.dy, scrollPositionV, ScrollOrientation.VerticalScroll);
                this.OnScroll(args);
            }

            return false;
        }

        /// <summary>
        /// Handle the EndScroll listview notification
        /// </summary>
        /// <param name="m"></param>
        /// <returns>True if the event was completely handled</returns>
        protected virtual bool HandleEndScroll(ref Message m) {
            //System.Diagnostics.Debug.WriteLine("LVN_BEGINSCROLL");

            // There is a bug in ListView under XP that causes the gridlines to be incorrectly scrolled
            // when the left button is clicked to scroll. This is supposedly documented at
            // KB 813791, but I couldn't find it anywhere. You can follow this thread to see the discussion
            // http://www.ureader.com/msg/1484143.aspx

            if (!ObjectListView.IsVistaOrLater && ObjectListView.IsLeftMouseDown && this.GridLines) {
                this.Invalidate();
                this.Update();
            }

            return false;
        }

        /// <summary>
        /// Handle the LinkClick listview notification
        /// </summary>
        /// <param name="m"></param>
        /// <returns>True if the event was completely handled</returns>
        protected virtual bool HandleLinkClick(ref Message m) {
            //System.Diagnostics.Debug.WriteLine("HandleLinkClick");

            NativeMethods.NMLVLINK nmlvlink = (NativeMethods.NMLVLINK)m.GetLParam(typeof(NativeMethods.NMLVLINK));

            // Find the group that was clicked and trigger an event
            foreach (OLVGroup x in this.OLVGroups) {
                if (x.GroupId == nmlvlink.iSubItem) {
                    this.OnGroupTaskClicked(new GroupTaskClickedEventArgs(x));
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// The cell tooltip control wants information about the tool tip that it should show.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected virtual void HandleCellToolTipShowing(object sender, ToolTipShowingEventArgs e) {
            this.BuildCellEvent(e, this.PointToClient(Cursor.Position));
            if (e.Item != null) {
                e.Text = this.GetCellToolTip(e.ColumnIndex, e.RowIndex);
                this.OnCellToolTip(e);
            }
        }

        /// <summary>
        /// Allow the HeaderControl to call back into HandleHeaderToolTipShowing without making that method public
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        internal void HeaderToolTipShowingCallback(object sender, ToolTipShowingEventArgs e) {
            this.HandleHeaderToolTipShowing(sender, e);
        }

        /// <summary>
        /// The header tooltip control wants information about the tool tip that it should show.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected virtual void HandleHeaderToolTipShowing(object sender, ToolTipShowingEventArgs e) {
            e.ColumnIndex = this.HeaderControl.ColumnIndexUnderCursor;
            if (e.ColumnIndex < 0)
                return;

            e.RowIndex = -1;
            e.Model = null;
            e.Column = this.GetColumn(e.ColumnIndex);
            e.Text = this.GetHeaderToolTip(e.ColumnIndex);
            e.ListView = this;
            this.OnHeaderToolTip(e);
        }

        /// <summary>
        /// Event handler for the column click event
        /// </summary>
        protected virtual void HandleColumnClick(object sender, ColumnClickEventArgs e) {
            if (!this.PossibleFinishCellEditing())
                return;

            // Toggle the sorting direction on successive clicks on the same column
            if (this.PrimarySortColumn != null && e.Column == this.PrimarySortColumn.Index)
                this.PrimarySortOrder = (this.PrimarySortOrder == SortOrder.Descending ? SortOrder.Ascending : SortOrder.Descending);
            else
                this.PrimarySortOrder = SortOrder.Ascending;

            this.BeginUpdate();
            try {
                this.Sort(e.Column);
            } finally {
                this.EndUpdate();
            }
        }

        #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)
        {
           
            // System.Diagnostics.Debug.WriteLine(m.Msg);
            switch (m.Msg) {
                case 2: // WM_DESTROY
                    if (!this.HandleDestroy(ref m))
                        base.WndProc(ref m);
                    break;
                //case 0x14: // WM_ERASEBKGND
                //    Can't do anything here since, when the control is double buffered, anything
                //    done here is immediately over-drawn
                //    break;
                case 0x0F: // WM_PAINT
                    if (!this.HandlePaint(ref m))
                        base.WndProc(ref m);
                    break;
                case 0x46: // WM_WINDOWPOSCHANGING
                    if (this.PossibleFinishCellEditing() && !this.HandleWindowPosChanging(ref m))
                        base.WndProc(ref m);
                    break;
                case 0x4E: // WM_NOTIFY
                    if (!this.HandleNotify(ref m))
                        base.WndProc(ref m);
                    break;
                case 0x0100: // WM_KEY_DOWN
                    if (!this.HandleKeyDown(ref m))
                        base.WndProc(ref m);
                    break;
                case 0x0102: // WM_CHAR
                    if (!this.HandleChar(ref m))
                        base.WndProc(ref m);
                    break;
                case 0x0200: // WM_MOUSEMOVE
                    if (!this.HandleMouseMove(ref m))
                        base.WndProc(ref m);
                    break;
                case 0x0201: // WM_LBUTTONDOWN
                    if (this.PossibleFinishCellEditing() && !this.HandleLButtonDown(ref m))
                        base.WndProc(ref m);
                    break;
                case 0x202:  // WM_LBUTTONUP
                    if (this.PossibleFinishCellEditing() && !this.HandleLButtonUp(ref m))
                        base.WndProc(ref m);
                    break;
                case 0x0203: // WM_LBUTTONDBLCLK
                    if (this.PossibleFinishCellEditing() && !this.HandleLButtonDoubleClick(ref m))
                        base.WndProc(ref m);
                    break;
                case 0x0204: // WM_RBUTTONDOWN
                    if (this.PossibleFinishCellEditing() && !this.HandleRButtonDown(ref m))
                        base.WndProc(ref m);
                    break;
                case 0x0206: // WM_RBUTTONDBLCLK
                    if (this.PossibleFinishCellEditing() && !this.HandleRButtonDoubleClick(ref m))
                        base.WndProc(ref m);
                    break;
                case 0x204E: // WM_REFLECT_NOTIFY
                    if (!this.HandleReflectNotify(ref m))
                        base.WndProc(ref m);
                    break;
                case 0x114: // WM_HSCROLL:
                case 0x115: // WM_VSCROLL:
                    //System.Diagnostics.Debug.WriteLine("WM_VSCROLL");
                    if (this.PossibleFinishCellEditing())
                        base.WndProc(ref m);
                    break;
                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;
                case 0x1000 + 18: // LVM_HITTEST:
                    //System.Diagnostics.Debug.WriteLine("LVM_HITTEST");
                    if (this.skipNextHitTest) {
                        //System.Diagnostics.Debug.WriteLine("SKIPPING LVM_HITTEST");
                        this.skipNextHitTest = false;
                    } else {
                        base.WndProc(ref m);
                    }
                    break;
                default:
                    base.WndProc(ref m);
                    break;
            }
        }

        /// <summary>
        /// Handle the search for item m if possible.
        /// </summary>
        /// <param name="m">The m to be processed</param>
        /// <returns>bool to indicate if the msg has been handled</returns>
        protected virtual bool HandleChar(ref Message m) {

            // Trigger a normal KeyPress event, which listeners can handle if they want.
            // Handling the event stops ObjectListView's fancy search-by-typing.
            if (this.ProcessKeyEventArgs(ref m))
                return true;

            const int MILLISECONDS_BETWEEN_KEYPRESSES = 1000;

            // What character did the user type and was it part of a longer string?
            char character = (char)m.WParam.ToInt32(); //TODO: Will this work on 64 bit or MBCS?
            if (character == (char)Keys.Back) {
                // Backspace forces the next key to be considered the start of a new search
                this.timeLastCharEvent = 0;
                return true;
            }

            if (System.Environment.TickCount < (this.timeLastCharEvent + MILLISECONDS_BETWEEN_KEYPRESSES))
                this.lastSearchString += character;
            else
                this.lastSearchString = character.ToString(CultureInfo.InvariantCulture);

            // If this control is showing checkboxes, we want to ignore single space presses,
            // since they are used to toggle the selected checkboxes.
            if (this.CheckBoxes && this.lastSearchString == " ") {
                this.timeLastCharEvent = 0;
                return true;
            }

            // Where should the search start?
            int start = 0;
            ListViewItem focused = this.FocusedItem;
            if (focused != null) {
                start = this.GetDisplayOrderOfItemIndex(focused.Index);

                // If the user presses a single key, we search from after the focused item,
                // being careful not to march past the end of the list
                if (this.lastSearchString.Length == 1) {
                    start += 1;
                    if (start == this.GetItemCount())
                        start = 0;
                }
            }

            // Give the world a chance to fiddle with or completely avoid the searching process
            BeforeSearchingEventArgs args = new BeforeSearchingEventArgs(this.lastSearchString, start);
            this.OnBeforeSearching(args);
            if (args.Canceled)
                return true;

            // The parameters of the search may have been changed
            string searchString = args.StringToFind;
            start = args.StartSearchFrom;

            // Do the actual search
            int found = this.FindMatchingRow(searchString, start, SearchDirectionHint.Down);
            if (found >= 0) {
                // Select and focus on the found item
                this.BeginUpdate();
                try {
                    this.SelectedIndices.Clear();
                    OLVListItem lvi = this.GetNthItemInDisplayOrder(found);
                    if (lvi != null) {
                        if (lvi.Enabled)
                            lvi.Selected = true;
                        lvi.Focused = true;
                        this.EnsureVisible(lvi.Index);
                    }
                } finally {
                    this.EndUpdate();
                }
            }

            // Tell the world that a search has occurred
            AfterSearchingEventArgs args2 = new AfterSearchingEventArgs(searchString, found);
            this.OnAfterSearching(args2);
            if (!args2.Handled) {
                if (found < 0)
                    System.Media.SystemSounds.Beep.Play();
            }

            // When did this event occur?
            this.timeLastCharEvent = System.Environment.TickCount;
            return true;
        }
        private int timeLastCharEvent;
        private string lastSearchString;

        /// <summary>
        /// The user wants to see the context menu.
        /// </summary>
        /// <param name="m">The windows m</param>
        /// <returns>A bool indicating if this m has been handled</returns>
        /// <remarks>
        /// We want to ignore context menu requests that are triggered by right clicks on the header
        /// </remarks>
        protected virtual 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 (m.LParam == this.minusOne)
                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.HeaderControl.Handle)
                return false;

            // OK. Looks like a right click in the header
            if (!this.PossibleFinishCellEditing())
                return true;

            int columnIndex = this.HeaderControl.ColumnIndexUnderCursor;
            return this.HandleHeaderRightClick(columnIndex);
        }
        readonly IntPtr minusOne = new IntPtr(-1);

        /// <summary>
        /// Handle the Custom draw series of notifications
        /// </summary>
        /// <param name="m">The message</param>
        /// <returns>True if the message has been handled</returns>
        protected virtual bool HandleCustomDraw(ref Message m) {
            const int CDDS_PREPAINT = 1;
            const int CDDS_POSTPAINT = 2;
            const int CDDS_PREERASE = 3;
            const int CDDS_POSTERASE = 4;
            //const int CDRF_NEWFONT = 2;
            //const int CDRF_SKIPDEFAULT = 4;
            const int CDDS_ITEM = 0x00010000;
            const int CDDS_SUBITEM = 0x00020000;
            const int CDDS_ITEMPREPAINT = (CDDS_ITEM | CDDS_PREPAINT);
            const int CDDS_ITEMPOSTPAINT = (CDDS_ITEM | CDDS_POSTPAINT);
            const int CDDS_ITEMPREERASE = (CDDS_ITEM | CDDS_PREERASE);
            const int CDDS_ITEMPOSTERASE = (CDDS_ITEM | CDDS_POSTERASE);
            const int CDDS_SUBITEMPREPAINT = (CDDS_SUBITEM | CDDS_ITEMPREPAINT);
            const int CDDS_SUBITEMPOSTPAINT = (CDDS_SUBITEM | CDDS_ITEMPOSTPAINT);
            const int CDRF_NOTIFYPOSTPAINT = 0x10;
            //const int CDRF_NOTIFYITEMDRAW = 0x20;
            //const int CDRF_NOTIFYSUBITEMDRAW = 0x20; // same value as above!
            const int CDRF_NOTIFYPOSTERASE = 0x40;

            // There is a bug in owner drawn virtual lists which causes lots of custom draw messages
            // to be sent to the control *outside* of a WmPaint event. AFAIK, these custom draw events
            // are spurious and only serve to make the control flicker annoyingly.
            // So, we ignore messages that are outside of a paint event.
            if (!this.isInWmPaintEvent)
                return true;

            // One more complication! Sometimes with owner drawn virtual lists, the act of drawing
            // the overlays triggers a second attempt to paint the control -- which makes an annoying
            // flicker. So, we only do the custom drawing once per WmPaint event.
            if (!this.shouldDoCustomDrawing)
                return true;

            NativeMethods.NMLVCUSTOMDRAW nmcustomdraw = (NativeMethods.NMLVCUSTOMDRAW)m.GetLParam(typeof(NativeMethods.NMLVCUSTOMDRAW));
            //System.Diagnostics.Debug.WriteLine(String.Format("cd: {0:x}, {1}, {2}", nmcustomdraw.nmcd.dwDrawStage, nmcustomdraw.dwItemType, nmcustomdraw.nmcd.dwItemSpec));

            // Ignore drawing of group items
            if (nmcustomdraw.dwItemType == 1) {
                // This is the basis of an idea about how to owner draw group headers

                //nmcustomdraw.clrText = ColorTranslator.ToWin32(Color.DeepPink);
                //nmcustomdraw.clrFace = ColorTranslator.ToWin32(Color.DeepPink);
                //nmcustomdraw.clrTextBk = ColorTranslator.ToWin32(Color.DeepPink);
                //Marshal.StructureToPtr(nmcustomdraw, m.LParam, false);
                //using (Graphics g = Graphics.FromHdc(nmcustomdraw.nmcd.hdc)) {
                //    g.DrawRectangle(Pens.Red, Rectangle.FromLTRB(nmcustomdraw.rcText.left, nmcustomdraw.rcText.top, nmcustomdraw.rcText.right, nmcustomdraw.rcText.bottom));
                //}
                //m.Result = (IntPtr)((int)m.Result | CDRF_SKIPDEFAULT);
                return true;
            }

            switch (nmcustomdraw.nmcd.dwDrawStage) {
                case CDDS_PREPAINT:
                    //System.Diagnostics.Debug.WriteLine("CDDS_PREPAINT");
                    // Remember which items were drawn during this paint cycle
                    if (this.prePaintLevel == 0)
                        this.drawnItems = new List<OLVListItem>();

                    // If there are any items, we have to wait until at least one has been painted
                    // before we draw the overlays. If there aren't any items, there will never be any
                    // item paint events, so we can draw the overlays whenever
                    this.isAfterItemPaint = (this.GetItemCount() == 0);
                    this.prePaintLevel++;
                    base.WndProc(ref m);

                    // Make sure that we get postpaint notifications
                    m.Result = (IntPtr)((int)m.Result | CDRF_NOTIFYPOSTPAINT | CDRF_NOTIFYPOSTERASE);
                    return true;

                case CDDS_POSTPAINT:
                    //System.Diagnostics.Debug.WriteLine("CDDS_POSTPAINT");
                    this.prePaintLevel--;

                    // When in group view, we have two problems. On XP, the control sends
                    // a whole heap of PREPAINT/POSTPAINT messages before drawing any items.
                    // We have to wait until after the first item paint before we draw overlays.
                    // On Vista, we have a different problem. On Vista, the control nests calls
                    // to PREPAINT and POSTPAINT. We only want to draw overlays on the outermost
                    // POSTPAINT.
                    if (this.prePaintLevel == 0 && (this.isMarqueSelecting || this.isAfterItemPaint)) {
                        this.shouldDoCustomDrawing = false;

                        // Draw our overlays after everything has been drawn
                        using (Graphics g = Graphics.FromHdc(nmcustomdraw.nmcd.hdc)) {
                            this.DrawAllDecorations(g, this.drawnItems);
                        }
                    }
                    break;

                case CDDS_ITEMPREPAINT:
                    //System.Diagnostics.Debug.WriteLine("CDDS_ITEMPREPAINT");

                    // When in group view on XP, the control send a whole heap of PREPAINT/POSTPAINT
                    // messages before drawing any items.
                    // We have to wait until after the first item paint before we draw overlays
                    this.isAfterItemPaint = true;

                    // This scheme of catching custom draw msgs works fine, except
                    // for Tile view. Something in .NET's handling of Tile view causes lots
                    // of invalidates and erases. So, we just ignore completely
                    // .NET's handling of Tile view and let the underlying control
                    // do its stuff. Strangely, if the Tile view is
                    // completely owner drawn, those erasures don't happen.
                    if (this.View == View.Tile) {
                        if (this.OwnerDraw && this.ItemRenderer != null)
                            base.WndProc(ref m);
                    } else {
                        base.WndProc(ref m);
                    }

                    m.Result = (IntPtr)((int)m.Result | CDRF_NOTIFYPOSTPAINT | CDRF_NOTIFYPOSTERASE);
                    return true;

                case CDDS_ITEMPOSTPAINT:
                    //System.Diagnostics.Debug.WriteLine("CDDS_ITEMPOSTPAINT");
                    // Remember which items have been drawn so we can draw any decorations for them
                    // once all other painting is finished
                    if (this.Columns.Count > 0) {
                        OLVListItem olvi = this.GetItem((int)nmcustomdraw.nmcd.dwItemSpec);
                        if (olvi != null)
                            this.drawnItems.Add(olvi);
                    }
                    break;

                case CDDS_SUBITEMPREPAINT:
                    //System.Diagnostics.Debug.WriteLine(String.Format("CDDS_SUBITEMPREPAINT ({0},{1})", (int)nmcustomdraw.nmcd.dwItemSpec, nmcustomdraw.iSubItem));

                    // There is a bug in the .NET framework which appears when column 0 of an owner drawn listview
                    // is dragged to another column position.
                    // The bounds calculation always returns the left edge of column 0 as being 0.
                    // The effects of this bug become apparent
                    // when the listview is scrolled horizontally: the control can think that column 0
                    // is no longer visible (the horizontal scroll position is subtracted from the bounds, giving a
                    // rectangle that is offscreen). In those circumstances, column 0 is not redraw because
                    // the control thinks it is not visible and so does not trigger a DrawSubItem event.

                    // To fix this problem, we have to detected the situation -- owner drawing column 0 in any column except 0 --
                    // trigger our own DrawSubItem, and then prevent the default processing from occuring.

                    // Are we owner drawing column 0 when it's in any column except 0?
                    if (!this.OwnerDraw) 
                         return false;

                    int columnIndex = nmcustomdraw.iSubItem;
                    if (columnIndex != 0)
                        return false;

                    int displayIndex = this.Columns[0].DisplayIndex;
                    if (displayIndex == 0)
                        return false;

                    int rowIndex = (int)nmcustomdraw.nmcd.dwItemSpec;
                    OLVListItem item = this.GetItem(rowIndex);
                    if (item == null)
                        return false;

                    // OK. We have the error condition, so lets do what the .NET framework should do.
                    // Trigger an event to draw column 0 when it is not at display index 0
                    using (Graphics g = Graphics.FromHdc(nmcustomdraw.nmcd.hdc)) {

                        // Correctly calculate the bounds of cell 0
                        Rectangle r = item.GetSubItemBounds(0);

                        // We can hardcode "0" here since we know we are only doing this for column 0
                        DrawListViewSubItemEventArgs args = new DrawListViewSubItemEventArgs(g, r, item, item.SubItems[0], rowIndex, 0,
                            this.Columns[0], (ListViewItemStates)nmcustomdraw.nmcd.uItemState);
                        this.OnDrawSubItem(args);

                        // If the event handler wants to do the default processing (i.e. DrawDefault = true), we are stuck.
                        // There is no way we can force the default drawing because of the bug in .NET we are trying to get around.
                        System.Diagnostics.Trace.Assert(!args.DrawDefault, "Default drawing is impossible in this situation");
                    }
                    m.Result = (IntPtr)4;

                    return true;

                case CDDS_SUBITEMPOSTPAINT:
                    //System.Diagnostics.Debug.WriteLine("CDDS_SUBITEMPOSTPAINT");
                    break;

                // I have included these stages, but it doesn't seem that they are sent for ListViews.
                // http://www.tech-archive.net/Archive/VC/microsoft.public.vc.mfc/2006-08/msg00220.html

                case CDDS_PREERASE:
                    //System.Diagnostics.Debug.WriteLine("CDDS_PREERASE");
                    break;

                case CDDS_POSTERASE:
                    //System.Diagnostics.Debug.WriteLine("CDDS_POSTERASE");
                    break;

                case CDDS_ITEMPREERASE:
                    //System.Diagnostics.Debug.WriteLine("CDDS_ITEMPREERASE");
                    break;

                case CDDS_ITEMPOSTERASE:
                    //System.Diagnostics.Debug.WriteLine("CDDS_ITEMPOSTERASE");
                    break;
            }

            return false;
        }
        bool isAfterItemPaint;
        List<OLVListItem> drawnItems;

        /// <summary>
        /// Handle the underlying control being destroyed
        /// </summary>
        /// <param name="m"></param>
        /// <returns></returns>
        protected virtual bool HandleDestroy(ref Message m) {
            //System.Diagnostics.Debug.WriteLine(String.Format("WM_DESTROY: Disposing={0}, IsDisposed={1}, VirtualMode={2}", Disposing, IsDisposed, VirtualMode));

            // Recreate the header control when the listview control is destroyed
            this.headerControl = null;

            // When the underlying control is destroyed, we need to recreate and reconfigure its tooltip
            if (this.cellToolTip != null) {
                this.cellToolTip.PushSettings();
                this.BeginInvoke((MethodInvoker)delegate {
                    this.UpdateCellToolTipHandle();
                    this.cellToolTip.PopSettings();
                });
            }

            return false;
        }

        /// <summary>
        /// Handle the search for item m if possible.
        /// </summary>
        /// <param name="m">The m to be processed</param>
        /// <returns>bool to indicate if the msg has been handled</returns>
        protected virtual bool HandleFindItem(ref Message m) {
            // NOTE: As far as I can see, this message is never actually sent to the control, making this
            // method redundant!

            const int LVFI_STRING = 0x0002;

            NativeMethods.LVFINDINFO findInfo = (NativeMethods.LVFINDINFO)m.GetLParam(typeof(NativeMethods.LVFINDINFO));

            // We can only handle string searches
            if ((findInfo.flags & LVFI_STRING) != LVFI_STRING)
                return false;

            int start = m.WParam.ToInt32();
            m.Result = (IntPtr)this.FindMatchingRow(findInfo.psz, start, SearchDirectionHint.Down);
            return true;
        }

        /// <summary>
        /// Find the first row after the given start in which the text value in the
        /// comparison column begins with the given text. The comparison column is column 0,
        /// unless IsSearchOnSortColumn is true, in which case the current sort column is used.
        /// </summary>
        /// <param name="text">The text to be prefix matched</param>
        /// <param name="start">The index of the first row to consider</param>
        /// <param name="direction">Which direction should be searched?</param>
        /// <returns>The index of the first row that matched, or -1</returns>
        /// <remarks>The text comparison is a case-insensitive, prefix match. The search will
        /// search the every row until a match is found, wrapping at the end if needed.</remarks>
        public virtual int FindMatchingRow(string text, int start, SearchDirectionHint direction) {
            // We also can't do anything if we don't have data
            int rowCount = this.GetItemCount();
            if (rowCount == 0)
                return -1;

            // Which column are we going to use for our comparing?
            OLVColumn column = this.GetColumn(0);
            if (this.IsSearchOnSortColumn && this.View == View.Details && this.PrimarySortColumn != null)
                column = this.PrimarySortColumn;

            // Do two searches if necessary to find a match. The second search is the wrap-around part of searching
            int i;
            if (direction == SearchDirectionHint.Down) {
                i = this.FindMatchInRange(text, start, rowCount - 1, column);
                if (i == -1 && start > 0)
                    i = this.FindMatchInRange(text, 0, start - 1, column);
            } else {
                i = this.FindMatchInRange(text, start, 0, column);
                if (i == -1 && start != rowCount)
                    i = this.FindMatchInRange(text, rowCount - 1, start + 1, column);
            }

            return i;
        }

        /// <summary>
        /// Find the first row in the given range of rows that prefix matches the string value of the given column.
        /// </summary>
        /// <param name="text"></param>
        /// <param name="first"></param>
        /// <param name="last"></param>
        /// <param name="column"></param>
        /// <returns>The index of the matched row, or -1</returns>
        protected virtual int FindMatchInRange(string text, int first, int last, OLVColumn column) {
            if (first <= last) {
                for (int i = first; i <= last; i++) {
                    string data = column.GetStringValue(this.GetNthItemInDisplayOrder(i).RowObject);
                    if (data.StartsWith(text, StringComparison.CurrentCultureIgnoreCase))
                        return i;
                }
            } else {
                for (int i = first; i >= last; i--) {
                    string data = column.GetStringValue(this.GetNthItemInDisplayOrder(i).RowObject);
                    if (data.StartsWith(text, StringComparison.CurrentCultureIgnoreCase))
                        return i;
                }
            }

            return -1;
        }

        /// <summary>
        /// Handle the Group Info series of notifications
        /// </summary>
        /// <param name="m">The message</param>
        /// <returns>True if the message has been handled</returns>
        protected virtual bool HandleGroupInfo(ref Message m)
        {
            NativeMethods.NMLVGROUP nmlvgroup = (NativeMethods.NMLVGROUP)m.GetLParam(typeof(NativeMethods.NMLVGROUP));

            //System.Diagnostics.Debug.WriteLine(String.Format("group: {0}, old state: {1}, new state: {2}",
            //    nmlvgroup.iGroupId, OLVGroup.StateToString(nmlvgroup.uOldState), OLVGroup.StateToString(nmlvgroup.uNewState)));

            // Ignore state changes that aren't related to selection, focus or collapsedness
            const uint INTERESTING_STATES = (uint) (GroupState.LVGS_COLLAPSED | GroupState.LVGS_FOCUSED | GroupState.LVGS_SELECTED);
            if ((nmlvgroup.uOldState & INTERESTING_STATES) == (nmlvgroup.uNewState & INTERESTING_STATES))
                return false;

            foreach (OLVGroup group in this.OLVGroups) {
                if (group.GroupId == nmlvgroup.iGroupId) {
                    GroupStateChangedEventArgs args = new GroupStateChangedEventArgs(group, (GroupState)nmlvgroup.uOldState, (GroupState)nmlvgroup.uNewState);
                    this.OnGroupStateChanged(args);
                    break;
                }
            }

            return false;
        }

        //private static string StateToString(uint state)
        //{
        //    if (state == 0)
        //        return Enum.GetName(typeof(GroupState), 0);

        //    List<string> names = new List<string>();
        //    foreach (int value in Enum.GetValues(typeof(GroupState)))
        //    {
        //        if (value != 0 && (state & value) == value)
        //        {
        //            names.Add(Enum.GetName(typeof(GroupState), value));
        //        }
        //    }
        //    return names.Count == 0 ? state.ToString("x") : String.Join("|", names.ToArray());
        //}

        /// <summary>
        /// Handle a key down message
        /// </summary>
        /// <param name="m"></param>
        /// <returns>True if the msg has been handled</returns>
        protected virtual bool HandleKeyDown(ref Message m) {

            // If this is a checkbox list, toggle the selected rows when the user presses Space
            if (this.CheckBoxes && m.WParam.ToInt32() == (int)Keys.Space && this.SelectedIndices.Count > 0) {
                this.ToggleSelectedRowCheckBoxes();
                return true;
            }

            // Remember the scroll position so we can decide if the listview has scrolled in the
            // handling of the event.
            int scrollPositionH = NativeMethods.GetScrollPosition(this, true);
            int scrollPositionV = NativeMethods.GetScrollPosition(this, false);

            base.WndProc(ref m);

            // It's possible that the processing in base.WndProc has actually destroyed this control
            if (this.IsDisposed)
                return true;

            // If the keydown processing changed the scroll position, trigger a Scroll event
            int newScrollPositionH = NativeMethods.GetScrollPosition(this, true);
            int newScrollPositionV = NativeMethods.GetScrollPosition(this, false);

            if (scrollPositionH != newScrollPositionH) {
                ScrollEventArgs args = new ScrollEventArgs(ScrollEventType.EndScroll,
                    scrollPositionH, newScrollPositionH, ScrollOrientation.HorizontalScroll);
                this.OnScroll(args);
            }
            if (scrollPositionV != newScrollPositionV) {
                ScrollEventArgs args = new ScrollEventArgs(ScrollEventType.EndScroll,
                    scrollPositionV, newScrollPositionV, ScrollOrientation.VerticalScroll);
                this.OnScroll(args);
            }

            if (scrollPositionH != newScrollPositionH || scrollPositionV != newScrollPositionV)
                this.RefreshHotItem();

            return true;
        }

        /// <summary>
        /// Toggle the checkedness of the selected rows
        /// </summary>
        /// <remarks>
        /// <para>
        /// Actually, this doesn't actually toggle all rows. It toggles the first row, and
        /// all other rows get the check state of that first row. This is actually a much
        /// more useful behaviour.
        /// </para>
        /// <para>
        /// If no rows are selected, this method does nothing.
        /// </para>
        /// </remarks>
        public void ToggleSelectedRowCheckBoxes() {
            if (this.SelectedIndices.Count == 0)
                return;
            Object primaryModel = this.GetItem(this.SelectedIndices[0]).RowObject;
            this.ToggleCheckObject(primaryModel);
            CheckState? state = this.GetCheckState(primaryModel);
            if (state.HasValue) {
                foreach (Object x in this.SelectedObjects)
                    this.SetObjectCheckedness(x, state.Value);
            }
        }

        /// <summary>
        /// Catch the Left Button down event.
        /// </summary>
        /// <param name="m">The m to be processed</param>
        /// <returns>bool to indicate if the msg has been handled</returns>
        protected virtual bool HandleLButtonDown(ref Message m) {
            // We have to intercept this low level message rather than the more natural
            // overridding of OnMouseDown, since ListCtrl's internal mouse down behavior
            // is to select (or deselect) rows when the mouse is released. We don't
            // want the selection to change when the user checks or unchecks a checkbox, so if the
            // mouse down event was to check/uncheck, we have to hide this mouse
            // down event from the control.

            int x = m.LParam.ToInt32() & 0xFFFF;
            int y = (m.LParam.ToInt32() >> 16) & 0xFFFF;

            return this.ProcessLButtonDown(this.OlvHitTest(x, y));
        }

        /// <summary>
        /// Handle a left mouse down at the given hit test location
        /// </summary>
        /// <remarks>Subclasses can override this to do something unique</remarks>
        /// <param name="hti"></param>
        /// <returns>True if the message has been handled</returns>
        protected virtual bool ProcessLButtonDown(OlvListViewHitTestInfo hti) {

            if (hti.Item == null)
                return false;

            // If the click occurs on a button, ignore it so the row isn't selected
            if (hti.HitTestLocation == HitTestLocation.Button) {
                this.Invalidate();

                return true;
            }

            // If they didn't click checkbox, we can just return
            if (hti.HitTestLocation != HitTestLocation.CheckBox)
                return false;

            // Disabled rows cannot change checkboxes
            if (!hti.Item.Enabled)
                return true;

            // Did they click a sub item checkbox?
            if (hti.Column != null && hti.Column.Index > 0) {
                if (hti.Column.IsEditable && hti.Item.Enabled)
                    this.ToggleSubItemCheckBox(hti.RowObject, hti.Column);
                return true;
            }

            // They must have clicked the primary checkbox
            this.ToggleCheckObject(hti.RowObject);

            // If they change the checkbox of a selected row, all the rows in the selection
            // should be given the same state
            if (hti.Item.Selected) {
                CheckState? state = this.GetCheckState(hti.RowObject);
                if (state.HasValue) {
                    foreach (Object x in this.SelectedObjects)
                        this.SetObjectCheckedness(x, state.Value);
                }
            }

            return true;
        }

        /// <summary>
        /// Catch the Left Button up event.
        /// </summary>
        /// <param name="m">The m to be processed</param>
        /// <returns>bool to indicate if the msg has been handled</returns>
        protected virtual bool HandleLButtonUp(ref Message m) {
            if (this.MouseMoveHitTest == null)
                return false;

            int x = m.LParam.ToInt32() & 0xFFFF;
            int y = (m.LParam.ToInt32() >> 16) & 0xFFFF;

            // Did they click an enabled, non-empty button?
            if (this.MouseMoveHitTest.HitTestLocation == HitTestLocation.Button) {
                // If a button was hit, Item and Column must be non-null
                if (this.MouseMoveHitTest.Item.Enabled || this.MouseMoveHitTest.Column.EnableButtonWhenItemIsDisabled) {
                    string buttonText = this.MouseMoveHitTest.Column.GetStringValue(this.MouseMoveHitTest.RowObject);
                    if (!String.IsNullOrEmpty(buttonText)) {
                        this.Invalidate();
                        CellClickEventArgs args = new CellClickEventArgs();
                        this.BuildCellEvent(args, new Point(x, y), this.MouseMoveHitTest);
                        this.OnButtonClick(args);
                        return true;
                    }
                }
            }

            // Are they trying to expand/collapse a group?
            if (this.MouseMoveHitTest.HitTestLocation == HitTestLocation.GroupExpander) {
                if (this.TriggerGroupExpandCollapse(this.MouseMoveHitTest.Group))
                    return true;
            }

            if (ObjectListView.IsVistaOrLater && this.HasCollapsibleGroups)
                base.DefWndProc(ref m);

            return false;
        }

        /// <summary>
        /// Trigger a GroupExpandCollapse event and return true if the action was cancelled
        /// </summary>
        /// <param name="group"></param>
        /// <returns></returns>
        protected virtual bool TriggerGroupExpandCollapse(OLVGroup group)
        {
            GroupExpandingCollapsingEventArgs args = new GroupExpandingCollapsingEventArgs(group);
            this.OnGroupExpandingCollapsing(args);
            return args.Canceled;
        }

        /// <summary>
        /// Catch the Right Button down event.
        /// </summary>
        /// <param name="m">The m to be processed</param>
        /// <returns>bool to indicate if the msg has been handled</returns>
        protected virtual bool HandleRButtonDown(ref Message m) {
            int x = m.LParam.ToInt32() & 0xFFFF;
            int y = (m.LParam.ToInt32() >> 16) & 0xFFFF;

            return this.ProcessRButtonDown(this.OlvHitTest(x, y));
        }

        /// <summary>
        /// Handle a left mouse down at the given hit test location
        /// </summary>
        /// <remarks>Subclasses can override this to do something unique</remarks>
        /// <param name="hti"></param>
        /// <returns>True if the message has been handled</returns>
        protected virtual bool ProcessRButtonDown(OlvListViewHitTestInfo hti) {
            if (hti.Item == null)
                return false;

            // Ignore clicks on checkboxes
            return (hti.HitTestLocation == HitTestLocation.CheckBox);
        }

        /// <summary>
        /// Catch the Left Button double click event.
        /// </summary>
        /// <param name="m">The m to be processed</param>
        /// <returns>bool to indicate if the msg has been handled</returns>
        protected virtual bool HandleLButtonDoubleClick(ref Message m) {
            int x = m.LParam.ToInt32() & 0xFFFF;
            int y = (m.LParam.ToInt32() >> 16) & 0xFFFF;

            return this.ProcessLButtonDoubleClick(this.OlvHitTest(x, y));
        }

        /// <summary>
        /// Handle a mouse double click at the given hit test location
        /// </summary>
        /// <remarks>Subclasses can override this to do something unique</remarks>
        /// <param name="hti"></param>
        /// <returns>True if the message has been handled</returns>
        protected virtual bool ProcessLButtonDoubleClick(OlvListViewHitTestInfo hti) {

            // If the user double clicked on a checkbox, ignore it
            return (hti.HitTestLocation == HitTestLocation.CheckBox);
        }

        /// <summary>
        /// Catch the right Button double click event.
        /// </summary>
        /// <param name="m">The m to be processed</param>
        /// <returns>bool to indicate if the msg has been handled</returns>
        protected virtual bool HandleRButtonDoubleClick(ref Message m) {
            int x = m.LParam.ToInt32() & 0xFFFF;
            int y = (m.LParam.ToInt32() >> 16) & 0xFFFF;

            return this.ProcessRButtonDoubleClick(this.OlvHitTest(x, y));
        }

        /// <summary>
        /// Handle a right mouse double click at the given hit test location
        /// </summary>
        /// <remarks>Subclasses can override this to do something unique</remarks>
        /// <param name="hti"></param>
        /// <returns>True if the message has been handled</returns>
        protected virtual bool ProcessRButtonDoubleClick(OlvListViewHitTestInfo hti) {

            // If the user double clicked on a checkbox, ignore it
            return (hti.HitTestLocation == HitTestLocation.CheckBox);
        }

        /// <summary>
        /// Catch the MouseMove event.
        /// </summary>
        /// <param name="m">The m to be processed</param>
        /// <returns>bool to indicate if the msg has been handled</returns>
        protected virtual bool HandleMouseMove(ref Message m)
        {
            //int x = m.LParam.ToInt32() & 0xFFFF;
            //int y = (m.LParam.ToInt32() >> 16) & 0xFFFF;

            //this.lastMouseMoveX = x;
            //this.lastMouseMoveY = y;

            return false;
        }
        //private int lastMouseMoveX = -1;
        //private int lastMouseMoveY = -1;

        /// <summary>
        /// Handle notifications that have been reflected back from the parent window
        /// </summary>
        /// <param name="m">The m to be processed</param>
        /// <returns>bool to indicate if the msg has been handled</returns>
        protected virtual bool HandleReflectNotify(ref Message m) {
            const int NM_CLICK = -2;
            const int NM_DBLCLK = -3;
            const int NM_RDBLCLK = -6;
            const int NM_CUSTOMDRAW = -12;
            const int NM_RELEASEDCAPTURE = -16;
            const int LVN_FIRST = -100;
            const int LVN_ITEMCHANGED = LVN_FIRST - 1;
            const int LVN_ITEMCHANGING = LVN_FIRST - 0;
            const int LVN_HOTTRACK = LVN_FIRST - 21;
            const int LVN_MARQUEEBEGIN = LVN_FIRST - 56;
            const int LVN_GETINFOTIP = LVN_FIRST - 58;
            const int LVN_GETDISPINFO = LVN_FIRST - 77;
            const int LVN_BEGINSCROLL = LVN_FIRST - 80;
            const int LVN_ENDSCROLL = LVN_FIRST - 81;
            const int LVN_LINKCLICK = LVN_FIRST - 84;
            const int LVN_GROUPINFO = LVN_FIRST - 88; // undocumented
            const int LVIF_STATE = 8;
            //const int LVIS_FOCUSED = 1;
            const int LVIS_SELECTED = 2;

            bool isMsgHandled = false;

            // TODO: Don't do any logic in this method. Create separate methods for each message

            NativeMethods.NMHDR nmhdr = (NativeMethods.NMHDR)m.GetLParam(typeof(NativeMethods.NMHDR));
            //System.Diagnostics.Debug.WriteLine(String.Format("rn: {0}", nmhdr->code));

            switch (nmhdr.code) {
                case NM_CLICK:
                    // The standard ListView does some strange stuff here when the list has checkboxes.
                    // If you shift click on non-primary columns when FullRowSelect is true, the 
                    // checkedness of the selected rows changes. 
                    // We can't just not do the base class stuff because it sets up state that is used to
                    // determine mouse up events later on.
                    // So, we sabotage the base class's process of the click event. The base class does a HITTEST
                    // in order to determine which row was clicked -- if that fails, the base class does nothing.
                    // So when we get a CLICK, we know that the base class is going to send a HITTEST very soon,
                    // so we ignore the next HITTEST message, which will cause the click processing to fail.
                    //System.Diagnostics.Debug.WriteLine("NM_CLICK");
                    this.skipNextHitTest = true;
                    break;

                case LVN_BEGINSCROLL:
                    //System.Diagnostics.Debug.WriteLine("LVN_BEGINSCROLL");
                    isMsgHandled = this.HandleBeginScroll(ref m);
                    break;

                case LVN_ENDSCROLL:
                    isMsgHandled = this.HandleEndScroll(ref m);
                    break;

                case LVN_LINKCLICK:
                    isMsgHandled = this.HandleLinkClick(ref m);
                    break;

                case LVN_MARQUEEBEGIN:
                    //System.Diagnostics.Debug.WriteLine("LVN_MARQUEEBEGIN");
                    this.isMarqueSelecting = true;
                    break;

                case LVN_GETINFOTIP:
                    //System.Diagnostics.Debug.WriteLine("LVN_GETINFOTIP");
                    // When virtual lists are in SmallIcon view, they generates tooltip message with invalid item indicies.
                    try
                    {

                        var lparam = m.GetLParam(typeof(NativeMethods.NMLVGETINFOTIP));
                        NativeMethods.NMLVGETINFOTIP nmGetInfoTip = (NativeMethods.NMLVGETINFOTIP)lparam;
                        isMsgHandled = nmGetInfoTip.iItem >= this.GetItemCount();
                    }
                    catch { isMsgHandled= false; }
                    break;

                case NM_RELEASEDCAPTURE:
                    //System.Diagnostics.Debug.WriteLine("NM_RELEASEDCAPTURE");
                    this.isMarqueSelecting = false;
                    this.Invalidate();
                    break;

                case NM_CUSTOMDRAW:
                    //System.Diagnostics.Debug.WriteLine("NM_CUSTOMDRAW");
                    isMsgHandled = this.HandleCustomDraw(ref m);
                    break;

                case NM_DBLCLK:
                    // The default behavior of a .NET ListView with checkboxes is to toggle the checkbox on
                    // double-click. That's just silly, if you ask me :)
                    if (this.CheckBoxes) {
                        // How do we make ListView not do that silliness? We could just ignore the message
                        // but the last part of the base code sets up state information, and without that
                        // state, the ListView doesn't trigger MouseDoubleClick events. So we fake a
                        // right button double click event, which sets up the same state, but without
                        // toggling the checkbox.
                        nmhdr.code = NM_RDBLCLK;
                        Marshal.StructureToPtr(nmhdr, m.LParam, false);
                    }
                    break;

                case LVN_ITEMCHANGED:
                    //System.Diagnostics.Debug.WriteLine("LVN_ITEMCHANGED");
                    NativeMethods.NMLISTVIEW nmlistviewPtr2 = (NativeMethods.NMLISTVIEW)m.GetLParam(typeof(NativeMethods.NMLISTVIEW));
                    if ((nmlistviewPtr2.uChanged & LVIF_STATE) != 0) {
                        CheckState currentValue = this.CalculateCheckState(nmlistviewPtr2.uOldState);
                        CheckState newCheckValue = this.CalculateCheckState(nmlistviewPtr2.uNewState);
                        if (currentValue != newCheckValue)
                        {
                            // Remove the state indicies so that we don't trigger the OnItemChecked method
                            // when we call our base method after exiting this method
                            nmlistviewPtr2.uOldState = (nmlistviewPtr2.uOldState & 0x0FFF);
                            nmlistviewPtr2.uNewState = (nmlistviewPtr2.uNewState & 0x0FFF);
                            Marshal.StructureToPtr(nmlistviewPtr2, m.LParam, false);
                        }
                        else
                        {
                            bool isSelected = (nmlistviewPtr2.uNewState & LVIS_SELECTED) == LVIS_SELECTED;

                            if (isSelected)
                            {
                               // System.Diagnostics.Debug.WriteLine(String.Format("Selected: {0}", nmlistviewPtr2.iItem));
                                bool isShiftDown = (Control.ModifierKeys & Keys.Shift) == Keys.Shift;

                                // -1 indicates that all rows are to be selected -- in fact, they already have been.
                                // We now have to deselect all the disabled objects.
                                if (nmlistviewPtr2.iItem == -1 || isShiftDown) {
                                    Stopwatch sw = Stopwatch.StartNew();
                                    foreach (object disabledModel in this.DisabledObjects)
                                    {
                                        int modelIndex = this.IndexOf(disabledModel);
                                        if (modelIndex >= 0)
                                            NativeMethods.DeselectOneItem(this, modelIndex);
                                    }
                                    System.Diagnostics.Debug.WriteLine(String.Format("PERF - Deselecting took {0}ms / {1} ticks", sw.ElapsedMilliseconds, sw.ElapsedTicks));
                                }
                                else
                                {
                                    // If the object just selected is disabled, explicitly de-select it
                                    OLVListItem olvi = this.GetItem(nmlistviewPtr2.iItem);
                                    if (olvi != null && !olvi.Enabled)
                                        NativeMethods.DeselectOneItem(this, nmlistviewPtr2.iItem);
                                }
                            }
                        }
                    }
                    break;

                case LVN_ITEMCHANGING:
                    //System.Diagnostics.Debug.WriteLine("LVN_ITEMCHANGING");
                    NativeMethods.NMLISTVIEW nmlistviewPtr = (NativeMethods.NMLISTVIEW)m.GetLParam(typeof(NativeMethods.NMLISTVIEW));
                    if ((nmlistviewPtr.uChanged & LVIF_STATE) != 0) {
                        CheckState currentValue = this.CalculateCheckState(nmlistviewPtr.uOldState);
                        CheckState newCheckValue = this.CalculateCheckState(nmlistviewPtr.uNewState);

                        if (currentValue != newCheckValue) {
                            // Prevent the base method from seeing the state change,
                            // since we handled it elsewhere
                            nmlistviewPtr.uChanged &= ~LVIF_STATE;
                            Marshal.StructureToPtr(nmlistviewPtr, m.LParam, false);
                        }
                    }
                    break;

                case LVN_HOTTRACK:
                    break;

                case LVN_GETDISPINFO:
                    break;

                case LVN_GROUPINFO:
                    //System.Diagnostics.Debug.WriteLine("reflect notify: GROUP INFO");
                    isMsgHandled = this.HandleGroupInfo(ref m);
                    break;

                //default:
                    //System.Diagnostics.Debug.WriteLine(String.Format("reflect notify: {0}", nmhdr.code));
                    //break;
            }

            return isMsgHandled;
        }
        private bool skipNextHitTest;

        private CheckState CalculateCheckState(int state) {
            switch ((state & 0xf000) >> 12) {
                case 1:
                    return CheckState.Unchecked;
                case 2:
                    return CheckState.Checked;
                case 3:
                    return CheckState.Indeterminate;
                default:
                    return CheckState.Checked;
            }
        }

        /// <summary>
        /// In the notification messages, we handle attempts to change the width of our columns
        /// </summary>
        /// <param name="m">The m 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 NM_CUSTOMDRAW = -12;

            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.NMHEADER nmheader = (NativeMethods.NMHEADER)m.GetLParam(typeof(NativeMethods.NMHEADER));
            //System.Diagnostics.Debug.WriteLine(String.Format("not: {0}", nmhdr->code));

            //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.
            switch (nmheader.nhdr.code) {

                case NM_CUSTOMDRAW:
                    if (!this.OwnerDrawnHeader)
                        isMsgHandled = this.HeaderControl.HandleHeaderCustomDraw(ref m);
                    break;

                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;
                    }
                    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_ENDTRACKA:
                case HDN_ENDTRACKW:
                    //if (this.ShowGroups)
                    //    this.ResizeLastGroup();
                    break;
                case HDN_TRACKA:
                case HDN_TRACKW:
                    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);
                        if (hditem.cxy < column.MinimumWidth)
                            hditem.cxy = column.MinimumWidth;
                        else if (column.MaximumWidth != -1 && hditem.cxy > column.MaximumWidth)
                            hditem.cxy = column.MaximumWidth;
                        Marshal.StructureToPtr(hditem, nmheader.pHDITEM, false);
                    }
                    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;

                case ToolTipControl.TTN_SHOW:
                    //System.Diagnostics.Debug.WriteLine("olv TTN_SHOW");
                    if (this.CellToolTip.Handle == nmheader.nhdr.hwndFrom)
                        isMsgHandled = this.CellToolTip.HandleShow(ref m);
                    break;

                case ToolTipControl.TTN_POP:
                    //System.Diagnostics.Debug.WriteLine("olv TTN_POP");
                    if (this.CellToolTip.Handle == nmheader.nhdr.hwndFrom)
                        isMsgHandled = this.CellToolTip.HandlePop(ref m);
                    break;

                case ToolTipControl.TTN_GETDISPINFO:
                    //System.Diagnostics.Debug.WriteLine("olv TTN_GETDISPINFO");
                    if (this.CellToolTip.Handle == nmheader.nhdr.hwndFrom)
                        isMsgHandled = this.CellToolTip.HandleGetDispInfo(ref m);
                    break;

//                default:
//                    System.Diagnostics.Debug.WriteLine(String.Format("notify: {0}", nmheader.nhdr.code));
//                    break;
            }

            return isMsgHandled;
        }

        /// <summary>
        /// Create a ToolTipControl to manage the tooltip control used by the listview control
        /// </summary>
        protected virtual void CreateCellToolTip() {
            this.cellToolTip = new ToolTipControl();
            this.cellToolTip.AssignHandle(NativeMethods.GetTooltipControl(this));
            this.cellToolTip.Showing += new EventHandler<ToolTipShowingEventArgs>(HandleCellToolTipShowing);
            this.cellToolTip.SetMaxWidth();
            NativeMethods.MakeTopMost(this.cellToolTip);
        }

        /// <summary>
        /// Update the handle used by our cell tooltip to be the tooltip used by
        /// the underlying Windows listview control.
        /// </summary>
        protected virtual void UpdateCellToolTipHandle() {
            if (this.cellToolTip != null && this.cellToolTip.Handle == IntPtr.Zero)
                this.cellToolTip.AssignHandle(NativeMethods.GetTooltipControl(this));
        }

        /// <summary>
        /// Handle the WM_PAINT event
        /// </summary>
        /// <param name="m"></param>
        /// <returns>Return true if the msg has been handled and nothing further should be done</returns>
        protected virtual bool HandlePaint(ref Message m) {
            //System.Diagnostics.Debug.WriteLine("> WMPAINT");

            // We only want to custom draw the control within WmPaint message and only
            // once per paint event. We use these bools to insure this.
            this.isInWmPaintEvent = true;
            this.shouldDoCustomDrawing = true;
            this.prePaintLevel = 0;

            this.ShowOverlays();

            this.HandlePrePaint();
            base.WndProc(ref m);
            this.HandlePostPaint();
            this.isInWmPaintEvent = false;
            //System.Diagnostics.Debug.WriteLine("< WMPAINT");
            return true;
        }
        private int prePaintLevel;

        /// <summary>
        /// Perform any steps needed before painting the control
        /// </summary>
        protected virtual 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); // we no longer need this, but keep the code so we can see it later

            //// 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 list 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 virtual void HandlePostPaint() {
            // This message is no longer necessary, but we keep it for compatibility
        }

        /// <summary>
        /// Handle the window position changing.
        /// </summary>
        /// <param name="m">The m to be processed</param>
        /// <returns>bool to indicate if the msg has been handled</returns>
        protected virtual bool HandleWindowPosChanging(ref Message m) {
            const int SWP_NOSIZE = 1;

            NativeMethods.WINDOWPOS pos = (NativeMethods.WINDOWPOS)m.GetLParam(typeof(NativeMethods.WINDOWPOS));
            if ((pos.flags & SWP_NOSIZE) == 0) {
                if (pos.cx < this.Bounds.Width) // only when shrinking
                    // pos.cx is the window width, not the client area width, so we have to subtract the border widths
                    this.ResizeFreeSpaceFillingColumns(pos.cx - (this.Bounds.Width - this.ClientSize.Width));
            }

            return false;
        }

        #endregion

        #region Column header clicking, column hiding and resizing

        /// <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>
        protected virtual bool HandleHeaderRightClick(int columnIndex) {
            ColumnClickEventArgs eventArgs = new ColumnClickEventArgs(columnIndex);
            this.OnColumnRightClick(eventArgs);

            // TODO: Allow users to say they have handled this event
            
            return this.ShowHeaderRightClickMenu(columnIndex, Cursor.Position);
        }

        /// <summary>
        /// Show a menu that is appropriate when the given column header is clicked.
        /// </summary>
        /// <param name="columnIndex">The index of the header that was clicked. This
        /// can be -1, indicating that the header was clicked outside of a column</param>
        /// <param name="pt">Where should the menu be shown</param>
        /// <returns>True if a menu was displayed</returns>
        protected virtual bool ShowHeaderRightClickMenu(int columnIndex, Point pt) {
            ToolStripDropDown m = this.MakeHeaderRightClickMenu(columnIndex);
            if (m.Items.Count > 0) {
                m.Show(pt);
                return true;
            }

            return false;
        }

        /// <summary>
        /// Create the menu that should be displayed when the user right clicks
        /// on the given column header.
        /// </summary>
        /// <param name="columnIndex">Index of the column that was right clicked.
        /// This can be negative, which indicates a click outside of any header.</param>
        /// <returns>The toolstrip that should be displayed</returns>
        protected virtual ToolStripDropDown MakeHeaderRightClickMenu(int columnIndex) {
            ToolStripDropDown m = new ContextMenuStrip();

            if (columnIndex >= 0 && this.UseFiltering && this.ShowFilterMenuOnRightClick)
                m = this.MakeFilteringMenu(m, columnIndex);

            if (columnIndex >= 0 && this.ShowCommandMenuOnRightClick) 
                m = this.MakeColumnCommandMenu(m, columnIndex);

            if (this.SelectColumnsOnRightClickBehaviour != ColumnSelectBehaviour.None) {
                m = this.MakeColumnSelectMenu(m);
            }

            return m;
        }

        /// <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")]
        protected virtual bool HandleHeaderRightClick() {
            return false;
        }

        /// <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>
        [Obsolete("Use ShowHeaderRightClickMenu instead")]
        protected virtual void ShowColumnSelectMenu(Point pt) {
            ToolStripDropDown m = this.MakeColumnSelectMenu(new ContextMenuStrip());
            m.Show(pt);
        }

        /// <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="columnIndex"></param>
        /// <param name="pt">Where should the menu be placed</param>
        [Obsolete("Use ShowHeaderRightClickMenu instead")]
        protected virtual void ShowColumnCommandMenu(int columnIndex, Point pt) {
            ToolStripDropDown m = this.MakeColumnCommandMenu(new ContextMenuStrip(), columnIndex);
            if (this.SelectColumnsOnRightClick) {
                if (m.Items.Count > 0)
                    m.Items.Add(new ToolStripSeparator());
                this.MakeColumnSelectMenu(m);
            }
            m.Show(pt);
        }

        /// <summary>
        /// 获取或设置要用于升序排序命令的文本
        /// </summary>
        [Description("获取或设置要用于升序排序命令的文本")]
        [Category("标签 - ObjectListView"), DefaultValue("按【{0}】增序"), Localizable(true)]
        public string MenuLabelSortAscending {
            get { return this.menuLabelSortAscending; }
            set { this.menuLabelSortAscending = value; }
        }
        private string menuLabelSortAscending = "按【{0}】增序";

        /// <summary>
        /// 获取或设置要用于降序排序命令的文本
        /// </summary>
        [Description("获取或设置要用于降序排序命令的文本")]
        [Category("标签 - ObjectListView"), DefaultValue("按【{0}】降序"), Localizable(true)]
        public string MenuLabelSortDescending {
            get { return this.menuLabelSortDescending; }
            set { this.menuLabelSortDescending = value; }
        }
        private string menuLabelSortDescending = "按【{0}】降序";

        /// <summary>
        /// 获取或设置要用于分组命令的文本
        /// </summary>
        [Description("获取或设置要用于分组命令的文本")]
        [Category("标签 - ObjectListView"), DefaultValue("按【{0}】分组"), Localizable(true)]
        public string MenuLabelGroupBy {
            get { return this.menuLabelGroupBy; }
            set { this.menuLabelGroupBy = value; }
        }
        private string menuLabelGroupBy = "按【{0}】分组";

        /// <summary>
        /// 获取或设置要用于锁定分组命令的文本
        /// </summary>
        [Description("获取或设置要用于锁定分组命令的文本")]
        [Category("标签 - ObjectListView"), DefaultValue("锁定按【{0}】分组"), Localizable(true)]
        public string MenuLabelLockGroupingOn {
            get { return this.menuLabelLockGroupingOn; }
            set { this.menuLabelLockGroupingOn = value; }
        }
        private string menuLabelLockGroupingOn = "锁定按【{0}】分组";

        /// <summary>
        /// 获取或设置要用于不锁定分组命令的文本
        /// </summary>
        [Description("获取或设置要用于不锁定分组命令的文本")]
        [Category("标签 - ObjectListView"), DefaultValue("不锁定按【{0}】分组"), Localizable(true)]
        public string MenuLabelUnlockGroupingOn {
            get { return this.menuLabelUnlockGroupingOn; }
            set { this.menuLabelUnlockGroupingOn = value; }
        }
        private string menuLabelUnlockGroupingOn = "不锁定按【{0}】分组";

        /// <summary>
        /// 获取或设置要用于关闭分组命令的文本
        /// </summary>
        [Description("获取或设置要用于关闭分组命令的文本")]
        [Category("标签 - ObjectListView"), DefaultValue("关闭分组"), Localizable(true)]
        public string MenuLabelTurnOffGroups {
            get { return this.menuLabelTurnOffGroups; }
            set { this.menuLabelTurnOffGroups = value; }
        }
        private string menuLabelTurnOffGroups = "关闭分组";

        /// <summary>
        /// 获取或设置要用于不排序命令的文本
        /// </summary>
        [Description("获取或设置要用于不排序命令的文本")]
        [Category("标签 - ObjectListView"), DefaultValue("不排序"), Localizable(true)]
        public string MenuLabelUnsort {
            get { return this.menuLabelUnsort; }
            set { this.menuLabelUnsort = value; }
        }
        private string menuLabelUnsort = "不排序";

        /// <summary>
        /// 获取或设置要用于列描述的文本
        /// </summary>
        [Description("获取或设置要用于列描述的文本")]
        [Category("标签 - ObjectListView"), DefaultValue("列"), Localizable(true)]
        public string MenuLabelColumns {
            get { return this.menuLabelColumns; }
            set { this.menuLabelColumns = value; }
        }
        private string menuLabelColumns = "列";

        /// <summary>
        /// 获取或设置要用于选择列描述的文本
        /// </summary>
        [Description("获取或设置要用于选择列描述的文本")]
        [Category("标签 - ObjectListView"), DefaultValue("选择列..."), Localizable(true)]
        public string MenuLabelSelectColumns {
            get { return this.menuLabelSelectColumns; }
            set { this.menuLabelSelectColumns = value; }
        }
        private string menuLabelSelectColumns = "选择列...";

        /// <summary>
        /// Gets or sets the image that will be place next to the Sort Ascending command
        /// </summary>
        static public Bitmap SortAscendingImage = ryControls.Properties.Resources.SortAscending;

        /// <summary>
        /// Gets or sets the image that will be placed next to the Sort Descending command
        /// </summary>
        static public Bitmap SortDescendingImage = ryControls.Properties.Resources.SortDescending;

        /// <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.</param>
        /// <param name="columnIndex"></param>
        /// <returns>Return the menu to which the items were added</returns>
        public virtual ToolStripDropDown MakeColumnCommandMenu(ToolStripDropDown strip, int columnIndex) {
            OLVColumn column = this.GetColumn(columnIndex);
            if (column == null)
                return strip;

            if (strip.Items.Count > 0)
                strip.Items.Add(new ToolStripSeparator());

            string label = String.Format(this.MenuLabelSortAscending, column.Text);
            if (column.Sortable && !String.IsNullOrEmpty(label)) {
                strip.Items.Add(label, ObjectListView.SortAscendingImage, (EventHandler)delegate(object sender, EventArgs args) {
                    this.Sort(column, SortOrder.Ascending);
                });
            }
            label = String.Format(this.MenuLabelSortDescending, column.Text);
            if (column.Sortable && !String.IsNullOrEmpty(label)) {
                strip.Items.Add(label, ObjectListView.SortDescendingImage, (EventHandler)delegate(object sender, EventArgs args) {
                    this.Sort(column, SortOrder.Descending);
                });
            }
            if (this.CanShowGroups) {
                label = String.Format(this.MenuLabelGroupBy, column.Text);
                if (column.Groupable && !String.IsNullOrEmpty(label)) {
                    strip.Items.Add(label, null, (EventHandler)delegate(object sender, EventArgs args) {
                        this.ShowGroups = true;
                        this.PrimarySortColumn = column;
                        this.PrimarySortOrder = SortOrder.Ascending;
                        this.BuildList();
                    });
                }
            }
            if (this.ShowGroups) {
                if (this.AlwaysGroupByColumn == column) {
                    label = String.Format(this.MenuLabelUnlockGroupingOn, column.Text);
                    if (!String.IsNullOrEmpty(label)) {
                        strip.Items.Add(label, null, (EventHandler)delegate(object sender, EventArgs args) {
                            this.AlwaysGroupByColumn = null;
                            this.AlwaysGroupBySortOrder = SortOrder.None;
                            this.BuildList();
                        });
                    }
                } else {
                    label = String.Format(this.MenuLabelLockGroupingOn, column.Text);
                    if (column.Groupable && !String.IsNullOrEmpty(label)) {
                        strip.Items.Add(label, null, (EventHandler)delegate(object sender, EventArgs args) {
                            this.ShowGroups = true;
                            this.AlwaysGroupByColumn = column;
                            this.AlwaysGroupBySortOrder = SortOrder.Ascending;
                            this.BuildList();
                        });
                    }
                }
                label = String.Format(this.MenuLabelTurnOffGroups, column.Text);
                if (!String.IsNullOrEmpty(label)) {
                    strip.Items.Add(label, null, (EventHandler)delegate(object sender, EventArgs args) {
                        this.ShowGroups = false;
                        this.BuildList();
                    });
                }
            } else {
                label = String.Format(this.MenuLabelUnsort, column.Text);
                if (column.Sortable && !String.IsNullOrEmpty(label) && this.PrimarySortOrder != SortOrder.None) {
                    strip.Items.Add(label, null, (EventHandler)delegate(object sender, EventArgs args) {
                        this.Unsort();
                    });
                }
            }

            return strip;
        }

        /// <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.</param>
        /// <returns>Return the menu to which the items were added</returns>
        public virtual ToolStripDropDown MakeColumnSelectMenu(ToolStripDropDown strip) {

            System.Diagnostics.Debug.Assert(this.SelectColumnsOnRightClickBehaviour != ColumnSelectBehaviour.None);

            // Append a separator if the menu isn't empty and the last item isn't already a separator
            if (strip.Items.Count > 0 && (!(strip.Items[strip.Items.Count-1] is ToolStripSeparator)))
                strip.Items.Add(new ToolStripSeparator());

            if (this.AllColumns.Count > 0 && this.AllColumns[0].LastDisplayIndex == -1)
                this.RememberDisplayIndicies();

            if (this.SelectColumnsOnRightClickBehaviour == ColumnSelectBehaviour.ModelDialog) {
                strip.Items.Add(this.MenuLabelSelectColumns, null, delegate(object sender, EventArgs args) {
                    (new ColumnSelectionForm()).OpenOn(this);
                });
            }

            if (this.SelectColumnsOnRightClickBehaviour == ColumnSelectBehaviour.Submenu) {
                ToolStripMenuItem menu = new ToolStripMenuItem(this.MenuLabelColumns);
                menu.DropDownItemClicked += new ToolStripItemClickedEventHandler(this.ColumnSelectMenuItemClicked);
                strip.Items.Add(menu);
                this.AddItemsToColumnSelectMenu(menu.DropDownItems);
            }

            if (this.SelectColumnsOnRightClickBehaviour == ColumnSelectBehaviour.InlineMenu) {
                strip.ItemClicked += new ToolStripItemClickedEventHandler(this.ColumnSelectMenuItemClicked);
                strip.Closing += new ToolStripDropDownClosingEventHandler(this.ColumnSelectMenuClosing);
                this.AddItemsToColumnSelectMenu(strip.Items);
            }

            return strip;
        }

        /// <summary>
        /// Create the menu items that will allow columns to be choosen and add them to the 
        /// given collection
        /// </summary>
        /// <param name="items"></param>
        protected void AddItemsToColumnSelectMenu(ToolStripItemCollection items) {

            // Sort columns by display order
            List<OLVColumn> columns = new List<OLVColumn>(this.AllColumns);
            columns.Sort(delegate(OLVColumn x, OLVColumn y) { return (x.LastDisplayIndex - y.LastDisplayIndex); });

            // Build menu from sorted columns
            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.CanBeHidden;
                items.Add(mi);
            }
        }

        private void ColumnSelectMenuItemClicked(object sender, ToolStripItemClickedEventArgs e) {
            this.contextMenuStaysOpen = false;
            ToolStripMenuItem menuItemClicked = e.ClickedItem as ToolStripMenuItem;
            if (menuItemClicked == null)
                return;
            OLVColumn col = menuItemClicked.Tag as OLVColumn;
            if (col == null)
                return;
            menuItemClicked.Checked = !menuItemClicked.Checked;
            col.IsVisible = menuItemClicked.Checked;
            this.contextMenuStaysOpen = this.SelectColumnsMenuStaysOpen;
            this.BeginInvoke(new MethodInvoker(this.RebuildColumns));
        }
        private bool contextMenuStaysOpen;

        private void ColumnSelectMenuClosing(object sender, ToolStripDropDownClosingEventArgs e) {
            e.Cancel = this.contextMenuStaysOpen && e.CloseReason == ToolStripDropDownCloseReason.ItemClicked;
            this.contextMenuStaysOpen = false;
        }

        /// <summary>
        /// Create a Filtering menu
        /// </summary>
        /// <param name="strip"></param>
        /// <param name="columnIndex"></param>
        /// <returns></returns>
        public virtual ToolStripDropDown MakeFilteringMenu(ToolStripDropDown strip, int columnIndex) {
            OLVColumn column = this.GetColumn(columnIndex);
            if (column == null)
                return strip;

            FilterMenuBuilder strategy = this.FilterMenuBuildStrategy;
            if (strategy == null)
                return strip;

            return strategy.MakeFilterMenu(strip, this, column);
        }

        /// <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;
        }

        /// <summary>
        /// When the column widths are changing, resize the space filling columns
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected virtual void HandleColumnWidthChanging(object sender, ColumnWidthChangingEventArgs e) {
            if (this.UpdateSpaceFillingColumnsWhenDraggingColumnDivider && !this.GetColumn(e.ColumnIndex).FillsFreeSpace) {
                // If the width of a column is increasing, resize any space filling columns allowing the extra
                // space that the new column width is going to consume
                int oldWidth = this.GetColumn(e.ColumnIndex).Width;
                if (e.NewWidth > oldWidth)
                    this.ResizeFreeSpaceFillingColumns(this.ClientSize.Width - (e.NewWidth - oldWidth));
                else
                    this.ResizeFreeSpaceFillingColumns();
            }
        }

        /// <summary>
        /// When the column widths change, resize the space filling columns
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected virtual void HandleColumnWidthChanged(object sender, ColumnWidthChangedEventArgs e) {
            if (!this.GetColumn(e.ColumnIndex).FillsFreeSpace)
                this.ResizeFreeSpaceFillingColumns();
        }

        /// <summary>
        /// When the size of the control changes, we have to resize our space filling columns.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected virtual void HandleLayout(object sender, LayoutEventArgs e) {
            // We have to delay executing the recalculation of the columns, since virtual lists
            // get terribly confused if we resize the column widths during this event.
            if (!this.hasResizeColumnsHandler) {
                this.hasResizeColumnsHandler = true;
                this.RunWhenIdle(this.HandleApplicationIdleResizeColumns);
            }
        }

        private void RunWhenIdle(EventHandler eventHandler) {
            Application.Idle += eventHandler;
            if (!this.CanUseApplicationIdle) {
                SynchronizationContext.Current.Post(delegate(object x) { Application.RaiseIdle(EventArgs.Empty); }, null);
            }
        }

        /// <summary>
        /// Resize our space filling columns so they fill any unoccupied width in the control
        /// </summary>
        protected virtual void ResizeFreeSpaceFillingColumns() {
            this.ResizeFreeSpaceFillingColumns(this.ClientSize.Width);
        }

        /// <summary>
        /// Resize our space filling columns so they fill any unoccupied width in the control
        /// </summary>
        protected virtual void ResizeFreeSpaceFillingColumns(int freeSpace) {
            // It's too confusing to dynamically resize columns at design time.
            if (this.DesignMode)
                return;

            if (this.Frozen)
                return;

            // Calculate the free space available
            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>
        /// Check all rows
        /// </summary>
        public virtual void CheckAll()
        {
            this.CheckedObjects = EnumerableToArray(this.Objects, false);
        }

        /// <summary>
        /// Check the checkbox in the given column header
        /// </summary>
        /// <remarks>If the given columns header check box is linked to the cell check boxes,
        /// then checkboxes in all cells will also be checked.</remarks>
        /// <param name="column"></param>
        public virtual void CheckHeaderCheckBox(OLVColumn column)
        {
            if (column == null)
                return;

            ChangeHeaderCheckBoxState(column, CheckState.Checked);
        }

        /// <summary>
        /// Mark the checkbox in the given column header as having an indeterminate value
        /// </summary>
        /// <param name="column"></param>
        public virtual void CheckIndeterminateHeaderCheckBox(OLVColumn column)
        {
            if (column == null)
                return;

            ChangeHeaderCheckBoxState(column, CheckState.Indeterminate);
        }

        /// <summary>
        /// Mark the given object as indeterminate check state
        /// </summary>
        /// <param name="modelObject">The model object to be marked indeterminate</param>
        public virtual void CheckIndeterminateObject(object modelObject) {
            this.SetObjectCheckedness(modelObject, CheckState.Indeterminate);
        }

        /// <summary>
        /// Mark the given object as checked in the list
        /// </summary>
        /// <param name="modelObject">The model object to be checked</param>
        public virtual void CheckObject(object modelObject) {
            this.SetObjectCheckedness(modelObject, CheckState.Checked);
        }

        /// <summary>
        /// Mark the given objects as checked in the list
        /// </summary>
        /// <param name="modelObjects">The model object to be checked</param>
        public virtual void CheckObjects(IEnumerable modelObjects) {
            foreach (object model in modelObjects)
                this.CheckObject(model);
        }

        /// <summary>
        /// Put a check into the check box at the given cell
        /// </summary>
        /// <param name="rowObject"></param>
        /// <param name="column"></param>
        public virtual void CheckSubItem(object rowObject, OLVColumn column) {
            if (column == null || rowObject == null || !column.CheckBoxes)
                return;

            column.PutCheckState(rowObject, CheckState.Checked);
            this.RefreshObject(rowObject);
        }

        /// <summary>
        /// Put an indeterminate check into the check box at the given cell
        /// </summary>
        /// <param name="rowObject"></param>
        /// <param name="column"></param>
        public virtual void CheckIndeterminateSubItem(object rowObject, OLVColumn column) {
            if (column == null || rowObject == null || !column.CheckBoxes)
                return;

            column.PutCheckState(rowObject, CheckState.Indeterminate);
            this.RefreshObject(rowObject);
        }

        /// <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 virtual bool IsChecked(object modelObject) {
            return this.GetCheckState(modelObject) == CheckState.Checked;
        }

        /// <summary>
        /// Return true of the given object is indeterminately checked
        /// </summary>
        /// <param name="modelObject">The model object whose checkedness is returned</param>
        /// <returns>Is the given object indeterminately checked?</returns>
        /// <remarks>If the given object is not in the list, this method returns false.</remarks>
        public virtual bool IsCheckedIndeterminate(object modelObject) {
            return this.GetCheckState(modelObject) == CheckState.Indeterminate;
        }

        /// <summary>
        /// Is there a check at the check box at the given cell
        /// </summary>
        /// <param name="rowObject"></param>
        /// <param name="column"></param>
        public virtual bool IsSubItemChecked(object rowObject, OLVColumn column) {
            if (column == null || rowObject == null || !column.CheckBoxes) 
                return false;
            return (column.GetCheckState(rowObject) == CheckState.Checked);
        }

        /// <summary>
        /// Get the checkedness of an object from the model. Returning null means the
        /// model does not know and the value from the control will be used.
        /// </summary>
        /// <param name="modelObject"></param>
        /// <returns></returns>
        protected virtual CheckState? GetCheckState(Object modelObject) {
            if (this.CheckStateGetter != null) 
                return this.CheckStateGetter(modelObject);
            return this.PersistentCheckBoxes ? this.GetPersistentCheckState(modelObject) : (CheckState?)null;
        }

        /// <summary>
        /// Record the change of checkstate for the given object in the model.
        /// This does not update the UI -- only the model
        /// </summary>
        /// <param name="modelObject"></param>
        /// <param name="state"></param>
        /// <returns>The check state that was recorded and that should be used to update
        /// the control.</returns>
        protected virtual CheckState PutCheckState(Object modelObject, CheckState state) {
            if (this.CheckStatePutter != null) 
                return this.CheckStatePutter(modelObject, state);
            return this.PersistentCheckBoxes ? this.SetPersistentCheckState(modelObject, state) : state;
        }

        /// <summary>
        /// Change the check state of the given object to be the given state.
        /// </summary>
        /// <remarks>
        /// If the given model object isn't in the list, we still try to remember
        /// its state, in case it is referenced in the future.</remarks>
        /// <param name="modelObject"></param>
        /// <param name="state"></param>
        /// <returns>True if the checkedness of the model changed</returns>
        protected virtual bool SetObjectCheckedness(object modelObject, CheckState state) {

            if (GetCheckState(modelObject) == state)
                return false;

            OLVListItem olvi = this.ModelToItem(modelObject);

            // If we didn't find the given, we still try to record the check state.
            if (olvi == null) {
                this.PutCheckState(modelObject, state);
                return true;
            }

            // Trigger checkbox changing event
            ItemCheckEventArgs ice = new ItemCheckEventArgs(olvi.Index, state, olvi.CheckState);
            this.OnItemCheck(ice);
            if (ice.NewValue == olvi.CheckState)
                return false;

            olvi.CheckState = this.PutCheckState(modelObject, state);
            this.RefreshItem(olvi);

            // Trigger check changed event
            this.OnItemChecked(new ItemCheckedEventArgs(olvi));
            return true;
        }

        /// <summary>
        /// Toggle the checkedness of the given object. A checked object becomes
        /// unchecked; an unchecked or indeterminate object becomes checked.
        /// If the list has tristate checkboxes, the order is:
        ///    unchecked -> checked -> indeterminate -> unchecked ...
        /// </summary>
        /// <param name="modelObject">The model object to be checked</param>
        public virtual void ToggleCheckObject(object modelObject) {
            OLVListItem olvi = this.ModelToItem(modelObject);
            if (olvi == null)
                return;

            CheckState newState = CheckState.Checked;

            if (olvi.CheckState == CheckState.Checked) {
                newState = this.TriStateCheckBoxes ? CheckState.Indeterminate : CheckState.Unchecked;
            } else {
                if (olvi.CheckState == CheckState.Indeterminate && this.TriStateCheckBoxes)
                    newState = CheckState.Unchecked;
            }
            this.SetObjectCheckedness(modelObject, newState);
        }

        /// <summary>
        /// Toggle the checkbox in the header of the given column
        /// </summary>
        /// <remarks>Obviously, this is only useful if the column actually has a header checkbox.</remarks>
        /// <param name="column"></param>
        public virtual void ToggleHeaderCheckBox(OLVColumn column) {
            if (column == null)
                return;

            CheckState newState = CalculateToggledCheckState(column.HeaderCheckState, column.HeaderTriStateCheckBox, column.HeaderCheckBoxDisabled);
            ChangeHeaderCheckBoxState(column, newState);
        }

        private void ChangeHeaderCheckBoxState(OLVColumn column, CheckState newState) {
            // Tell the world the checkbox was clicked
            HeaderCheckBoxChangingEventArgs args = new HeaderCheckBoxChangingEventArgs();
            args.Column = column;
            args.NewCheckState = newState;

            this.OnHeaderCheckBoxChanging(args);
            if (args.Cancel || column.HeaderCheckState == args.NewCheckState)
                return;

            Stopwatch sw = Stopwatch.StartNew();

            column.HeaderCheckState = args.NewCheckState;
            this.HeaderControl.Invalidate(column);

            if (column.HeaderCheckBoxUpdatesRowCheckBoxes) {
                if (column.Index == 0)
                    this.UpdateAllPrimaryCheckBoxes(column);
                else
                    this.UpdateAllSubItemCheckBoxes(column);
            }

            Debug.WriteLine(String.Format("PERF - Changing row checkboxes on {2} objects took {0}ms / {1} ticks", sw.ElapsedMilliseconds, sw.ElapsedTicks, this.GetItemCount()));
        }

        private void UpdateAllPrimaryCheckBoxes(OLVColumn column) {
            if (!this.CheckBoxes || column.HeaderCheckState == CheckState.Indeterminate)
                return;

            if (column.HeaderCheckState == CheckState.Checked)
                CheckAll();
            else
                UncheckAll();
        }

        private void UpdateAllSubItemCheckBoxes(OLVColumn column) {
            if (!column.CheckBoxes || column.HeaderCheckState == CheckState.Indeterminate)
                return;

            foreach (object model in this.Objects)
                column.PutCheckState(model, column.HeaderCheckState);
            this.BuildList(true);
        }

        /// <summary>
        /// Toggle the check at the check box of the given cell
        /// </summary>
        /// <param name="rowObject"></param>
        /// <param name="column"></param>
        public virtual void ToggleSubItemCheckBox(object rowObject, OLVColumn column) {
            CheckState currentState = column.GetCheckState(rowObject);
            CheckState newState = CalculateToggledCheckState(currentState, column.TriStateCheckBoxes, false);

            SubItemCheckingEventArgs args = new SubItemCheckingEventArgs(column, this.ModelToItem(rowObject), column.Index, currentState, newState);
            this.OnSubItemChecking(args);
            if (args.Canceled)
                return;

            switch (args.NewValue) {
                case CheckState.Checked:
                    this.CheckSubItem(rowObject, column);
                    break;
                case CheckState.Indeterminate:
                    this.CheckIndeterminateSubItem(rowObject, column);
                    break;
                case CheckState.Unchecked:
                    this.UncheckSubItem(rowObject, column);
                    break;
            }
        }

        /// <summary>
        /// Uncheck all rows
        /// </summary>
        public virtual void UncheckAll()
        {
            this.CheckedObjects = null;
        }

        /// <summary>
        /// Mark the given object as unchecked in the list
        /// </summary>
        /// <param name="modelObject">The model object to be unchecked</param>
        public virtual void UncheckObject(object modelObject) {
            this.SetObjectCheckedness(modelObject, CheckState.Unchecked);
        }

        /// <summary>
        /// Mark the given objects as unchecked in the list
        /// </summary>
        /// <param name="modelObjects">The model object to be checked</param>
        public virtual void UncheckObjects(IEnumerable modelObjects) {
            foreach (object model in modelObjects)
                this.UncheckObject(model);
        }

        /// <summary>
        /// Uncheck the checkbox in the given column header
        /// </summary>
        /// <param name="column"></param>
        public virtual void UncheckHeaderCheckBox(OLVColumn column)
        {
            if (column == null)
                return;

            ChangeHeaderCheckBoxState(column, CheckState.Unchecked);
        }

        /// <summary>
        /// Uncheck the check at the given cell
        /// </summary>
        /// <param name="rowObject"></param>
        /// <param name="column"></param>
        public virtual void UncheckSubItem(object rowObject, OLVColumn column)
        {
            if (column == null || rowObject == null || !column.CheckBoxes)
                return;

            column.PutCheckState(rowObject, CheckState.Unchecked);
            this.RefreshObject(rowObject);
        }

        #endregion

        #region OLV accessing

        /// <summary>
        /// 返回给定索引处的列
        /// </summary>
        /// <param name="index">Index of the column to be returned</param>
        /// <returns>An OLVColumn</returns>
        public virtual OLVColumn GetColumn(int index) {
            return (OLVColumn)this.Columns[index];
        }

        /// <summary>
        /// 返回给定标题处的列,如果有多个符合要求的标题,则默认只返回第一个
        /// </summary>
        /// <param name="name">要返回的列的名称</param>
        /// <returns>An OLVColumn</returns>
        public virtual OLVColumn GetColumn(string name) {
            foreach (ColumnHeader column in this.Columns) {
                if (column.Text == name)
                    return (OLVColumn)column;
            }
            return null;
        }

        /// <summary>
        /// 返回给定视图可见的列的集合。只有Tile和Details有列；所有其他视图都有0列。
        /// </summary>
        /// <param name="view">正在为哪个视图计算列？</param>
        /// <returns>A list of columns</returns>
        public virtual List<OLVColumn> GetFilteredColumns(View view) {
            // For both detail and tile view, the first column must be included. Normally, we would
            // use the ColumnHeader.Index property, but if the header is not currently part of a ListView
            // that property returns -1. So, we track the index of
            // the column header, and always include the first header.

            int index = 0;
            return this.AllColumns.FindAll(delegate(OLVColumn x) {
                return (index++ == 0) || x.IsVisible;
            });
        }

        /// <summary>
        /// 返回列表中的项目数
        /// </summary>
        /// <returns>列表中的项目数</returns>
        /// <remarks>如果安装了筛选器，这将返回与筛选器匹配的项目数。</remarks>
        public virtual int GetItemCount() {
            return this.Items.Count;
        }

        /// <summary>
        /// 返回给定索引处的项
        /// </summary>
        /// <param name="index">Index of the item to be returned</param>
        /// <returns>An OLVListItem</returns>
        public virtual OLVListItem GetItem(int index) {
            if (index < 0 || index >= this.GetItemCount()) 
                return null;
            
            return (OLVListItem)this.Items[index];
        }

        /// <summary>
        /// 返回给定索引处的模型对象
        /// </summary>
        /// <param name="index">Index of the model object to be returned</param>
        /// <returns>A model object</returns>
        public virtual object GetModelObject(int index) {
            OLVListItem item = this.GetItem(index);
            return item == null ? null : item.RowObject;
        }

        /// <summary>
        /// 找出在给定的坐标下的行和列</summary>
        /// <param name="x">X 坐标</param>
        /// <param name="y">Y 坐标</param>
        /// <param name="hitColumn">给定点下的列项</param>
        /// <returns>在给定坐标点下的行项。可以为null。</returns>
        public virtual OLVListItem GetItemAt(int x, int y, out OLVColumn hitColumn) {
            hitColumn = 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);
                hitColumn = this.GetColumn(subItemIndex);
            }

            return (OLVListItem)info.Item;
        }

        /// <summary>
        /// 返回给定索引/列的子项
        /// </summary>
        /// <param name="index">Index of the item to be returned</param>
        /// <param name="columnIndex">Index of the subitem to be returned</param>
        /// <returns>An OLVListSubItem</returns>
        public virtual OLVListSubItem GetSubItem(int index, int columnIndex) {
            OLVListItem olvi = this.GetItem(index);
            return olvi == null ? null : olvi.GetSubItem(columnIndex);
        }

        #endregion

        #region Object manipulation

        /// <summary>
        /// 滚动列表视图，使给定组位于顶部。
        /// </summary>
        /// <param name="lvg">The group to be revealed</param>
        /// <remarks><para>
        ///如果该组可见，如果可能，列表仍将滚动以将该组移至顶部。
        /// </para>
        /// <para>这只在列表显示分组时才有效(显然)。</para>
        /// <para>这在虚拟列表上不起作用，因为虚拟列表不使用ListViewGroups进行分组。使用 <see cref="VirtualObjectListView.EnsureNthGroupVisible"/> 代替.</para>
        /// </remarks>
        public virtual void EnsureGroupVisible(ListViewGroup lvg) {
            if (!this.ShowGroups || lvg == null)
                return;

            int groupIndex = this.Groups.IndexOf(lvg);
            if (groupIndex <= 0) {
                // There is no easy way to scroll back to the beginning of the list
                int delta = 0 - NativeMethods.GetScrollPosition(this, false);
                NativeMethods.Scroll(this, 0, delta);
            } else {
                // Find the display rectangle of the last item in the previous group
                ListViewGroup previousGroup = this.Groups[groupIndex - 1];
                ListViewItem lastItemInGroup = previousGroup.Items[previousGroup.Items.Count - 1];
                Rectangle r = this.GetItemRect(lastItemInGroup.Index);

                // Scroll so that the last item of the previous group is just out of sight,
                // which will make the desired group header visible.
                int delta = r.Y + r.Height / 2;
                NativeMethods.Scroll(this, 0, delta);
            }
        }

        /// <summary>
        /// 使给定的模型对象可见
        /// </summary>
        /// <param name="modelObject">The model object to be revealed</param>
        public virtual void EnsureModelVisible(Object modelObject) {
            int index = this.IndexOf(modelObject);
            if (index >= 0)
                this.EnsureVisible(index);
        }

        /// <summary>
        /// 返回选定行的模型对象，如果没有选择或有多个选择，则返回null
        /// </summary>
        /// <returns>Model object or null</returns>
        [Obsolete("使用 SelectedObject 属性代替本方法")]
        public virtual object GetSelectedObject() {
            return this.SelectedObject;
        }

        /// <summary>
        ///返回选定行的模型对象，如果没有选择，则返回空集合
        /// </summary>
        /// <returns>ArrayList</returns>
        [Obsolete("使用 SelectedObjects 属性代替本方法")]
        public virtual ArrayList GetSelectedObjects() {
            return ObjectListView.EnumerableToArray(this.SelectedObjects, false);
        }

        /// <summary>
        /// 返回选中行的模型对象；如果未选中行或选中多行，则返回NULL
        /// </summary>
        /// <returns>Model object or null</returns>
        /// <remarks>使用 CheckedObject 属性代替本方法</remarks>
        [Obsolete("使用 CheckedObject 属性代替本方法")]
        public virtual object GetCheckedObject() {
            return this.CheckedObject;
        }

        /// <summary>
        /// 获取选中的模型对象的集合。
        /// </summary>
        /// <remarks>使用 CheckedObjects 属性代替本方法</remarks>
        [Obsolete("使用 CheckedObjects 属性代替本方法")]
        public virtual ArrayList GetCheckedObjects() {
            return ObjectListView.EnumerableToArray(this.CheckedObjects, false);
        }

        /// <summary>
        /// 在列表视图中查找给定的模型对象并返回其索引
        /// </summary>
        /// <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 virtual int IndexOf(Object modelObject) {
            for (int i = 0; i < this.GetItemCount(); i++) {
                if (this.GetModelObject(i).Equals(modelObject))
                    return i;
            }
            return -1;
        }

        /// <summary>
        /// Rebuild the given 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>
        public virtual void RefreshItem(OLVListItem olvi) {
            olvi.UseItemStyleForSubItems = true;
            olvi.SubItems.Clear();
            this.FillInValues(olvi, olvi.RowObject);
            this.PostProcessOneRow(olvi.Index, this.GetDisplayOrderOfItemIndex(olvi.Index), olvi);
        }

        /// <summary>
        /// Rebuild the data on the row that is showing the given object.
        /// </summary>
        /// <remarks>
        /// <para>
        /// This method does not resort or regroup the view.
        /// </para>
        /// <para>
        /// The given object is *not* used as the source of data for the rebuild.
        /// It is only used to locate the matching model in the <see cref="Objects"/> collection,
        /// then that matching model is used as the data source. This distinction is
        /// only important in model classes that have overridden the Equals() method.
        /// </para>
        /// <para>
        /// If you want the given model object to replace the pre-existing model,
        /// use <see cref="UpdateObject"/>. 
        /// </para>
        /// </remarks>
        public virtual 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>
        public virtual 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.ReplaceModel(olvi, modelObject);
                    this.RefreshItem(olvi);
                }
            }
        }

        private void ReplaceModel(OLVListItem olvi, object newModel) {
            if (ReferenceEquals(olvi.RowObject, newModel))
                return;

            this.TakeOwnershipOfObjects();
            ArrayList array = ObjectListView.EnumerableToArray(this.Objects, false);
            int i = array.IndexOf(olvi.RowObject);
            if (i >= 0)
                array[i] = newModel;

            olvi.RowObject = newModel;
        }

        /// <summary>
        /// 更新选定的行
        /// </summary>
        /// <remarks>This method does not resort or regroup the view.</remarks>
        public virtual void RefreshSelectedObjects() {
            foreach (ListViewItem lvi in this.SelectedItems)
                this.RefreshItem((OLVListItem)lvi);
        }

        /// <summary>
        /// 除任何当前选择外，还选择显示给定模型对象的行。
        /// </summary>
        /// <param name="modelObject">The object to be selected</param>
        /// <remarks>Use the <see cref="SelectedObject"/> property to deselect all other rows</remarks>
        public virtual void SelectObject(object modelObject) {
            this.SelectObject(modelObject, false);
        }

        /// <summary>
        /// 除任何当前选择外，还选择显示给定模型对象的行。
        /// </summary>
        /// <param name="modelObject">The object to be selected</param>
        /// <param name="setFocus">对象是否也应该被聚焦</param>
        /// <remarks>Use the <see cref="SelectedObject"/> property to deselect all other rows</remarks>
        public virtual void SelectObject(object modelObject, bool setFocus) {
            OLVListItem olvi = this.ModelToItem(modelObject);
            if (olvi != null && olvi.Enabled) {
                olvi.Selected = true;
                if (setFocus)
                    olvi.Focused = true;
            }
        }

        /// <summary>
        /// 选择显示任意给定模型对象的行。取消选择所有其他行。
        /// </summary>
        /// <param name="modelObjects">A collection of model objects</param>
        public virtual void SelectObjects(IList modelObjects) {
            this.SelectedIndices.Clear();

            if (modelObjects == null)
                return;

            foreach (object modelObject in modelObjects) {
                OLVListItem olvi = this.ModelToItem(modelObject);
                if (olvi != null && olvi.Enabled)
                    olvi.Selected = true;
            }
        }

        #endregion

        #region Freezing/Suspending

        /// <summary>
        ///获取或设置是否冻结列表视图。当列表视图被冻结时，它不会自我更新。
        /// </summary>
        /// <remarks><para>Frozen属性与Freeze()/UnFreeze()方法类似，
        /// 不同之处在于将Frozen属性设置为False会立即解冻控件，
        /// 而不管有多少未完成的Freeze()调用。</para></remarks>
        /// <example>objectListView1.Frozen = false; // unfreeze the control now!
        /// </example>
        [Browsable(false),
         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual bool Frozen {
            get { return freezeCount > 0; }
            set {
                if (value)
                    Freeze();
                else if (freezeCount > 0) {
                    freezeCount = 1;
                    Unfreeze();
                }
            }
        }
        private int freezeCount;

        /// <summary>
        /// 冻结列表视图，使其不再自我更新。
        /// </summary>
        /// <remarks>Freeze()/Unfreeze() calls nest correctly</remarks>
        public virtual void Freeze() {
            if (freezeCount == 0)
                DoFreeze();

            freezeCount++;
            this.OnFreezing(new FreezeEventArgs(freezeCount));
        }

        /// <summary>
        /// 解冻列表视图。如果此调用是最外层的unFreeze()，则将重新构建listview的内容。
        /// </summary>
        /// <remarks>Freeze()/Unfreeze() calls nest correctly</remarks>
        public virtual void Unfreeze() {
            if (freezeCount <= 0)
                return;

            freezeCount--;
            if (freezeCount == 0)
                DoUnfreeze();

            this.OnFreezing(new FreezeEventArgs(freezeCount));
        }

        /// <summary>
        /// 在冻结列表视图时执行所需的实际工作
        /// </summary>
        protected virtual void DoFreeze() {
            this.BeginUpdate();
        }

        /// <summary>
        /// 解冻列表视图时执行所需的实际工作
        /// </summary>
        protected virtual void DoUnfreeze()
        {
            this.EndUpdate();
            this.ResizeFreeSpaceFillingColumns();
            this.BuildList();
        }

        /// <summary>
        /// 如果选择事件当前挂起，则返回TRUE。在挂起选择事件时，不会引发SelectedIndexChanged或SelectionChanged事件。
        /// </summary>
        [Browsable(false),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        protected bool SelectionEventsSuspended {
            get { return this.suspendSelectionEventCount > 0; }
        }

        /// <summary>
        /// Suspend selection events until a matching ResumeSelectionEvents()
        /// is called.
        /// </summary>
        /// <remarks>Calls to this method nest correctly. Every call to SuspendSelectionEvents()
        /// must have a matching ResumeSelectionEvents().</remarks>
        protected void SuspendSelectionEvents() {
            this.suspendSelectionEventCount++;
        }

        /// <summary>
        /// Resume raising selection events.
        /// </summary>
        protected void ResumeSelectionEvents() {
            Debug.Assert(this.SelectionEventsSuspended, "Mismatched called to ResumeSelectionEvents()");
            this.suspendSelectionEventCount--;
        }

        /// <summary>
        /// Returns a disposable that will disable selection events
        /// during a using() block.
        /// </summary>
        /// <returns></returns>
        protected IDisposable SuspendSelectionEventsDuring() {
            return new SuspendSelectionDisposable(this);
        }

        /// <summary>
        /// Implementation only class that suspends and resumes selection
        /// events on instance creation and disposal.
        /// </summary>
        private class SuspendSelectionDisposable : IDisposable {
            public SuspendSelectionDisposable(ObjectListView objectListView) {
                this.objectListView = objectListView;
                this.objectListView.SuspendSelectionEvents();
            }

            public void Dispose() {
                this.objectListView.ResumeSelectionEvents();
            }

            private readonly ObjectListView objectListView;
        }

        #endregion

        #region Column sorting

        /// <summary>
        /// 按最后一个排序列和顺序对项目进行排序
        /// </summary>
        new public void Sort() {
            this.Sort(this.PrimarySortColumn, this.PrimarySortOrder);
        }

        /// <summary>
        /// 按给定列中的值和上次排序顺序对列表视图中的项进行排序
        /// </summary>
        /// <param name="columnToSortName">其值将用于排序的列的名称</param>
        public virtual void Sort(string columnToSortName) {
            this.Sort(this.GetColumn(columnToSortName), this.PrimarySortOrder);
        }

        /// <summary>
        ///按给定列中的值和上次排序顺序对列表视图中的项进行排序
        /// </summary>
        /// <param name="columnToSortIndex">其值将用于排序的列的索引</param>
        public virtual void Sort(int columnToSortIndex) {
            if (columnToSortIndex >= 0 && columnToSortIndex < this.Columns.Count)
                this.Sort(this.GetColumn(columnToSortIndex), this.PrimarySortOrder);
        }

        /// <summary>
        /// 按给定列中的值和上次排序顺序对列表视图中的项进行排序
        /// </summary>
        /// <param name="columnToSort">其值将用于排序的列</param>
        public virtual void Sort(OLVColumn columnToSort) {
            if (this.InvokeRequired) {
                this.Invoke((MethodInvoker)delegate { this.Sort(columnToSort); });
            } else {
                this.Sort(columnToSort, this.PrimarySortOrder);
            }
        }

        /// <summary>
        /// 按给定列中的值和上次排序顺序对列表视图中的项进行排序
        /// </summary>
        /// <param name="columnToSort">The column whose values will be used for the sorting.
        /// If null, the first column will be used.</param>
        /// <param name="order">The ordering to be used for sorting. If this is None,
        /// this.Sorting and then SortOrder.Ascending will be used</param>
        /// <remarks>If ShowGroups is true, the rows will be grouped by the given column.
        /// If AlwaysGroupsByColumn is not null, the rows will be grouped by that column,
        /// and the rows within each group will be sorted by the given column.</remarks>
        public virtual void Sort(OLVColumn columnToSort, SortOrder order) {
            if (this.InvokeRequired) {
                this.Invoke((MethodInvoker)delegate { this.Sort(columnToSort, order); });
            } else {
                this.DoSort(columnToSort, order);
                this.PostProcessRows();
            }
        }

        private void DoSort(OLVColumn columnToSort, SortOrder order) {
            // Sanity checks
            if (this.GetItemCount() == 0 || this.Columns.Count == 0)
                return;

            // Fill in default values, if the parameters don't make sense
            if (this.ShowGroups) {
                columnToSort = columnToSort ?? this.GetColumn(0);
                if (order == SortOrder.None) {
                    order = this.Sorting;
                    if (order == SortOrder.None)
                        order = SortOrder.Ascending;
                }
            }

            // Give the world a chance to fiddle with or completely avoid the sorting process
            BeforeSortingEventArgs args = this.BuildBeforeSortingEventArgs(columnToSort, order);
            this.OnBeforeSorting(args);
            if (args.Canceled)
                return;

            // Virtual lists don't preserve selection, so we have to do it specifically
            // THINK: Do we need to preserve focus too?
            IList selection = this.VirtualMode ? this.SelectedObjects : null;
            this.SuspendSelectionEvents();

            this.ClearHotItem();

            // Finally, do the work of sorting, unless an event handler has already done the sorting for us
            if (!args.Handled) {
                // Sanity checks
                if (args.ColumnToSort != null && args.SortOrder != SortOrder.None) {
                    if (this.ShowGroups)
                        this.BuildGroups(args.ColumnToGroupBy, args.GroupByOrder, args.ColumnToSort, args.SortOrder,
                            args.SecondaryColumnToSort, args.SecondarySortOrder);
                    else if (this.CustomSorter != null)
                        this.CustomSorter(args.ColumnToSort, args.SortOrder);
                    else
                        this.ListViewItemSorter = new ColumnComparer(args.ColumnToSort, args.SortOrder,
                            args.SecondaryColumnToSort, args.SecondarySortOrder);
                }
            }

            if (this.ShowSortIndicators)
                this.ShowSortIndicator(args.ColumnToSort, args.SortOrder);

            this.PrimarySortColumn = args.ColumnToSort;
            this.PrimarySortOrder = args.SortOrder;

            if (selection != null && selection.Count > 0)
                this.SelectedObjects = selection;
            this.ResumeSelectionEvents();

            this.RefreshHotItem();

            this.OnAfterSorting(new AfterSortingEventArgs(args));
        }

        /// <summary>
        /// Put a sort indicator next to the text of the sort column
        /// </summary>
        public virtual void ShowSortIndicator() {
            if (this.ShowSortIndicators && this.PrimarySortOrder != SortOrder.None)
                this.ShowSortIndicator(this.PrimarySortColumn, this.PrimarySortOrder);
        }

        /// <summary>
        /// Put a sort indicator next to the text of the given given column
        /// </summary>
        /// <param name="columnToSort">The column to be marked</param>
        /// <param name="sortOrder">The sort order in effect on that column</param>
        protected virtual void ShowSortIndicator(OLVColumn columnToSort, SortOrder sortOrder) {
            int imageIndex = -1;

            if (!NativeMethods.HasBuiltinSortIndicators()) {
                // If we can't use builtin image, we have to make and then locate the index of the
                // sort indicator we want to use. SortOrder.None doesn't show an image.
                if (this.SmallImageList == null || !this.SmallImageList.Images.ContainsKey(SORT_INDICATOR_UP_KEY))
                    MakeSortIndicatorImages();

                if (this.SmallImageList != null)
                {
                    string key = sortOrder == SortOrder.Ascending ? SORT_INDICATOR_UP_KEY : SORT_INDICATOR_DOWN_KEY;
                    imageIndex = this.SmallImageList.Images.IndexOfKey(key);
                }
            }

            // Set the image for each column
            for (int i = 0; i < this.Columns.Count; i++) {
                if (columnToSort != null && i == columnToSort.Index)
                    NativeMethods.SetColumnImage(this, i, sortOrder, imageIndex);
                else
                    NativeMethods.SetColumnImage(this, i, SortOrder.None, -1);
            }
        }

        /// <summary>
        /// The name of the image used when a column is sorted ascending
        /// </summary>
        /// <remarks>This image is only used on pre-XP systems. System images are used for XP and later</remarks>
        public const string SORT_INDICATOR_UP_KEY = "sort-indicator-up";

        /// <summary>
        /// The name of the image used when a column is sorted descending
        /// </summary>
        /// <remarks>This image is only used on pre-XP systems. System images are used for XP and later</remarks>
        public const string SORT_INDICATOR_DOWN_KEY = "sort-indicator-down";

        /// <summary>
        /// If the sort indicator images don't already exist, this method will make and install them
        /// </summary>
        protected virtual void MakeSortIndicatorImages() {
            // Don't mess with the image list in design mode
            if (this.DesignMode)
                return;

            ImageList il = this.SmallImageList;
            if (il == null) {
                il = new ImageList();
                il.ImageSize = new Size(16, 16);
                il.ColorDepth = ColorDepth.Depth32Bit;
            }

            // This arrangement of points works well with (16,16) images, and OK with others
            int midX = il.ImageSize.Width / 2;
            int midY = (il.ImageSize.Height / 2) - 1;
            int deltaX = midX - 2;
            int deltaY = deltaX / 2;

            if (il.Images.IndexOfKey(SORT_INDICATOR_UP_KEY) == -1) {
                Point pt1 = new Point(midX - deltaX, midY + deltaY);
                Point pt2 = new Point(midX, midY - deltaY - 1);
                Point pt3 = new Point(midX + deltaX, midY + deltaY);
                il.Images.Add(SORT_INDICATOR_UP_KEY, this.MakeTriangleBitmap(il.ImageSize, new Point[] { pt1, pt2, pt3 }));
            }

            if (il.Images.IndexOfKey(SORT_INDICATOR_DOWN_KEY) == -1) {
                Point pt1 = new Point(midX - deltaX, midY - deltaY);
                Point pt2 = new Point(midX, midY + deltaY);
                Point pt3 = new Point(midX + deltaX, midY - deltaY);
                il.Images.Add(SORT_INDICATOR_DOWN_KEY, this.MakeTriangleBitmap(il.ImageSize, new Point[] { pt1, pt2, pt3 }));
            }

            this.SmallImageList = il;
        }

        private Bitmap MakeTriangleBitmap(Size sz, Point[] pts) {
            Bitmap bm = new Bitmap(sz.Width, sz.Height);
            Graphics g = Graphics.FromImage(bm);
            g.FillPolygon(new SolidBrush(Color.Gray), pts);
            return bm;
        }

        /// <summary>
        /// Remove any sorting and revert to the given order of the model objects
        /// </summary>
        public virtual void Unsort() {
            this.ShowGroups = false;
            this.PrimarySortColumn = null;
            this.PrimarySortOrder = SortOrder.None;
            this.BuildList();
        }

        #endregion

        #region Utilities

        private static CheckState CalculateToggledCheckState(CheckState currentState, bool isTriState, bool isDisabled)
        {
            if (isDisabled)
                return currentState;
            switch (currentState)
            {
                case CheckState.Checked: return isTriState ? CheckState.Indeterminate : CheckState.Unchecked;
                case CheckState.Indeterminate: return CheckState.Unchecked;
                default: return CheckState.Checked;
            }
        }

        /// <summary>
        /// Do the actual work of creating the given list of groups
        /// </summary>
        /// <param name="groups"></param>
        protected virtual void CreateGroups(IEnumerable<OLVGroup> groups) {
            this.Groups.Clear();
            // The group must be added before it is given items, otherwise an exception is thrown (is this documented?)
            foreach (OLVGroup group in groups) {
                group.InsertGroupOldStyle(this);
                group.SetItemsOldStyle();
            }
        }

        /// <summary>
        /// For some reason, UseItemStyleForSubItems doesn't work for the colors
        /// when owner drawing the list, so we have to specifically give each subitem
        /// the desired colors
        /// </summary>
        /// <param name="olvi">The item whose subitems are to be corrected</param>
        /// <remarks>Cells drawn via BaseRenderer don't need this, but it is needed
        /// when an owner drawn cell uses DrawDefault=true</remarks>
        protected virtual void CorrectSubItemColors(ListViewItem olvi) {
        }

        /// <summary>
        /// Fill in the given OLVListItem with values of the given row
        /// </summary>
        /// <param name="lvi">the OLVListItem that is to be stuff with values</param>
        /// <param name="rowObject">the model object from which values will be taken</param>
        protected virtual void FillInValues(OLVListItem lvi, object rowObject) {
            if (this.Columns.Count == 0)
                return;

            OLVListSubItem subItem = this.MakeSubItem(rowObject, this.GetColumn(0));
            lvi.SubItems[0] = subItem;
            lvi.ImageSelector = subItem.ImageSelector;

            // Give the item the same font/colors as the control
            lvi.Font = this.Font;
            lvi.BackColor = this.BackColor;
            lvi.ForeColor = this.ForeColor;

            // Should the row be selectable?
            lvi.Enabled = !this.IsDisabled(rowObject);

            // Only Details and Tile views have subitems
            switch (this.View) {
                case View.Details:
                    for (int i = 1; i < this.Columns.Count; i++) {
                        lvi.SubItems.Add(this.MakeSubItem(rowObject, this.GetColumn(i)));
                    }
                    break;
                case View.Tile:
                    for (int i = 1; i < this.Columns.Count; i++) {
                        OLVColumn column = this.GetColumn(i);
                        if (column.IsTileViewColumn)
                            lvi.SubItems.Add(this.MakeSubItem(rowObject, column));
                    }
                    break;
            }

            // Should the row be selectable?
            if (!lvi.Enabled) {
                lvi.UseItemStyleForSubItems = false;
                ApplyRowStyle(lvi, this.DisabledItemStyle ?? ObjectListView.DefaultDisabledItemStyle);
            }

            // Set the check state of the row, if we are showing check boxes
            if (this.CheckBoxes) {
                CheckState? state = this.GetCheckState(lvi.RowObject);
                if (state.HasValue)
                    lvi.CheckState = state.Value;
            }

            // Give the RowFormatter a chance to mess with the item
            if (this.RowFormatter != null) {
                this.RowFormatter(lvi);
            }
        }

        private OLVListSubItem MakeSubItem(object rowObject, OLVColumn column) {
            object cellValue = column.GetValue(rowObject);
            OLVListSubItem subItem = new OLVListSubItem(cellValue,
                                                        column.ValueToString(rowObject,cellValue),
                                                        column.GetImage(rowObject));
            if (this.UseHyperlinks && column.Hyperlink) {
                IsHyperlinkEventArgs args = new IsHyperlinkEventArgs();
                args.ListView = this;
                args.Model = rowObject;
                args.Column = column;
                args.Text = subItem.Text;
                args.Url = subItem.Text;
                args.IsHyperlink = !this.IsDisabled(rowObject);
                this.OnIsHyperlink(args);
                subItem.Url = args.IsHyperlink ? args.Url : null;
            }

            return subItem;
        }

        private void ApplyHyperlinkStyle(OLVListItem olvi) {

            for (int i = 0; i < this.Columns.Count; i++) {
                OLVListSubItem subItem = olvi.GetSubItem(i);
                if (subItem == null)
                    continue;
                OLVColumn column = this.GetColumn(i);
                if (column.Hyperlink && !String.IsNullOrEmpty(subItem.Url)) 
                    this.ApplyCellStyle(olvi, i, this.IsUrlVisited(subItem.Url) ? this.HyperlinkStyle.Visited : this.HyperlinkStyle.Normal);
            }
        }


        /// <summary>
        /// Make sure the ListView has the extended style that says to display subitem images.
        /// </summary>
        /// <remarks>This method must be called after any .NET call that update the extended styles
        /// since they seem to erase this setting.</remarks>
        protected virtual void ForceSubItemImagesExStyle() {
            // Virtual lists can't show subitem images natively, so don't turn on this flag
            if (!this.VirtualMode)
                NativeMethods.ForceSubItemImagesExStyle(this);
        }

        /// <summary>
        /// Convert the given image selector to an index into our image list.
        /// Return -1 if that's not possible
        /// </summary>
        /// <param name="imageSelector"></param>
        /// <returns>Index of the image in the imageList, or -1</returns>
        protected virtual int GetActualImageIndex(Object imageSelector) {
            if (imageSelector == null)
                return -1;

            if (imageSelector is Int32)
                return (int)imageSelector;

            String imageSelectorAsString = imageSelector as String;
            if (imageSelectorAsString != null && this.SmallImageList != null)
                return this.SmallImageList.Images.IndexOfKey(imageSelectorAsString);

            return -1;
        }

        /// <summary>
        /// Return the tooltip that should be shown when the mouse is hovered over the given column
        /// </summary>
        /// <param name="columnIndex">The column index whose tool tip is to be fetched</param>
        /// <returns>A string or null if no tool tip is to be shown</returns>
        public virtual String GetHeaderToolTip(int columnIndex) {
            OLVColumn column = this.GetColumn(columnIndex);
            if (column == null)
                return null;
            String tooltip = column.ToolTipText;
            if (this.HeaderToolTipGetter != null)
                tooltip = this.HeaderToolTipGetter(column);
            return tooltip;
        }

        /// <summary>
        /// 返回当鼠标悬停在给定单元格上时应显示的工具提示
        /// </summary>
        /// <param name="columnIndex">The column index whose tool tip is to be fetched</param>
        /// <param name="rowIndex">The row index whose tool tip is to be fetched</param>
        /// <returns>A string or null if no tool tip is to be shown</returns>
        public virtual String GetCellToolTip(int columnIndex, int rowIndex) {
            if (this.CellToolTipGetter != null)
                return this.CellToolTipGetter(this.GetColumn(columnIndex), this.GetModelObject(rowIndex));

            // Show the URL in the tooltip if it's different to the text
            if (columnIndex >= 0) {
                OLVListSubItem subItem = this.GetSubItem(rowIndex, columnIndex);
                if (subItem != null && !String.IsNullOrEmpty(subItem.Url) && subItem.Url != subItem.Text &&
                    this.HotCellHitLocation == HitTestLocation.Text)
                    return subItem.Url;
            }

            return null;
        }

        /// <summary>
        /// 返回显示给定模型对象的OLVListItem
        /// </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>
        public virtual OLVListItem ModelToItem(object modelObject) {
            if (modelObject == null)
                return null;

            foreach (OLVListItem olvi in this.Items) {
                if (olvi.RowObject != null && olvi.RowObject.Equals(modelObject))
                    return olvi;
            }
            return null;
        }

        /// <summary>
        /// Do the work required after the items in a listview have been created
        /// </summary>
        protected virtual void PostProcessRows() {
            // If this method is called during a BeginUpdate/EndUpdate pair, changes to the
            // Items collection are cached. Getting the Count flushes that cache.
#pragma warning disable 168
// ReSharper disable once UnusedVariable
            int count = this.Items.Count;
#pragma warning restore 168

            int i = 0;
            if (this.ShowGroups) {
                foreach (ListViewGroup group in this.Groups) {
                    foreach (OLVListItem olvi in group.Items) {
                        this.PostProcessOneRow(olvi.Index, i, olvi);
                        i++;
                    }
                }
            } else {
                foreach (OLVListItem olvi in this.Items) {
                    this.PostProcessOneRow(olvi.Index, i, olvi);
                    i++;
                }
            }
        }

        /// <summary>
        /// Do the work required after one item in a listview have been created
        /// </summary>
        protected virtual void PostProcessOneRow(int rowIndex, int displayIndex, OLVListItem olvi) {
            if (this.UseAlternatingBackColors && this.View == View.Details && olvi.Enabled) {
                olvi.UseItemStyleForSubItems = true;
                olvi.BackColor = displayIndex % 2 == 1 ? this.AlternateRowBackColorOrDefault : this.BackColor;
            }
            if (this.ShowImagesOnSubItems && !this.VirtualMode) 
                this.SetSubItemImages(rowIndex, olvi);

            bool needToTriggerFormatCellEvents = this.TriggerFormatRowEvent(rowIndex, displayIndex, olvi);

            // We only need cell level events if we are in details view
            if (this.View != View.Details)
                return;

            // If we're going to have per cell formatting, we need to copy the formatting
            // of the item into each cell, before triggering the cell format events
            if (needToTriggerFormatCellEvents) {
                PropagateFormatFromRowToCells(olvi);
                this.TriggerFormatCellEvents(rowIndex, displayIndex, olvi);
            }

            // Similarly, if any cell in the row has hyperlinks, we have to copy formatting
            // from the item into each cell before applying the hyperlink style
            if (this.UseHyperlinks && olvi.HasAnyHyperlinks) {
                PropagateFormatFromRowToCells(olvi);
                this.ApplyHyperlinkStyle(olvi);
            }
        }

        /// <summary>
        /// 准备列表视图以显示交替的行背景色
        /// </summary>
        /// <remarks>We cannot rely on lvi.Index in this method.
        /// In a straight list, lvi.Index is the display index, and can be used to determine
        /// whether the row should be colored. But when organised by groups, lvi.Index is not
        /// useable because it still refers to the position in the overall list, not the display order.
        ///</remarks>
        [Obsolete("不再使用此方法。重写PostProcessOneRow()以获得类似的结果")]
        protected virtual void PrepareAlternateBackColors() {
        }

        /// <summary>
        /// Setup all subitem images on all rows
        /// </summary>
        [Obsolete("This method is not longer maintained and will be removed", false)]
        protected virtual void SetAllSubItemImages() {
            //if (!this.ShowImagesOnSubItems || this.OwnerDraw)
            //    return;

            //this.ForceSubItemImagesExStyle();

            //for (int rowIndex = 0; rowIndex < this.GetItemCount(); rowIndex++)
            //    SetSubItemImages(rowIndex, this.GetItem(rowIndex));
        }

        /// <summary>
        /// 告诉基础列表控件针对子项显示哪些图像
        /// </summary>
        /// <param name="rowIndex">the index at which the item occurs</param>
        /// <param name="item">the item whose subitems are to be set</param>
        protected virtual void SetSubItemImages(int rowIndex, OLVListItem item) {
            this.SetSubItemImages(rowIndex, item, false);
        }

        /// <summary>
        /// Tell the underlying list control which images to show against the subitems
        /// </summary>
        /// <param name="rowIndex">the index at which the item occurs</param>
        /// <param name="item">the item whose subitems are to be set</param>
        /// <param name="shouldClearImages">will existing images be cleared if no new image is provided?</param>
        protected virtual void SetSubItemImages(int rowIndex, OLVListItem item, bool shouldClearImages) {
            if (!this.ShowImagesOnSubItems || this.OwnerDraw)
                return;

            for (int i = 1; i < item.SubItems.Count; i++) {
                this.SetSubItemImage(rowIndex, i, item.GetSubItem(i), shouldClearImages);
            }
        }

        /// <summary>
        /// Set the subitem image natively
        /// </summary>
        /// <param name="rowIndex"></param>
        /// <param name="subItemIndex"></param>
        /// <param name="subItem"></param>
        /// <param name="shouldClearImages"></param>
        public virtual void SetSubItemImage(int rowIndex, int subItemIndex, OLVListSubItem subItem, bool shouldClearImages) {
            int imageIndex = this.GetActualImageIndex(subItem.ImageSelector);
            if (shouldClearImages || imageIndex != -1)
                NativeMethods.SetSubItemImage(this, rowIndex, subItemIndex, imageIndex);
        }

        /// <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>
        protected virtual void TakeOwnershipOfObjects() {
            if (this.isOwnerOfObjects)
                return;

            this.isOwnerOfObjects = true;

            this.objects = ObjectListView.EnumerableToArray(this.objects, true);
        }

        /// <summary>
        /// 触发给定项目的FormatRow和可能的FormatCell事件
        /// </summary>
        /// <param name="rowIndex"></param>
        /// <param name="displayIndex"></param>
        /// <param name="olvi"></param>
        protected virtual bool TriggerFormatRowEvent(int rowIndex, int displayIndex, OLVListItem olvi) {
            FormatRowEventArgs args = new FormatRowEventArgs();
            args.ListView = this;
            args.RowIndex = rowIndex;
            args.DisplayIndex = displayIndex;
            args.Item = olvi;
            args.UseCellFormatEvents = this.UseCellFormatEvents;
            this.OnFormatRow(args);
            return args.UseCellFormatEvents;
        }

        /// <summary>
        /// 触发给定项目的FormatCell事件
        /// </summary>
        /// <param name="rowIndex"></param>
        /// <param name="displayIndex"></param>
        /// <param name="olvi"></param>
        protected virtual void TriggerFormatCellEvents(int rowIndex, int displayIndex, OLVListItem olvi) {

            PropagateFormatFromRowToCells(olvi);

            // Fire one event per cell
            FormatCellEventArgs args2 = new FormatCellEventArgs();
            args2.ListView = this;
            args2.RowIndex = rowIndex;
            args2.DisplayIndex = displayIndex;
            args2.Item = olvi;
            for (int i = 0; i < this.Columns.Count; i++) {
                args2.ColumnIndex = i;
                args2.Column = this.GetColumn(i);
                args2.SubItem = olvi.GetSubItem(i);
                this.OnFormatCell(args2);
            }
        }

        private static void PropagateFormatFromRowToCells(OLVListItem olvi) {
            // If a cell isn't given its own colors, it *should* use the colors of the item.
            // However, there is a bug in the .NET framework where the cell are given
            // the colors of the ListView instead of the colors of the row. 

            // If we've already done this, don't do it again
            if (olvi.UseItemStyleForSubItems == false)
                return;

            // So we have to explicitly give each cell the fore and back colors and the font that it should have.
            olvi.UseItemStyleForSubItems = false;
            Color backColor = olvi.BackColor;
            Color foreColor = olvi.ForeColor;
            Font font = olvi.Font;
            foreach (OLVListSubItem subitem in olvi.SubItems) {
                subitem.BackColor = backColor;
                subitem.ForeColor = foreColor;
                subitem.Font = font;
            }
        }

        /// <summary>
        /// Make the list forget everything -- all rows and all columns
        /// </summary>
        /// <remarks>Use <see cref="ClearObjects"/> if you want to remove just the rows.</remarks>
        public virtual void Reset() {
            this.Clear();
            this.AllColumns.Clear();
            this.ClearObjects();
            this.PrimarySortColumn = null;
            this.SecondarySortColumn = null;
            this.ClearDisabledObjects();
            this.ClearPersistentCheckState();
            this.ClearUrlVisited();
            this.ClearHotItem();
        }


        #endregion

        #region ISupportInitialize Members

        void ISupportInitialize.BeginInit() {
            this.Frozen = true;
        }

        void ISupportInitialize.EndInit() {
            if (this.RowHeight != -1) {
                this.SmallImageList = this.SmallImageList;
                if (this.CheckBoxes)
                    this.InitializeStateImageList();
            }

            if (this.UseSubItemCheckBoxes || (this.VirtualMode && this.CheckBoxes))
                this.SetupSubItemCheckBoxes();

            this.Frozen = false;
        }

        #endregion

        #region Image list manipulation

        /// <summary>
        /// Update our externally visible image list so it holds the same images as our shadow list, but sized correctly
        /// </summary>
        private void SetupBaseImageList() {
            // If a row height hasn't been set, or an image list has been give which is the required size, just assign it
            if (rowHeight == -1 ||
                this.View != View.Details ||
                (this.shadowedImageList != null && this.shadowedImageList.ImageSize.Height == rowHeight))
                this.BaseSmallImageList = this.shadowedImageList;
            else {
                int width = (this.shadowedImageList == null ? 16 : this.shadowedImageList.ImageSize.Width);
                this.BaseSmallImageList = this.MakeResizedImageList(width, rowHeight, shadowedImageList);
            }
        }

        /// <summary>
        /// Return a copy of the given source image list, where each image has been resized to be height x height in size.
        /// If source is null, an empty image list of the given size is returned
        /// </summary>
        /// <param name="width">Height and width of the new images</param>
        /// <param name="height">Height and width of the new images</param>
        /// <param name="source">Source of the images (can be null)</param>
        /// <returns>A new image list</returns>
        private ImageList MakeResizedImageList(int width, int height, ImageList source) {
            ImageList il = new ImageList();
            il.ImageSize = new Size(width, height);

            // If there's nothing to copy, just return the new list
            if (source == null)
                return il;

            il.TransparentColor = source.TransparentColor;
            il.ColorDepth = source.ColorDepth;

            // Fill the imagelist with resized copies from the source
            for (int i = 0; i < source.Images.Count; i++) {
                Bitmap bm = this.MakeResizedImage(width, height, source.Images[i], source.TransparentColor);
                il.Images.Add(bm);
            }

            // Give each image the same key it has in the original
            foreach (String key in source.Images.Keys) {
                il.Images.SetKeyName(source.Images.IndexOfKey(key), key);
            }

            return il;
        }

        /// <summary>
        /// Return a bitmap of the given height x height, which shows the given image, centred.
        /// </summary>
        /// <param name="width">Height and width of new bitmap</param>
        /// <param name="height">Height and width of new bitmap</param>
        /// <param name="image">Image to be centred</param>
        /// <param name="transparent">The background color</param>
        /// <returns>A new bitmap</returns>
        private Bitmap MakeResizedImage(int width, int height, Image image, Color transparent) {
            Bitmap bm = new Bitmap(width, height);
            Graphics g = Graphics.FromImage(bm);
            g.Clear(transparent);
            int x = Math.Max(0, (bm.Size.Width - image.Size.Width) / 2);
            int y = Math.Max(0, (bm.Size.Height - image.Size.Height) / 2);
            g.DrawImage(image, x, y, image.Size.Width, image.Size.Height);
            return bm;
        }

        /// <summary>
        /// Initialize the state image list with the required checkbox images
        /// </summary>
        protected virtual void InitializeStateImageList() {
            if (this.DesignMode)
                return;

            if (!this.CheckBoxes)
                return;

            if (this.StateImageList == null) {
                this.StateImageList = new ImageList();
                this.StateImageList.ImageSize = new Size(16, this.RowHeight == -1 ? 25 : this.RowHeight);
                this.StateImageList.ColorDepth = ColorDepth.Depth32Bit;
            }

            if (this.RowHeight != -1 &&
                this.View == View.Details &&
                this.StateImageList.ImageSize.Height != this.RowHeight) {
                this.StateImageList = new ImageList();
                this.StateImageList.ImageSize = new Size(16, this.RowHeight);
                this.StateImageList.ColorDepth = ColorDepth.Depth32Bit;
            }

            // The internal logic of ListView cycles through the state images when the primary
            // checkbox is clicked. So we have to get exactly the right number of images in the 
            // image list.
            if (this.StateImageList.Images.Count == 0)
                this.AddCheckStateBitmap(this.StateImageList, UNCHECKED_KEY, CheckBoxState.UncheckedNormal);
            if (this.StateImageList.Images.Count <= 1)
                this.AddCheckStateBitmap(this.StateImageList, CHECKED_KEY, CheckBoxState.CheckedNormal);
            if (this.TriStateCheckBoxes && this.StateImageList.Images.Count <= 2)
                this.AddCheckStateBitmap(this.StateImageList, INDETERMINATE_KEY, CheckBoxState.MixedNormal);
            else {
                if (this.StateImageList.Images.ContainsKey(INDETERMINATE_KEY))
                    this.StateImageList.Images.RemoveByKey(INDETERMINATE_KEY);
            }
        }

        /// <summary>
        /// The name of the image used when a check box is checked
        /// </summary>
        public const string CHECKED_KEY = "checkbox-checked";

        /// <summary>
        /// The name of the image used when a check box is unchecked
        /// </summary>
        public const string UNCHECKED_KEY = "checkbox-unchecked";

        /// <summary>
        /// The name of the image used when a check box is Indeterminate
        /// </summary>
        public const string INDETERMINATE_KEY = "checkbox-indeterminate";

        /// <summary>
        /// Setup this control so it can display check boxes on subitems
        /// (or primary checkboxes in virtual mode)
        /// </summary>
        /// <remarks>This gives the ListView a small image list, if it doesn't already have one.</remarks>
        public virtual void SetupSubItemCheckBoxes() {
            this.ShowImagesOnSubItems = true;
            if (this.SmallImageList == null || !this.SmallImageList.Images.ContainsKey(CHECKED_KEY))
                this.InitializeSubItemCheckBoxImages();
        }

        /// <summary>
        /// Make sure the small image list for this control has checkbox images 
        /// (used for sub-item checkboxes).
        /// </summary>
        /// <remarks>
        /// <para>
        /// This gives the ListView a small image list, if it doesn't already have one.
        /// </para>
        /// <para>
        /// ObjectListView has to manage checkboxes on subitems separate from the checkboxes on each row.
        /// The underlying ListView knows about the per-row checkboxes, and to make them work, OLV has to 
        /// correctly configure the StateImageList. However, the ListView cannot do checkboxes in subitems,
        /// so ObjectListView has to handle them in a differnt fashion. So, per-row checkboxes are controlled
        /// by images in the StateImageList, but per-cell checkboxes are handled by images in the SmallImageList.
        /// </para>
        /// </remarks>
        protected virtual void InitializeSubItemCheckBoxImages() {
            // Don't mess with the image list in design mode
            if (this.DesignMode)
                return;

            ImageList il = this.SmallImageList;
            if (il == null) {
                il = new ImageList();
                il.ImageSize = new Size(16, 16);
                il.ColorDepth = ColorDepth.Depth32Bit;
            }

            this.AddCheckStateBitmap(il, CHECKED_KEY, CheckBoxState.CheckedNormal);
            this.AddCheckStateBitmap(il, UNCHECKED_KEY, CheckBoxState.UncheckedNormal);
            this.AddCheckStateBitmap(il, INDETERMINATE_KEY, CheckBoxState.MixedNormal);

            this.SmallImageList = il;
        }

        private void AddCheckStateBitmap(ImageList il, string key, CheckBoxState boxState) {
            Bitmap b = new Bitmap(il.ImageSize.Width, il.ImageSize.Height);
            Graphics g = Graphics.FromImage(b);
            g.Clear(il.TransparentColor);
            Point location = new Point(b.Width / 2 - 5, b.Height / 2 - 6);
            CheckBoxRenderer.DrawCheckBox(g, location, boxState);
            il.Images.Add(key, b);
        }

        #endregion

        #region Owner drawing

        /// <summary>
        /// Owner draw the column header
        /// </summary>
        /// <param name="e"></param>
        protected override void OnDrawColumnHeader(DrawListViewColumnHeaderEventArgs e) {
            e.DrawDefault = true;
            base.OnDrawColumnHeader(e);
        }

        /// <summary>
        /// Owner draw the item
        /// </summary>
        /// <param name="e"></param>
        protected override void OnDrawItem(DrawListViewItemEventArgs e) {
            if (this.View == View.Details)
                e.DrawDefault = false;
            else {
                if (this.ItemRenderer == null)
                    e.DrawDefault = true;
                else {
                    Object row = ((OLVListItem)e.Item).RowObject;
                    e.DrawDefault = !this.ItemRenderer.RenderItem(e, e.Graphics, e.Bounds, row);
                }
            }

            if (e.DrawDefault)
                base.OnDrawItem(e);
        }

        /// <summary>
        /// Owner draw a single subitem
        /// </summary>
        /// <param name="e"></param>
        protected override void OnDrawSubItem(DrawListViewSubItemEventArgs e) {
            //System.Diagnostics.Debug.WriteLine(String.Format("OnDrawSubItem ({0}, {1})", e.ItemIndex, e.ColumnIndex));
            // Don't try to do owner drawing at design time
            if (this.DesignMode) {
                e.DrawDefault = true;
                return;
            }

            object rowObject = ((OLVListItem)e.Item).RowObject;

            // Calculate where the subitem should be drawn
            Rectangle r = e.Bounds;

            // Get the special renderer for this column. If there isn't one, use the default draw mechanism.
            OLVColumn column = this.GetColumn(e.ColumnIndex);
            IRenderer renderer = this.GetCellRenderer(rowObject, column);

            // Get a graphics context for the renderer to use.
            // But we have more complications. Virtual lists have a nasty habit of drawing column 0
            // whenever there is any mouse move events over a row, and doing it in an un-double-buffered manner,
            // which results in nasty flickers! There are also some unbuffered draw when a mouse is first
            // hovered over column 0 of a normal row. So, to avoid all complications,
            // we always manually double-buffer the drawing.
            // Except with Mono, which doesn't seem to handle double buffering at all :-(
            BufferedGraphics buffer = BufferedGraphicsManager.Current.Allocate(e.Graphics, r);
            Graphics g = buffer.Graphics;

            g.TextRenderingHint = ObjectListView.TextRenderingHint;
            g.SmoothingMode = ObjectListView.SmoothingMode;

            // Finally, give the renderer a chance to draw something
            e.DrawDefault = !renderer.RenderSubItem(e, g, r, rowObject);

            if (!e.DrawDefault)
                buffer.Render();
            buffer.Dispose();
        }

        #endregion

        #region OnEvent Handling

        /// <summary>
        /// We need the click count in the mouse up event, but that is always 1.
        /// So we have to remember the click count from the preceding mouse down event.
        /// </summary>
        /// <param name="e"></param>
        protected override void OnMouseDown(MouseEventArgs e) {
            this.lastMouseDownClickCount = e.Clicks;
            base.OnMouseDown(e);
        }
        private int lastMouseDownClickCount;

        /// <summary>
        /// When the mouse leaves the control, remove any hot item highlighting
        /// </summary>
        /// <param name="e"></param>
        protected override void OnMouseLeave(EventArgs e) {
            base.OnMouseLeave(e);

            if (!this.Created)
                return;

            this.UpdateHotItem(new Point(-1,-1));
        }

        // We could change the hot item on the mouse hover event, but it looks wrong.

        //protected override void OnMouseHover(EventArgs e) {
        //    System.Diagnostics.Debug.WriteLine(String.Format("OnMouseHover"));
        //    base.OnMouseHover(e);
        //    this.UpdateHotItem(this.PointToClient(Cursor.Position));
        //}

        /// <summary>
        /// When the mouse moves, we might need to change the hot item.
        /// </summary>
        /// <param name="e"></param>
        protected override void OnMouseMove(MouseEventArgs e) {
            base.OnMouseMove(e);

            if (this.Created)
                HandleMouseMove(e.Location);
        }

        internal void HandleMouseMove(Point pt) {

            //System.Diagnostics.Debug.WriteLine(String.Format("HandleMouseMove: {0}", pt));

            CellOverEventArgs args = new CellOverEventArgs();
            this.BuildCellEvent(args, pt);
            this.OnCellOver(args);
            this.MouseMoveHitTest = args.HitTest;

            if (!args.Handled)
                this.UpdateHotItem(args.HitTest);
        }

        /// <summary>
        /// Check to see if we need to start editing a cell
        /// </summary>
        /// <param name="e"></param>
        protected override void OnMouseUp(MouseEventArgs e) {

            //System.Diagnostics.Debug.WriteLine(String.Format("OnMouseUp"));

            base.OnMouseUp(e);

            if (!this.Created)
                return;

            if (e.Button == MouseButtons.Right) {
                this.OnRightMouseUp(e);
                return;
            }

            // What event should we listen for to start cell editing?
            // ------------------------------------------------------
            //
            // We can't use OnMouseClick, OnMouseDoubleClick, OnClick, or OnDoubleClick
            // since they are not triggered for clicks on subitems without Full Row Select.
            //
            // We could use OnMouseDown, but selecting rows is done in OnMouseUp. This means
            // that if we start the editing during OnMouseDown, the editor will automatically
            // lose focus when mouse up happens.
            //

            // Tell the world about a cell click. If someone handles it, don't do anything else
            CellClickEventArgs args = new CellClickEventArgs();
            this.BuildCellEvent(args, e.Location);
            args.ClickCount = this.lastMouseDownClickCount;
            this.OnCellClick(args);
            if (args.Handled)
                return;

            // Did the user click a hyperlink?
            if (this.UseHyperlinks &&
                args.HitTest.HitTestLocation == HitTestLocation.Text &&
                args.SubItem != null &&
                !String.IsNullOrEmpty(args.SubItem.Url)) {
                // We have to delay the running of this process otherwise we can generate
                // a series of MouseUp events (don't ask me why)
                this.BeginInvoke((MethodInvoker)delegate { this.ProcessHyperlinkClicked(args); });
            }

            // No one handled it so check to see if we should start editing.
            if (!this.ShouldStartCellEdit(e))
                return;

            // We only start the edit if the user clicked on the image or text.
            if (args.HitTest.HitTestLocation == HitTestLocation.Nothing) 
                return;

            // We don't edit the primary column by single clicks -- only subitems.
            if (this.CellEditActivation == CellEditActivateMode.SingleClick && args.ColumnIndex <= 0) 
                return;

            // Don't start a cell edit operation when the user clicks on the background of a checkbox column -- it just looks wrong.
            // If the user clicks on the actual checkbox, changing the checkbox state is handled elsewhere.
            if (args.Column != null && args.Column.CheckBoxes)
                return;

            this.EditSubItem(args.Item, args.ColumnIndex);
        }

        /// <summary>
        /// Tell the world that a hyperlink was clicked and if the event isn't handled,
        /// do the default processing.
        /// </summary>
        /// <param name="e"></param>
        protected virtual void ProcessHyperlinkClicked(CellClickEventArgs e) {
            HyperlinkClickedEventArgs args = new HyperlinkClickedEventArgs();
            args.HitTest = e.HitTest;
            args.ListView = this;
            args.Location = new Point(-1, -1);
            args.Item = e.Item;
            args.SubItem = e.SubItem;
            args.Model = e.Model;
            args.ColumnIndex = e.ColumnIndex;
            args.Column = e.Column;
            args.RowIndex = e.RowIndex;
            args.ModifierKeys = Control.ModifierKeys;
            args.Url = e.SubItem.Url;
            this.OnHyperlinkClicked(args);
            if (!args.Handled) {
                this.StandardHyperlinkClickedProcessing(args);
            }
        }

        /// <summary>
        /// Do the default processing for a hyperlink clicked event, which
        /// is to try and open the url.
        /// </summary>
        /// <param name="args"></param>
        protected virtual void StandardHyperlinkClickedProcessing(HyperlinkClickedEventArgs args) {
            Cursor originalCursor = this.Cursor;
            try {
                this.Cursor = Cursors.WaitCursor;
                System.Diagnostics.Process.Start(args.Url);
            } catch (Win32Exception) {
                System.Media.SystemSounds.Beep.Play();
                // ignore it
            } finally {
                this.Cursor = originalCursor;
            }
            this.MarkUrlVisited(args.Url);
            this.RefreshHotItem();
        }

        /// <summary>
        /// The user right clicked on the control
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnRightMouseUp(MouseEventArgs e) {
            CellRightClickEventArgs args = new CellRightClickEventArgs();
            this.BuildCellEvent(args, e.Location);
            this.OnCellRightClick(args);
            if (!args.Handled) {
                if (args.MenuStrip != null) {
                    args.MenuStrip.Show(this, args.Location);
                }
            }
        }

        internal void BuildCellEvent(CellEventArgs args, Point location) {
            BuildCellEvent(args, location, this.OlvHitTest(location.X, location.Y));
        }

        internal void BuildCellEvent(CellEventArgs args, Point location, OlvListViewHitTestInfo hitTest) {
            args.HitTest = hitTest;
            args.ListView = this;
            args.Location = location;
            args.Item = hitTest.Item;
            args.SubItem = hitTest.SubItem;
            args.Model = hitTest.RowObject;
            args.ColumnIndex = hitTest.ColumnIndex;
            args.Column = hitTest.Column;
            if (hitTest.Item != null)
                args.RowIndex = hitTest.Item.Index;
            args.ModifierKeys = Control.ModifierKeys;

            // In non-details view, we want any hit on an item to act as if it was a hit
            // on column 0 -- which, effectively, it was.
            if (args.Item != null && args.ListView.View != View.Details) {
                args.ColumnIndex = 0;
                args.Column = args.ListView.GetColumn(0);
                args.SubItem = args.Item.GetSubItem(0);
            }
        }

        /// <summary>
        /// This method is called every time a row is selected or deselected. This can be
        /// a pain if the user shift-clicks 100 rows. We override this method so we can
        /// trigger one event for any number of select/deselects that come from one user action
        /// </summary>
        /// <param name="e"></param>
        protected override void OnSelectedIndexChanged(EventArgs e) {
            if (this.SelectionEventsSuspended)
                return;

            base.OnSelectedIndexChanged(e);

            // If we haven't already scheduled an event, schedule it to be triggered
            // By using idle time, we will wait until all select events for the same
            // user action have finished before triggering the event.
            if (!this.hasIdleHandler) {
                this.hasIdleHandler = true;
                this.RunWhenIdle(HandleApplicationIdle);
            }
        }

        /// <summary>
        /// Called when the handle of the underlying control is created
        /// </summary>
        /// <param name="e"></param>
        protected override void OnHandleCreated(EventArgs e) {
            //Debug.WriteLine("OnHandleCreated");
            base.OnHandleCreated(e);

            this.Invoke((MethodInvoker)this.OnControlCreated);
        }

        /// <summary>
        /// This method is called after the control has been fully created.
        /// </summary>
        protected virtual void OnControlCreated() {

            //Debug.WriteLine("OnControlCreated");

            // Force the header control to be created when the listview handle is
            HeaderControl hc = this.HeaderControl;
            hc.WordWrap = this.HeaderWordWrap;

            // Make sure any overlays that are set on the hot item style take effect
            this.HotItemStyle = this.HotItemStyle;

            // Arrange for any group images to be installed after the control is created
            NativeMethods.SetGroupImageList(this, this.GroupImageList);

            this.UseExplorerTheme = this.UseExplorerTheme;

            this.RememberDisplayIndicies();
            this.SetGroupSpacing();

            if (this.VirtualMode)
                this.ApplyExtendedStyles();
        }

        #endregion

        #region Cell editing

        /// <summary>
        /// Should we start editing the cell in response to the given mouse button event?
        /// </summary>
        /// <param name="e"></param>
        /// <returns></returns>
        protected virtual bool ShouldStartCellEdit(MouseEventArgs e) {
            if (this.IsCellEditing)
                return false;

            if (e.Button != MouseButtons.Left && e.Button != MouseButtons.Right)
                return false;

            if ((Control.ModifierKeys & (Keys.Shift | Keys.Control | Keys.Alt)) != 0)
                return false;

            if (this.lastMouseDownClickCount == 1 && (
                this.CellEditActivation == CellEditActivateMode.SingleClick ||
                this.CellEditActivation == CellEditActivateMode.SingleClickAlways))
                return true;

            return (this.lastMouseDownClickCount == 2 && this.CellEditActivation == CellEditActivateMode.DoubleClick);
        }

        /// <summary>
        /// Handle a key press on this control. We specifically look for F2 which edits the primary column,
        /// or a Tab character during an edit operation, which tries to start editing on the next (or previous) cell.
        /// </summary>
        /// <param name="keyData"></param>
        /// <returns></returns>
        protected override bool ProcessDialogKey(Keys keyData) {

            if (this.IsCellEditing) 
                return this.CellEditKeyEngine.HandleKey(this, keyData);

            // Treat F2 as a request to edit the primary column
            if (keyData == Keys.F2) {
                this.EditSubItem((OLVListItem)this.FocusedItem, 0);
                return base.ProcessDialogKey(keyData);
            }

            // Treat Ctrl-C as Copy To Clipboard. 
            if (this.CopySelectionOnControlC && keyData == (Keys.C | Keys.Control)) {
                this.CopySelectionToClipboard();
                return true;
            }

            // Treat Ctrl-A as Select All.
            if (this.SelectAllOnControlA && keyData == (Keys.A | Keys.Control)) {
                this.SelectAll();
                return true;
            }

            return base.ProcessDialogKey(keyData);
        }
        
        /// <summary>
        /// Start an editing operation on the first editable column of the given model.
        /// </summary>
        /// <param name="rowModel"></param>
        /// <remarks>
        /// <para>
        /// If the model doesn't exist, or there are no editable columns, this method
        /// will do nothing.</para>
        /// <para>
        /// This will start an edit operation regardless of CellActivationMode.
        /// </para>
        /// </remarks>
        public virtual void EditModel(object rowModel) {
            OLVListItem olvItem = this.ModelToItem(rowModel);
            if (olvItem == null)
                return;

            for (int i = 0; i < olvItem.SubItems.Count; i++) {
                if (this.GetColumn(i).IsEditable) {
                    this.StartCellEdit(olvItem, i);
                    return;
                }
            }
        }

        /// <summary>
        /// Begin an edit operation on the given cell.
        /// </summary>
        /// <remarks>This performs various sanity checks and passes off the real work to StartCellEdit().</remarks>
        /// <param name="item">The row to be edited</param>
        /// <param name="subItemIndex">The index of the cell to be edited</param>
        public virtual void EditSubItem(OLVListItem item, int subItemIndex) {
            if (item == null)
                return;

            if (subItemIndex < 0 && subItemIndex >= item.SubItems.Count)
                return;

            if (this.CellEditActivation == CellEditActivateMode.None)
                return;

            if (!this.GetColumn(subItemIndex).IsEditable)
                return;

            if (!item.Enabled)
                return;

            this.StartCellEdit(item, subItemIndex);
        }

        /// <summary>
        /// Really start an edit operation on a given cell. The parameters are assumed to be sane.
        /// </summary>
        /// <param name="item">The row to be edited</param>
        /// <param name="subItemIndex">The index of the cell to be edited</param>
        public virtual void StartCellEdit(OLVListItem item, int subItemIndex) {
            OLVColumn column = this.GetColumn(subItemIndex);
            Control c = this.GetCellEditor(item, subItemIndex);
            Rectangle cellBounds = this.CalculateCellBounds(item, subItemIndex);
            //c.Bounds = this.CalculateCellEditorBounds(item, subItemIndex, c.PreferredSize);
            c.Bounds = cellBounds;
            // Try to align the control as the column is aligned. Not all controls support this property
            Munger.PutProperty(c, "TextAlign", column.TextAlign);

            // Give the control the value from the model
            this.SetControlValue(c, column.GetValue(item.RowObject), column.GetStringValue(item.RowObject));

            // Give the outside world the chance to munge with the process
            this.CellEditEventArgs = new CellEditEventArgs(column, c, cellBounds, item, subItemIndex);
            this.OnCellEditStarting(this.CellEditEventArgs);
            if (this.CellEditEventArgs.Cancel)
                return;

            // The event handler may have completely changed the control, so we need to remember it
            this.cellEditor = this.CellEditEventArgs.Control;
            
            this.Invalidate();
            this.Controls.Add(this.cellEditor);
            this.ConfigureControl();
            this.PauseAnimations(true);
        }
        private Control cellEditor;
        internal CellEditEventArgs CellEditEventArgs;

        /// <summary>
        /// Calculate the bounds of the edit control for the given item/column
        /// </summary>
        /// <param name="item"></param>
        /// <param name="subItemIndex"></param>
        /// <param name="preferredSize"> </param>
        /// <returns></returns>
        public Rectangle CalculateCellEditorBounds(OLVListItem item, int subItemIndex, Size preferredSize) {
            Rectangle r = CalculateCellBounds(item, subItemIndex);

            // Calculate the width of the cell's current contents
            return this.OwnerDraw 
                ? CalculateCellEditorBoundsOwnerDrawn(item, subItemIndex, r, preferredSize) 
                : CalculateCellEditorBoundsStandard(item, subItemIndex, r, preferredSize);
        }

        /// <summary>
        /// Calculate the bounds of the edit control for the given item/column, when the listview
        /// is being owner drawn.
        /// </summary>
        /// <param name="item"></param>
        /// <param name="subItemIndex"></param>
        /// <param name="r"></param>
        /// <param name="preferredSize"> </param>
        /// <returns>A rectangle that is the bounds of the cell editor</returns>
        protected Rectangle CalculateCellEditorBoundsOwnerDrawn(OLVListItem item, int subItemIndex, Rectangle r, Size preferredSize) {
            IRenderer renderer = this.View == View.Details
                ? this.GetCellRenderer(item.RowObject, this.GetColumn(subItemIndex))
                : this.ItemRenderer;

            if (renderer == null)
                return r;

            using (Graphics g = this.CreateGraphics()) {
                return renderer.GetEditRectangle(g, r, item, subItemIndex, preferredSize);
            }
        }

        /// <summary>
        /// Calculate the bounds of the edit control for the given item/column, when the listview
        /// is not being owner drawn.
        /// </summary>
        /// <param name="item"></param>
        /// <param name="subItemIndex"></param>
        /// <param name="cellBounds"></param>
        /// <param name="preferredSize"> </param>
        /// <returns>A rectangle that is the bounds of the cell editor</returns>
        protected Rectangle CalculateCellEditorBoundsStandard(OLVListItem item, int subItemIndex, Rectangle cellBounds, Size preferredSize) {
            if (this.View == View.Tile)
                return cellBounds;

            // Center the editor vertically
            if (cellBounds.Height != preferredSize.Height)
                cellBounds.Y += (cellBounds.Height - preferredSize.Height) / 2;

            // Only Details view needs more processing
            if (this.View != View.Details) 
                return cellBounds;

            // Allow for image (if there is one). 
            int offset = 0;
            object imageSelector = null;
            if (subItemIndex == 0)
                imageSelector = item.ImageSelector;
            else {
                // We only check for subitem images if we are owner drawn or showing subitem images
                if (this.OwnerDraw || this.ShowImagesOnSubItems)
                    imageSelector = item.GetSubItem(subItemIndex).ImageSelector;
            }
            if (this.GetActualImageIndex(imageSelector) != -1) {
                offset += this.SmallImageSize.Width + 2;
            }

            // Allow for checkbox
            if (this.CheckBoxes && this.StateImageList != null && subItemIndex == 0) {
                offset += this.StateImageList.ImageSize.Width + 2;
            }

            // Allow for indent (first column only)
            if (subItemIndex == 0 && item.IndentCount > 0) {
                offset += (this.SmallImageSize.Width * item.IndentCount);
            }

            // Do the adjustment
            if (offset > 0) {
                cellBounds.X += offset;
                cellBounds.Width -= offset;
            }

            return cellBounds;
        }

        /// <summary>
        /// Try to give the given value to the provided control. Fall back to assigning a string
        /// if the value assignment fails.
        /// </summary>
        /// <param name="control">A control</param>
        /// <param name="value">The value to be given to the control</param>
        /// <param name="stringValue">The string to be given if the value doesn't work</param>
        protected virtual void SetControlValue(Control control, Object value, String stringValue) {
            // Handle combobox explicitly
            ComboBox cb = control as ComboBox;
            if (cb != null) {
                if (cb.Created)
                    cb.SelectedValue = value;
                else
                    this.BeginInvoke(new MethodInvoker(delegate {
                        cb.SelectedValue = value;
                    }));
                return;
            }

            if (Munger.PutProperty(control, "Value", value))
                return;

            // There wasn't a Value property, or we couldn't set it, so set the text instead
            try
            {
                String valueAsString = value as String;
                control.Text = valueAsString ?? stringValue;
            }
            catch (ArgumentOutOfRangeException) {
                // The value couldn't be set via the Text property.
            }
        }

        /// <summary>
        /// Setup the given control to be a cell editor
        /// </summary>
        protected virtual void ConfigureControl() {
            this.cellEditor.Validating += new CancelEventHandler(CellEditor_Validating);
            this.cellEditor.Select();
        }

        /// <summary>
        /// Return the value that the given control is showing
        /// </summary>
        /// <param name="control"></param>
        /// <returns></returns>
        protected virtual Object GetControlValue(Control control) {
            if (control == null)
                return null;

            TextBox box = control as TextBox;
            if (box != null)
                return box.Text;

            ComboBox comboBox = control as ComboBox;
            if (comboBox != null)
                return comboBox.SelectedValue;

            CheckBox checkBox = control as CheckBox;
            if (checkBox != null)
                return checkBox.Checked;

            try {
                return control.GetType().InvokeMember("Value", BindingFlags.GetProperty, null, control, null);
            } catch (MissingMethodException) { // Microsoft throws this
                return control.Text;
            } catch (MissingFieldException) { // Mono throws this
                return control.Text;
            }
        }

        /// <summary>
        /// Called when the cell editor could be about to lose focus. Time to commit the change
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected virtual void CellEditor_Validating(object sender, CancelEventArgs e) {
            this.CellEditEventArgs.Cancel = false;
            this.CellEditEventArgs.NewValue = this.GetControlValue(this.cellEditor);
            this.OnCellEditorValidating(this.CellEditEventArgs);

            if (this.CellEditEventArgs.Cancel) {
                this.CellEditEventArgs.Control.Select();
                e.Cancel = true;
            } else
                FinishCellEdit();
        }

        /// <summary>
        /// Return the bounds of the given cell
        /// </summary>
        /// <param name="item">The row to be edited</param>
        /// <param name="subItemIndex">The index of the cell to be edited</param>
        /// <returns>A Rectangle</returns>
        public virtual Rectangle CalculateCellBounds(OLVListItem item, int subItemIndex) {

            // It seems on Win7, GetSubItemBounds() does not have the same problems with
            // column 0 that it did previously.

            // TODO - Check on XP

            if (this.View != View.Details)
                return this.GetItemRect(item.Index, ItemBoundsPortion.Label);
            
            Rectangle r = item.GetSubItemBounds(subItemIndex);
            r.Width -= 1;
            r.Height -= 1;
            return r;

            // We use ItemBoundsPortion.Label rather than ItemBoundsPortion.Item
            // since Label extends to the right edge of the cell, whereas Item gives just the
            // current text width.
            //return this.CalculateCellBounds(item, subItemIndex, ItemBoundsPortion.Label);
        }

        /// <summary>
        /// Return the bounds of the given cell only until the edge of the current text
        /// </summary>
        /// <param name="item">The row to be edited</param>
        /// <param name="subItemIndex">The index of the cell to be edited</param>
        /// <returns>A Rectangle</returns>
        public virtual Rectangle CalculateCellTextBounds(OLVListItem item, int subItemIndex) {
            return this.CalculateCellBounds(item, subItemIndex, ItemBoundsPortion.ItemOnly);
        }

        private Rectangle CalculateCellBounds(OLVListItem item, int subItemIndex, ItemBoundsPortion portion) {
            // SubItem.Bounds works for every subitem, except the first.
            if (subItemIndex > 0)
                return item.GetSubItemBounds(subItemIndex);

            // For non detail views, we just use the requested portion
            Rectangle r = this.GetItemRect(item.Index, portion);
            if (r.Y < -10000000 || r.Y > 10000000) {
                r.Y = item.Bounds.Y;
            }
            if (this.View != View.Details)
                return r;

            // Finding the bounds of cell 0 should not be a difficult task, but it is. Problems:
            // 1) item.SubItem[0].Bounds is always the full bounds of the entire row, not just cell 0.
            // 2) if column 0 has been dragged to some other position, the bounds always has a left edge of 0.

            // We avoid both these problems by using the position of sides the column header to calculate
            // the sides of the cell
            Point sides = NativeMethods.GetScrolledColumnSides(this, 0);
            r.X = sides.X + 4;
            r.Width = sides.Y - sides.X - 5;

            return r;
        }

        /// <summary>
        /// Calculate the visible bounds of the given column. The column's bottom edge is 
        /// either the bottom of the last row or the bottom of the control.
        /// </summary>
        /// <param name="bounds">The bounds of the control itself</param>
        /// <param name="column">The column</param>
        /// <returns>A Rectangle</returns>
        /// <remarks>This returns an empty rectnage if the control isn't in Details mode, 
        /// OR has doesn't have any rows, OR if the given column is hidden.</remarks>
        public virtual Rectangle CalculateColumnVisibleBounds(Rectangle bounds, OLVColumn column)
        {
            // Sanity checks
            if (column == null ||
                this.View != System.Windows.Forms.View.Details ||
                this.GetItemCount() == 0 ||
                !column.IsVisible)
                return Rectangle.Empty;

            Point sides = NativeMethods.GetScrolledColumnSides(this, column.Index);
            if (sides.X == -1)
                return Rectangle.Empty;

            Rectangle columnBounds = new Rectangle(sides.X, bounds.Top, sides.Y - sides.X, bounds.Bottom);

            // Find the bottom of the last item. The column only extends to there.
            OLVListItem lastItem = this.GetLastItemInDisplayOrder();
            if (lastItem != null)
            {
                Rectangle lastItemBounds = lastItem.Bounds;
                if (!lastItemBounds.IsEmpty && lastItemBounds.Bottom < columnBounds.Bottom)
                    columnBounds.Height = lastItemBounds.Bottom - columnBounds.Top;
            }

            return columnBounds;
        }

        /// <summary>
        /// Return a control that can be used to edit the value of the given cell.
        /// </summary>
        /// <param name="item">The row to be edited</param>
        /// <param name="subItemIndex">The index of the cell to be edited</param>
        /// <returns>A Control to edit the given cell</returns>
        protected virtual Control GetCellEditor(OLVListItem item, int subItemIndex) {
            OLVColumn column = this.GetColumn(subItemIndex);
            Object value = column.GetValue(item.RowObject) ?? this.GetFirstNonNullValue(column);

            // TODO: What do we do if value is still null here?

            // Ask the registry for an instance of the appropriate editor.
            // Use a default editor if the registry can't create one for us.
            Control editor = ObjectListView.EditorRegistry.GetEditor(item.RowObject, column, value) ??
                             this.MakeDefaultCellEditor(column);

            return editor;
        }

        /// <summary>
        /// Get the first non-null value of the given column.
        /// At most 1000 rows will be considered.
        /// </summary>
        /// <param name="column"></param>
        /// <returns>The first non-null value, or null if no non-null values were found</returns>
        internal object GetFirstNonNullValue(OLVColumn column) {
            for (int i = 0; i < Math.Min(this.GetItemCount(), 1000); i++) {
                object value = column.GetValue(this.GetModelObject(i));
                if (value != null)
                    return value;
            }
            return null;
        }

        /// <summary>
        /// Return a TextBox that can be used as a default cell editor.
        /// </summary>
        /// <param name="column">What column does the cell belong to?</param>
        /// <returns></returns>
        protected virtual Control MakeDefaultCellEditor(OLVColumn column) {
            TextBox tb = new TextBox();
            if (column.AutoCompleteEditor)
                this.ConfigureAutoComplete(tb, column);
            return tb;
        }

        /// <summary>
        /// Configure the given text box to autocomplete unique values
        /// from the given column. At most 1000 rows will be considered.
        /// </summary>
        /// <param name="tb">The textbox to configure</param>
        /// <param name="column">The column used to calculate values</param>
        public void ConfigureAutoComplete(TextBox tb, OLVColumn column) {
            this.ConfigureAutoComplete(tb, column, 1000);
        }


        /// <summary>
        /// Configure the given text box to autocomplete unique values
        /// from the given column. At most 1000 rows will be considered.
        /// </summary>
        /// <param name="tb">The textbox to configure</param>
        /// <param name="column">The column used to calculate values</param>
        /// <param name="maxRows">Consider only this many rows</param>
        public void ConfigureAutoComplete(TextBox tb, OLVColumn column, int maxRows) {
            // Don't consider more rows than we actually have
            maxRows = Math.Min(this.GetItemCount(), maxRows);

            // Reset any existing autocomplete
            tb.AutoCompleteCustomSource.Clear();

            // CONSIDER: Should we use ClusteringStrategy here?

            // Build a list of unique values, to be used as autocomplete on the editor
            Dictionary<string, bool> alreadySeen = new Dictionary<string, bool>();
            List<string> values = new List<string>();
            for (int i = 0; i < maxRows; i++) {
                string valueAsString = column.GetStringValue(this.GetModelObject(i));
                if (!String.IsNullOrEmpty(valueAsString) && !alreadySeen.ContainsKey(valueAsString)) {
                    values.Add(valueAsString);
                    alreadySeen[valueAsString] = true;
                }
            }

            tb.AutoCompleteCustomSource.AddRange(values.ToArray());
            tb.AutoCompleteSource = AutoCompleteSource.CustomSource;
            tb.AutoCompleteMode = column.AutoCompleteEditorMode;
        }

        /// <summary>
        /// Stop editing a cell and throw away any changes.
        /// </summary>
        public virtual void CancelCellEdit() {
            if (!this.IsCellEditing)
                return;

            // Let the world know that the user has cancelled the edit operation
            this.CellEditEventArgs.Cancel = true;
            this.CellEditEventArgs.NewValue = this.GetControlValue(this.cellEditor);
            this.OnCellEditFinishing(this.CellEditEventArgs);

            // Now cleanup the editing process
            this.CleanupCellEdit(false, this.CellEditEventArgs.AutoDispose);
        }

        /// <summary>
        /// If a cell edit is in progress, finish the edit.
        /// </summary>
        /// <returns>Returns false if the finishing process was cancelled
        /// (i.e. the cell editor is still on screen)</returns>
        /// <remarks>This method does not guarantee that the editing will finish. The validation
        /// process can cause the finishing to be aborted. Developers should check the return value
        /// or use IsCellEditing property after calling this method to see if the user is still
        /// editing a cell.</remarks>
        public virtual bool PossibleFinishCellEditing() {
            return this.PossibleFinishCellEditing(false);
        }

        /// <summary>
        /// If a cell edit is in progress, finish the edit.
        /// </summary>
        /// <returns>Returns false if the finishing process was cancelled
        /// (i.e. the cell editor is still on screen)</returns>
        /// <remarks>This method does not guarantee that the editing will finish. The validation
        /// process can cause the finishing to be aborted. Developers should check the return value
        /// or use IsCellEditing property after calling this method to see if the user is still
        /// editing a cell.</remarks>
        /// <param name="expectingCellEdit">True if it is likely that another cell is going to be 
        /// edited immediately after this cell finishes editing</param>
        public virtual bool PossibleFinishCellEditing(bool expectingCellEdit) {
            if (!this.IsCellEditing)
                return true;

            this.CellEditEventArgs.Cancel = false;
            this.CellEditEventArgs.NewValue = this.GetControlValue(this.cellEditor);
            this.OnCellEditorValidating(this.CellEditEventArgs);

            if (this.CellEditEventArgs.Cancel)
                return false;

            this.FinishCellEdit(expectingCellEdit);

            return true;
        }

        /// <summary>
        /// Finish the cell edit operation, writing changed data back to the model object
        /// </summary>
        /// <remarks>This method does not trigger a Validating event, so it always finishes
        /// the cell edit.</remarks>
        public virtual void FinishCellEdit() {
            this.FinishCellEdit(false);
        }

        /// <summary>
        /// Finish the cell edit operation, writing changed data back to the model object
        /// </summary>
        /// <remarks>This method does not trigger a Validating event, so it always finishes
        /// the cell edit.</remarks>
        /// <param name="expectingCellEdit">True if it is likely that another cell is going to be 
        /// edited immediately after this cell finishes editing</param>
        public virtual void FinishCellEdit(bool expectingCellEdit) {
            if (!this.IsCellEditing)
                return;

            this.CellEditEventArgs.Cancel = false;
            this.CellEditEventArgs.NewValue = this.GetControlValue(this.cellEditor);
            this.OnCellEditFinishing(this.CellEditEventArgs);

            // If someone doesn't cancel the editing process, write the value back into the model
            if (!this.CellEditEventArgs.Cancel) {
                this.CellEditEventArgs.Column.PutValue(this.CellEditEventArgs.RowObject, this.CellEditEventArgs.NewValue);
                this.RefreshItem(this.CellEditEventArgs.ListViewItem);
            }

            this.CleanupCellEdit(expectingCellEdit, this.CellEditEventArgs.AutoDispose);

            // Tell the world that the cell has been edited
            this.OnCellEditFinished(this.CellEditEventArgs);
        }

        /// <summary>
        /// Remove all trace of any existing cell edit operation
        /// </summary>
        /// <param name="expectingCellEdit">True if it is likely that another cell is going to be 
        /// edited immediately after this cell finishes editing</param>
        /// <param name="disposeOfCellEditor">True if the cell editor should be disposed </param>
        protected virtual void CleanupCellEdit(bool expectingCellEdit, bool disposeOfCellEditor) {
            if (this.cellEditor == null)
                return;

            this.cellEditor.Validating -= new CancelEventHandler(CellEditor_Validating);

            Control soonToBeOldCellEditor = this.cellEditor;
            this.cellEditor = null;

            // Delay cleaning up the cell editor so that if we are immediately going to 
            // start a new cell edit (because the user pressed Tab) the new cell editor
            // has a chance to grab the focus. Without this, the ListView gains focus
            // momentarily (after the cell editor is remove and before the new one is created)
            // causing the list's selection to flash momentarily.
            EventHandler toBeRun = null;
            toBeRun = delegate(object sender, EventArgs e) {
                Application.Idle -= toBeRun;
                this.Controls.Remove(soonToBeOldCellEditor);
                if (disposeOfCellEditor)
                    soonToBeOldCellEditor.Dispose();
                this.Invalidate();

                if (!this.IsCellEditing) {
                    if (this.Focused)
                        this.Select();
                    this.PauseAnimations(false);
                }
            };

            // We only want to delay the removal of the control if we are expecting another cell
            // to be edited. Otherwise, we remove the control immediately.
            if (expectingCellEdit)
                this.RunWhenIdle(toBeRun);
            else 
                toBeRun(null, null);
        }

        #endregion

        #region Hot row and cell handling

        /// <summary>
        /// Force the hot item to be recalculated
        /// </summary>
        public virtual void ClearHotItem() {
            this.UpdateHotItem(new Point(-1, -1));
        }

        /// <summary>
        /// Force the hot item to be recalculated
        /// </summary>
        public virtual void RefreshHotItem() {
            this.UpdateHotItem(this.PointToClient(Cursor.Position));
        }

        /// <summary>
        /// The mouse has moved to the given pt. See if the hot item needs to be updated
        /// </summary>
        /// <param name="pt">Where is the mouse?</param>
        /// <remarks>This is the main entry point for hot item handling</remarks>
        protected virtual void UpdateHotItem(Point pt) {
            this.UpdateHotItem(this.OlvHitTest(pt.X, pt.Y));
        }

        /// <summary>
        /// The mouse has moved to the given pt. See if the hot item needs to be updated
        /// </summary>
        /// <param name="hti"></param>
        /// <remarks>This is the main entry point for hot item handling</remarks>
        protected virtual void UpdateHotItem(OlvListViewHitTestInfo hti) {

            // We only need to do the work of this method when the list has hot parts 
            // (i.e. some element whose visual appearance changes when under the mouse)?
            // Hot item decorations and hyperlinks are obvious, but if we have checkboxes
            // or buttons, those are also "hot". It's difficult to quickly detect if there are any
            // columns that have checkboxes or buttons, so we just abdicate responsibililty and 
            // provide a property (UseHotControls) which lets the programmer say whether to do
            // the hot processing or not.
            if (!this.UseHotItem && !this.UseHyperlinks && !this.UseHotControls)
                return;

            int newHotRow = hti.RowIndex;
            int newHotColumn = hti.ColumnIndex;
            HitTestLocation newHotCellHitLocation = hti.HitTestLocation;
            HitTestLocationEx newHotCellHitLocationEx = hti.HitTestLocationEx;
            OLVGroup newHotGroup = hti.Group;

            // In non-details view, we treat any hit on a row as if it were a hit
            // on column 0 -- which (effectively) it is!
            if (newHotRow >= 0 && this.View != View.Details)
                newHotColumn = 0;

            if (this.HotRowIndex == newHotRow &&
                this.HotColumnIndex == newHotColumn &&
                this.HotCellHitLocation == newHotCellHitLocation &&
                this.HotCellHitLocationEx == newHotCellHitLocationEx &&
                this.HotGroup == newHotGroup) {
                return;
            }

            // Trigger the hotitem changed event
            HotItemChangedEventArgs args = new HotItemChangedEventArgs();
            args.HotCellHitLocation = newHotCellHitLocation;
            args.HotCellHitLocationEx = newHotCellHitLocationEx;
            args.HotColumnIndex = newHotColumn;
            args.HotRowIndex = newHotRow;
            args.HotGroup = newHotGroup;
            args.OldHotCellHitLocation = this.HotCellHitLocation;
            args.OldHotCellHitLocationEx = this.HotCellHitLocationEx;
            args.OldHotColumnIndex = this.HotColumnIndex;
            args.OldHotRowIndex = this.HotRowIndex;
            args.OldHotGroup = this.HotGroup;
            this.OnHotItemChanged(args);

            // Update the state of the control
            this.HotRowIndex = newHotRow;
            this.HotColumnIndex = newHotColumn;
            this.HotCellHitLocation = newHotCellHitLocation;
            this.HotCellHitLocationEx = newHotCellHitLocationEx;
            this.HotGroup = newHotGroup;

            // If the event handler handled it complete, don't do anything else
            if (args.Handled)
                return;

//            System.Diagnostics.Debug.WriteLine(String.Format("Changed hot item: {0}", args));

            this.BeginUpdate();
            try {
                this.Invalidate();
                if (args.OldHotRowIndex != -1)
                    this.UnapplyHotItem(args.OldHotRowIndex);

                if (this.HotRowIndex != -1) {
                    // Virtual lists apply hot item style when fetching their rows
                    if (this.VirtualMode) {
                        this.ClearCachedInfo();
                        this.RedrawItems(this.HotRowIndex, this.HotRowIndex, true);
                    } else {
                        this.UpdateHotRow(this.HotRowIndex, this.HotColumnIndex, this.HotCellHitLocation, hti.Item);
                    }
                }

                if (this.UseHotItem && this.HotItemStyleOrDefault.Overlay != null) {
                    this.RefreshOverlays();
                }
            }
            finally {
                this.EndUpdate();
            }
        }

        /// <summary>
        /// Update the given row using the current hot item information
        /// </summary>
        /// <param name="olvi"></param>
        protected virtual void UpdateHotRow(OLVListItem olvi) {
            this.UpdateHotRow(this.HotRowIndex, this.HotColumnIndex, this.HotCellHitLocation, olvi);
        }

        /// <summary>
        /// Update the given row using the given hot item information
        /// </summary>
        /// <param name="rowIndex"></param>
        /// <param name="columnIndex"></param>
        /// <param name="hitLocation"></param>
        /// <param name="olvi"></param>
        protected virtual void UpdateHotRow(int rowIndex, int columnIndex, HitTestLocation hitLocation, OLVListItem olvi) {
            if (rowIndex < 0 || columnIndex < 0)
                return;

           // System.Diagnostics.Debug.WriteLine(String.Format("UpdateHotRow: {0}, {1}, {2}", rowIndex, columnIndex, hitLocation));

            if (this.UseHyperlinks) {
                OLVColumn column = this.GetColumn(columnIndex);
                OLVListSubItem subItem = olvi.GetSubItem(columnIndex);
                if (column.Hyperlink && hitLocation == HitTestLocation.Text && !String.IsNullOrEmpty(subItem.Url)) {
                    this.ApplyCellStyle(olvi, columnIndex, this.HyperlinkStyle.Over);
                    this.Cursor = this.HyperlinkStyle.OverCursor ?? Cursors.Default;
                } else {
                    this.Cursor = Cursors.Default;
                }
            }

            if (this.UseHotItem) {
                if (!olvi.Selected && olvi.Enabled) {
                    this.ApplyRowStyle(olvi, this.HotItemStyleOrDefault);
                }
            }
        }

        /// <summary>
        /// Apply a style to the given row
        /// </summary>
        /// <param name="olvi"></param>
        /// <param name="style"></param>
        public virtual void ApplyRowStyle(OLVListItem olvi, IItemStyle style) {
            if (style == null)
                return;

            Font font = style.Font ?? olvi.Font;

            if (style.FontStyle != FontStyle.Regular)
                font = new Font(font ?? this.Font, style.FontStyle);

            if (!Equals(font, olvi.Font)) {
                if (olvi.UseItemStyleForSubItems)
                    olvi.Font = font;
                else {
                    foreach (ListViewItem.ListViewSubItem x in olvi.SubItems)
                        x.Font = font;
                }
            }

            if (!style.ForeColor.IsEmpty) {
                if (olvi.UseItemStyleForSubItems)
                    olvi.ForeColor = style.ForeColor;
                else {
                    foreach (ListViewItem.ListViewSubItem x in olvi.SubItems)
                        x.ForeColor = style.ForeColor;
                }
            }

            if (!style.BackColor.IsEmpty) {
                if (olvi.UseItemStyleForSubItems)
                    olvi.BackColor = style.BackColor;
                else {
                    foreach (ListViewItem.ListViewSubItem x in olvi.SubItems)
                        x.BackColor = style.BackColor;
                }
            }
        }

        /// <summary>
        /// Apply a style to a cell
        /// </summary>
        /// <param name="olvi"></param>
        /// <param name="columnIndex"></param>
        /// <param name="style"></param>
        protected virtual void ApplyCellStyle(OLVListItem olvi, int columnIndex, IItemStyle style) {
            if (style == null)
                return;

            // Don't apply formatting to subitems when not in Details view
            if (this.View != View.Details && columnIndex > 0)
                return;

            olvi.UseItemStyleForSubItems = false;

            ListViewItem.ListViewSubItem subItem = olvi.SubItems[columnIndex];
            if (style.Font != null)
                subItem.Font = style.Font;

            if (style.FontStyle != FontStyle.Regular)
                subItem.Font = new Font(subItem.Font ?? olvi.Font ?? this.Font, style.FontStyle);

            if (!style.ForeColor.IsEmpty)
                subItem.ForeColor = style.ForeColor;

            if (!style.BackColor.IsEmpty)
                subItem.BackColor = style.BackColor;
        }

        /// <summary>
        /// Remove hot item styling from the given row
        /// </summary>
        /// <param name="index"></param>
        protected virtual void UnapplyHotItem(int index) {
            this.Cursor = Cursors.Default;
            // Virtual lists will apply the appropriate formatting when the row is fetched
            if (this.VirtualMode) {
                if (index < this.VirtualListSize)
                    this.RedrawItems(index, index, true);
            } else {
                OLVListItem olvi = this.GetItem(index);
                if (olvi != null) {
                    //this.PostProcessOneRow(index, index, olvi);
                    this.RefreshItem(olvi);
                }
            }
        }


        #endregion

        #region Drag and drop

        /// <summary>
        /// 
        /// </summary>
        /// <param name="e"></param>
        protected override void OnItemDrag(ItemDragEventArgs e) {
            base.OnItemDrag(e);

            if (this.DragSource == null)
                return;

            Object data = this.DragSource.StartDrag(this, e.Button, (OLVListItem)e.Item);
            if (data != null) {
                DragDropEffects effect = this.DoDragDrop(data, this.DragSource.GetAllowedEffects(data));
                this.DragSource.EndDrag(data, effect);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="args"></param>
        protected override void OnDragEnter(DragEventArgs args) {
            base.OnDragEnter(args);

            if (this.DropSink != null)
                this.DropSink.Enter(args);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="args"></param>
        protected override void OnDragOver(DragEventArgs args) {
            base.OnDragOver(args);

            if (this.DropSink != null)
                this.DropSink.Over(args);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="args"></param>
        protected override void OnDragDrop(DragEventArgs args) {
            base.OnDragDrop(args);

            if (this.DropSink != null)
                this.DropSink.Drop(args);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="e"></param>
        protected override void OnDragLeave(EventArgs e) {
            base.OnDragLeave(e);

            if (this.DropSink != null)
                this.DropSink.Leave();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="args"></param>
        protected override void OnGiveFeedback(GiveFeedbackEventArgs args) {
            base.OnGiveFeedback(args);

            if (this.DropSink != null)
                this.DropSink.GiveFeedback(args);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="args"></param>
        protected override void OnQueryContinueDrag(QueryContinueDragEventArgs args) {
            base.OnQueryContinueDrag(args);

            if (this.DropSink != null)
                this.DropSink.QueryContinue(args);
        }

        #endregion

        #region Decorations and Overlays

        /// <summary>
        /// Add the given decoration to those on this list and make it appear
        /// </summary>
        /// <param name="decoration">The decoration</param>
        /// <remarks>
        /// A decoration scrolls with the listview. An overlay stays fixed in place.
        /// </remarks>
        public virtual void AddDecoration(IDecoration decoration) {
            if (decoration == null)
                return;
            this.Decorations.Add(decoration);
            this.Invalidate();
        }

        /// <summary>
        /// Add the given overlay to those on this list and make it appear
        /// </summary>
        /// <param name="overlay">The overlay</param>
        public virtual void AddOverlay(IOverlay overlay) {
            if (overlay == null)
                return;
            this.Overlays.Add(overlay);
            this.Invalidate();
        }

        /// <summary>
        /// Draw all the decorations
        /// </summary>
        /// <param name="g">A Graphics</param>
        /// <param name="itemsThatWereRedrawn">The items that were redrawn and whose decorations should also be redrawn</param>
        protected virtual void DrawAllDecorations(Graphics g, List<OLVListItem> itemsThatWereRedrawn) {
            g.TextRenderingHint = ObjectListView.TextRenderingHint;
            g.SmoothingMode = ObjectListView.SmoothingMode;

            Rectangle contentRectangle = this.ContentRectangle;

            if (this.HasEmptyListMsg && this.GetItemCount() == 0) {
                this.EmptyListMsgOverlay.Draw(this, g, contentRectangle);
            }

            // Let the drop sink draw whatever feedback it likes
            if (this.DropSink != null) {
                this.DropSink.DrawFeedback(g, contentRectangle);
            }

            // Draw our item and subitem decorations
            foreach (OLVListItem olvi in itemsThatWereRedrawn) {
                if (olvi.HasDecoration) {
                    foreach (IDecoration d in olvi.Decorations) {
                        d.ListItem = olvi;
                        d.SubItem = null;
                        d.Draw(this, g, contentRectangle);
                    }
                }
                foreach (OLVListSubItem subItem in olvi.SubItems) {
                    if (subItem.HasDecoration) {
                        foreach (IDecoration d in subItem.Decorations) {
                            d.ListItem = olvi;
                            d.SubItem = subItem;
                            d.Draw(this, g, contentRectangle);
                        }
                    }
                }
                if (this.SelectedRowDecoration != null && olvi.Selected && olvi.Enabled) {
                    this.SelectedRowDecoration.ListItem = olvi;
                    this.SelectedRowDecoration.SubItem = null;
                    this.SelectedRowDecoration.Draw(this, g, contentRectangle);
                }
            }

            // Now draw the specifically registered decorations
            foreach (IDecoration decoration in this.Decorations) {
                decoration.ListItem = null;
                decoration.SubItem = null;
                decoration.Draw(this, g, contentRectangle);
            }

            // Finally, draw any hot item decoration
            if (this.UseHotItem) {
                IDecoration hotItemDecoration = this.HotItemStyleOrDefault.Decoration;
                if (hotItemDecoration != null) {
                    hotItemDecoration.ListItem = this.GetItem(this.HotRowIndex);
                    if (hotItemDecoration.ListItem == null || hotItemDecoration.ListItem.Enabled) {
                        hotItemDecoration.SubItem = hotItemDecoration.ListItem == null ? null : hotItemDecoration.ListItem.GetSubItem(this.HotColumnIndex);
                        hotItemDecoration.Draw(this, g, contentRectangle);
                    }
                }
            }

            // If we are in design mode, we don't want to use the glass panels,
            // so we draw the background overlays here
            if (this.DesignMode) {
                foreach (IOverlay overlay in this.Overlays) {
                    overlay.Draw(this, g, contentRectangle);
                }
            }
        }

        /// <summary>
        /// Is the given decoration shown on this list
        /// </summary>
        /// <param name="decoration">The overlay</param>
        public virtual bool HasDecoration(IDecoration decoration) {
            return this.Decorations.Contains(decoration);
        }

        /// <summary>
        /// Is the given overlay shown on this list?
        /// </summary>
        /// <param name="overlay">The overlay</param>
        public virtual bool HasOverlay(IOverlay overlay) {
            return this.Overlays.Contains(overlay);
        }

        /// <summary>
        /// Hide any overlays.
        /// </summary>
        /// <remarks>
        /// This is only a temporary hiding -- the overlays will be shown
        /// the next time the ObjectListView redraws.
        /// </remarks>
        public virtual void HideOverlays() {
            foreach (GlassPanelForm glassPanel in this.glassPanels) {
                glassPanel.HideGlass();
            }
        }

        /// <summary>
        /// Create and configure the empty list msg overlay
        /// </summary>
        protected virtual void InitializeEmptyListMsgOverlay() {
            TextOverlay overlay = new TextOverlay();
            overlay.Alignment = System.Drawing.ContentAlignment.MiddleCenter;
            overlay.TextColor = SystemColors.ControlDarkDark;
            overlay.BackColor = Color.BlanchedAlmond;
            overlay.BorderColor = SystemColors.ControlDark;
            overlay.BorderWidth = 2.0f;
            this.EmptyListMsgOverlay = overlay;
        }

        /// <summary>
        /// Initialize the standard image and text overlays
        /// </summary>
        protected virtual void InitializeStandardOverlays() {
            this.OverlayImage = new ImageOverlay();
            this.AddOverlay(this.OverlayImage);
            this.OverlayText = new TextOverlay();
            this.AddOverlay(this.OverlayText);
        }

        /// <summary>
        /// Make sure that any overlays are visible.
        /// </summary>
        public virtual void ShowOverlays() {
            // If we shouldn't show overlays, then don't create glass panels
            if (!this.ShouldShowOverlays())
                return;

            // Make sure that each overlay has its own glass panels
            if (this.Overlays.Count != this.glassPanels.Count) {
                foreach (IOverlay overlay in this.Overlays) {
                    GlassPanelForm glassPanel = this.FindGlassPanelForOverlay(overlay);
                    if (glassPanel == null) {
                        glassPanel = new GlassPanelForm();
                        glassPanel.Bind(this, overlay);
                        this.glassPanels.Add(glassPanel);
                    }
                }
            }
            foreach (GlassPanelForm glassPanel in this.glassPanels) {
                glassPanel.ShowGlass();
            }
        }

        private bool ShouldShowOverlays() {
            // If we are in design mode, we dont show the overlays
            if (this.DesignMode)
                return false;

            // If we are explicitly not using overlays, also don't show them
            if (!this.UseOverlays)
                return false;

            // If there are no overlays, guess...
            if (!this.HasOverlays)
                return false;

            // If we don't have 32-bit display, alpha blending doesn't work, so again, no overlays
            // TODO: This should actually figure out which screen(s) the control is on, and make sure
            // that each one is 32-bit.
            if (Screen.PrimaryScreen.BitsPerPixel < 32)
                return false;

            // Finally, we can show the overlays
            return true;
        }

        private GlassPanelForm FindGlassPanelForOverlay(IOverlay overlay) {
            return this.glassPanels.Find(delegate(GlassPanelForm x) { return x.Overlay == overlay; });
        }

        /// <summary>
        /// Refresh the display of the overlays
        /// </summary>
        public virtual void RefreshOverlays() {
            foreach (GlassPanelForm glassPanel in this.glassPanels) {
                glassPanel.Invalidate();
            }
        }

        /// <summary>
        /// Refresh the display of just one overlays
        /// </summary>
        public virtual void RefreshOverlay(IOverlay overlay) {
            GlassPanelForm glassPanel = this.FindGlassPanelForOverlay(overlay);
            if (glassPanel != null)
                glassPanel.Invalidate();
        }

        /// <summary>
        /// Remove the given decoration from this list
        /// </summary>
        /// <param name="decoration">The decoration to remove</param>
        public virtual void RemoveDecoration(IDecoration decoration) {
            if (decoration == null)
                return;
            this.Decorations.Remove(decoration);
            this.Invalidate();
        }

        /// <summary>
        /// Remove the given overlay to those on this list
        /// </summary>
        /// <param name="overlay">The overlay</param>
        public virtual void RemoveOverlay(IOverlay overlay) {
            if (overlay == null)
                return;
            this.Overlays.Remove(overlay);
            GlassPanelForm glassPanel = this.FindGlassPanelForOverlay(overlay);
            if (glassPanel != null) {
                this.glassPanels.Remove(glassPanel);
                glassPanel.Unbind();
                glassPanel.Dispose();
            }
        }

        #endregion

        #region Filtering

        /// <summary>
        /// 创建一个筛选器，该筛选器将执行当前安装在可见列上的所有筛选。
        /// </summary>
        public virtual IModelFilter CreateColumnFilter() {
            List<IModelFilter> filters = new List<IModelFilter>();
            foreach (OLVColumn column in this.Columns) {
                IModelFilter filter = column.ValueBasedFilter;
                if (filter != null)
                    filters.Add(filter);
            }
            return (filters.Count == 0) ? null : new CompositeAllFilter(filters);
        }

        /// <summary>
        /// Do the actual work of filtering
        /// </summary>
        /// <param name="originalObjects"></param>
        /// <param name="aModelFilter"></param>
        /// <param name="aListFilter"></param>
        /// <returns></returns>
        virtual protected IEnumerable FilterObjects(IEnumerable originalObjects, IModelFilter aModelFilter, IListFilter aListFilter) {
            // Being cautious
            originalObjects = originalObjects ?? new ArrayList();

            // Tell the world to filter the objects. If they do so, don't do anything else
// ReSharper disable PossibleMultipleEnumeration
            FilterEventArgs args = new FilterEventArgs(originalObjects);
            this.OnFilter(args);
            if (args.FilteredObjects != null)
                return args.FilteredObjects;

            // Apply a filter to the list as a whole
            if (aListFilter != null)
                originalObjects = aListFilter.Filter(originalObjects);

            // Apply the object filter if there is one
            if (aModelFilter != null) {
                ArrayList filteredObjects = new ArrayList();
                foreach (object model in originalObjects) {
                    if (aModelFilter.Filter(model))
                        filteredObjects.Add(model);
                }
                originalObjects = filteredObjects;
            }

            return originalObjects;
// ReSharper restore PossibleMultipleEnumeration
        }

        /// <summary>
        /// 删除所有列筛选。
        /// </summary>
        public virtual void ResetColumnFiltering() {
            foreach (OLVColumn column in this.Columns) {
                column.ValuesChosenForFiltering.Clear();
            }
            this.UpdateColumnFiltering();
        }

        /// <summary>
        /// 根据每列中定义的值筛选更新此ObjectListView的筛选
        /// </summary>
        public virtual void UpdateColumnFiltering() {
            //List<IModelFilter> filters = new List<IModelFilter>();
            //IModelFilter columnFilter = this.CreateColumnFilter();
            //if (columnFilter != null)
            //    filters.Add(columnFilter);
            //if (this.AdditionalFilter != null)
            //    filters.Add(this.AdditionalFilter);
            //this.ModelFilter = filters.Count == 0 ? null : new CompositeAllFilter(filters);

            if (this.AdditionalFilter == null)
                this.ModelFilter = this.CreateColumnFilter();
            else {
                IModelFilter columnFilter = this.CreateColumnFilter();
                if (columnFilter == null)
                    this.ModelFilter = this.AdditionalFilter;
                else {
                    List<IModelFilter> filters = new List<IModelFilter>();
                    filters.Add(columnFilter);
                    filters.Add(this.AdditionalFilter);
                    this.ModelFilter = new CompositeAllFilter(filters);
                }
            }
        }

        /// <summary>
        /// When some setting related to filtering changes, this method is called.
        /// </summary>
        protected virtual void UpdateFiltering() {
            this.BuildList(true);
        }

        /// <summary>
        /// 使用当前安装的模型过滤器更新所有渲染器
        /// </summary>
        protected virtual void NotifyNewModelFilter() {
            IFilterAwareRenderer filterAware = this.DefaultRenderer as IFilterAwareRenderer;
            if (filterAware != null)
                filterAware.Filter = this.ModelFilter;

            foreach (OLVColumn column in this.AllColumns) {
                filterAware = column.Renderer as IFilterAwareRenderer;
                if (filterAware != null)
                    filterAware.Filter = this.ModelFilter;
            }
        }

        #endregion

        #region Persistent check state

        /// <summary>
        /// 获取给定模型的复选框状态。
        /// </summary>
        /// <param name="model">The model</param>
        /// <returns>模型的复选框状态。默认为未选中。</returns>
        protected virtual CheckState GetPersistentCheckState(object model) {
            CheckState state;
            if (model != null && this.CheckStateMap.TryGetValue(model, out state))
                return state;
            return CheckState.Unchecked;
        }

        /// <summary>
        /// 设置给定模型对象的复选框状态
        /// </summary>
        /// <param name="model">The model to be remembered</param>
        /// <param name="state">The model's checkedness</param>
        /// <returns>The state given to the method</returns>
        protected virtual CheckState SetPersistentCheckState(object model, CheckState state) {
            if (model == null)
                return CheckState.Unchecked;

            this.CheckStateMap[model] = state;
            return state;
        }

        /// <summary>
        /// 忽略任何持久复选框状态
        /// </summary>
        protected virtual void ClearPersistentCheckState() {
            this.CheckStateMap = null;
        }

        #endregion

        #region Implementation variables

        private bool isOwnerOfObjects; // does this ObjectListView own the Objects collection?
        private bool hasIdleHandler; // has an Idle handler already been installed?
        private bool hasResizeColumnsHandler; // has an idle handler been installed which will handle column resizing?
        private bool isInWmPaintEvent; // is a WmPaint event currently being handled?
        private bool shouldDoCustomDrawing; // should the list do its custom drawing?
        private bool isMarqueSelecting; // Is a marque selection in progress?
        private int suspendSelectionEventCount; // How many unmatched SuspendSelectionEvents() calls have been made?

        private readonly List<GlassPanelForm> glassPanels = new List<GlassPanelForm>(); // The transparent panel that draws overlays
        private Dictionary<string, bool> visitedUrlMap = new Dictionary<string, bool>(); // Which urls have been visited?

        // TODO
        //private CheckBoxSettings checkBoxSettings = new CheckBoxSettings();

        #endregion
    }
}
