/*
 * 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>
            /// 每个单元格都将具有相同大小的按钮，如ButtonSize属性所示
            /// </summary>
            FixedBounds,

            /// <summary>
            ///每个单元格都将绘制一个填充单元格的按钮，该按钮由ButtonPadding插入
            /// </summary>
            CellBounds,

            /// <summary>
            /// 将调整每个按钮的大小以包含文本内容
            /// </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>
        /// 此委托将用于提取要在此列中显示的值。
        /// </summary>
        /// <remarks>
        /// 如果设置, AspectName属性将被忽略.
        /// </remarks>
        [Browsable(false),
         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public AspectGetterDelegate AspectGetter {
            get { return aspectGetter; }
            set { aspectGetter = value; }
        }
        private AspectGetterDelegate aspectGetter;

        /// <summary>
        /// 请记住，如果当前列的AspectGetter是内部生成的，依旧可以随意重新生成
        /// </summary>
        [Obsolete("不再维护此属性", true),
         Browsable(false),
         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public bool AspectGetterAutoGenerated {
            get { return aspectGetterAutoGenerated; }
            set { aspectGetterAutoGenerated = value; }
        }
        private bool aspectGetterAutoGenerated;

        /// <summary>
        /// 应调用以获取要在此列中显示的值的属性或方法的名称。
        /// 仅当未指定ValueGetterDelegate时才使用此选项。
        /// </summary>
        /// <remarks>此名称可以用来表示对属性或无参数方法的链引用。</remarks>
        /// <example>"DateOfBirth"</example>
        /// <example>"Owner.HomeAddress.Postcode"</example>
        [Category("ObjectListView"),
         Description("应调用以获取要在此列中显示的值的属性或方法的名称。"),
         DefaultValue(null)]
        public string AspectName {
            get { return aspectName; }
            set {
                aspectName = value;
                this.aspectMunger = null;
            }
        }
        private string aspectName;

        /// <summary>
        /// 此委托将用于将编辑后的值放回模型对象中。
        /// </summary>
        /// <remarks>
        ///如果IsEdable==false，则不执行任何操作。
        /// </remarks>
        [Browsable(false),
         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public AspectPutterDelegate AspectPutter {
            get { return aspectPutter; }
            set { aspectPutter = value; }
        }
        private AspectPutterDelegate aspectPutter;

        /// <summary>
        /// 用于将要在此列中显示的Aspect转换为字符串的委托。
        /// </summary>
        /// <remarks>如果设置了此值，AspectToStringFormat将被忽略。</remarks>
        [Browsable(false),
         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public AspectToStringConverterDelegate AspectToStringConverter {
            get { return aspectToStringConverter; }
            set { aspectToStringConverter = value; }
        }
        private AspectToStringConverterDelegate aspectToStringConverter;

        /// <summary>
        /// 将Aspect转换成字符串的格式文本
        /// </summary>
        /// <remarks>
        /// 此字符串作为第一个参数传递给String.Format()方法。
        /// 仅当尚未设置AspectToStringConverter时才使用此选项。</remarks>
        /// <example>"{0:C}" 表示转换数字到货币</example>
        [Category("ObjectListView"),
         Description("将Aspect转换成字符串的格式文本"),
         DefaultValue(null)]
        public string AspectToStringFormat {
            get { return aspectToStringFormat; }
            set { aspectToStringFormat = value; }
        }
        private string aspectToStringFormat;

        /// <summary>
        /// 获取或设置单元格编辑器是否应使用自动完成
        /// </summary>
        [Category("ObjectListView"),
         Description("获取或设置单元格编辑器是否应使用自动完成"),
         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>
        ///获取或设置单元格编辑器是否应使用自动完成
        /// </summary>
        [Category("ObjectListView"),
         Description("获取或设置单元格编辑器是否应使用自动完成"),
         DefaultValue(AutoCompleteMode.Append)]
        public AutoCompleteMode AutoCompleteEditorMode {
            get { return autoCompleteEditorMode; }
            set { autoCompleteEditorMode = value; }
        }
        private AutoCompleteMode autoCompleteEditorMode = AutoCompleteMode.Append;

        /// <summary>
        ///获取用户操作是否可以隐藏此列
        /// </summary>
        /// <remarks>这会同时考虑Hideable属性以及此列是否为列表视图的主列(列0)。</remarks>
        [Browsable(false)]
        public bool CanBeHidden {
            get {
                return this.Hideable && (this.Index != 0);
            }
        }

        /// <summary>
        /// 编辑单元格时，是否应该使用整个单元格(减去复选框或图像使用的任何空间)？
        /// </summary>
        /// <remarks>
        /// <para>如果控件不是所有者绘制(owner drawn)的，则始终将其视为true。</para>
        /// <para>
        ///如果该值为False(默认值)并且控件是所有者绘制(owner drawn)的，
        ///ObjectListView将尝试计算单元格实际内容的宽度，然后将编辑控件的大小调整为恰到好处的宽度。
        ///如果为真，则无论单元格的内容如何，都将使用单元格的整个宽度。
        /// </para>
        /// <para>如果未在列上设置此属性，则将使用控件中的值
        /// </para>
        /// <para>仅当控件处于详细信息视图中时才使用此值。</para>
        /// <para>无论此设置如何，开发人员都可以通过侦听CellEditStarting事件来指定编辑控件的确切大小。</para>
        /// </remarks>
        [Category("ObjectListView"),
         Description("编辑单元格时，是否应该使用整个单元格？"),
         DefaultValue(null)]
        public virtual bool? CellEditUseWholeCell
        {
            get { return cellEditUseWholeCell; }
            set { cellEditUseWholeCell = value; }
        }
        private bool? cellEditUseWholeCell;

        /// <summary>
        ///获取编辑此列中的单元格时是否应使用整个单元格
        /// </summary>
        /// <remarks>这将计算当前有效值，该值可能与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>
        /// 获取或设置此列中此单元格周围将留空的像素数
        /// </summary>
        /// <remarks>此设置仅在控件为所有者绘制(owner drawn)时生效。</remarks>
        [Category("ObjectListView"),
         Description("获取或设置此列中此单元格周围将留空的像素数"),
         DefaultValue(null)]
        public Rectangle? CellPadding
        {
            get { return this.cellPadding; }
            set { this.cellPadding = value; }
        }
        private Rectangle? cellPadding;

        /// <summary>
        /// 获取或设置此列中的单元格垂直对齐的方式。
        /// </summary>
        /// <remarks>
        /// <para>
        /// 此设置仅在控件为所有者绘制(owner drawn)时生效。
        /// </para>        
        /// <para>
        ///如果未设置，将使用控件本身的值。
        /// </para>
        /// </remarks>
        [Category("ObjectListView"),
         Description("获取或设置此列中的单元格垂直对齐的方式"),
         DefaultValue(null)]
        public virtual StringAlignment? CellVerticalAlignment {
            get { return this.cellVerticalAlignment; }
            set { this.cellVerticalAlignment = value; }
        }
        private StringAlignment? cellVerticalAlignment;

        /// <summary>
        /// 获取或设置此列是否显示复选框。
        /// </summary>
        /// <remarks>
        /// 在第0列上设置此选项不起作用。列0复选框由ObjectListView本身的CheckBox属性控制。
        /// </remarks>
        [Category("ObjectListView"),
         Description("获取或设置此列是否显示复选框"),
         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>
        /// 获取或设置是否启用此列中的按钮(如果此列是按钮)，即使该行本身被禁用
        /// </summary>
        [Category("ObjectListView"),
         Description("获取或设置是否启用此列中的按钮(如果此列是按钮)，即使该行本身被禁用"),
         DefaultValue(false)]
        public bool EnableButtonWhenItemIsDisabled
        {
            get { return this.enableButtonWhenItemIsDisabled; }
            set { this.enableButtonWhenItemIsDisabled = value; }
        }
        private bool enableButtonWhenItemIsDisabled;

        /// <summary>
        /// 此列是否应该调整大小以填充列表视图中的空闲空间？
        /// </summary>
        /// <remarks>
        /// <para>
        ///如果希望两列(或更多列)平均共享可用空间，请将此属性设置为True。
        /// 如果希望此列具有更大或更小的可用空间份额，则必须显式设置FreeSpaceProportion属性。
        /// </para>
        /// <para>
        /// 空间填充列仍然由MinimumWidth和MaximumWidth属性控制。
        /// </para>
        /// /// </remarks>
        [Category("ObjectListView"),
         Description("此列是否应该调整大小以填充列表视图中的空闲空间"),
         DefaultValue(false)]
        public bool FillsFreeSpace {
            get { return this.FreeSpaceProportion > 0; }
            set { this.FreeSpaceProportion = value ? 1 : 0; }
        }

        /// <summary>
        /// 控件中未占用的水平空间应分配给此列的比例是多少？
        /// </summary>
        /// <remarks>
        /// <para>
        /// 在某些情况下，如果列(通常是最右边的列)可以随着列表视图的扩展而扩展，
        /// 这样就可以在不必水平滚动的情况下尽可能多地看到列(您永远不应该让用户必须水平滚动任何内容！)。
        /// </para>
        /// <para>
        ///调整空间填充列的大小以占据列表视图的未占用宽度的一部分(未占用宽度是一旦所有非填充列都被赋予其空间后剩余的宽度)。
        ///此属性指示将分配给此列的未占用空间的相对比例。此属性的实际值并不重要，重要的是它的值相对于其他列中的值。
        ///例子:
        /// <list type="bullet">
        /// <item><description>
        /// 如果只有一个空间填充列，则无论FreeSpaceProportion中的值如何，都将为其提供所有可用空间。
        /// </description></item>
        /// <item><description>
        ///如果有两个或多个空间填充列，并且它们的FreeSpaceProportion值都相同，则它们将平等地共享空闲空间。
        /// </description></item>
        /// <item><description>
        /// 如果FreeSpaceProportion有三个值为3、2和1的空间填充列，则第一列将占用一半的空闲空间，第二列将占用三分之一的空闲空间，第三列将占用六分之一的空闲空间。
        /// </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>
        /// 获取或设置在单击此列的标题时是否对此列值重新生成组。
        /// </summary>
        /// <remarks>
        /// <para>此设置仅在ShowGroups为true时使用。</para>
        /// <para>
        /// 如果为False，则单击标题不会重建组。
        /// </para>
        /// <para>如果为false，则仍会激发BeforeCreatingGroups事件，这些事件可用于根据具体情况进行分组或提供反馈。</para>
        /// </remarks>
        [Category("ObjectListView"),
         Description("获取或设置在单击此列的标题时是否对此列值重新生成组"),
         DefaultValue(true)]
        public bool Groupable {
            get { return groupable; }
            set { groupable = value; }
        }
        private bool groupable = true;

        /// <summary>
        /// 当组已创建但尚未成为真正的ListViewGroup时，将调用此委托。用户可以利用此机会填写有关该组的许多其他详细信息。
        /// </summary>
        [Browsable(false),
         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public GroupFormatterDelegate GroupFormatter {
            get { return groupFormatter; }
            set { groupFormatter = value; }
        }
        private GroupFormatterDelegate groupFormatter;

        /// <summary>
        /// 调用此委托以获取对象，该对象是给定行所属的组的键。
        /// </summary>
        [Browsable(false),
         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public GroupKeyGetterDelegate GroupKeyGetter {
            get { return groupKeyGetter; }
            set { groupKeyGetter = value; }
        }
        private GroupKeyGetterDelegate groupKeyGetter;

        /// <summary>
        /// 调用此委托将组键转换为该组的标题。
        /// </summary>
        [Browsable(false),
         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public GroupKeyToTitleConverterDelegate GroupKeyToTitleConverter {
            get { return groupKeyToTitleConverter; }
            set { groupKeyToTitleConverter = value; }
        }
        private GroupKeyToTitleConverterDelegate groupKeyToTitleConverter;

        /// <summary>
        /// 当列表视图按此列分组并且组标题有项目计数时，应如何设置标签的格式
        /// </summary>
        /// <remarks>
        /// 给定的格式字符串支持以下两个占位符：
        /// <list type="bullet">
        /// <item><description>{0} - 原组标题</description></item>
        /// <item><description>{1} - 该组项目数</description></item>
        /// </list>
        /// </remarks>
        /// <example>"{0} [{1} items]"</example>
        [Category("ObjectListView"),
         Description("为组标题添加项目计数后缀时要使用的格式"),
         DefaultValue(null),
         Localizable(true)]
        public string GroupWithItemCountFormat {
            get { return groupWithItemCountFormat; }
            set { groupWithItemCountFormat = value; }
        }
        private string groupWithItemCountFormat;

        /// <summary>
        /// 获取this.GroupWithItemCountFormat或默认值
        /// </summary>
        /// <remarks>
        /// 如果未设置GroupWithItemCountFormat，则如果可能，将从ObjectListView中获取其值。
        /// </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>
        /// 当列表视图按此列分组并且组标题有项目计数时，如果组中只有一个项目，标签应该如何格式化
        /// </summary>
        /// <remarks>
        ///给定的格式字符串支持以下两个占位符：
        /// <list type="bullet">
        /// <item><description>{0} - 原组标题</description></item>
        /// <item><description>{1} - 该组项目数 (始终为1)</description></item>
        /// </list>
        /// </remarks>
        /// <example>"{0} [{1} item]"</example>
        [Category("ObjectListView"),
         Description("当列表视图按此列分组并且组标题有项目计数时，如果组中只有一个项目，标签应该如何格式化"),
         DefaultValue(null),
         Localizable(true)]
        public string GroupWithItemCountSingularFormat {
            get { return groupWithItemCountSingularFormat; }
            set { groupWithItemCountSingularFormat = value; }
        }
        private string groupWithItemCountSingularFormat;

        /// <summary>
        ///获取this.GroupWithItemCountSingularFormat或默认值
        /// </summary>
        /// <remarks>
        /// <para>如果未设置此值，将使用列表视图中的值</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>
        /// 获取是否应在列标题中使用筛选器指示符绘制此列。
        /// </summary>
        [Browsable(false)]
        public bool HasFilterIndicator {
            get {
                return this.UseFiltering && this.ValuesChosenForFiltering != null && this.ValuesChosenForFiltering.Count > 0;
            }
        }

        /// <summary>
        /// 获取或设置将用于所有者绘制标题列的委托。
        /// </summary>
        [Browsable(false),
         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public HeaderDrawingDelegate HeaderDrawing {
            get { return headerDrawing; }
            set { headerDrawing = value; }
        }
        private HeaderDrawingDelegate headerDrawing;

        /// <summary>
        /// 获取或设置将用于绘制此列标题的样式
        /// </summary>
        /// <remarks>仅当拥有的ObjectListView将HeaderUsesThemes设置为False时才使用此选项。</remarks>
        [Category("ObjectListView"),
         Description("获取或设置将用于绘制此列标题的样式"),
         DefaultValue(null)]
        public HeaderFormatStyle HeaderFormatStyle {
            get { return this.headerFormatStyle; }
            set { this.headerFormatStyle = value; }
        }
        private HeaderFormatStyle headerFormatStyle;

        /// <summary>
        /// 获取或设置绘制此列的标题时使用的字体
        /// </summary>
        /// <remarks>您可能应该使用HeaderFormatStyle而不是此属性</remarks>
        /// <remarks>这仅在HeaderUsesThemes为false时使用。</remarks>
        [Category("ObjectListView"),
        Description("获取或设置绘制此列的标题时使用的字体"),
        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>
        /// 获取或设置绘制此列标题文本的颜色
        /// </summary>
        /// <remarks>您可能应该使用HeaderFormatStyle而不是此属性</remarks>
        /// <remarks>这仅在HeaderUsesThemes为false时使用。</remarks>
        [Category("ObjectListView"),
         Description("获取或设置绘制此列标题文本的颜色"),
         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>
        /// 获取或设置将在列标题中显示的图像键
        /// </summary>
        /// <remarks>这仅在HeaderUsesThemes为false时使用</remarks>
        [Category("ObjectListView"),
         Description("获取或设置将在列标题中显示的图像键。"),
         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>
        /// 获取或设置Header文本的对齐方式
        /// </summary>
        [Category("ObjectListView"),
         Description("Header文本将如何对齐？如果未设置此项，则Header的对齐方式将遵循列的对齐方式"),
         DefaultValue(null)]
        public HorizontalAlignment? HeaderTextAlign {
            get { return headerTextAlign; }
            set { headerTextAlign = value; }
        }
        private HorizontalAlignment? headerTextAlign;

        /// <summary>
        ///返回Header的文本对齐方式。这将是显式设置的，或者将遵循列中文本的对齐方式
        /// </summary>
        [Browsable(false)]
        public HorizontalAlignment HeaderTextAlignOrDefault
        {
            get { return headerTextAlign.HasValue ? headerTextAlign.Value : this.TextAlign; }
        }

        /// <summary>
        /// 获取转换为StringAlignment的Header对齐方式
        /// </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>
        ///获取此列的标题中是否有图像
        /// </summary>
        [Browsable(false)]
        public bool HasHeaderImage {
            get {
                return (this.ListView != null &&
                    this.ListView.SmallImageList != null &&
                    this.ListView.SmallImageList.Images.ContainsKey(this.HeaderImageKey));
            }
        }

        /// <summary>
        /// 获取或设置此Header是否在Header中放置复选框
        /// </summary>
        [Category("ObjectListView"),
         Description("获取或设置此Header是否在Header中放置复选框"),
         DefaultValue(false)]
        public bool HeaderCheckBox
        {
            get { return headerCheckBox; }
            set { headerCheckBox = value; }
        }
        private bool headerCheckBox;

        /// <summary>
        /// 获取或设置此Header是否在Header中放置三态复选框
        /// </summary>
        [Category("ObjectListView"),
        Description("获取或设置此Header是否在Header中放置三态复选框"),
         DefaultValue(false)]
        public bool HeaderTriStateCheckBox
        {
            get { return headerTriStateCheckBox; }
            set { headerTriStateCheckBox = value; }
        }
        private bool headerTriStateCheckBox;

        /// <summary>
        ///获取或设置此列Header中复选框的选中状态
        /// </summary>
        [Category("ObjectListView"),
         Description("获取或设置此列Header中复选框的选中状态"),
         DefaultValue(CheckState.Unchecked)]
        public CheckState HeaderCheckState
        {
            get { return headerCheckState; }
            set { headerCheckState = value; }
        }
        private CheckState headerCheckState = CheckState.Unchecked;

        /// <summary>
        /// 获取或设置选中/取消选中标题复选框的值是否会导致将此列中所有单元格的复选框设置为相同的选中/取消选中。
        /// 默认值为true.
        /// </summary>
        /// <remarks>
        /// <para>
        /// 当单元格的复选框状态改变时，该函数不会与自动更新标题的功能相反。
        /// </para>
        /// <para>
        /// 此属性在TreeListView上的行为最好描述为未定义，应该避免。
        /// </para>
        /// <para>
        /// 此操作(检查/取消检查所有行)的性能为O(n)，其中n是行数。它将在大型虚拟列表上工作，但可能需要一些时间。
        /// </para>
        /// </remarks>
        [Category("ObjectListView"),
         Description("当用户点击Header复选框时,同步更新行复选框"),
         DefaultValue(true)]
        public bool HeaderCheckBoxUpdatesRowCheckBoxes {
            get { return headerCheckBoxUpdatesRowCheckBoxes; }
            set { headerCheckBoxUpdatesRowCheckBoxes = value; }
        }
        private bool headerCheckBoxUpdatesRowCheckBoxes = true;

        /// <summary>
        ///获取或设置是否禁用标题中的复选框
        /// </summary>
        /// <remarks>
        /// 单击禁用的复选框不会更改其值，但会引发HeaderCheckBoxChanging事件，使程序员有机会执行适当的操作。</remarks>
        [Category("ObjectListView"),
        Description("获取或设置是否禁用标题中的复选框"),
         DefaultValue(false)]
        public bool HeaderCheckBoxDisabled
        {
            get { return headerCheckBoxDisabled; }
            set { headerCheckBoxDisabled = value; }
        }
        private bool headerCheckBoxDisabled;

        /// <summary>
        /// 获取或设置用户是否可以隐藏此列。
        /// </summary>
        /// <remarks>
        /// <para>无论此设置如何，列0永远不能隐藏。</para>
        /// </remarks>
        [Category("ObjectListView"),
         Description("获取或设置用户是否可以隐藏此列"),
         DefaultValue(true)]
        public bool Hideable {
            get { return hideable; }
            set { hideable = value; }
        }
        private bool hideable = true;

        /// <summary>
        /// 获取或设置此列中的文本值是否类似于超链接
        /// </summary>
        [Category("ObjectListView"),
         Description("获取或设置此列中的文本值是否类似于超链接"),
         DefaultValue(false)]
        public bool Hyperlink {
            get { return hyperlink; }
            set { hyperlink = value; }
        }
        private bool hyperlink;

        /// <summary>
        /// 这是属性的名称，将调用该属性来获取应该在此列中显示的图像的图像选择器。
        /// 它可以返回int、String、Image或NULL。
        /// </summary>
        /// <remarks>
        /// <para>如果ImageGetter不为空，则忽略此项。</para>
        /// <para>该属性可以使用以下返回值来标识图像：</para>
        /// <list type="bullet">
        /// <item><description>null或-1 --表示无图像</description></item>
        /// <item><description>int -- Int值将用作图像列表的索引</description></item>
        /// <item><description>String -- 字符串值将用作图像列表的关键字</description></item>
        /// <item><description>Image -- 将直接绘制图像(仅在OwnerDrawn模式下)</description></item>
        /// </list>
        /// </remarks>
        [Category("ObjectListView"),
         Description("图像选择器的属性名称"),
         DefaultValue(null)]
        public string ImageAspectName {
            get { return imageAspectName; }
            set { imageAspectName = value; }
        }
        private string imageAspectName;

        /// <summary>
        /// 调用此委托以获取应该在此列中显示的图像的图像选择器。它可以返回int、String、Image或NULL。
        /// </summary>
        /// <remarks>        
        /// <para>该属性可以使用以下返回值来标识图像：</para>
        /// <list type="bullet">
        /// <item><description>null或-1 --表示无图像</description></item>
        /// <item><description>int -- Int值将用作图像列表的索引</description></item>
        /// <item><description>String -- 字符串值将用作图像列表的关键字</description></item>
        /// <item><description>Image -- 将直接绘制图像(仅在OwnerDrawn模式下)</description></item>
        /// </list>
        /// </remarks>
        [Browsable(false),
         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public ImageGetterDelegate ImageGetter {
            get { return imageGetter; }
            set { imageGetter = value; }
        }
        private ImageGetterDelegate imageGetter;

        /// <summary>
        /// 获取或设置此列是否在其单元格中绘制按钮
        /// </summary>
        /// <remarks>
        /// <para>
        /// 如果将其设置为true，则列的呈现器将成为ColumnButtonRenender(如果尚未成为ColumnButtonRenender)。如果设置为False，则将丢弃以前的任何按钮渲染器
        /// </para>
        /// 如果单元格的Aspect为Null或空，则不会在单元格中绘制任何内容。</remarks>
        [Category("ObjectListView"),
         Description("获取或设置此列是否在其单元格中绘制按钮"),
         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>
        /// 获取或设置按钮可以占用的最大宽度。
        /// -1 表示不限制最大宽度
        /// </summary>
        /// <remarks>仅当SizingMode为TextBound时才生效</remarks>
        [Category("ObjectListView"),
         Description("获取或设置按钮可以占用的最大宽度"),
         DefaultValue(-1)]
        public int ButtonMaxWidth {
            get { return this.buttonMaxWidth; }
            set {
                this.buttonMaxWidth = value;
                FillInColumnButtonRenderer();
            }
        }
        private int buttonMaxWidth = -1;

        /// <summary>
        /// 获取或设置当SizingMode为TextBound时单元格周围的额外空间
        /// </summary>
        [Category("ObjectListView"),
         Description("获取或设置当SizingMode为TextBound时单元格周围的额外空间"),
         DefaultValue(null)]
        public Size? ButtonPadding {
            get { return this.buttonPadding; }
            set {
                this.buttonPadding = value;
                this.FillInColumnButtonRenderer();
            }
        }
        private Size? buttonPadding;

        /// <summary>
        /// 获取或设置SizingMode为FixedBound时按钮的大小
        /// </summary>
        /// <remarks>如果未设置，将使用单元格的边界</remarks>
        [Category("ObjectListView"),
         Description("获取或设置SizingMode为FixedBound时按钮的大小"),
         DefaultValue(null)]
        public Size? ButtonSize {
            get { return this.buttonSize; }
            set {
                this.buttonSize = value;
                this.FillInColumnButtonRenderer();
            }
        }
        private Size? buttonSize;

        /// <summary>
        /// 获取或设置此列显示按钮时如何调整每个按钮的大小
        /// </summary>
        [Category("ObjectListView"),
         Description("获取或设置此列显示按钮时如何调整每个按钮的大小"),
         DefaultValue(ButtonSizingMode.TextBounds)]
        public ButtonSizingMode ButtonSizing {
            get { return this.buttonSizing; }
            set {
                this.buttonSizing = value;
                this.FillInColumnButtonRenderer();
            }
        }
        private ButtonSizingMode buttonSizing = ButtonSizingMode.TextBounds;

        /// <summary>
        /// 此列中显示的值是否可以编辑
        /// </summary>
        /// <remarks>此默认值为false，因为控制列表视图的可编辑性的主要方法是列表视图本身。
        /// 列表视图可编辑后，所有列也可编辑，除非程序员显式将它们标记为不可编辑</remarks>
        [Category("ObjectListView"),
         Description("此列中显示的值是否可以编辑"),
         DefaultValue(false)]
        public bool IsEditable
        {
            get { return isEditable; }
            set { isEditable = value; }
        }
        private bool isEditable = false;

        /// <summary>
        /// 是否是固定宽度
        /// </summary>
        [Browsable(false)]
        public bool IsFixedWidth {
            get {
                return (this.MinimumWidth != -1 && this.MaximumWidth != -1 && this.MinimumWidth >= this.MaximumWidth);
            }
        }

        /// <summary>
        /// 获取/设置当视图切换到平铺视图(TileView)时是否使用此列。
        /// </summary>
        /// <remarks>无论此设置如何，第0列始终包含在平铺视图中。平铺视图不能很好地处理许多“列”信息。两三个最好。</remarks>
        [Category("ObjectListView"),
         Description("获取/设置当视图切换到平铺视图(TileView)时是否使用此列。"),
         DefaultValue(false)]
        public bool IsTileViewColumn {
            get { return isTileViewColumn; }
            set { isTileViewColumn = value; }
        }
        private bool isTileViewColumn;

        /// <summary>
        /// 获取或设置Header的文本是否应垂直呈现。
        /// </summary>
        /// <remarks>
        /// <para>如果为True，最好将ToolTipText设置为列的名称，以便于阅读。</para>
        /// <para>垂直Header仅为文本。他们不会画出图像。</para>
        /// </remarks>
        [Category("ObjectListView"),
         Description("获取或设置Header的文本是否应垂直呈现"),
         DefaultValue(false)]
        public bool IsHeaderVertical {
            get { return isHeaderVertical; }
            set { isHeaderVertical = value; }
        }
        private bool isHeaderVertical;

        /// <summary>
        /// 该列是否可见
        /// </summary>
        /// <remarks>更改此值后，必须调用RebuildColumns()才能使更改生效。</remarks>
        [Category("ObjectListView"),
         Description("该列是否可见"),
         DefaultValue(true)]
        public bool IsVisible {
            get { return isVisible; }
            set
            {
                if (isVisible == value)
                    return;

                isVisible = value;
                OnVisibilityChanged(EventArgs.Empty);
            }
        }
        private bool isVisible = true;

        /// <summary>
        /// 此列最后一次定位在详细信息视图列中的位置是什么
        /// </summary>
        /// <remarks>DisplayIndex是易失性的。一旦从控件中移除列，就无法发现它在显示顺序中的位置。
        /// 即使列不在列表视图的活动列中，此属性也会保护该信息。</remarks>
        [Browsable(false),
         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public int LastDisplayIndex {
            get { return this.lastDisplayIndex; }
            set { this.lastDisplayIndex = value;  }
        }
        private int lastDisplayIndex = -1;

        /// <summary>
        /// 列最大宽度
        /// </summary>
        /// <remarks>-1表示不限制. 将该值指定为与MinimumWidth相同的值，以生成固定宽度的列。</remarks>
        [Category("ObjectListView"),
         Description("列最大宽度,-1表示不限制"),
         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>
        /// 列最小宽度
        /// </summary>
        /// <remarks>-1表示不限制. 将该值指定为与MaximumWidth相同的值，以生成固定宽度的列。</remarks>
        [Category("ObjectListView"),
         Description("列最小宽度,-1表示不限制"),
         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>
        ///获取或设置执行文本搜索时是否使用此列的单元格中的文本。
        /// </summary>
        /// <remarks>
        /// <para>
        /// 如果为False，则文本筛选器在查找匹配项时不会尝试搜索此列单元格。
        /// </para>
        /// </remarks>
        [Category("ObjectListView"),
         Description("获取或设置执行文本搜索时是否使用此列的单元格中的文本"),
         DefaultValue(true)]
        public bool Searchable {
            get { return searchable; }
            set { searchable = value; }
        }
        private bool searchable = true;

        /// <summary>
        /// 获取或设置一个委托，该委托将返回在使用基于文本的筛选器时应考虑进行文本匹配的文本值数组。
        /// </summary>
        [Browsable(false),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public SearchValueGetterDelegate SearchValueGetter {
            get { return searchValueGetter; }
            set { searchValueGetter = value; }
        }
        private SearchValueGetterDelegate searchValueGetter;

        /// <summary>
        ///获取或设置此列的标题是否将包括该列的文本。
        /// </summary>
        /// <remarks>
        /// <para>
        /// 如果为false，则列标题中呈现的唯一内容将是来自 <see cref="HeaderImageKey"/>.
        /// </para>
        /// <para>只有在以下情况下才会考虑此设置: ObjectListView中的<see cref="ObjectListView.HeaderUsesThemes"/> 为false .</para>
        /// </remarks>
        [Category("ObjectListView"),
         Description("获取或设置此列的标题是否将包括该列的文本"),
         DefaultValue(true)]
        public bool ShowTextInHeader {
            get { return showTextInHeader; }
            set { showTextInHeader = value; }
        }
        private bool showTextInHeader = true;

        /// <summary>
        /// 获取或设置当用户单击此列的标题时是否重新排序列表内容。
        /// </summary>
        /// <remarks>
        /// <para>
        /// 如果为False，则单击标题将不会对列表进行排序，但也不会提供有关列表未排序原因的任何反馈。提供适当的反馈是程序员的责任。
        /// </para>
        /// <para>如果为false，则仍会触发BeforeSorting事件，该事件可用于根据具体情况进行排序或提供反馈。</para>
        /// </remarks>
        [Category("ObjectListView"),
         Description("获取或设置当用户单击此列的标题时是否重新排序列表内容"),
         DefaultValue(true)]
        public bool Sortable {
            get { return sortable; }
            set { sortable = value; }
        }
        private bool sortable = true;

        /// <summary>
        /// 获取或设置列内容的水平对齐方式。
        /// </summary>
        /// <remarks>NET将不允许列0具有除左对齐以外的任何对齐方式。我们不能更改列表视图的基本行为，但当所有者绘制时，列0现在可以有其他对齐方式。</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>
        /// 获取列文本对齐的StringAlignment等效项
        /// </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>
        /// 当鼠标悬停在该列的标题上时，应该显示什么字符串？
        /// </summary>
        /// <remarks>如果拥有的ObjectListView上安装了HeaderToolTipGetter，则将忽略此值。</remarks>
        [Category("ObjectListView"),
         Description("当鼠标悬停在该列的标题上时，应该显示什么字符串提示"),
         DefaultValue((String)null),
         Localizable(true)]
        public String ToolTipText {
            get { return toolTipText; }
            set { toolTipText = value; }
        }
        private String toolTipText;

        /// <summary>
        /// 此列是否应该有一个三态复选框
        /// </summary>
        /// <remarks>
        /// 如果为True，用户可以选择第三种状态(通常是不确定的)。
        /// </remarks>
        [Category("ObjectListView"),
         Description("此列是否应该有一个三态复选框"),
         DefaultValue(false)]
        public virtual bool TriStateCheckBoxes {
            get { return triStateCheckBoxes; }
            set {
                triStateCheckBoxes = value;
                if (value && !this.CheckBoxes)
                    this.CheckBoxes = true;
            }
        }
        private bool triStateCheckBoxes;

        /// <summary>
        ///按列纵横比的首字母对对象进行分组
        /// </summary>
        /// <remarks>
        /// 一种常见的模式是按该组的值的首字母对列进行分组。aspect必须是字符串(显然)。
        /// </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>
        /// 获取或设置此列是否应为用户可筛选的列
        /// </summary>
        [Category("ObjectListView"),
         Description("获取或设置此列是否应为用户可筛选的列"),
         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>
        ///列宽
        /// </summary>
        [Category("ObjectListView"),
        Description("列宽"),
        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>
        /// 获取或设置此列单元格的内容是否应自动换行
        /// </summary>
        /// <remarks>如果此列使用自定义IRenender(即，不是从BaseRenender派生的)，则该呈现器负责实现自动换行。</remarks>
        [Category("ObjectListView"),
         Description("获取或设置此列单元格的内容是否应自动换行"),
         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(rowObject,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(null,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);
        }
        /// <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 = "rowobject" ></param>
        /// <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 rowobject,object value)
        {
            // Give the installed converter a chance to work (even if the value is null)
            if (this.AspectToStringConverter != null)
                return this.AspectToStringConverter(rowobject,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
    }
}
