/* * OLVColumn - A column in an ObjectListView * * Author: Phillip Piper * Date: 31-March-2011 5:53 pm * * Change log: * 2011-05-27 JPP - Added Sortable, Hideable, Groupable, Searchable, ShowTextInHeader properties * 2011-04-12 JPP - Added HasFilterIndicator * 2011-03-31 JPP - Split into its own file * * Copyright (C) 2011 Phillip Piper * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you wish to use this code in a closed source application, please contact phillip_piper@bigfoot.com. */ using System; using System.ComponentModel; using System.Windows.Forms; using System.Drawing; using System.Collections; namespace BrightIdeasSoftware { /// /// An OLVColumn knows which aspect of an object it should present. /// /// /// The column knows how to: /// /// extract its aspect from the row object /// convert an aspect to a string /// calculate the image for the row object /// extract a group "key" from the row object /// convert a group "key" into a title for the group /// /// 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. /// [Browsable(false)] public partial class OLVColumn : ColumnHeader { /// /// Create an OLVColumn /// public OLVColumn() { } /// /// Initialize a column to have the given title, and show the given aspect /// /// The title of the column /// The aspect to be shown in the column public OLVColumn(string title, string aspect) : this() { this.Text = title; this.AspectName = aspect; } #region Public Properties /// /// This delegate will be used to extract a value to be displayed in this column. /// /// /// If this is set, AspectName is ignored. /// [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public AspectGetterDelegate AspectGetter { get { return aspectGetter; } set { aspectGetter = value; } } private AspectGetterDelegate aspectGetter; /// /// Remember if this aspect getter for this column was generated internally, and can therefore /// be regenerated at will /// [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; /// /// 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. /// /// This name can be dotted to chain references to properties or parameter-less methods. /// "DateOfBirth" /// "Owner.HomeAddress.Postcode" [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; /// /// This delegate will be used to put an edited value back into the model object. /// /// /// This does nothing if IsEditable == false. /// [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public AspectPutterDelegate AspectPutter { get { return aspectPutter; } set { aspectPutter = value; } } private AspectPutterDelegate aspectPutter; /// /// The delegate that will be used to translate the aspect to display in this column into a string. /// /// If this value is set, AspectToStringFormat will be ignored. [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public AspectToStringConverterDelegate AspectToStringConverter { get { return aspectToStringConverter; } set { aspectToStringConverter = value; } } private AspectToStringConverterDelegate aspectToStringConverter; /// /// This format string will be used to convert an aspect to its string representation. /// /// /// This string is passed as the first parameter to the String.Format() method. /// This is only used if AspectToStringConverter has not been set. /// "{0:C}" to convert a number to currency [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; /// /// Gets or sets whether the cell editor should use AutoComplete /// [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; } } /// /// Gets or sets whether the cell editor should use AutoComplete /// [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; /// /// Gets whether this column can be hidden by user actions /// /// This take into account both the Hideable property and whether this column /// is the primary column of the listview (column 0). [Browsable(false)] public bool CanBeHidden { get { return this.Hideable && (this.Index != 0); } } /// /// Gets or sets whether this column will show a checkbox. /// /// /// Setting this on column 0 has no effect. Column 0 check box is controlled /// by the CheckBoxes property on the ObjectListView itself. /// [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; /// /// Gets or sets the clustering strategy used for this column. /// /// /// /// The clustering strategy is used to build a Filtering menu for this item. /// If this is null, a useful default will be chosen. /// /// /// To disable filtering on this colummn, set UseFiltering to false. /// /// /// Cluster strategies belong to a particular column. The same instance /// cannot be shared between multiple columns. /// /// [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; /// /// Should this column resize to fill the free space in the listview? /// /// /// /// 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. /// /// /// Space filling columns are still governed by the MinimumWidth and MaximumWidth properties. /// /// /// [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; } } /// /// What proportion of the unoccupied horizontal space in the control should be given to this column? /// /// /// /// 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!). /// /// /// 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: /// /// /// If there is only one space filling column, it will be given all the free space, regardless of the value in FreeSpaceProportion. /// /// /// 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. /// /// /// 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. /// /// /// /// [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public int FreeSpaceProportion { get { return freeSpaceProportion; } set { freeSpaceProportion = Math.Max(0, value); } } private int freeSpaceProportion; /// /// Gets or sets whether groups will be rebuild on this columns values when this column's header is clicked. /// /// /// This setting is only used when ShowGroups is true. /// /// 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. /// /// 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. /// [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; /// /// 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. /// [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public GroupFormatterDelegate GroupFormatter { get { return groupFormatter; } set { groupFormatter = value; } } private GroupFormatterDelegate groupFormatter; /// /// This delegate is called to get the object that is the key for the group /// to which the given row belongs. /// [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public GroupKeyGetterDelegate GroupKeyGetter { get { return groupKeyGetter; } set { groupKeyGetter = value; } } private GroupKeyGetterDelegate groupKeyGetter; /// /// This delegate is called to convert a group key into a title for that group. /// [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public GroupKeyToTitleConverterDelegate GroupKeyToTitleConverter { get { return groupKeyToTitleConverter; } set { groupKeyToTitleConverter = value; } } private GroupKeyToTitleConverterDelegate groupKeyToTitleConverter; /// /// When the listview is grouped by this column and group title has an item count, /// how should the lable be formatted? /// /// /// The given format string can/should have two placeholders: /// /// {0} - the original group title /// {1} - the number of items in the group /// /// /// "{0} [{1} items]" [Category("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; /// /// Gets this.GroupWithItemCountFormat or a reasonable default /// /// /// If GroupWithItemCountFormat is not set, its value will be taken from the ObjectListView if possible. /// [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; /// /// 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? /// /// /// The given format string can/should have two placeholders: /// /// {0} - the original group title /// {1} - the number of items in the group (always 1) /// /// /// "{0} [{1} item]" [Category("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; /// /// Get this.GroupWithItemCountSingularFormat or a reasonable default /// /// /// If this value is not set, the values from the list view will be used /// [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; /// /// Gets whether this column should be drawn with a filter indicator in the column header. /// [Browsable(false)] public bool HasFilterIndicator { get { return this.UseFiltering && this.ValuesChosenForFiltering != null && this.ValuesChosenForFiltering.Count > 0; } } /// /// Gets or sets a delegate that will be used to own draw header column. /// [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public HeaderDrawingDelegate HeaderDrawing { get { return headerDrawing; } set { headerDrawing = value; } } private HeaderDrawingDelegate headerDrawing; /// /// Gets or sets the style that will be used to draw the header for this column /// /// This is only uses when the owning ObjectListView has HeaderUsesThemes set to false. [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; /// /// Gets or sets the font in which the header for this column will be drawn /// /// This property will be made obsolete in v2.5. Use HeaderFormatStyle instead /// This is only uses when HeaderUsesThemes is false. [Browsable(false)] [DefaultValue(null)] public Font HeaderFont { get { return this.HeaderFormatStyle == null ? null : this.HeaderFormatStyle.Normal.Font; } set { if (value == null && this.HeaderFormatStyle == null) return; if (this.HeaderFormatStyle == null) this.HeaderFormatStyle = new HeaderFormatStyle(); this.HeaderFormatStyle.SetFont(value); } } /// /// Gets or sets the color in which the text of the header for this column will be drawn /// /// This property will be made obsolete in v2.5. Use HeaderFormatStyle instead /// This is only uses when HeaderUsesThemes is false. [Browsable(false)] [DefaultValue(typeof(Color), "")] public Color HeaderForeColor { get { return this.HeaderFormatStyle == null ? Color.Empty : this.HeaderFormatStyle.Normal.ForeColor; } set { if (value.IsEmpty && this.HeaderFormatStyle == null) return; if (this.HeaderFormatStyle == null) this.HeaderFormatStyle = new HeaderFormatStyle(); this.HeaderFormatStyle.SetForeColor(value); } } /// /// Gets or sets whether the text values in this column will act like hyperlinks /// /// This is only taken into account when HeaderUsesThemes is false. [Category("ObjectListView"), Description("Name of the image that will be shown in the column header."), DefaultValue(null), TypeConverter(typeof(ImageKeyConverter)), Editor("System.Windows.Forms.Design.ImageIndexEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(System.Drawing.Design.UITypeEditor)), RefreshProperties(RefreshProperties.Repaint)] public string HeaderImageKey { get { return headerImageKey; } set { headerImageKey = value; } } private string headerImageKey; /// /// Gets or sets how the text of the header will be drawn? /// [Category("ObjectListView"), Description("How will the header text be aligned?"), DefaultValue(HorizontalAlignment.Left)] public HorizontalAlignment HeaderTextAlign { get { return headerTextAlign.HasValue ? headerTextAlign.Value : this.TextAlign; } set { headerTextAlign = value; } } private HorizontalAlignment? headerTextAlign; /// /// Gets the header alignment converted to a StringAlignment /// [Browsable(false)] public StringAlignment HeaderTextAlignAsStringAlignment { get { switch (this.HeaderTextAlign) { case HorizontalAlignment.Left: return StringAlignment.Near; case HorizontalAlignment.Center: return StringAlignment.Center; case HorizontalAlignment.Right: return StringAlignment.Far; default: return StringAlignment.Near; } } } /// /// Gets whether or not this column has an image in the header /// [Browsable(false)] public bool HasHeaderImage { get { return (this.ListView != null && this.ListView.SmallImageList != null && this.ListView.SmallImageList.Images.ContainsKey(this.HeaderImageKey)); } } /// /// Gets or sets whether this column can be hidden by the user. /// /// /// Column 0 can never be hidden, regardless of this setting. /// [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; /// /// Gets or sets whether the text values in this column will act like hyperlinks /// [Category("ObjectListView"), Description("Will the text values of this column act like hyperlinks?"), DefaultValue(false)] public bool Hyperlink { get { return hyperlink; } set { hyperlink = value; } } private bool hyperlink; /// /// 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. /// /// /// This is ignored if ImageGetter is not null. /// The property can use these return value to identify the image: /// /// null or -1 -- indicates no image /// an int -- the int value will be used as an index into the image list /// a String -- the string value will be used as a key into the image list /// an Image -- the Image will be drawn directly (only in OwnerDrawn mode) /// /// [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; /// /// 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. /// /// This delegate can use these return value to identify the image: /// /// null or -1 -- indicates no image /// an int -- the int value will be used as an index into the image list /// a String -- the string value will be used as a key into the image list /// an Image -- the Image will be drawn directly (only in OwnerDrawn mode) /// /// [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public ImageGetterDelegate ImageGetter { get { return imageGetter; } set { imageGetter = value; } } private ImageGetterDelegate imageGetter; /// /// Can the values shown in this column be edited? /// /// 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 [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; /// /// Is this column a fixed width column? /// [Browsable(false)] public bool IsFixedWidth { get { return (this.MinimumWidth != -1 && this.MaximumWidth != -1 && this.MinimumWidth >= this.MaximumWidth); } } /// /// Get/set whether this column should be used when the view is switched to tile view. /// /// 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. [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; /// /// Gets or sets whether the text of this header should be /// rendered vertically. /// /// /// If this is true, it is a good idea to set ToolTipText to the name of the column so it's easy to read. /// Currently (2010-08), vertical headers are text only. They do not draw their image. /// [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; /// /// Can this column be seen by the user? /// /// After changing this value, you must call RebuildColumns() before the changes will be effected. [Category("ObjectListView"), Description("Can this column be seen by the user?"), DefaultValue(true)] public bool IsVisible { get { return isVisible; } set { isVisible = value; } } private bool isVisible = true; /// /// Where was this column last positioned within the Detail view columns /// /// 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. [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public int LastDisplayIndex { get { return this.lastDisplayIndex; } set { this.lastDisplayIndex = value; } } private int lastDisplayIndex = -1; /// /// What is the maximum width that the user can give to this column? /// /// -1 means there is no maximum width. Give this the same value as MinimumWidth to make a fixed width column. [Category("ObjectListView"), Description("What is the maximum width to which the user can resize this column?"), DefaultValue(-1)] public int MaximumWidth { get { return maxWidth; } set { maxWidth = value; if (maxWidth != -1 && this.Width > maxWidth) this.Width = maxWidth; } } private int maxWidth = -1; /// /// What is the minimum width that the user can give to this column? /// /// -1 means there is no minimum width. Give this the same value as MaximumWidth to make a fixed width column. [Category("ObjectListView"), Description("What is the minimum width to which the user can resize this column?"), DefaultValue(-1)] public int MinimumWidth { get { return minWidth; } set { minWidth = value; if (this.Width < minWidth) this.Width = minWidth; } } private int minWidth = -1; /// /// Get/set the renderer that will be invoked when a cell needs to be redrawn /// [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; /// /// This delegate is called when a cell needs to be drawn in OwnerDrawn mode. /// /// This method is kept primarily for backwards compatibility. /// New code should implement an IRenderer, though this property will be maintained. [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public RenderDelegate RendererDelegate { get { if (this.Renderer is Version1Renderer) return ((Version1Renderer)this.Renderer).RenderDelegate; else return null; } set { this.Renderer = value == null ? null : new Version1Renderer(value); } } /// /// Gets or sets whether the text in this column's cell will be used when doing text searching. /// /// /// /// If this is false, text filters will not trying searching this columns cells when looking for matches. /// /// [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; /// /// Gets or sets whether the header for this column will include the column's Text. /// /// /// /// If this is false, the only thing rendered in the column header will be the image from . /// /// This setting is only considered when is false on the owning ObjectListView. /// [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; /// /// Gets or sets whether the contents of the list will be resorted when the user clicks the /// header of this column. /// /// /// /// 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. /// /// 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. /// [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; /// /// Gets or sets the horizontal alignment of the contents of the column. /// /// .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. new public HorizontalAlignment TextAlign { get { return this.textAlign.HasValue ? this.textAlign.Value : base.TextAlign; } set { this.textAlign = value; base.TextAlign = value; } } private HorizontalAlignment? textAlign; /// /// Gets the StringAlignment equivilent of the column text alignment /// [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; } } } /// /// What string should be displayed when the mouse is hovered over the header of this column? /// /// If a HeaderToolTipGetter is installed on the owning ObjectListView, this /// value will be ignored. [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; /// /// Should this column have a tri-state checkbox? /// /// /// If this is true, the user can choose the third state (normally Indeterminate). /// [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; /// /// Group objects by the initial letter of the aspect of the column /// /// /// One common pattern is to group column by the initial letter of the value for that group. /// The aspect must be a string (obviously). /// [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; /// /// Gets or sets whether or not this column should be user filterable /// [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; /// /// 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 /// [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public IModelFilter ValueBasedFilter { get { if (!this.UseFiltering) return null; if (valueBasedFilter != null) return valueBasedFilter; if (this.ClusteringStrategy == null) return null; if (this.ValuesChosenForFiltering == null || this.ValuesChosenForFiltering.Count == 0) return null; return new OneOfFilter(this.ClusteringStrategy.GetClusterKey, this.ValuesChosenForFiltering); } set { valueBasedFilter = value; } } private IModelFilter valueBasedFilter; /// /// 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. /// [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public IList ValuesChosenForFiltering { get { return this.valuesChosenForFiltering; } set { this.valuesChosenForFiltering = value; } } private IList valuesChosenForFiltering = new ArrayList(); /// /// What is the width of this column? /// [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); } } /// /// Gets or set whether the contents of this column's cells should be word wrapped /// /// 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. [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 /// /// For a given group value, return the string that should be used as the groups title. /// /// The group key that is being converted to a title /// string public string ConvertGroupKeyToTitle(object value) { if (this.groupKeyToTitleConverter != null) return this.groupKeyToTitleConverter(value); return value == null ? "{null}" : this.ValueToString(value); } /// /// Get the checkedness of the given object for this column /// /// The row object that is being displayed /// The checkedness of the object 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; } /// /// Put the checkedness of the given object for this column /// /// The row object that is being displayed /// /// The checkedness of the object 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); } /// /// For a given row object, extract the value indicated by the AspectName property of this column. /// /// The row object that is being displayed /// An object, which is the aspect named by AspectName public object GetAspectByName(object rowObject) { if (this.aspectMunger == null) this.aspectMunger = new Munger(this.AspectName); return this.aspectMunger.GetValue(rowObject); } private Munger aspectMunger; /// /// For a given row object, return the object that is the key of the group that this row belongs to. /// /// The row object that is being displayed /// Group key object public object GetGroupKey(object rowObject) { if (this.groupKeyGetter == null) { object key = this.GetValue(rowObject); String keyAsString = key as String; if (keyAsString != null && this.UseInitialLetterForGroup) { if (keyAsString.Length > 0) key = keyAsString.Substring(0, 1).ToUpper(); } return key; } else return this.groupKeyGetter(rowObject); } /// /// For a given row object, return the image selector of the image that should displayed in this column. /// /// The row object that is being displayed /// int or string or Image. int or string will be used as index into image list. null or -1 means no image 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; /// /// Return the image that represents the check box for the given model /// /// /// 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; } /// /// For a given row object, return the string representation of the value shown in this column. /// /// /// 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. /// /// /// public string GetStringValue(object rowObject) { return this.ValueToString(this.GetValue(rowObject)); } /// /// For a given row object, return the object that is to be displayed in this column. /// /// The row object that is being displayed /// An object, which is the aspect to be displayed public object GetValue(object rowObject) { if (this.AspectGetter == null) return this.GetAspectByName(rowObject); else return this.AspectGetter(rowObject); } /// /// Update the given model object with the given value using the column's /// AspectName. /// /// The model object to be updated /// The value to be put into the model public void PutAspectByName(Object rowObject, Object newValue) { if (this.aspectMunger == null) this.aspectMunger = new Munger(this.AspectName); this.aspectMunger.PutValue(rowObject, newValue); } /// /// Update the given model object with the given value /// /// The model object to be updated /// The value to be put into the model public void PutValue(Object rowObject, Object newValue) { if (this.aspectPutter == null) this.PutAspectByName(rowObject, newValue); else this.aspectPutter(rowObject, newValue); } /// /// Convert the aspect object to its string representation. /// /// /// 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. /// /// The value of the aspect that should be displayed /// A string representation of the aspect 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 /// /// Decide the clustering strategy that will be used for this column /// /// private IClusteringStrategy DecideDefaultClusteringStrategy() { if (!this.UseFiltering) return null; if (this.DataType == typeof(DateTime)) return new DateTimeClusteringStrategy(); return new ClustersFromGroupsStrategy(); } /// /// Gets or sets the type of data shown in this column. /// /// If this is not set, it will try to get the type /// by looking through the rows of the listview. [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; /// /// Create groupies /// This is an untyped version to help with Generator and OLVColumn attributes /// /// /// public void MakeGroupies(object[] values, string[] descriptions) { this.MakeGroupies(values, descriptions, null, null, null); } /// /// Create groupies /// /// /// /// public void MakeGroupies(T[] values, string[] descriptions) { this.MakeGroupies(values, descriptions, null, null, null); } /// /// Create groupies /// /// /// /// /// public void MakeGroupies(T[] values, string[] descriptions, object[] images) { this.MakeGroupies(values, descriptions, images, null, null); } /// /// Create groupies /// /// /// /// /// /// public void MakeGroupies(T[] values, string[] descriptions, object[] images, string[] subtitles) { this.MakeGroupies(values, descriptions, images, subtitles, null); } /// /// 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". /// /// Array of values. Values must be able to be /// compared to the aspect (using IComparable) /// The description for the matching value. The last element is the default description. /// If there are n values, there must be n+1 descriptions. /// /// this.salaryColumn.MakeGroupies( /// new UInt32[] { 20000, 100000 }, /// new string[] { "Lowly worker", "Middle management", "Rarified elevation"}); /// /// /// /// /// public void MakeGroupies(T[] values, string[] descriptions, object[] images, string[] subtitles, string[] tasks) { // Sanity checks if (values == null) throw new ArgumentNullException("values"); if (descriptions == null) throw new ArgumentNullException("descriptions"); if (values.Length + 1 != descriptions.Length) throw new ArgumentException("descriptions must have one more element than values."); // Install a delegate that returns the index of the description to be shown this.GroupKeyGetter = delegate(object row) { Object aspect = this.GetValue(row); if (aspect == null || aspect == System.DBNull.Value) return -1; IComparable comparable = (IComparable)aspect; for (int i = 0; i < values.Length; i++) { if (comparable.CompareTo(values[i]) < 0) return i; } // Display the last element in the array return descriptions.Length - 1; }; // Install a delegate that simply looks up the given index in the descriptions. this.GroupKeyToTitleConverter = delegate(object key) { if ((int)key < 0) return ""; return descriptions[(int)key]; }; // Install one delegate that does all the other formatting this.GroupFormatter = delegate(OLVGroup group, GroupingParameters parms) { int key = (int)group.Key; // we know this is an int since we created it in GroupKeyGetter if (key >= 0) { if (images != null && key < images.Length) group.TitleImage = images[key]; if (subtitles != null && key < subtitles.Length) group.Subtitle = subtitles[key]; if (tasks != null && key < tasks.Length) group.Task = tasks[key]; } }; } /// /// Create groupies based on exact value matches. /// /// /// 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]". /// /// Array of values. Values must be able to be /// equated to the aspect /// The description for the matching value. /// /// this.marriedColumn.MakeEqualGroupies( /// new MaritalStatus[] { MaritalStatus.Single, MaritalStatus.Married, MaritalStatus.Divorced, MaritalStatus.Partnered }, /// new string[] { "Looking", "Content", "Looking again", "Mostly content" }); /// /// /// /// /// public void MakeEqualGroupies(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 } }