/*
* ObjectListView - A listview to show various aspects of a collection of objects
*
* Author: Phillip Piper
* Date: 9/10/2006 11:15 AM
*
* Change log:
* 2008-07-23 JPP - Consistently use copy-on-write semantics with Add/RemoveObject methods
* 2008-07-10 JPP - Enable validation on cell editors through a CellEditValidating event.
* (thanks to Artiom Chilaru for the initial suggestion and implementation).
* 2008-07-09 JPP - Added HeaderControl.Handle to allow OLV to be used within UserControls.
* (thanks to Michael Coffey for tracking this down).
* 2008-06-23 JPP - Split the more generally useful CopyObjectsToClipboard() method
* out of CopySelectionToClipboard()
* 2008-06-22 JPP - Added AlwaysGroupByColumn and AlwaysGroupBySortOrder, which
* force the list view to always be grouped by a particular column.
* 2008-05-31 JPP - Allow check boxes on FastObjectListViews
* - Added CheckedObject and CheckedObjects properties
* 2008-05-11 JPP - Allow selection foreground and background colors to be changed.
* Windows doesn't allow this, so we can only make it happen when owner
* drawing. Set the HighlightForegroundColor and HighlightBackgroundColor
* properties and then call EnableCustomSelectionColors().
* v1.12
* 2008-05-08 JPP - Fixed bug where the column select menu would not appear if the
* ObjectListView has a context menu installed.
* 2008-05-05 JPP - Non detail views can now be owner drawn. The renderer installed for
* primary column is given the chance to render the whole item.
* See BusinessCardRenderer in the demo for an example.
* - BREAKING CHANGE: RenderDelegate now returns a bool to indicate if default
* rendering should be done. Previously returned void. Only important if your
* code used RendererDelegate directly. Renderers derived from BaseRenderer
* are unchanged.
* 2008-05-03 JPP - Changed cell editing to use values directly when the values are Strings.
* Previously, values were always handed to the AspectToStringConverter.
* - When editing a cell, tabbing no longer tries to edit the next subitem
* when not in details view!
* 2008-05-02 JPP - MappedImageRenderer can now handle a Aspects that return a collection
* of values. Each value will be drawn as its own image.
* - Made AddObjects() and RemoveObjects() work for all flavours (or at least not crash)
* - Fixed bug with clearing virtual lists that has been scrolled vertically
* - Made TopItemIndex work with virtual lists.
* 2008-05-01 JPP - Added AddObjects() and RemoveObjects() to allow faster mods to the list
* - Reorganised public properties. Now alphabetical.
* - Made the class ObjectListViewState internal, as it always should have been.
* v1.11
* 2008-04-29 JPP - Preserve scroll position when building the list or changing columns.
* - Added TopItemIndex property. Due to problems with the underlying control, this
* property is not always reliable. See property docs for info.
* 2008-04-27 JPP - Added SelectedIndex property.
* - Use a different, more general strategy to handle Invoke(). Removed all delegates
* that were only declared to support Invoke().
* - Check all native structures for 64-bit correctness.
* 2008-04-25 JPP - Released on SourceForge.
* 2008-04-13 JPP - Added ColumnRightClick event.
* - Made the assembly CLS-compliant. To do this, our cell editors were made internal, and
* the constraint on FlagRenderer template parameter was removed (the type must still
* be an IConvertible, but if it isn't, the error will be caught at runtime, not compile time).
* 2008-04-12 JPP - Changed HandleHeaderRightClick() to have a columnIndex parameter, which tells
* exactly which column was right-clicked.
* 2008-03-31 JPP - Added SaveState() and RestoreState()
* - When cell editing, scrolling with a mouse wheel now ends the edit operation.
* v1.10
* 2008-03-25 JPP - Added space filling columns. See OLVColumn.FreeSpaceProportion property for details.
* A space filling columns fills all (or a portion) of the width unoccupied by other columns.
* 2008-03-23 JPP - Finished tinkering with support for Mono. Compile with conditional compilation symbol 'MONO'
* to enable. On Windows, current problems with Mono:
* - grid lines on virtual lists crashes
* - when grouped, items sometimes are not drawn when any item is scrolled out of view
* - i can't seem to get owner drawing to work
* - when editing cell values, the editing controls always appear behind the listview,
* where they function fine -- the user just can't see them :-)
* 2008-03-16 JPP - Added some methods suggested by Chris Marlowe (thanks for the suggestions Chris)
* - ClearObjects()
* - GetCheckedObject(), GetCheckedObjects()
* - GetItemAt() variation that gets both the item and the column under a point
* 2008-02-28 JPP - Fixed bug with subitem colors when using OwnerDrawn lists and a RowFormatter.
* v1.9.1
* 2008-01-29 JPP - Fixed bug that caused owner-drawn virtual lists to use 100% CPU
* - Added FlagRenderer to help draw bitwise-OR'ed flag values
* 2008-01-23 JPP - Fixed bug (introduced in v1.9) that made alternate row colour with groups not quite right
* - Ensure that DesignerSerializationVisibility.Hidden is set on all non-browsable properties
* - Make sure that sort indicators are shown after changing which columns are visible
* 2008-01-21 JPP - Added FastObjectListView
* v1.9
* 2008-01-18 JPP - Added IncrementalUpdate()
* 2008-01-16 JPP - Right clicking on column header will allow the user to choose which columns are visible.
* Set SelectColumnsOnRightClick to false to prevent this behaviour.
* - Added ImagesRenderer to draw more than one images in a column
* - Changed the positioning of the empty list msg to use all the client area. Thanks to Matze.
* 2007-12-13 JPP - Added CopySelectionToClipboard(). Ctrl-C invokes this method. Supports text
* and HTML formats.
* 2007-12-12 JPP - Added support for checkboxes via CheckStateGetter and CheckStatePutter properties.
* - Made ObjectListView and OLVColumn into partial classes so that others can extend them.
* 2007-12-09 JPP - Added ability to have hidden columns, i.e. columns that the ObjectListView knows
* about but that are not visible to the user. Controlled by OLVColumn.IsVisible.
* Added ColumnSelectionForm to the project to show how it could be used in an application.
*
* v1.8
* 2007-11-26 JPP - Cell editing fully functional
* 2007-11-21 JPP - Added SelectionChanged event. This event is triggered once when the
* selection changes, no matter how many items are selected or deselected (in
* contrast to SelectedIndexChanged which is called once for every row that
* is selected or deselected). Thanks to lupokehl42 (Daniel) for his suggestions and
* improvements on this idea.
* 2007-11-19 JPP - First take at cell editing
* 2007-11-17 JPP - Changed so that items within a group are not sorted if lastSortOrder == None
* - Only call MakeSortIndicatorImages() if we haven't already made the sort indicators
* (Corrected misspelling in the name of the method too)
* 2007-11-06 JPP - Added ability to have secondary sort criteria when sorting
* (SecondarySortColumn and SecondarySortOrder properties)
* - Added SortGroupItemsByPrimaryColumn to allow group items to be sorted by the
* primary column. Previous default was to sort by the grouping column.
* v1.7
* No big changes to this version but made to work with ListViewPrinter and released with it.
*
* 2007-11-05 JPP - Changed BaseRenderer to use DrawString() rather than TextRenderer, since TextRenderer
* does not work when printing.
* v1.6
* 2007-11-03 JPP - Fixed some bugs in the rebuilding of DataListView.
* 2007-10-31 JPP - Changed to use builtin sort indicators on XP and later. This also avoids alignment
* problems on Vista. (thanks to gravybod for the suggestion and example implementation)
* 2007-10-21 JPP - Added MinimumWidth and MaximumWidth properties to OLVColumn.
* - Added ability for BuildList() to preserve selection. Calling BuildList() directly
* tries to preserve selection; calling SetObjects() does not.
* - Added SelectAll() and DeselectAll() methods. Useful for working with large lists.
* 2007-10-08 JPP - Added GetNextItem() and GetPreviousItem(), which walk sequentially through the
* listview items, even when the view is grouped.
* - Added SelectedItem property
* 2007-09-28 JPP - Optimized aspect-to-string conversion. BuildList() 15% faster.
* - Added empty implementation of RefreshObjects() to VirtualObjectListView since
* RefreshObjects() cannot work on virtual lists.
* 2007-09-13 JPP - Corrected bug with custom sorter in VirtualObjectListView (thanks for mpgjunky)
* 2007-09-07 JPP - Corrected image scaling bug in DrawAlignedImage() (thanks to krita970)
* 2007-08-29 JPP - Allow item count labels on groups to be set per column (thanks to cmarlow for idea)
* 2007-08-14 JPP - Major rework of DataListView based on Ian Griffiths's great work
* 2007-08-11 JPP - When empty, the control can now draw a "List Empty" message
* - Added GetColumn() and GetItem() methods
* v1.5
* 2007-08-03 JPP - Support animated GIFs in ImageRenderer
* - Allow height of rows to be specified - EXPERIMENTAL!
* 2007-07-26 JPP - Optimised redrawing of owner-drawn lists by remembering the update rect
* - Allow sort indicators to be turned off
* 2007-06-30 JPP - Added RowFormatter delegate
* - Allow a different label when there is only one item in a group (thanks to cmarlow)
* v1.4
* 2007-04-12 JPP - Allow owner drawn on steriods!
* - Column headers now display sort indicators
* - ImageGetter delegates can now return ints, strings or Images
* (Images are only visible if the list is owner drawn)
* - Added OLVColumn.MakeGroupies to help with group partitioning
* - All normal listview views are now supported
* - Allow dotted aspect names, e.g. Owner.Workgroup.Name (thanks to OlafD)
* - Added SelectedObject and SelectedObjects properties
* v1.3
* 2007-03-01 JPP - Added DataListView
* - Added VirtualObjectListView
* - Added Freeze/Unfreeze capabilities
* - Allowed sort handler to be installed
* - Simplified sort comparisons: handles 95% of cases with only 6 lines of code!
* - Fixed bug with alternative line colors on unsorted lists (thanks to cmarlow)
* 2007-01-13 JPP - Fixed bug with lastSortOrder (thanks to Kwan Fu Sit)
* - Non-OLVColumns are no longer allowed
* 2007-01-04 JPP - Clear sorter before rebuilding list. 10x faster! (thanks to aaberg)
* - Include GetField in GetAspectByName() so field values can be Invoked too.
* - Fixed subtle bug in RefreshItem() that erased background colors.
* 2006-11-01 JPP - Added alternate line colouring
* 2006-10-20 JPP - Refactored all sorting comparisons and made it extendable. See ComparerManager.
* - Improved IDE integration
* - Made control DoubleBuffered
* - Added object selection methods
* 2006-10-13 JPP Implemented grouping and column sorting
* 2006-10-09 JPP Initial version
*
* TO DO:
* - Extend MappedImageRender to be able to draw more than image if its Aspect returns an ICollection.
*
* Copyright (C) 2006-2008 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
* If you wish to use this code in a closed source application, please contact phillip_piper@bigfoot.com.
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Design;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using System.Runtime.Serialization.Formatters.Binary;
namespace BrightIdeasSoftware
{
///
/// An object list displays 'aspects' of a collection of objects in a listview control.
///
///
///
/// The intelligence for this control is in the columns. OLVColumns are
/// extended so they understand how to fetch an 'aspect' from each row
/// object. They also understand how to sort by their aspect, and
/// how to group them.
///
///
/// 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.
///
///
/// Sorting by column clicking and grouping by column are handled automatically.
///
///
/// Right clicking on the column header should present a popup menu that allows the user to
/// choose which columns will be visible in the list. This behaviour can be disabled by
/// setting SelectColumnsOnRightClick to false.
///
///
/// This list puts sort indicators in the column headers to show the column sorting direction.
/// On Windows XP and later, the system standard images are used.
/// If you wish to replace the standard images with your own images, put entries in the small image list
/// with the key values "sort-indicator-up" and "sort-indicator-down".
///
///
/// For these classes to build correctly, the project must have references to these assemblies:
///
/// System.Data
/// System.Design
/// System.Drawing
/// System.Windows.Forms (obviously)
///
///
///
public partial class ObjectListView : ListView, ISupportInitialize
{
///
/// Create an ObjectListView
///
public ObjectListView()
: base()
{
this.ColumnClick += new ColumnClickEventHandler(this.HandleColumnClick);
this.ItemCheck += new ItemCheckEventHandler(this.HandleItemCheck);
this.Layout += new LayoutEventHandler(this.HandleLayout);
this.ColumnWidthChanging += new ColumnWidthChangingEventHandler(this.HandleColumnWidthChanging);
this.ColumnWidthChanged += new ColumnWidthChangedEventHandler(this.HandleColumnWidthChanged);
base.View = View.Details;
this.DoubleBuffered = true; // kill nasty flickers. hiss... me hates 'em
this.AlternateRowBackColor = Color.Empty;
this.ShowSortIndicators = true;
this.isOwnerOfObjects = false;
}
#region Public properties
///
/// Get or set all the columns that this control knows about.
/// Only those columns where IsVisible is true will be seen by the user.
///
/// 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.
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public List AllColumns
{
get
{
// If someone has wiped out the columns, put the list back
if (allColumns == null)
allColumns = new List();
// If we don't know the columns, use the columns from the control.
// This handles legacy cases
if (allColumns.Count == 0 && this.Columns.Count > 0) {
for (int i = 0; i < this.Columns.Count; i++) {
this.allColumns.Add(this.GetColumn(i));
}
}
return allColumns;
}
set { allColumns = value; }
}
private List allColumns = new List();
///
/// If every second row has a background different to the control, what color should it be?
///
[Category("Appearance"),
Description("If using alternate colors, what color should alterate rows be?"),
DefaultValue(typeof(Color), "Empty")]
public Color AlternateRowBackColor
{
get { return alternateRowBackColor; }
set { alternateRowBackColor = value; }
}
private Color alternateRowBackColor = Color.Empty;
///
/// Return the alternate row background color that has been set, or the default color
///
[Browsable(false)]
public Color AlternateRowBackColorOrDefault
{
get
{
if (alternateRowBackColor == Color.Empty)
return Color.LemonChiffon;
else
return alternateRowBackColor;
}
}
///
/// This property forces the ObjectListView to always group items by the given column.
///
public OLVColumn AlwaysGroupByColumn
{
get { return alwaysGroupByColumn; }
set { alwaysGroupByColumn = value; }
}
private OLVColumn alwaysGroupByColumn;
///
/// If AlwaysGroupByColumn is not null, this property will be used to decide how
/// those groups are sorted. If this property has the value SortOrder.None, then
/// the sort order will toggle according to the users last header click.
///
public SortOrder AlwaysGroupBySortOrder
{
get { return alwaysGroupBySortOrder; }
set { alwaysGroupBySortOrder = value; }
}
private SortOrder alwaysGroupBySortOrder = SortOrder.None;
///
/// Give access to the image list that is actually being used by the control
///
[Browsable(false)]
public ImageList BaseSmallImageList
{
get { return base.SmallImageList; }
}
///
/// How does a user indicate that they want to edit cells?
///
public enum CellEditActivateMode
{
///
/// This list cannot be edited. F2 does nothing.
///
None = 0,
///
/// A single click on a subitem will edit the value. Single clicking the primary column,
/// selects the row just like normal. The user must press F2 to edit the primary column.
///
SingleClick = 1,
///
/// Double clicking a subitem or the primary column will edit that cell.
/// F2 will edit the primary column.
///
DoubleClick = 2,
///
/// Pressing F2 is the only way to edit the cells. Once the primary column is being edited,
/// the other cells in the row can be edited by pressing Tab.
///
F2Only = 3
}
///
/// How does the user indicate that they want to edit a cell?
/// None means that the listview cannot be edited.
///
/// Columns can also be marked as editable.
[Category("Behavior"),
Description("How does the user indicate that they want to edit a cell?"),
DefaultValue(CellEditActivateMode.None)]
public CellEditActivateMode CellEditActivation
{
get { return cellEditActivation; }
set { cellEditActivation = value; }
}
private CellEditActivateMode cellEditActivation = CellEditActivateMode.None;
///
/// Return the model object of the row that is checked or null if no row is checked
/// or more than one row is checked
///
public Object CheckedObject
{
get {
ArrayList checkedObjects = this.GetCheckedObjects();
if (checkedObjects.Count == 1)
return checkedObjects[0];
else
return null;
}
}
///
/// 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.
///
///
///
/// 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.
///
///
/// The CheckedItems property is not helpful. It is just a short-hand for
/// iterating through the list looking for items that are checked.
///
///
/// The performance of this method is O(n). Be careful on long lists.
///
///
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public IList CheckedObjects
{
get {
ArrayList objects = new ArrayList();
if (this.CheckBoxes) {
for (int i = 0; i < this.GetItemCount(); i++) {
OLVListItem olvi = this.GetItem(i);
if (olvi.Checked)
objects.Add(olvi.RowObject);
}
}
return objects;
}
set {
if (this.CheckBoxes) {
if (value == null)
value = new ArrayList();
for (int i = 0; i < this.GetItemCount(); i++) {
OLVListItem olvi = this.GetItem(i);
bool newValue = value.Contains(olvi.RowObject);
if (olvi.Checked != newValue)
this.ChangeCheckItem(olvi, olvi.Checked, newValue);
}
}
}
}
///
/// Get/set the list of columns that should be used when the list switches to tile view.
///
/// If no list of columns has been installed, this value will default to the
/// first column plus any column where IsTileViewColumn is true.
[Browsable(false),
Obsolete("Use GetFilteredColumns() and OLVColumn.IsTileViewColumn instead"),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public List ColumnsForTileView
{
get { return this.GetFilteredColumns(View.Tile); }
}
///
/// Return the visible columns in the order they are displayed to the user
///
[Browsable(false)]
public List ColumnsInDisplayOrder
{
get
{
List columnsInDisplayOrder = new List(this.Columns.Count);
for (int i = 0; i < this.Columns.Count; i++)
columnsInDisplayOrder.Add(null);
for (int i = 0; i < this.Columns.Count; i++) {
OLVColumn col = this.GetColumn(i);
columnsInDisplayOrder[col.DisplayIndex] = col;
}
return columnsInDisplayOrder;
}
}
///
/// If there are no items in this list view, what message should be drawn onto the control?
///
[Category("Appearance"),
Description("When the list has no items, show this message in the control"),
DefaultValue("")]
public String EmptyListMsg
{
get { return emptyListMsg; }
set
{
if (emptyListMsg != value) {
emptyListMsg = value;
this.Invalidate();
}
}
}
private String emptyListMsg = "";
///
/// What font should the 'list empty' message be drawn in?
///
[Category("Appearance"),
Description("What font should the 'list empty' message be drawn in?"),
DefaultValue(null)]
public Font EmptyListMsgFont
{
get { return emptyListMsgFont; }
set { emptyListMsgFont = value; }
}
private Font emptyListMsgFont;
///
/// Return the font for the 'list empty' message or a default
///
[Browsable(false)]
public Font EmptyListMsgFontOrDefault
{
get
{
if (this.EmptyListMsgFont == null)
return new Font("Tahoma", 14);
else
return this.EmptyListMsgFont;
}
}
///
/// Get or set whether or not the listview is frozen. When the listview is
/// frozen, it will not update itself.
///
/// The Frozen property is similar to the methods Freeze()/Unfreeze()
/// except that changes to the Frozen property do not nest.
/// objectListView1.Frozen = false; // unfreeze the control regardless of the number of Freeze() calls
///
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public bool Frozen
{
get { return freezeCount > 0; }
set
{
if (value)
Freeze();
else if (freezeCount > 0) {
freezeCount = 1;
Unfreeze();
}
}
}
private int freezeCount = 0;
///
/// When a group title has an item count, how should the lable be formatted?
///
///
/// The given format string can/should have two placeholders:
///
/// {0} - the original group title
/// {1} - the number of items in the group
///
///
/// "{0} [{1} items]"
[Category("Behavior"),
Description("The format to use when suffixing item counts to group titles"),
DefaultValue(null)]
public string GroupWithItemCountFormat
{
get { return groupWithItemCountFormat; }
set { groupWithItemCountFormat = value; }
}
private string groupWithItemCountFormat;
///
/// Return this.GroupWithItemCountFormat or a reasonable default
///
[Browsable(false)]
public string GroupWithItemCountFormatOrDefault
{
get
{
if (String.IsNullOrEmpty(this.GroupWithItemCountFormat))
return "{0} [{1} items]";
else
return this.GroupWithItemCountFormat;
}
}
///
/// When a group title has an item count, how should the lable be formatted if
/// there is only one item in the group?
///
///
/// The given format string can/should have two placeholders:
///
/// {0} - the original group title
/// {1} - the number of items in the group (always 1)
///
///
/// "{0} [{1} item]"
[Category("Behavior"),
Description("The format to use when suffixing item counts to group titles"),
DefaultValue(null)]
public string GroupWithItemCountSingularFormat
{
get { return groupWithItemCountSingularFormat; }
set { groupWithItemCountSingularFormat = value; }
}
private string groupWithItemCountSingularFormat;
///
/// Return this.GroupWithItemCountSingularFormat or a reasonable default
///
[Browsable(false)]
public string GroupWithItemCountSingularFormatOrDefault
{
get
{
if (String.IsNullOrEmpty(this.GroupWithItemCountSingularFormat))
return "{0} [{1} item]";
else
return this.GroupWithItemCountSingularFormat;
}
}
///
/// Does this listview have a message that should be drawn when the list is empty?
///
[Browsable(false)]
public bool HasEmptyListMsg
{
get { return !String.IsNullOrEmpty(this.EmptyListMsg); }
}
///
/// What color should be used for the background of selected rows?
///
/// Windows does not give the option of changing the selection background.
/// So this color is only used when control is owner drawn and when columns have a
/// renderer installed -- a basic new BaseRenderer() will suffice.
[Category("Appearance"),
Description("The background color of selected rows when the control is owner drawn"),
DefaultValue(typeof(Color), "Empty")]
public Color HighlightBackgroundColor
{
get { return highlightBackgroundColor; }
set { highlightBackgroundColor = value; }
}
private Color highlightBackgroundColor = Color.Empty;
///
/// Return the color should be used for the background of selected rows or a reasonable default
///
[Browsable(false)]
public Color HighlightBackgroundColorOrDefault
{
get {
if (this.HighlightBackgroundColor.IsEmpty)
return SystemColors.Highlight;
else
return this.HighlightBackgroundColor;
}
}
///
/// Setup the list so it will draw selected rows using custom colours.
///
///
/// This method makes the list owner drawn, and ensures that all columns have at
/// least a BaseRender installed.
///
public void EnableCustomSelectionColors()
{
this.OwnerDraw = true;
foreach (OLVColumn column in this.AllColumns) {
if (column.RendererDelegate == null)
column.Renderer = new BaseRenderer();
}
}
///
/// What color should be used for the foreground of selected rows?
///
/// Windows does not give the option of changing the selection foreground (text color).
/// So this color is only used when control is owner drawn and when columns have a
/// renderer installed -- a basic new BaseRenderer() will suffice.
[Category("Appearance"),
Description("The foreground color of selected rows when the control is owner drawn"),
DefaultValue(typeof(Color), "Empty")]
public Color HighlightForegroundColor
{
get { return highlightForegroundColor; }
set { highlightForegroundColor = value; }
}
private Color highlightForegroundColor = Color.Empty;
///
/// Return the color should be used for the foreground of selected rows or a reasonable default
///
public Color HighlightForegroundColorOrDefault
{
get {
if (this.HighlightForegroundColor.IsEmpty)
return SystemColors.HighlightText;
else
return this.HighlightForegroundColor;
}
}
///
/// Return true if a cell edit operation is currently happening
///
[Browsable(false)]
public bool IsCellEditing
{
get { return this.cellEditor != null; }
}
///
/// Get/set the collection of objects that this list will show
///
/// The contents of the control will be updated immediately after setting this property
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public IEnumerable Objects
{
get { return this.objects; }
set { this.SetObjects(value); }
}
private IEnumerable objects;
///
/// Take ownership of the 'objects' collection. This separats our collection from the source.
///
///
///
/// 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.
///
///
/// This method has the intentional side-effect of converting our list of objects to an ArrayList.
///
///
virtual protected void TakeOwnershipOfObjects()
{
if (this.isOwnerOfObjects)
return;
this.isOwnerOfObjects = true;
if (this.objects == null)
this.objects = new ArrayList();
else if (this.objects is ICollection)
this.objects = new ArrayList((ICollection)this.objects);
else {
ArrayList newObjects = new ArrayList();
foreach (object x in this.objects)
newObjects.Add(x);
this.objects = newObjects;
}
}
///
/// Specify the height of each row in the control in pixels.
///
/// The row height in a listview is normally determined by the font size and the small image list size.
/// This setting allows that calculation to be overridden (within reason: you still cannot set the line height to be
/// less than the line height of the font used in the control).
/// Setting it to -1 means use the normal calculation method.
/// This feature is experiemental! Strange things may happen to your program,
/// your spouse or your pet if you use it.
///
[Category("Appearance"),
DefaultValue(-1)]
public int RowHeight
{
get { return rowHeight; }
set
{
if (value < 1)
rowHeight = -1;
else
rowHeight = value;
this.SetupExternalImageList();
}
}
private int rowHeight = -1;
///
/// Get/set the column that will be used to resolve comparisons that are equal when sorting.
///
/// There is no user interface for this setting. It must be set programmatically.
/// The default is the first column.
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public OLVColumn SecondarySortColumn
{
get
{
if (this.secondarySortColumn == null) {
if (this.Columns.Count > 0)
return this.GetColumn(0);
else
return null;
} else
return this.secondarySortColumn;
}
set
{
this.secondarySortColumn = value;
}
}
private OLVColumn secondarySortColumn;
///
/// When the SecondarySortColumn is used, in what order will it compare results?
///
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public SortOrder SecondarySortOrder
{
get { return this.secondarySortOrder; }
set { this.secondarySortOrder = value; }
}
private SortOrder secondarySortOrder = SortOrder.Ascending;
///
/// When the user right clicks on the column headers, should a menu be presented which will allow
/// them to choose which columns will be shown in the view?
///
[Category("Behavior"),
Description("When the user right clicks on the column headers, should a menu be presented which will allow them to choose which columns will be shown in the view?"),
DefaultValue(true)]
public bool SelectColumnsOnRightClick
{
get { return selectColumnsOnRightClick; }
set { selectColumnsOnRightClick = value; }
}
private bool selectColumnsOnRightClick = true;
///
/// When the column select menu is open, should it stay open after an item is selected?
/// Staying open allows the user to turn more than one column on or off at a time.
///
[Category("Behavior"),
Description("When the column select menu is open, should it stay open after an item is selected?"),
DefaultValue(true)]
public bool SelectColumnsMenuStaysOpen
{
get { return selectColumnsMenuStaysOpen; }
set { selectColumnsMenuStaysOpen = value; }
}
private bool selectColumnsMenuStaysOpen = true;
///
/// Return the index of the row that is currently selected. If no row is selected,
/// or more than one is selected, return -1.
///
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public int SelectedIndex
{
get
{
if (this.SelectedIndices.Count == 1)
return this.SelectedIndices[0];
else
return -1;
}
set
{
this.SelectedIndices.Clear();
if (value >= 0 && value < this.Items.Count)
this.SelectedIndices.Add(value);
}
}
///
/// Get the ListViewItem that is currently selected . If no row is selected, or more than one is selected, return null.
///
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public ListViewItem SelectedItem
{
get
{
if (this.SelectedIndices.Count == 1)
return this.GetItem(this.SelectedIndices[0]);
else
return null;
}
set
{
this.SelectedIndices.Clear();
if (value != null)
this.SelectedIndices.Add(value.Index);
}
}
///
/// Get the model object from the currently selected row. If no row is selected, or more than one is selected, return null.
/// Select the row that is displaying the given model object. All other rows are deselected.
///
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Object SelectedObject
{
get { return this.GetSelectedObject(); }
set { this.SelectObject(value); }
}
///
/// Get the model objects from the currently selected rows. If no row is selected, the returned List will be empty.
/// When setting this value, select the rows that is displaying the given model objects. All other rows are deselected.
///
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public ArrayList SelectedObjects
{
get { return this.GetSelectedObjects(); }
set { this.SelectObjects(value); }
}
///
/// Should the list view show a bitmap in the column header to show the sort direction?
///
///
/// 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.
///
[Category("Behavior"),
Description("Should the list view show sort indicators in the column headers?"),
DefaultValue(true)]
public bool ShowSortIndicators
{
get { return showSortIndicators; }
set { showSortIndicators = value; }
}
private bool showSortIndicators;
///
/// Should the list view show images on subitems?
///
///
/// Under Windows, this works by sending messages to the underlying
/// Windows control. To make this work under Mono, we would have to owner drawing the items :-(
[Category("Behavior"),
Description("Should the list view show images on subitems?"),
DefaultValue(false)]
public bool ShowImagesOnSubItems
{
get
{
#if MONO
return false;
#else
return showImagesOnSubItems;
#endif
}
set { showImagesOnSubItems = value; }
}
private bool showImagesOnSubItems;
///
/// This property controls whether group labels will be suffixed with a count of items.
///
///
/// The format of the suffix is controlled by GroupWithItemCountFormat/GroupWithItemCountSingularFormat properties
///
[Category("Behavior"),
Description("Will group titles be suffixed with a count of the items in the group?"),
DefaultValue(false)]
public bool ShowItemCountOnGroups
{
get { return showItemCountOnGroups; }
set { showItemCountOnGroups = value; }
}
private bool showItemCountOnGroups;
///
/// Override the SmallImageList property so we can correctly shadow its operations.
///
/// If you use the RowHeight property to specify the row height, the SmallImageList
/// must be fully initialised before setting/changing the RowHeight. If you add new images to the image
/// list after setting the RowHeight, you must assign the imagelist to the control again. Something as simple
/// as this will work:
/// listView1.SmallImageList = listView1.SmallImageList;
///
new public ImageList SmallImageList
{
get { return this.shadowedImageList; }
set
{
this.shadowedImageList = value;
this.SetupExternalImageList();
}
}
private ImageList shadowedImageList = null;
///
/// When the listview is grouped, should the items be sorted by the primary column?
/// If this is false, the items will be sorted by the same column as they are grouped.
///
[Category("Behavior"),
Description("When the listview is grouped, should the items be sorted by the primary column? If this is false, the items will be sorted by the same column as they are grouped."),
DefaultValue(true)]
public bool SortGroupItemsByPrimaryColumn
{
get { return this.sortGroupItemsByPrimaryColumn; }
set { this.sortGroupItemsByPrimaryColumn = value; }
}
private bool sortGroupItemsByPrimaryColumn = true;
///
/// When resizing a column by dragging its divider, should any space filling columns be
/// resized at each mouse move? If this is false, the filling columns will be
/// updated when the mouse is released.
///
///
/// I think that this looks very ugly, but it does give more immediate feedback.
/// It looks ugly because every
/// column to the right of the divider being dragged gets updated twice: once when
/// the column be resized changes size (this moves
/// all the columns slightly to the right); then again when the filling columns are updated, but they will be shrunk
/// so that the combined width is not more than the control, so everything jumps slightly back to the left again.
///
[Category("Behavior"),
Description("When resizing a column by dragging its divider, should any space filling columns be resized at each mouse move?"),
DefaultValue(false)]
public bool UpdateSpaceFillingColumnsWhenDraggingColumnDivider
{
get { return updateSpaceFillingColumnsWhenDraggingColumnDivider; }
set { updateSpaceFillingColumnsWhenDraggingColumnDivider = value; }
}
private bool updateSpaceFillingColumnsWhenDraggingColumnDivider = false;
///
/// Should the list give a different background color to every second row?
///
/// The color of the alternate rows is given by AlternateRowBackColor.
/// There is a "feature" in .NET for listviews in non-full-row-select mode, where
/// selected rows are not drawn with their correct background color.
[Category("Appearance"),
Description("Should the list view use a different backcolor to alternate rows?"),
DefaultValue(false)]
public bool UseAlternatingBackColors
{
get { return useAlternatingBackColors; }
set { useAlternatingBackColors = value; }
}
private bool useAlternatingBackColors;
///
/// Get/set the style of view that this listview is using
///
/// Switching to tile or details view installs the columns appropriate to that view.
/// Confusingly, in tile view, every column is shown as a row of information.
new public View View
{
get { return base.View; }
set
{
if (base.View == value)
return;
if (this.Frozen) {
base.View = value;
return;
}
this.Freeze();
// If we are switching to a Detail or Tile view, setup the columns needed for that view
if (value == View.Details || value == View.Tile) {
this.ChangeToFilteredColumns(value);
if (value == View.Tile)
this.CalculateReasonableTileSize();
}
base.View = value;
this.Unfreeze();
}
}
#endregion
#region Callbacks
///
/// This delegate can be used to sort the table in a custom fasion.
///
///
///
/// What the delegate has to do depends on the type of ObjectListView it is sorting:
///
///
///
/// If it is sorting a normal ObjectListView, the delegate must install a ListViewItemSorter on the ObjectListView. This install ItemSorter will actually do the work of sorting the ListViewItems. See ColumnComparer in the code for an example of what an ItemSorter has to do.
///
///
/// If the delegate is sorting a VirtualObjectListView or a FastObjectListView, the delegate must sort the model objects that are sourcing the list (remember, in a virtual list, the application holds the model objects and the list just askes for them as it needs them).
///
///
///
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public SortDelegate CustomSorter
{
get { return customSorter; }
set { customSorter = value; }
}
private SortDelegate customSorter;
///
/// This delegate can be used to format a OLVListItem before it is added to the control.
///
///
/// The model object for the row can be found through the RowObject property of the OLVListItem object.
/// 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:myListViewItem.UseItemStyleForSubItems = false;.
///
/// 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.
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public RowFormatterDelegate RowFormatter
{
get { return rowFormatter; }
set { rowFormatter = value; }
}
private RowFormatterDelegate rowFormatter;
///
/// This delegate will be called whenever the ObjectListView needs to know the check state
/// of the row associated with a given model object
///
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public CheckStateGetterDelegate CheckStateGetter
{
get { return checkStateGetter; }
set { checkStateGetter = value; }
}
private CheckStateGetterDelegate checkStateGetter;
///
/// This delegate will be called whenever the user tries to change the check state
/// of a row. The delegate should return the value that the listview should actuall
/// use, which may be different to the one given to the delegate.
///
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public CheckStatePutterDelegate CheckStatePutter
{
get { return checkStatePutter; }
set { checkStatePutter = value; }
}
private CheckStatePutterDelegate checkStatePutter;
#endregion
#region List commands
///
/// Set the collection of objects that will be shown in this list view.
///
/// This method can safely be called from background threads.
/// The list is updated immediately
/// The objects to be displayed
virtual public void SetObjects(IEnumerable collection)
{
if (this.InvokeRequired) {
this.Invoke((MethodInvoker)delegate { this.SetObjects(collection); });
return;
}
// If we own the current list and they change to another list, we don't own it anymore
if (this.isOwnerOfObjects && this.objects != collection)
this.isOwnerOfObjects = false;
this.objects = collection;
this.BuildList(false);
}
///
/// Add the given model object to this control.
///
/// The model object to be displayed
/// See AddObjects() for more details
public void AddObject(object modelObject)
{
this.AddObjects(new object[] { modelObject });
}
///
/// Add the given collection of model objects to this control.
///
/// A collection of model objects
///
/// The added objects will appear in their correct sort position, if sorting
/// is active. Otherwise, they will appear at the end of the list.
/// No check is performed to see if any of the objects are already in the ListView.
/// The method uses the ObjectsAsList property. See that property for a
/// description of what is does.
/// Null objects are silently ignored.
///
virtual public void AddObjects(ICollection modelObjects)
{
if (modelObjects == null)
return;
this.BeginUpdate();
this.TakeOwnershipOfObjects();
ArrayList ourObjects = (ArrayList)this.Objects;
List itemList = new List();
foreach (object modelObject in modelObjects) {
if (modelObject != null) {
ourObjects.Add(modelObject);
OLVListItem lvi = new OLVListItem(modelObject);
this.FillInValues(lvi, modelObject);
itemList.Add(lvi);
}
}
this.Items.AddRange(itemList.ToArray());
this.Sort(this.lastSortColumn);
foreach (OLVListItem lvi in itemList) {
this.SetSubItemImages(lvi.Index, lvi);
}
this.EndUpdate();
}
///
/// Remove the given model object from the ListView
///
/// The model to be removed
/// See RemoveObjects() for more details
public void RemoveObject(object modelObject)
{
this.RemoveObjects(new object[] { modelObject });
}
///
/// Remove all of the given objects from the control
///
/// Collection of objects to be removed
///
/// Nulls and model objects that are not in the ListView are silently ignored.
///
virtual public void RemoveObjects(ICollection modelObjects)
{
if (modelObjects == null)
return;
this.BeginUpdate();
this.TakeOwnershipOfObjects();
ArrayList ourObjects = (ArrayList)this.Objects;
foreach (object modelObject in modelObjects) {
if (modelObject != null) {
ourObjects.Remove(modelObject);
int i = this.IndexOf(modelObject);
if (i >= 0)
this.Items.RemoveAt(i);
}
}
this.EndUpdate();
}
///
/// Update the list to reflect the contents of the given collection, without affecting
/// the scrolling position, selection or sort order.
///
/// The objects to be displayed
///
/// This method is about twice as slow as SetObjects().
/// This method is experimental -- it may disappear in later versions of the code.
/// There has to be a better way to do this! JPP 15/1/2008
/// In most situations, if you need this functionality, use a FastObjectListView instead. JPP 2/2/2008
///
[Obsolete("Use a FastObjectListView instead of this method.", false)]
virtual public void IncrementalUpdate(IEnumerable collection)
{
if (this.InvokeRequired) {
this.Invoke((MethodInvoker)delegate { this.IncrementalUpdate(collection); });
return;
}
this.BeginUpdate();
this.ListViewItemSorter = null;
ArrayList previousSelection = this.SelectedObjects;
// Replace existing rows, creating new listviewitems if we get to the end of the list
List newItems = new List();
int rowIndex = 0;
int itemCount = this.Items.Count;
foreach (object model in collection) {
if (rowIndex < itemCount) {
OLVListItem lvi = this.GetItem(rowIndex);
lvi.RowObject = model;
this.RefreshItem(lvi);
} else {
OLVListItem lvi = new OLVListItem(model);
this.FillInValues(lvi, model);
newItems.Add(lvi);
}
rowIndex++;
}
// Delete any excess rows
int numRowsToDelete = itemCount - rowIndex;
for (int i = 0; i < numRowsToDelete; i++)
this.Items.RemoveAt(rowIndex);
this.Items.AddRange(newItems.ToArray());
this.Sort(this.lastSortColumn);
SetAllSubItemImages();
this.SelectedObjects = previousSelection;
this.EndUpdate();
this.objects = collection;
}
///
/// Remove all items from this list
///
/// This method can safely be called from background threads.
virtual public void ClearObjects()
{
if (this.InvokeRequired)
this.Invoke(new MethodInvoker(ClearObjects));
else
this.Items.Clear();
}
///
/// Build/rebuild all the list view items in the list
///
virtual public void BuildList()
{
this.BuildList(true);
}
///
/// Build/rebuild all the list view items in the list
///
/// If this is true, the control will try to preserve the selection
/// and the scroll position (see Remarks)
///
///
///
/// Use this method in situations were the contents of the list is basically the same
/// as previously.
///
///
/// Due to limitations in .NET's ListView, the scroll position is only preserved if
/// the control is in Details view AND it is not showing groups.
///
///
virtual public void BuildList(bool shouldPreserveState)
{
if (this.Frozen)
return;
ArrayList previousSelection = new ArrayList();
int previousTopIndex = this.TopItemIndex;
if (shouldPreserveState && this.objects != null)
previousSelection = this.SelectedObjects;
this.BeginUpdate();
this.Items.Clear();
this.ListViewItemSorter = null;
if (this.objects != null) {
// Build a list of all our items and then display them. (Building
// a list and then doing one AddRange is about 10-15% faster than individual adds)
List itemList = new List();
foreach (object rowObject in this.objects) {
OLVListItem lvi = new OLVListItem(rowObject);
this.FillInValues(lvi, rowObject);
itemList.Add(lvi);
}
this.Items.AddRange(itemList.ToArray());
this.SetAllSubItemImages();
this.Sort(this.lastSortColumn);
if (shouldPreserveState)
this.SelectedObjects = previousSelection;
}
this.EndUpdate();
// We can only restore the scroll position after the EndUpdate() because
// of caching that the ListView does internally during a BeginUpdate/EndUpdate pair.
if (shouldPreserveState)
this.TopItemIndex = previousTopIndex;
}
///
/// Get or set the index of the top item of this listview
///
///
///
/// This property only works when the listview is in Details view and not showing groups.
///
///
/// The reason that it does not work when showing groups is that, when groups are enabled,
/// the Windows message LVM_GETTOPINDEX always returns 0, regardless of the
/// scroll position.
///
///
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public int TopItemIndex
{
get
{
if (this.View == View.Details && this.TopItem != null)
return this.TopItem.Index;
else
return -1;
}
set
{
int newTopIndex = Math.Min(value, this.GetItemCount() - 1);
if (this.View == View.Details && newTopIndex >= 0) {
this.TopItem = this.Items[newTopIndex];
// Setting the TopItem sometimes gives off by one errors,
// that (bizarrely) are correct on a second attempt
if (this.TopItem != null && this.TopItem.Index != newTopIndex)
this.TopItem = this.GetItem(newTopIndex);
}
}
}
///
/// Sort the items by the last sort column
///
new public void Sort()
{
this.Sort(this.lastSortColumn);
}
///
/// Organise the view items into groups, based on the last sort column or the first column
/// if there is no last sort column
///
public void BuildGroups()
{
this.BuildGroups(this.lastSortColumn);
}
///
/// Organise the view items into groups, based on the given column
///
/// If the AlwaysGroupByColumn property is not null,
/// the list view items will be organisd by that column,
/// and the 'column' parameter will be ignored.
/// The column whose values should be used for sorting.
virtual public void BuildGroups(OLVColumn column)
{
if (column == null)
column = this.GetColumn(0);
// If a specific column has been given as the group by column, we always
// group by that column, regardless of what the user just clicked.
OLVColumn groupByColumn = this.AlwaysGroupByColumn;
if (groupByColumn == null)
groupByColumn = column;
SortOrder groupSortOrder = this.AlwaysGroupBySortOrder;
if (groupSortOrder == SortOrder.None)
groupSortOrder = this.lastSortOrder;
this.Groups.Clear();
// Getting the Count forces any internal cache of the ListView to be flushed. Without
// this, iterating over the Items will not work correctly if the ListView handle
// has not yet been created.
int dummy = this.Items.Count;
// Separate the list view items into groups, using the group key as the descrimanent
Dictionary