Files
mRemoteNG/ObjectListView/OLVColumn.cs
2025-02-04 16:05:27 +01:00

1912 lines
79 KiB
C#

/*
* OLVColumn - A column in an ObjectListView
*
* Author: Phillip Piper
* Date: 31-March-2011 5:53 pm
*
* Change log:
* 2015-06-12 JPP - HeaderTextAlign became nullable so that it can be "not set" (this was always the intent)
* 2014-09-07 JPP - Added ability to have checkboxes in headers
*
* 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-2014 Phillip Piper
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If you wish to use this code in a closed source application, please contact phillip.piper@gmail.com.
*/
using System;
using System.ComponentModel;
using System.Windows.Forms;
using System.Drawing;
using System.Collections;
using System.Diagnostics;
using System.Drawing.Design;
namespace BrightIdeasSoftware {
// TODO
//[TypeConverter(typeof(ExpandableObjectConverter))]
//public class CheckBoxSettings
//{
// private bool useSettings;
// private Image checkedImage;
// public bool UseSettings {
// get { return useSettings; }
// set { useSettings = value; }
// }
// public Image CheckedImage {
// get { return checkedImage; }
// set { checkedImage = value; }
// }
// public Image UncheckedImage {
// get { return checkedImage; }
// set { checkedImage = value; }
// }
// public Image IndeterminateImage {
// get { return checkedImage; }
// set { checkedImage = value; }
// }
//}
/// <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>
/// How should the button be sized?
/// </summary>
public enum ButtonSizingMode
{
/// <summary>
/// Every cell will have the same sized button, as indicated by ButtonSize property
/// </summary>
FixedBounds,
/// <summary>
/// Every cell will draw a button that fills the cell, inset by ButtonPadding
/// </summary>
CellBounds,
/// <summary>
/// Each button will be resized to contain the text of the Aspect
/// </summary>
TextBounds
}
#region Life and death
/// <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;
}
#endregion
#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>
/// When a cell is edited, should the whole cell be used (minus any space used by checkbox or image)?
/// </summary>
/// <remarks>
/// <para>This is always treated as true when the control is NOT owner drawn.</para>
/// <para>
/// When this is false (the default) and the control is owner drawn,
/// ObjectListView will try to calculate the width of the cell's
/// actual contents, and then size the editing control to be just the right width. If this is true,
/// the whole width of the cell will be used, regardless of the cell's contents.
/// </para>
/// <para>If this property is not set on the column, the value from the control will be used
/// </para>
/// <para>This value is only used when the control is in Details view.</para>
/// <para>Regardless of this setting, developers can specify the exact size of the editing control
/// by listening for the CellEditStarting event.</para>
/// </remarks>
[Category("ObjectListView"),
Description("When a cell is edited, should the whole cell be used?"),
DefaultValue(null)]
public virtual bool? CellEditUseWholeCell
{
get { return cellEditUseWholeCell; }
set { cellEditUseWholeCell = value; }
}
private bool? cellEditUseWholeCell;
/// <summary>
/// Get whether the whole cell should be used when editing a cell in this column
/// </summary>
/// <remarks>This calculates the current effective value, which may be different to CellEditUseWholeCell</remarks>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public virtual bool CellEditUseWholeCellEffective {
get {
bool? columnSpecificValue = this.ListView.View == View.Details ? this.CellEditUseWholeCell : (bool?) null;
return (columnSpecificValue ?? ((ObjectListView) this.ListView).CellEditUseWholeCell);
}
}
/// <summary>
/// Gets or sets how many pixels will be left blank around this cells in this column
/// </summary>
/// <remarks>This setting only takes effect when the control is owner drawn.</remarks>
[Category("ObjectListView"),
Description("How many pixels will be left blank around the cells in this column?"),
DefaultValue(null)]
public Rectangle? CellPadding
{
get { return this.cellPadding; }
set { this.cellPadding = value; }
}
private Rectangle? cellPadding;
/// <summary>
/// Gets or sets how cells in this column will be vertically aligned.
/// </summary>
/// <remarks>
/// <para>
/// This setting only takes effect when the control is owner drawn.
/// </para>
/// <para>
/// If this is not set, the value from the control itself will be used.
/// </para>
/// </remarks>
[Category("ObjectListView"),
Description("How will cell values be vertically aligned?"),
DefaultValue(null)]
public virtual StringAlignment? CellVerticalAlignment {
get { return this.cellVerticalAlignment; }
set { this.cellVerticalAlignment = value; }
}
private StringAlignment? cellVerticalAlignment;
/// <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>
/// Gets or sets whether the button in this column (if this column is drawing buttons) will be enabled
/// even if the row itself is disabled
/// </summary>
[Category("ObjectListView"),
Description("If this column contains a button, should the button be enabled even if the row is disabled?"),
DefaultValue(false)]
public bool EnableButtonWhenItemIsDisabled
{
get { return this.enableButtonWhenItemIsDisabled; }
set { this.enableButtonWhenItemIsDisabled = value; }
}
private bool enableButtonWhenItemIsDisabled;
/// <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>You should probably use a HeaderFormatStyle instead of this property</remarks>
/// <remarks>This is only uses when HeaderUsesThemes is false.</remarks>
[Category("ObjectListView"),
Description("Which font will be used to draw the header?"),
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>You should probably use a HeaderFormatStyle instead of this property</remarks>
/// <remarks>This is only uses when HeaderUsesThemes is false.</remarks>
[Category("ObjectListView"),
Description("In what color will the header text be drawn?"),
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(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? If this is not set, the alignment of the header will follow the alignment of the column"),
DefaultValue(null)]
public HorizontalAlignment? HeaderTextAlign {
get { return headerTextAlign; }
set { headerTextAlign = value; }
}
private HorizontalAlignment? headerTextAlign;
/// <summary>
/// Return the text alignment of the header. This will either have been set explicitly,
/// or will follow the alignment of the text in the column
/// </summary>
[Browsable(false)]
public HorizontalAlignment HeaderTextAlignOrDefault
{
get { return headerTextAlign.HasValue ? headerTextAlign.Value : this.TextAlign; }
}
/// <summary>
/// Gets the header alignment converted to a StringAlignment
/// </summary>
[Browsable(false)]
public StringAlignment HeaderTextAlignAsStringAlignment {
get {
switch (this.HeaderTextAlignOrDefault) {
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 header will place a checkbox in the header
/// </summary>
[Category("ObjectListView"),
Description("Draw a checkbox in the header of this column"),
DefaultValue(false)]
public bool HeaderCheckBox
{
get { return headerCheckBox; }
set { headerCheckBox = value; }
}
private bool headerCheckBox;
/// <summary>
/// Gets or sets whether this header will place a tri-state checkbox in the header
/// </summary>
[Category("ObjectListView"),
Description("Draw a tri-state checkbox in the header of this column"),
DefaultValue(false)]
public bool HeaderTriStateCheckBox
{
get { return headerTriStateCheckBox; }
set { headerTriStateCheckBox = value; }
}
private bool headerTriStateCheckBox;
/// <summary>
/// Gets or sets the checkedness of the checkbox in the header of this column
/// </summary>
[Category("ObjectListView"),
Description("Checkedness of the header checkbox"),
DefaultValue(CheckState.Unchecked)]
public CheckState HeaderCheckState
{
get { return headerCheckState; }
set { headerCheckState = value; }
}
private CheckState headerCheckState = CheckState.Unchecked;
/// <summary>
/// Gets or sets whether the
/// checking/unchecking the value of the header's checkbox will result in the
/// checkboxes for all cells in this column being set to the same checked/unchecked.
/// Defaults to true.
/// </summary>
/// <remarks>
/// <para>
/// There is no reverse of this function that automatically updates the header when the
/// checkedness of a cell changes.
/// </para>
/// <para>
/// This property's behaviour on a TreeListView is probably best describes as undefined
/// and should be avoided.
/// </para>
/// <para>
/// The performance of this action (checking/unchecking all rows) is O(n) where n is the
/// number of rows. It will work on large virtual lists, but it may take some time.
/// </para>
/// </remarks>
[Category("ObjectListView"),
Description("Update row checkboxs when the header checkbox is clicked by the user"),
DefaultValue(true)]
public bool HeaderCheckBoxUpdatesRowCheckBoxes {
get { return headerCheckBoxUpdatesRowCheckBoxes; }
set { headerCheckBoxUpdatesRowCheckBoxes = value; }
}
private bool headerCheckBoxUpdatesRowCheckBoxes = true;
/// <summary>
/// Gets or sets whether the checkbox in the header is disabled
/// </summary>
/// <remarks>
/// Clicking on a disabled checkbox does not change its value, though it does raise
/// a HeaderCheckBoxChanging event, which allows the programmer the opportunity to do
/// something appropriate.</remarks>
[Category("ObjectListView"),
Description("Is the checkbox in the header of this column disabled"),
DefaultValue(false)]
public bool HeaderCheckBoxDisabled
{
get { return headerCheckBoxDisabled; }
set { headerCheckBoxDisabled = value; }
}
private bool headerCheckBoxDisabled;
/// <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 in the cells 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>
/// Gets or sets whether this column will draw buttons in its cells
/// </summary>
/// <remarks>
/// <para>
/// When this is set to true, the renderer for the column is become a ColumnButtonRenderer
/// if it isn't already. If this is set to false, any previous button renderer will be discarded
/// </para>
/// If the cell's aspect is null or empty, nothing will be drawn in the cell.</remarks>
[Category("ObjectListView"),
Description("Does this column draw its cells as buttons?"),
DefaultValue(false)]
public bool IsButton {
get { return isButton; }
set {
isButton = value;
if (value) {
ColumnButtonRenderer buttonRenderer = this.Renderer as ColumnButtonRenderer;
if (buttonRenderer == null) {
this.Renderer = this.CreateColumnButtonRenderer();
this.FillInColumnButtonRenderer();
}
} else {
if (this.Renderer is ColumnButtonRenderer)
this.Renderer = null;
}
}
}
private bool isButton;
/// <summary>
/// Create a ColumnButtonRenderer to draw buttons in this column
/// </summary>
/// <returns></returns>
protected virtual ColumnButtonRenderer CreateColumnButtonRenderer() {
return new ColumnButtonRenderer();
}
/// <summary>
/// Fill in details to our ColumnButtonRenderer based on the properties set on the column
/// </summary>
protected virtual void FillInColumnButtonRenderer() {
ColumnButtonRenderer buttonRenderer = this.Renderer as ColumnButtonRenderer;
if (buttonRenderer == null)
return;
buttonRenderer.SizingMode = this.ButtonSizing;
buttonRenderer.ButtonSize = this.ButtonSize;
buttonRenderer.ButtonPadding = this.ButtonPadding;
buttonRenderer.MaxButtonWidth = this.ButtonMaxWidth;
}
/// <summary>
/// Gets or sets the maximum width that a button can occupy.
/// -1 means there is no maximum width.
/// </summary>
/// <remarks>This is only considered when the SizingMode is TextBounds</remarks>
[Category("ObjectListView"),
Description("The maximum width that a button can occupy when the SizingMode is TextBounds"),
DefaultValue(-1)]
public int ButtonMaxWidth {
get { return this.buttonMaxWidth; }
set {
this.buttonMaxWidth = value;
FillInColumnButtonRenderer();
}
}
private int buttonMaxWidth = -1;
/// <summary>
/// Gets or sets the extra space that surrounds the cell when the SizingMode is TextBounds
/// </summary>
[Category("ObjectListView"),
Description("The extra space that surrounds the cell when the SizingMode is TextBounds"),
DefaultValue(null)]
public Size? ButtonPadding {
get { return this.buttonPadding; }
set {
this.buttonPadding = value;
this.FillInColumnButtonRenderer();
}
}
private Size? buttonPadding;
/// <summary>
/// Gets or sets the size of the button when the SizingMode is FixedBounds
/// </summary>
/// <remarks>If this is not set, the bounds of the cell will be used</remarks>
[Category("ObjectListView"),
Description("The size of the button when the SizingMode is FixedBounds"),
DefaultValue(null)]
public Size? ButtonSize {
get { return this.buttonSize; }
set {
this.buttonSize = value;
this.FillInColumnButtonRenderer();
}
}
private Size? buttonSize;
/// <summary>
/// Gets or sets how each button will be sized if this column is displaying buttons
/// </summary>
[Category("ObjectListView"),
Description("If this column is showing buttons, how each button will be sized"),
DefaultValue(ButtonSizingMode.TextBounds)]
public ButtonSizingMode ButtonSizing {
get { return this.buttonSizing; }
set {
this.buttonSizing = value;
this.FillInColumnButtonRenderer();
}
}
private ButtonSizingMode buttonSizing = ButtonSizingMode.TextBounds;
/// <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>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 take effect.</remarks>
[Category("ObjectListView"),
Description("Can this column be seen by the user?"),
DefaultValue(true)]
public bool IsVisible {
get { return isVisible; }
set
{
if (isVisible == value)
return;
isVisible = value;
OnVisibilityChanged(EventArgs.Empty);
}
}
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? -1 means no limit"),
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? -1 means no limit"),
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 {
Version1Renderer version1Renderer = this.Renderer as Version1Renderer;
return version1Renderer != null ? version1Renderer.RenderDelegate : 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 a delegate which will return the array of text values that should be
/// considered for text matching when using a text based filter.
/// </summary>
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public SearchValueGetterDelegate SearchValueGetter {
get { return searchValueGetter; }
set { searchValueGetter = value; }
}
private SearchValueGetterDelegate searchValueGetter;
/// <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 this.ClusteringStrategy.CreateFilter(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 ? ObjectListView.GroupTitleDefault : 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)
return this.groupKeyGetter(rowObject);
object key = this.GetValue(rowObject);
if (this.UseInitialLetterForGroup) {
String keyAsString = key as String;
if (!String.IsNullOrEmpty(keyAsString))
return keyAsString.Substring(0, 1).ToUpper();
}
return key;
}
/// <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 strings that will be searched when trying to filter by string.
/// </summary>
/// <remarks>
/// This will normally be the simple GetStringValue result, but if this column is non-textual (e.g. image)
/// you might want to install a SearchValueGetter delegate which can return something that could be used
/// for text filtering.
/// </remarks>
/// <param name="rowObject"></param>
/// <returns>The array of texts to be searched. If this returns null, search will not match that object.</returns>
public string[] GetSearchValues(object rowObject) {
if (this.SearchValueGetter != null)
return this.SearchValueGetter(rowObject);
var stringValue = this.GetStringValue(rowObject);
DescribedTaskRenderer dtr = this.Renderer as DescribedTaskRenderer;
if (dtr != null) {
return new string[] { stringValue, dtr.GetDescription(rowObject) };
}
return new string[] { stringValue };
}
/// <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;
#region Events
/// <summary>
/// This event is triggered when the visibility of this column changes.
/// </summary>
[Category("ObjectListView"),
Description("This event is triggered when the visibility of the column changes.")]
public event EventHandler<EventArgs> VisibilityChanged;
/// <summary>
/// Tell the world when visibility of a column changes.
/// </summary>
public virtual void OnVisibilityChanged(EventArgs e)
{
if (this.VisibilityChanged != null)
this.VisibilityChanged(this, e);
}
#endregion
/// <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 == 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
}
}