Files
duplicati/thirdparty/ObjectListView/ObjectListView/OLVColumn.cs
T
2011-11-20 08:45:25 +00:00

1489 lines
64 KiB
C#

/*
* OLVColumn - A column in an ObjectListView
*
* Author: Phillip Piper
* Date: 31-March-2011 5:53 pm
*
* Change log:
* 2011-05-27 JPP - Added Sortable, Hideable, Groupable, Searchable, ShowTextInHeader properties
* 2011-04-12 JPP - Added HasFilterIndicator
* 2011-03-31 JPP - Split into its own file
*
* Copyright (C) 2011 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip_piper@bigfoot.com.
*/
using System;
using System.ComponentModel;
using System.Windows.Forms;
using System.Drawing;
using System.Collections;
namespace BrightIdeasSoftware {
/// <summary>
/// An OLVColumn knows which aspect of an object it should present.
/// </summary>
/// <remarks>
/// The column knows how to:
/// <list type="bullet">
/// <item><description>extract its aspect from the row object</description></item>
/// <item><description>convert an aspect to a string</description></item>
/// <item><description>calculate the image for the row object</description></item>
/// <item><description>extract a group "key" from the row object</description></item>
/// <item><description>convert a group "key" into a title for the group</description></item>
/// </list>
/// <para>For sorting to work correctly, aspects from the same column
/// must be of the same type, that is, the same aspect cannot sometimes
/// return strings and other times integers.</para>
/// </remarks>
[Browsable(false)]
public partial class OLVColumn : ColumnHeader {
/// <summary>
/// Create an OLVColumn
/// </summary>
public OLVColumn() {
}
/// <summary>
/// Initialize a column to have the given title, and show the given aspect
/// </summary>
/// <param name="title">The title of the column</param>
/// <param name="aspect">The aspect to be shown in the column</param>
public OLVColumn(string title, string aspect)
: this() {
this.Text = title;
this.AspectName = aspect;
}
#region Public Properties
/// <summary>
/// This delegate will be used to extract a value to be displayed in this column.
/// </summary>
/// <remarks>
/// If this is set, AspectName is ignored.
/// </remarks>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public AspectGetterDelegate AspectGetter {
get { return aspectGetter; }
set { aspectGetter = value; }
}
private AspectGetterDelegate aspectGetter;
/// <summary>
/// Remember if this aspect getter for this column was generated internally, and can therefore
/// be regenerated at will
/// </summary>
[Obsolete("This property is no longer maintained", true),
Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public bool AspectGetterAutoGenerated {
get { return aspectGetterAutoGenerated; }
set { aspectGetterAutoGenerated = value; }
}
private bool aspectGetterAutoGenerated;
/// <summary>
/// The name of the property or method that should be called to get the value to display in this column.
/// This is only used if a ValueGetterDelegate has not been given.
/// </summary>
/// <remarks>This name can be dotted to chain references to properties or parameter-less methods.</remarks>
/// <example>"DateOfBirth"</example>
/// <example>"Owner.HomeAddress.Postcode"</example>
[Category("ObjectListView"),
Description("The name of the property or method that should be called to get the aspect to display in this column"),
DefaultValue(null)]
public string AspectName {
get { return aspectName; }
set {
aspectName = value;
this.aspectMunger = null;
}
}
private string aspectName;
/// <summary>
/// This delegate will be used to put an edited value back into the model object.
/// </summary>
/// <remarks>
/// This does nothing if IsEditable == false.
/// </remarks>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public AspectPutterDelegate AspectPutter {
get { return aspectPutter; }
set { aspectPutter = value; }
}
private AspectPutterDelegate aspectPutter;
/// <summary>
/// The delegate that will be used to translate the aspect to display in this column into a string.
/// </summary>
/// <remarks>If this value is set, AspectToStringFormat will be ignored.</remarks>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public AspectToStringConverterDelegate AspectToStringConverter {
get { return aspectToStringConverter; }
set { aspectToStringConverter = value; }
}
private AspectToStringConverterDelegate aspectToStringConverter;
/// <summary>
/// This format string will be used to convert an aspect to its string representation.
/// </summary>
/// <remarks>
/// This string is passed as the first parameter to the String.Format() method.
/// This is only used if AspectToStringConverter has not been set.</remarks>
/// <example>"{0:C}" to convert a number to currency</example>
[Category("ObjectListView"),
Description("The format string that will be used to convert an aspect to its string representation"),
DefaultValue(null)]
public string AspectToStringFormat {
get { return aspectToStringFormat; }
set { aspectToStringFormat = value; }
}
private string aspectToStringFormat;
/// <summary>
/// Gets or sets whether the cell editor should use AutoComplete
/// </summary>
[Category("ObjectListView"),
Description("Should the editor for cells of this column use AutoComplete"),
DefaultValue(true)]
public bool AutoCompleteEditor {
get { return this.AutoCompleteEditorMode != AutoCompleteMode.None; }
set {
if (value) {
if (this.AutoCompleteEditorMode == AutoCompleteMode.None)
this.AutoCompleteEditorMode = AutoCompleteMode.Append;
} else
this.AutoCompleteEditorMode = AutoCompleteMode.None;
}
}
/// <summary>
/// Gets or sets whether the cell editor should use AutoComplete
/// </summary>
[Category("ObjectListView"),
Description("Should the editor for cells of this column use AutoComplete"),
DefaultValue(AutoCompleteMode.Append)]
public AutoCompleteMode AutoCompleteEditorMode {
get { return autoCompleteEditorMode; }
set { autoCompleteEditorMode = value; }
}
private AutoCompleteMode autoCompleteEditorMode = AutoCompleteMode.Append;
/// <summary>
/// Gets whether this column can be hidden by user actions
/// </summary>
/// <remarks>This take into account both the Hideable property and whether this column
/// is the primary column of the listview (column 0).</remarks>
[Browsable(false)]
public bool CanBeHidden {
get {
return this.Hideable && (this.Index != 0);
}
}
/// <summary>
/// Gets or sets whether this column will show a checkbox.
/// </summary>
/// <remarks>
/// Setting this on column 0 has no effect. Column 0 check box is controlled
/// by the CheckBoxes property on the ObjectListView itself.
/// </remarks>
[Category("ObjectListView"),
Description("Should values in this column be treated as a checkbox, rather than a string?"),
DefaultValue(false)]
public virtual bool CheckBoxes {
get { return checkBoxes; }
set {
if (this.checkBoxes == value)
return;
this.checkBoxes = value;
if (this.checkBoxes) {
if (this.Renderer == null)
this.Renderer = new CheckStateRenderer();
} else {
if (this.Renderer is CheckStateRenderer)
this.Renderer = null;
}
}
}
private bool checkBoxes;
/// <summary>
/// Gets or sets the clustering strategy used for this column.
/// </summary>
/// <remarks>
/// <para>
/// The clustering strategy is used to build a Filtering menu for this item.
/// If this is null, a useful default will be chosen.
/// </para>
/// <para>
/// To disable filtering on this colummn, set UseFiltering to false.
/// </para>
/// <para>
/// Cluster strategies belong to a particular column. The same instance
/// cannot be shared between multiple columns.
/// </para>
/// </remarks>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public IClusteringStrategy ClusteringStrategy {
get {
if (this.clusteringStrategy == null)
this.ClusteringStrategy = this.DecideDefaultClusteringStrategy();
return clusteringStrategy;
}
set {
this.clusteringStrategy = value;
if (this.clusteringStrategy != null)
this.clusteringStrategy.Column = this;
}
}
private IClusteringStrategy clusteringStrategy;
/// <summary>
/// Should this column resize to fill the free space in the listview?
/// </summary>
/// <remarks>
/// <para>
/// If you want two (or more) columns to equally share the available free space, set this property to True.
/// If you want this column to have a larger or smaller share of the free space, you must
/// set the FreeSpaceProportion property explicitly.
/// </para>
/// <para>
/// Space filling columns are still governed by the MinimumWidth and MaximumWidth properties.
/// </para>
/// /// </remarks>
[Category("ObjectListView"),
Description("Will this column resize to fill unoccupied horizontal space in the listview?"),
DefaultValue(false)]
public bool FillsFreeSpace {
get { return this.FreeSpaceProportion > 0; }
set { this.freeSpaceProportion = value ? 1 : 0; }
}
/// <summary>
/// What proportion of the unoccupied horizontal space in the control should be given to this column?
/// </summary>
/// <remarks>
/// <para>
/// There are situations where it would be nice if a column (normally the rightmost one) would expand as
/// the list view expands, so that as much of the column was visible as possible without having to scroll
/// horizontally (you should never, ever make your users have to scroll anything horizontally!).
/// </para>
/// <para>
/// A space filling column is resized to occupy a proportion of the unoccupied width of the listview (the
/// unoccupied width is the width left over once all the the non-filling columns have been given their space).
/// This property indicates the relative proportion of that unoccupied space that will be given to this column.
/// The actual value of this property is not important -- only its value relative to the value in other columns.
/// For example:
/// <list type="bullet">
/// <item><description>
/// If there is only one space filling column, it will be given all the free space, regardless of the value in FreeSpaceProportion.
/// </description></item>
/// <item><description>
/// If there are two or more space filling columns and they all have the same value for FreeSpaceProportion,
/// they will share the free space equally.
/// </description></item>
/// <item><description>
/// If there are three space filling columns with values of 3, 2, and 1
/// for FreeSpaceProportion, then the first column with occupy half the free space, the second will
/// occupy one-third of the free space, and the third column one-sixth of the free space.
/// </description></item>
/// </list>
/// </para>
/// </remarks>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public int FreeSpaceProportion {
get { return freeSpaceProportion; }
set { freeSpaceProportion = Math.Max(0, value); }
}
private int freeSpaceProportion;
/// <summary>
/// Gets or sets whether groups will be rebuild on this columns values when this column's header is clicked.
/// </summary>
/// <remarks>
/// <para>This setting is only used when ShowGroups is true.</para>
/// <para>
/// If this is false, clicking the header will not rebuild groups. It will not provide
/// any feedback as to why the list is not being regrouped. It is the programmers responsibility to
/// provide appropriate feedback.
/// </para>
/// <para>When this is false, BeforeCreatingGroups events are still fired, which can be used to allow grouping
/// or give feedback, on a case by case basis.</para>
/// </remarks>
[Category("ObjectListView"),
Description("Will the list create groups when this header is clicked?"),
DefaultValue(true)]
public bool Groupable {
get { return groupable; }
set { groupable = value; }
}
private bool groupable = true;
/// <summary>
/// This delegate is called when a group has been created but not yet made
/// into a real ListViewGroup. The user can take this opportunity to fill
/// in lots of other details about the group.
/// </summary>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public GroupFormatterDelegate GroupFormatter {
get { return groupFormatter; }
set { groupFormatter = value; }
}
private GroupFormatterDelegate groupFormatter;
/// <summary>
/// This delegate is called to get the object that is the key for the group
/// to which the given row belongs.
/// </summary>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public GroupKeyGetterDelegate GroupKeyGetter {
get { return groupKeyGetter; }
set { groupKeyGetter = value; }
}
private GroupKeyGetterDelegate groupKeyGetter;
/// <summary>
/// This delegate is called to convert a group key into a title for that group.
/// </summary>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public GroupKeyToTitleConverterDelegate GroupKeyToTitleConverter {
get { return groupKeyToTitleConverter; }
set { groupKeyToTitleConverter = value; }
}
private GroupKeyToTitleConverterDelegate groupKeyToTitleConverter;
/// <summary>
/// When the listview is grouped by this column and group title has an item count,
/// how should the lable be formatted?
/// </summary>
/// <remarks>
/// The given format string can/should have two placeholders:
/// <list type="bullet">
/// <item><description>{0} - the original group title</description></item>
/// <item><description>{1} - the number of items in the group</description></item>
/// </list>
/// </remarks>
/// <example>"{0} [{1} items]"</example>
[Category("ObjectListView"),
Description("The format to use when suffixing item counts to group titles"),
DefaultValue(null),
Localizable(true)]
public string GroupWithItemCountFormat {
get { return groupWithItemCountFormat; }
set { groupWithItemCountFormat = value; }
}
private string groupWithItemCountFormat;
/// <summary>
/// Gets this.GroupWithItemCountFormat or a reasonable default
/// </summary>
/// <remarks>
/// If GroupWithItemCountFormat is not set, its value will be taken from the ObjectListView if possible.
/// </remarks>
[Browsable(false)]
public string GroupWithItemCountFormatOrDefault {
get {
if (!String.IsNullOrEmpty(this.GroupWithItemCountFormat))
return this.GroupWithItemCountFormat;
if (this.ListView != null) {
cachedGroupWithItemCountFormat = ((ObjectListView)this.ListView).GroupWithItemCountFormatOrDefault;
return cachedGroupWithItemCountFormat;
}
// There is one rare but pathelogically possible case where the ListView can
// be null (if the column is grouping a ListView, but is not one of the columns
// for that ListView) so we have to provide a workable default for that rare case.
return cachedGroupWithItemCountFormat ?? "{0} [{1} items]";
}
}
private string cachedGroupWithItemCountFormat;
/// <summary>
/// When the listview is grouped by this column and a group title has an item count,
/// how should the lable be formatted if there is only one item in the group?
/// </summary>
/// <remarks>
/// The given format string can/should have two placeholders:
/// <list type="bullet">
/// <item><description>{0} - the original group title</description></item>
/// <item><description>{1} - the number of items in the group (always 1)</description></item>
/// </list>
/// </remarks>
/// <example>"{0} [{1} item]"</example>
[Category("ObjectListView"),
Description("The format to use when suffixing item counts to group titles"),
DefaultValue(null),
Localizable(true)]
public string GroupWithItemCountSingularFormat {
get { return groupWithItemCountSingularFormat; }
set { groupWithItemCountSingularFormat = value; }
}
private string groupWithItemCountSingularFormat;
/// <summary>
/// Get this.GroupWithItemCountSingularFormat or a reasonable default
/// </summary>
/// <remarks>
/// <para>If this value is not set, the values from the list view will be used</para>
/// </remarks>
[Browsable(false)]
public string GroupWithItemCountSingularFormatOrDefault {
get {
if (!String.IsNullOrEmpty(this.GroupWithItemCountSingularFormat))
return this.GroupWithItemCountSingularFormat;
if (this.ListView != null) {
cachedGroupWithItemCountSingularFormat = ((ObjectListView)this.ListView).GroupWithItemCountSingularFormatOrDefault;
return cachedGroupWithItemCountSingularFormat;
}
// There is one rare but pathelogically possible case where the ListView can
// be null (if the column is grouping a ListView, but is not one of the columns
// for that ListView) so we have to provide a workable default for that rare case.
return cachedGroupWithItemCountSingularFormat ?? "{0} [{1} item]";
}
}
private string cachedGroupWithItemCountSingularFormat;
/// <summary>
/// Gets whether this column should be drawn with a filter indicator in the column header.
/// </summary>
[Browsable(false)]
public bool HasFilterIndicator {
get {
return this.UseFiltering && this.ValuesChosenForFiltering != null && this.ValuesChosenForFiltering.Count > 0;
}
}
/// <summary>
/// Gets or sets a delegate that will be used to own draw header column.
/// </summary>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public HeaderDrawingDelegate HeaderDrawing {
get { return headerDrawing; }
set { headerDrawing = value; }
}
private HeaderDrawingDelegate headerDrawing;
/// <summary>
/// Gets or sets the style that will be used to draw the header for this column
/// </summary>
/// <remarks>This is only uses when the owning ObjectListView has HeaderUsesThemes set to false.</remarks>
[Category("ObjectListView"),
Description("What style will be used to draw the header of this column"),
DefaultValue(null)]
public HeaderFormatStyle HeaderFormatStyle {
get { return this.headerFormatStyle; }
set { this.headerFormatStyle = value; }
}
private HeaderFormatStyle headerFormatStyle;
/// <summary>
/// Gets or sets the font in which the header for this column will be drawn
/// </summary>
/// <remarks>This property will be made obsolete in v2.5. Use HeaderFormatStyle instead</remarks>
/// <remarks>This is only uses when HeaderUsesThemes is false.</remarks>
[Browsable(false)]
[DefaultValue(null)]
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>
/// Gets or sets the color in which the text of the header for this column will be drawn
/// </summary>
/// <remarks>This property will be made obsolete in v2.5. Use HeaderFormatStyle instead</remarks>
/// <remarks>This is only uses when HeaderUsesThemes is false.</remarks>
[Browsable(false)]
[DefaultValue(typeof(Color), "")]
public Color HeaderForeColor {
get { return this.HeaderFormatStyle == null ? Color.Empty : this.HeaderFormatStyle.Normal.ForeColor; }
set {
if (value.IsEmpty && this.HeaderFormatStyle == null)
return;
if (this.HeaderFormatStyle == null)
this.HeaderFormatStyle = new HeaderFormatStyle();
this.HeaderFormatStyle.SetForeColor(value);
}
}
/// <summary>
/// Gets or sets whether the text values in this column will act like hyperlinks
/// </summary>
/// <remarks>This is only taken into account when HeaderUsesThemes is false.</remarks>
[Category("ObjectListView"),
Description("Name of the image that will be shown in the column header."),
DefaultValue(null),
TypeConverter(typeof(ImageKeyConverter)),
Editor("System.Windows.Forms.Design.ImageIndexEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(System.Drawing.Design.UITypeEditor)),
RefreshProperties(RefreshProperties.Repaint)]
public string HeaderImageKey {
get { return headerImageKey; }
set { headerImageKey = value; }
}
private string headerImageKey;
/// <summary>
/// Gets or sets how the text of the header will be drawn?
/// </summary>
[Category("ObjectListView"),
Description("How will the header text be aligned?"),
DefaultValue(HorizontalAlignment.Left)]
public HorizontalAlignment HeaderTextAlign {
get { return headerTextAlign.HasValue ? headerTextAlign.Value : this.TextAlign; }
set { headerTextAlign = value; }
}
private HorizontalAlignment? headerTextAlign;
/// <summary>
/// Gets the header alignment converted to a StringAlignment
/// </summary>
[Browsable(false)]
public StringAlignment HeaderTextAlignAsStringAlignment {
get {
switch (this.HeaderTextAlign) {
case HorizontalAlignment.Left: return StringAlignment.Near;
case HorizontalAlignment.Center: return StringAlignment.Center;
case HorizontalAlignment.Right: return StringAlignment.Far;
default: return StringAlignment.Near;
}
}
}
/// <summary>
/// Gets whether or not this column has an image in the header
/// </summary>
[Browsable(false)]
public bool HasHeaderImage {
get {
return (this.ListView != null &&
this.ListView.SmallImageList != null &&
this.ListView.SmallImageList.Images.ContainsKey(this.HeaderImageKey));
}
}
/// <summary>
/// Gets or sets whether this column can be hidden by the user.
/// </summary>
/// <remarks>
/// <para>Column 0 can never be hidden, regardless of this setting.</para>
/// </remarks>
[Category("ObjectListView"),
Description("Will the user be able to choose to hide this column?"),
DefaultValue(true)]
public bool Hideable {
get { return hideable; }
set { hideable = value; }
}
private bool hideable = true;
/// <summary>
/// Gets or sets whether the text values in this column will act like hyperlinks
/// </summary>
[Category("ObjectListView"),
Description("Will the text values of this column act like hyperlinks?"),
DefaultValue(false)]
public bool Hyperlink {
get { return hyperlink; }
set { hyperlink = value; }
}
private bool hyperlink;
/// <summary>
/// This is the name of property that will be invoked to get the image selector of the
/// image that should be shown in this column.
/// It can return an int, string, Image or null.
/// </summary>
/// <remarks>
/// <para>This is ignored if ImageGetter is not null.</para>
/// <para>The property can use these return value to identify the image:</para>
/// <list type="bullet">
/// <item><description>null or -1 -- indicates no image</description></item>
/// <item><description>an int -- the int value will be used as an index into the image list</description></item>
/// <item><description>a String -- the string value will be used as a key into the image list</description></item>
/// <item><description>an Image -- the Image will be drawn directly (only in OwnerDrawn mode)</description></item>
/// </list>
/// </remarks>
[Category("ObjectListView"),
Description("The name of the property that holds the image selector"),
DefaultValue(null)]
public string ImageAspectName {
get { return imageAspectName; }
set { imageAspectName = value; }
}
private string imageAspectName;
/// <summary>
/// This delegate is called to get the image selector of the image that should be shown in this column.
/// It can return an int, string, Image or null.
/// </summary>
/// <remarks><para>This delegate can use these return value to identify the image:</para>
/// <list type="bullet">
/// <item><description>null or -1 -- indicates no image</description></item>
/// <item><description>an int -- the int value will be used as an index into the image list</description></item>
/// <item><description>a String -- the string value will be used as a key into the image list</description></item>
/// <item><description>an Image -- the Image will be drawn directly (only in OwnerDrawn mode)</description></item>
/// </list>
/// </remarks>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public ImageGetterDelegate ImageGetter {
get { return imageGetter; }
set { imageGetter = value; }
}
private ImageGetterDelegate imageGetter;
/// <summary>
/// Can the values shown in this column be edited?
/// </summary>
/// <remarks>This defaults to true, since the primary means to control the editability of a listview
/// is on the listview itself. Once a listview is editable, all the columns are too, unless the
/// programmer explicitly marks them as not editable</remarks>
[Category("ObjectListView"),
Description("Can the value in this column be edited?"),
DefaultValue(true)]
public bool IsEditable {
get { return isEditable; }
set { isEditable = value; }
}
private bool isEditable = true;
/// <summary>
/// Is this column a fixed width column?
/// </summary>
[Browsable(false)]
public bool IsFixedWidth {
get {
return (this.MinimumWidth != -1 && this.MaximumWidth != -1 && this.MinimumWidth >= this.MaximumWidth);
}
}
/// <summary>
/// Get/set whether this column should be used when the view is switched to tile view.
/// </summary>
/// <remarks>Column 0 is always included in tileview regardless of this setting.
/// Tile views do not work well with many "columns" of information.
/// Two or three works best.</remarks>
[Category("ObjectListView"),
Description("Will this column be used when the view is switched to tile view"),
DefaultValue(false)]
public bool IsTileViewColumn {
get { return isTileViewColumn; }
set { isTileViewColumn = value; }
}
private bool isTileViewColumn;
/// <summary>
/// Gets or sets whether the text of this header should be
/// rendered vertically.
/// </summary>
/// <remarks>
/// <para>If this is true, it is a good idea to set ToolTipText to the name of the column so it's easy to read.</para>
/// <para>Currently (2010-08), vertical headers are text only. They do not draw their image.</para>
/// </remarks>
[Category("ObjectListView"),
Description("Will the header for this column be drawn vertically?"),
DefaultValue(false)]
public bool IsHeaderVertical {
get { return isHeaderVertical; }
set { isHeaderVertical = value; }
}
private bool isHeaderVertical;
/// <summary>
/// Can this column be seen by the user?
/// </summary>
/// <remarks>After changing this value, you must call RebuildColumns() before the changes will be effected.</remarks>
[Category("ObjectListView"),
Description("Can this column be seen by the user?"),
DefaultValue(true)]
public bool IsVisible {
get { return isVisible; }
set { isVisible = value; }
}
private bool isVisible = true;
/// <summary>
/// Where was this column last positioned within the Detail view columns
/// </summary>
/// <remarks>DisplayIndex is volatile. Once a column is removed from the control,
/// there is no way to discover where it was in the display order. This property
/// guards that information even when the column is not in the listview's active columns.</remarks>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public int LastDisplayIndex {
get { return this.lastDisplayIndex; }
set { this.lastDisplayIndex = value; }
}
private int lastDisplayIndex = -1;
/// <summary>
/// What is the maximum width that the user can give to this column?
/// </summary>
/// <remarks>-1 means there is no maximum width. Give this the same value as MinimumWidth to make a fixed width column.</remarks>
[Category("ObjectListView"),
Description("What is the maximum width to which the user can resize this column?"),
DefaultValue(-1)]
public int MaximumWidth {
get { return maxWidth; }
set {
maxWidth = value;
if (maxWidth != -1 && this.Width > maxWidth)
this.Width = maxWidth;
}
}
private int maxWidth = -1;
/// <summary>
/// What is the minimum width that the user can give to this column?
/// </summary>
/// <remarks>-1 means there is no minimum width. Give this the same value as MaximumWidth to make a fixed width column.</remarks>
[Category("ObjectListView"),
Description("What is the minimum width to which the user can resize this column?"),
DefaultValue(-1)]
public int MinimumWidth {
get { return minWidth; }
set {
minWidth = value;
if (this.Width < minWidth)
this.Width = minWidth;
}
}
private int minWidth = -1;
/// <summary>
/// Get/set the renderer that will be invoked when a cell needs to be redrawn
/// </summary>
[Category("ObjectListView"),
Description("The renderer will draw this column when the ListView is owner drawn"),
DefaultValue(null)]
public IRenderer Renderer {
get { return renderer; }
set { renderer = value; }
}
private IRenderer renderer;
/// <summary>
/// This delegate is called when a cell needs to be drawn in OwnerDrawn mode.
/// </summary>
/// <remarks>This method is kept primarily for backwards compatibility.
/// New code should implement an IRenderer, though this property will be maintained.</remarks>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public RenderDelegate RendererDelegate {
get {
if (this.Renderer is Version1Renderer)
return ((Version1Renderer)this.Renderer).RenderDelegate;
else
return null;
}
set {
this.Renderer = value == null ? null : new Version1Renderer(value);
}
}
/// <summary>
/// Gets or sets whether the text in this column's cell will be used when doing text searching.
/// </summary>
/// <remarks>
/// <para>
/// If this is false, text filters will not trying searching this columns cells when looking for matches.
/// </para>
/// </remarks>
[Category("ObjectListView"),
Description("Will the text of the cells in this column be considered when searching?"),
DefaultValue(true)]
public bool Searchable {
get { return searchable; }
set { searchable = value; }
}
private bool searchable = true;
/// <summary>
/// Gets or sets whether the header for this column will include the column's Text.
/// </summary>
/// <remarks>
/// <para>
/// If this is false, the only thing rendered in the column header will be the image from <see cref="HeaderImageKey"/>.
/// </para>
/// <para>This setting is only considered when <see cref="ObjectListView.HeaderUsesThemes"/> is false on the owning ObjectListView.</para>
/// </remarks>
[Category("ObjectListView"),
Description("Will the header for this column include text?"),
DefaultValue(true)]
public bool ShowTextInHeader {
get { return showTextInHeader; }
set { showTextInHeader = value; }
}
private bool showTextInHeader = true;
/// <summary>
/// Gets or sets whether the contents of the list will be resorted when the user clicks the
/// header of this column.
/// </summary>
/// <remarks>
/// <para>
/// If this is false, clicking the header will not sort the list, but will not provide
/// any feedback as to why the list is not being sorted. It is the programmers responsibility to
/// provide appropriate feedback.
/// </para>
/// <para>When this is false, BeforeSorting events are still fired, which can be used to allow sorting
/// or give feedback, on a case by case basis.</para>
/// </remarks>
[Category("ObjectListView"),
Description("Will clicking this columns header resort the list?"),
DefaultValue(true)]
public bool Sortable {
get { return sortable; }
set { sortable = value; }
}
private bool sortable = true;
/// <summary>
/// Gets or sets the horizontal alignment of the contents of the column.
/// </summary>
/// <remarks>.NET will not allow column 0 to have any alignment except
/// to the left. We can't change the basic behaviour of the listview,
/// but when owner drawn, column 0 can now have other alignments.</remarks>
new public HorizontalAlignment TextAlign {
get {
return this.textAlign.HasValue ? this.textAlign.Value : base.TextAlign;
}
set {
this.textAlign = value;
base.TextAlign = value;
}
}
private HorizontalAlignment? textAlign;
/// <summary>
/// Gets the StringAlignment equivilent of the column text alignment
/// </summary>
[Browsable(false)]
public StringAlignment TextStringAlign {
get {
switch (this.TextAlign) {
case HorizontalAlignment.Center:
return StringAlignment.Center;
case HorizontalAlignment.Left:
return StringAlignment.Near;
case HorizontalAlignment.Right:
return StringAlignment.Far;
default:
return StringAlignment.Near;
}
}
}
/// <summary>
/// What string should be displayed when the mouse is hovered over the header of this column?
/// </summary>
/// <remarks>If a HeaderToolTipGetter is installed on the owning ObjectListView, this
/// value will be ignored.</remarks>
[Category("ObjectListView"),
Description("The tooltip to show when the mouse is hovered over the header of this column"),
DefaultValue((String)null),
Localizable(true)]
public String ToolTipText {
get { return toolTipText; }
set { toolTipText = value; }
}
private String toolTipText;
/// <summary>
/// Should this column have a tri-state checkbox?
/// </summary>
/// <remarks>
/// If this is true, the user can choose the third state (normally Indeterminate).
/// </remarks>
[Category("ObjectListView"),
Description("Should values in this column be treated as a tri-state checkbox?"),
DefaultValue(false)]
public virtual bool TriStateCheckBoxes {
get { return triStateCheckBoxes; }
set {
triStateCheckBoxes = value;
if (value && !this.CheckBoxes)
this.CheckBoxes = true;
}
}
private bool triStateCheckBoxes;
/// <summary>
/// Group objects by the initial letter of the aspect of the column
/// </summary>
/// <remarks>
/// One common pattern is to group column by the initial letter of the value for that group.
/// The aspect must be a string (obviously).
/// </remarks>
[Category("ObjectListView"),
Description("The name of the property or method that should be called to get the aspect to display in this column"),
DefaultValue(false)]
public bool UseInitialLetterForGroup {
get { return useInitialLetterForGroup; }
set { useInitialLetterForGroup = value; }
}
private bool useInitialLetterForGroup;
/// <summary>
/// Gets or sets whether or not this column should be user filterable
/// </summary>
[Category("ObjectListView"),
Description("Does this column want to show a Filter menu item when its header is right clicked"),
DefaultValue(true)]
public bool UseFiltering {
get { return useFiltering; }
set { useFiltering = value; }
}
private bool useFiltering = true;
/// <summary>
/// Gets or sets a filter that will only include models where the model's value
/// for this column is one of the values in ValuesChosenForFiltering
/// </summary>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public IModelFilter ValueBasedFilter {
get {
if (!this.UseFiltering)
return null;
if (valueBasedFilter != null)
return valueBasedFilter;
if (this.ClusteringStrategy == null)
return null;
if (this.ValuesChosenForFiltering == null || this.ValuesChosenForFiltering.Count == 0)
return null;
return new OneOfFilter(this.ClusteringStrategy.GetClusterKey, this.ValuesChosenForFiltering);
}
set { valueBasedFilter = value; }
}
private IModelFilter valueBasedFilter;
/// <summary>
/// Gets or sets the values that will be used to generate a filter for this
/// column. For a model to be included by the generated filter, its value for this column
/// must be in this list. If the list is null or empty, this column will
/// not be used for filtering.
/// </summary>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public IList ValuesChosenForFiltering {
get { return this.valuesChosenForFiltering; }
set { this.valuesChosenForFiltering = value; }
}
private IList valuesChosenForFiltering = new ArrayList();
/// <summary>
/// What is the width of this column?
/// </summary>
[Category("ObjectListView"),
Description("The width in pixels of this column"),
DefaultValue(60)]
new public int Width {
get { return base.Width; }
set {
if (this.MaximumWidth != -1 && value > this.MaximumWidth)
base.Width = this.MaximumWidth;
else
base.Width = Math.Max(this.MinimumWidth, value);
}
}
/// <summary>
/// Gets or set whether the contents of this column's cells should be word wrapped
/// </summary>
/// <remarks>If this column uses a custom IRenderer (that is, one that is not descended
/// from BaseRenderer), then that renderer is responsible for implementing word wrapping.</remarks>
[Category("ObjectListView"),
Description("Draw this column cell's word wrapped"),
DefaultValue(false)]
public bool WordWrap {
get { return wordWrap; }
set {
wordWrap = value;
// If there isn't a renderer and they are turning word wrap off, we don't need to do anything
if (this.Renderer == null && !wordWrap)
return;
// All other cases require a renderer of some sort
if (this.Renderer == null)
this.Renderer = new HighlightTextRenderer();
BaseRenderer baseRenderer = this.Renderer as BaseRenderer;
// If there is a custom renderer (not descended from BaseRenderer),
// we leave it up to them to implement wrapping
if (baseRenderer == null)
return;
baseRenderer.CanWrap = wordWrap;
}
}
private bool wordWrap;
#endregion
#region Object commands
/// <summary>
/// For a given group value, return the string that should be used as the groups title.
/// </summary>
/// <param name="value">The group key that is being converted to a title</param>
/// <returns>string</returns>
public string ConvertGroupKeyToTitle(object value) {
if (this.groupKeyToTitleConverter != null)
return this.groupKeyToTitleConverter(value);
return value == null ? "{null}" : this.ValueToString(value);
}
/// <summary>
/// Get the checkedness of the given object for this column
/// </summary>
/// <param name="rowObject">The row object that is being displayed</param>
/// <returns>The checkedness of the object</returns>
public CheckState GetCheckState(object rowObject) {
if (!this.CheckBoxes)
return CheckState.Unchecked;
bool? aspectAsBool = this.GetValue(rowObject) as bool?;
if (aspectAsBool.HasValue) {
if (aspectAsBool.Value)
return CheckState.Checked;
else
return CheckState.Unchecked;
} else
return CheckState.Indeterminate;
}
/// <summary>
/// Put the checkedness of the given object for this column
/// </summary>
/// <param name="rowObject">The row object that is being displayed</param>
/// <param name="newState"></param>
/// <returns>The checkedness of the object</returns>
public void PutCheckState(object rowObject, CheckState newState) {
if (newState == CheckState.Checked)
this.PutValue(rowObject, true);
else
if (newState == CheckState.Unchecked)
this.PutValue(rowObject, false);
else
this.PutValue(rowObject, null);
}
/// <summary>
/// For a given row object, extract the value indicated by the AspectName property of this column.
/// </summary>
/// <param name="rowObject">The row object that is being displayed</param>
/// <returns>An object, which is the aspect named by AspectName</returns>
public object GetAspectByName(object rowObject) {
if (this.aspectMunger == null)
this.aspectMunger = new Munger(this.AspectName);
return this.aspectMunger.GetValue(rowObject);
}
private Munger aspectMunger;
/// <summary>
/// For a given row object, return the object that is the key of the group that this row belongs to.
/// </summary>
/// <param name="rowObject">The row object that is being displayed</param>
/// <returns>Group key object</returns>
public object GetGroupKey(object rowObject) {
if (this.groupKeyGetter == null) {
object key = this.GetValue(rowObject);
String keyAsString = key as String;
if (keyAsString != null && this.UseInitialLetterForGroup) {
if (keyAsString.Length > 0)
key = keyAsString.Substring(0, 1).ToUpper();
}
return key;
} else
return this.groupKeyGetter(rowObject);
}
/// <summary>
/// For a given row object, return the image selector of the image that should displayed in this column.
/// </summary>
/// <param name="rowObject">The row object that is being displayed</param>
/// <returns>int or string or Image. int or string will be used as index into image list. null or -1 means no image</returns>
public Object GetImage(object rowObject) {
if (this.CheckBoxes)
return this.GetCheckStateImage(rowObject);
if (this.ImageGetter != null)
return this.ImageGetter(rowObject);
if (!String.IsNullOrEmpty(this.ImageAspectName)) {
if (this.imageAspectMunger == null)
this.imageAspectMunger = new Munger(this.ImageAspectName);
return this.imageAspectMunger.GetValue(rowObject);
}
// I think this is wrong. ImageKey is meant for the image in the header, not in the rows
if (!String.IsNullOrEmpty(this.ImageKey))
return this.ImageKey;
return this.ImageIndex;
}
private Munger imageAspectMunger;
/// <summary>
/// Return the image that represents the check box for the given model
/// </summary>
/// <param name="rowObject"></param>
/// <returns></returns>
public string GetCheckStateImage(Object rowObject) {
CheckState checkState = this.GetCheckState(rowObject);
if (checkState == CheckState.Checked)
return ObjectListView.CHECKED_KEY;
if (checkState == CheckState.Unchecked)
return ObjectListView.UNCHECKED_KEY;
return ObjectListView.INDETERMINATE_KEY;
}
/// <summary>
/// For a given row object, return the string representation of the value shown in this column.
/// </summary>
/// <remarks>
/// For aspects that are string (e.g. aPerson.Name), the aspect and its string representation are the same.
/// For non-strings (e.g. aPerson.DateOfBirth), the string representation is very different.
/// </remarks>
/// <param name="rowObject"></param>
/// <returns></returns>
public string GetStringValue(object rowObject) {
return this.ValueToString(this.GetValue(rowObject));
}
/// <summary>
/// For a given row object, return the object that is to be displayed in this column.
/// </summary>
/// <param name="rowObject">The row object that is being displayed</param>
/// <returns>An object, which is the aspect to be displayed</returns>
public object GetValue(object rowObject) {
if (this.AspectGetter == null)
return this.GetAspectByName(rowObject);
else
return this.AspectGetter(rowObject);
}
/// <summary>
/// Update the given model object with the given value using the column's
/// AspectName.
/// </summary>
/// <param name="rowObject">The model object to be updated</param>
/// <param name="newValue">The value to be put into the model</param>
public void PutAspectByName(Object rowObject, Object newValue) {
if (this.aspectMunger == null)
this.aspectMunger = new Munger(this.AspectName);
this.aspectMunger.PutValue(rowObject, newValue);
}
/// <summary>
/// Update the given model object with the given value
/// </summary>
/// <param name="rowObject">The model object to be updated</param>
/// <param name="newValue">The value to be put into the model</param>
public void PutValue(Object rowObject, Object newValue) {
if (this.aspectPutter == null)
this.PutAspectByName(rowObject, newValue);
else
this.aspectPutter(rowObject, newValue);
}
/// <summary>
/// Convert the aspect object to its string representation.
/// </summary>
/// <remarks>
/// If the column has been given a AspectToStringConverter, that will be used to do
/// the conversion, otherwise just use ToString().
/// The returned value will not be null. Nulls are always converted
/// to empty strings.
/// </remarks>
/// <param name="value">The value of the aspect that should be displayed</param>
/// <returns>A string representation of the aspect</returns>
public string ValueToString(object value) {
// Give the installed converter a chance to work (even if the value is null)
if (this.AspectToStringConverter != null)
return this.AspectToStringConverter(value) ?? String.Empty;
// Without a converter, nulls become simple empty strings
if (value == null)
return String.Empty;
string fmt = this.AspectToStringFormat;
if (String.IsNullOrEmpty(fmt))
return value.ToString();
else
return String.Format(fmt, value);
}
#endregion
#region Utilities
/// <summary>
/// Decide the clustering strategy that will be used for this column
/// </summary>
/// <returns></returns>
private IClusteringStrategy DecideDefaultClusteringStrategy() {
if (!this.UseFiltering)
return null;
if (this.DataType == typeof(DateTime))
return new DateTimeClusteringStrategy();
return new ClustersFromGroupsStrategy();
}
/// <summary>
/// Gets or sets the type of data shown in this column.
/// </summary>
/// <remarks>If this is not set, it will try to get the type
/// by looking through the rows of the listview.</remarks>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Type DataType {
get {
if (this.dataType == null) {
ObjectListView olv = this.ListView as ObjectListView;
if (olv != null) {
object value = olv.GetFirstNonNullValue(this);
if (value != null)
return value.GetType(); // THINK: Should we cache this?
}
}
return this.dataType;
}
set {
this.dataType = value;
}
}
private Type dataType;
/// <summary>
/// Create groupies
/// This is an untyped version to help with Generator and OLVColumn attributes
/// </summary>
/// <param name="values"></param>
/// <param name="descriptions"></param>
public void MakeGroupies(object[] values, string[] descriptions) {
this.MakeGroupies(values, descriptions, null, null, null);
}
/// <summary>
/// Create groupies
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="values"></param>
/// <param name="descriptions"></param>
public void MakeGroupies<T>(T[] values, string[] descriptions) {
this.MakeGroupies(values, descriptions, null, null, null);
}
/// <summary>
/// Create groupies
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="values"></param>
/// <param name="descriptions"></param>
/// <param name="images"></param>
public void MakeGroupies<T>(T[] values, string[] descriptions, object[] images) {
this.MakeGroupies(values, descriptions, images, null, null);
}
/// <summary>
/// Create groupies
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="values"></param>
/// <param name="descriptions"></param>
/// <param name="images"></param>
/// <param name="subtitles"></param>
public void MakeGroupies<T>(T[] values, string[] descriptions, object[] images, string[] subtitles) {
this.MakeGroupies(values, descriptions, images, subtitles, null);
}
/// <summary>
/// Create groupies.
/// Install delegates that will group the columns aspects into progressive partitions.
/// If an aspect is less than value[n], it will be grouped with description[n].
/// If an aspect has a value greater than the last element in "values", it will be grouped
/// with the last element in "descriptions".
/// </summary>
/// <param name="values">Array of values. Values must be able to be
/// compared to the aspect (using IComparable)</param>
/// <param name="descriptions">The description for the matching value. The last element is the default description.
/// If there are n values, there must be n+1 descriptions.</param>
/// <example>
/// this.salaryColumn.MakeGroupies(
/// new UInt32[] { 20000, 100000 },
/// new string[] { "Lowly worker", "Middle management", "Rarified elevation"});
/// </example>
/// <typeparam name="T"></typeparam>
/// <param name="images"></param>
/// <param name="subtitles"></param>
/// <param name="tasks"></param>
public void MakeGroupies<T>(T[] values, string[] descriptions, object[] images, string[] subtitles, string[] tasks) {
// Sanity checks
if (values == null)
throw new ArgumentNullException("values");
if (descriptions == null)
throw new ArgumentNullException("descriptions");
if (values.Length + 1 != descriptions.Length)
throw new ArgumentException("descriptions must have one more element than values.");
// Install a delegate that returns the index of the description to be shown
this.GroupKeyGetter = delegate(object row) {
Object aspect = this.GetValue(row);
if (aspect == null || aspect == System.DBNull.Value)
return -1;
IComparable comparable = (IComparable)aspect;
for (int i = 0; i < values.Length; i++) {
if (comparable.CompareTo(values[i]) < 0)
return i;
}
// Display the last element in the array
return descriptions.Length - 1;
};
// Install a delegate that simply looks up the given index in the descriptions.
this.GroupKeyToTitleConverter = delegate(object key) {
if ((int)key < 0)
return "";
return descriptions[(int)key];
};
// Install one delegate that does all the other formatting
this.GroupFormatter = delegate(OLVGroup group, GroupingParameters parms) {
int key = (int)group.Key; // we know this is an int since we created it in GroupKeyGetter
if (key >= 0) {
if (images != null && key < images.Length)
group.TitleImage = images[key];
if (subtitles != null && key < subtitles.Length)
group.Subtitle = subtitles[key];
if (tasks != null && key < tasks.Length)
group.Task = tasks[key];
}
};
}
/// <summary>
/// Create groupies based on exact value matches.
/// </summary>
/// <remarks>
/// Install delegates that will group rows into partitions based on equality of this columns aspects.
/// If an aspect is equal to value[n], it will be grouped with description[n].
/// If an aspect is not equal to any value, it will be grouped with "[other]".
/// </remarks>
/// <param name="values">Array of values. Values must be able to be
/// equated to the aspect</param>
/// <param name="descriptions">The description for the matching value.</param>
/// <example>
/// this.marriedColumn.MakeEqualGroupies(
/// new MaritalStatus[] { MaritalStatus.Single, MaritalStatus.Married, MaritalStatus.Divorced, MaritalStatus.Partnered },
/// new string[] { "Looking", "Content", "Looking again", "Mostly content" });
/// </example>
/// <typeparam name="T"></typeparam>
/// <param name="images"></param>
/// <param name="subtitles"></param>
/// <param name="tasks"></param>
public void MakeEqualGroupies<T>(T[] values, string[] descriptions, object[] images, string[] subtitles, string[] tasks) {
// Sanity checks
if (values == null)
throw new ArgumentNullException("values");
if (descriptions == null)
throw new ArgumentNullException("descriptions");
if (values.Length != descriptions.Length)
throw new ArgumentException("descriptions must have the same number of elements as values.");
ArrayList valuesArray = new ArrayList(values);
// Install a delegate that returns the index of the description to be shown
this.GroupKeyGetter = delegate(object row) {
return valuesArray.IndexOf(this.GetValue(row));
};
// Install a delegate that simply looks up the given index in the descriptions.
this.GroupKeyToTitleConverter = delegate(object key) {
int intKey = (int)key; // we know this is an int since we created it in GroupKeyGetter
return (intKey < 0) ? "[other]" : descriptions[intKey];
};
// Install one delegate that does all the other formatting
this.GroupFormatter = delegate(OLVGroup group, GroupingParameters parms) {
int key = (int)group.Key; // we know this is an int since we created it in GroupKeyGetter
if (key >= 0) {
if (images != null && key < images.Length)
group.TitleImage = images[key];
if (subtitles != null && key < subtitles.Length)
group.Subtitle = subtitles[key];
if (tasks != null && key < tasks.Length)
group.Task = tasks[key];
}
};
}
#endregion
}
}