File: winforms\Managed\System\WinForms\ColumnHeader.cs
Project: ndp\fx\src\System.Windows.Forms.csproj (System.Windows.Forms)
//------------------------------------------------------------------------------
// <copyright file="ColumnHeader.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>                                                                
//------------------------------------------------------------------------------
 
/*
 */
namespace System.Windows.Forms {
 
    using Microsoft.Win32;
    using System;
    using System.ComponentModel;
    using System.Drawing.Design;
    using System.Diagnostics;
    using System.Drawing;
    using System.IO;
    using System.Runtime.InteropServices;
    using System.Runtime.Remoting;
    using System.Windows.Forms;    
    using System.Globalization;
    
    /// <include file='doc\ColumnHeader.uex' path='docs/doc[@for="ColumnHeader"]/*' />
    /// <devdoc>
    ///    <para>
    ///       Displays a single column header in a <see cref='System.Windows.Forms.ListView'/>
    ///       control.
    ///
    ///    </para>
    /// </devdoc>
    [
    ToolboxItem(false),
    DesignTimeVisible(false),
    DefaultProperty("Text"),
    TypeConverterAttribute(typeof(ColumnHeaderConverter))
    ]
    public class ColumnHeader : Component, ICloneable {
 
// disable csharp compiler warning #0414: field assigned unused value
#pragma warning disable 0414
        internal int index = -1;
#pragma warning restore 0414
        internal string text = null;
        internal string name = null;
        internal int width = 60;
        // Use TextAlign property instead of this member variable, always
        private HorizontalAlignment textAlign = HorizontalAlignment.Left;
        private bool textAlignInitialized = false;
        private int displayIndexInternal = -1;
        private ColumnHeaderImageListIndexer imageIndexer = null;
 
        object userData;
 
        private ListView listview;
        // We need to send some messages to ListView when it gets initialized.
        internal ListView OwnerListview
        {
            get
            {
                return listview;
            }
            set
            {
                int width = this.Width;
 
                listview = value;
                
                // The below properties are set into the listview.
                this.Width = width;
            }
        }
 
        /// <include file='doc\ColumnHeader.uex' path='docs/doc[@for="ColumnHeader.ColumnHeader"]/*' />
        /// <devdoc>
        ///     Creates a new ColumnHeader object
        /// </devdoc>
        public ColumnHeader() {
            imageIndexer = new ColumnHeaderImageListIndexer(this);
        }
 
        /// <include file='doc\ColumnHeader.uex' path='docs/doc[@for="ColumnHeader.ColumnHeader1"]/*' />
        /// <devdoc>
        ///     Creates a new ColumnHeader object
        /// </devdoc>
        public ColumnHeader(int imageIndex) : this () {
            this.ImageIndex = imageIndex;
        }
 
        /// <include file='doc\ColumnHeader.uex' path='docs/doc[@for="ColumnHeader.ColumnHeader2"]/*' />
        /// <devdoc>
        ///     Creates a new ColumnHeader object
        /// </devdoc>
        public ColumnHeader(string imageKey) : this () {
            this.ImageKey = imageKey;
        }
 
        internal int ActualImageIndex_Internal {
            get {
                int imgIndex = this.imageIndexer.ActualIndex;
                if (this.ImageList == null || this.ImageList.Images == null || imgIndex >= this.ImageList.Images.Count) {
                    // the ImageIndex equivalent of a ImageKey that does not exist in the ImageList
                    return -1;
                } else {
                    return imgIndex;
                }
            }
        }
 
 
        /// <include file='doc\ColumnHeader.uex' path='docs/doc[@for="ColumnHeader.DisplayIndex"]/*' />
	[
        Localizable(true),
        RefreshProperties(RefreshProperties.Repaint),
	SRCategory(SR.CatBehavior),
	SRDescription(SR.ColumnHeaderDisplayIndexDescr)
	]
        public int DisplayIndex {
            get {
                return this.DisplayIndexInternal;
            }
		
	    set {
 
                // When the list is being deserialized we need
                // to take the display index as is. ListView
                // does correctly synchronize the indices.
                if (this.listview == null) {
                   this.DisplayIndexInternal = value;
                   return;
                }
 
	        if (value < 0 || value> (this.listview.Columns.Count - 1)) {
                    throw new ArgumentOutOfRangeException("DisplayIndex", SR.GetString(SR.ColumnHeaderBadDisplayIndex));
	        }
 
                int lowDI = Math.Min(this.DisplayIndexInternal, value);
                int hiDI = Math.Max(this.DisplayIndexInternal, value);
                int[] colsOrder = new int[this.listview.Columns.Count];
 
                // set the display indices. This is not an expensive operation because
                // we only set an integer in the column header class
                bool hdrMovedForward = value > this.DisplayIndexInternal;
                ColumnHeader movedHdr = null;
                for (int i = 0; i < this.listview.Columns.Count; i ++) {
 
                    ColumnHeader hdr = this.listview.Columns[i];
                    if (hdr.DisplayIndex == this.DisplayIndexInternal) {
                        movedHdr = hdr;
                    } else if (hdr.DisplayIndex >= lowDI && hdr.DisplayIndex <= hiDI) {
                        hdr.DisplayIndexInternal -= hdrMovedForward ? 1 : -1;
                    }
                    if (i != this.Index) {
                       colsOrder[ hdr.DisplayIndexInternal ] = i;
                    }
                }
 
                movedHdr.DisplayIndexInternal = value;
                colsOrder[ movedHdr.DisplayIndexInternal ] = movedHdr.Index;
                SetDisplayIndices( colsOrder );
	    }
	}
 
        internal int DisplayIndexInternal {
            get {
                return this.displayIndexInternal;
            }
            set {
                this.displayIndexInternal = value;
            }
        }
 
                /// <include file='doc\ColumnHeader.uex' path='docs/doc[@for="ColumnHeader.Index"]/*' />
        /// <devdoc>
        ///     The index of this column.  This index does not necessarily correspond
        ///     to the current visual position of the column in the ListView, because the
        ///     user may orerder columns if the allowColumnReorder property is true.
        /// </devdoc>
        [ Browsable(false)]
        public int Index {
            get {
                if (listview != null)
                    return listview.GetColumnIndex(this);
                return -1;  
            }
        }
 
        /// <include file='doc\ColumnHeader.uex' path='docs/doc[@for="ColumnHeader.ImageIndex"]/*' />
        [
        DefaultValue(-1),
        TypeConverterAttribute(typeof(ImageIndexConverter)),
        Editor("System.Windows.Forms.Design.ImageIndexEditor, " + AssemblyRef.SystemDesign, typeof(UITypeEditor)),
        RefreshProperties(RefreshProperties.Repaint),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
        ]
        public int ImageIndex {
            get {
                if (imageIndexer.Index != -1 && ImageList != null && imageIndexer.Index >= ImageList.Images.Count) {
                    return ImageList.Images.Count - 1;
                } 
                return imageIndexer.Index;
            }
            set {
                if (value < -1) {
                    throw new ArgumentOutOfRangeException("ImageIndex", SR.GetString(SR.InvalidLowBoundArgumentEx, "ImageIndex", (value).ToString(CultureInfo.CurrentCulture), (-1).ToString(CultureInfo.CurrentCulture)));
                }
 
                if (imageIndexer.Index != value) {
                    imageIndexer.Index = value;
 
                    if (ListView != null && ListView.IsHandleCreated) {
                        ListView.SetColumnInfo(NativeMethods.LVCF_IMAGE, this);
                    }
                }
            }
        }
 
        /// <include file='doc\ListViewItem.uex' path='docs/doc[@for="ListViewItem.ImageList"]/*' />
        [Browsable(false)]
        public ImageList ImageList {
            // we added the ImageList property so that the ImageIndexConverter can find our image list
            get {
                return this.imageIndexer.ImageList;
            }
        }
 
        /// <include file='doc\ColumnHeader.uex' path='docs/doc[@for="ColumnHeader.ImageKey"]/*' />
        [
        DefaultValue(""),
        TypeConverterAttribute(typeof(ImageKeyConverter)),
        Editor("System.Windows.Forms.Design.ImageIndexEditor, " + AssemblyRef.SystemDesign, typeof(UITypeEditor)),
        RefreshProperties(RefreshProperties.Repaint),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
        ]
        public string ImageKey {
            get {
                return imageIndexer.Key;
            }
            set {
                if (value != imageIndexer.Key) {
                    imageIndexer.Key = value;
 
                    if (ListView != null && ListView.IsHandleCreated) {
                        ListView.SetColumnInfo(NativeMethods.LVCF_IMAGE, this);
                    }
                }
            }
        }
 
        /// <include file='doc\ColumnHeader.uex' path='docs/doc[@for="ColumnHeader.ListView"]/*' />
        /// <devdoc>
        ///     Returns the ListView control that this column is displayed in.  May be null
        /// </devdoc>
        [ Browsable(false) ]
        public ListView ListView {
            get {
                return this.listview;
            }
        }
 
        /// <include file='doc\ColumnHeader.uex' path='docs/doc[@for="ColumnHeader.Text"]/*' />
        /// <devdoc>
        ///     The Name of the column header
        /// </devdoc>
        [
        Browsable(false),
        SRDescription(SR.ColumnHeaderNameDescr)
        ]
        public string Name {
            get {
                return WindowsFormsUtils.GetComponentName(this,name);
            }
            set {
                if (value == null) {
                    this.name = "";
                }
                else {
                    this.name = value;
                }
                if(Site != null) {
                    Site.Name = value;
                }
            }
 
        }
 
        /// <include file='doc\ColumnHeader.uex' path='docs/doc[@for="ColumnHeader.Text"]/*' />
        /// <devdoc>
        ///     The text displayed in the column header
        /// </devdoc>
        [
        Localizable(true),
        SRDescription(SR.ColumnCaption)
        ]
        public string Text {
            get {
                return(text != null ? text : "ColumnHeader");
            }
            set {
                if (value == null) {
                    this.text = "";
                }
                else {
                    this.text = value;
                }
                if (listview != null) {
                    listview.SetColumnInfo(NativeMethods.LVCF_TEXT, this);
                }
            }
 
        }
 
        /// <include file='doc\ColumnHeader.uex' path='docs/doc[@for="ColumnHeader.TextAlign"]/*' />
        /// <devdoc>
        ///     The horizontal alignment of the text contained in this column
        /// </devdoc>
        [
        SRDescription(SR.ColumnAlignment),
        Localizable(true),
        DefaultValue(HorizontalAlignment.Left)
        ]
        public HorizontalAlignment TextAlign {
            get {
                if (!textAlignInitialized && (listview != null))
                {
                        textAlignInitialized = true;
                        // See below for an explanation of (Index != 0)
                        //Added !IsMirrored for VSWhidbey # 365437
                        if ((Index != 0) && (listview.RightToLeft == RightToLeft.Yes) && !listview.IsMirrored)
                        {
                                this.textAlign = HorizontalAlignment.Right;
                        }
                }
                return this.textAlign;
            }
            set {
                //valid values are 0x0 to 0x2. 
                if (!ClientUtils.IsEnumValid(value, (int)value, (int)HorizontalAlignment.Left, (int)HorizontalAlignment.Center)){
                    throw new InvalidEnumArgumentException("value", (int)value, typeof(HorizontalAlignment));
                }
 
                this.textAlign = value;
                
                // The first column must be left-aligned
                if (Index == 0 && this.textAlign != HorizontalAlignment.Left) {
                    this.textAlign = HorizontalAlignment.Left;
                }
 
                if (listview != null) {
                    listview.SetColumnInfo(NativeMethods.LVCF_FMT, this);
                    listview.Invalidate();
                }
            }
        }
 
        /// <include file='doc\ColumnHeader.uex' path='docs/doc[@for="ColumnHeader.Tag"]/*' />
        [
        SRCategory(SR.CatData),
        Localizable(false),
        Bindable(true),
        SRDescription(SR.ControlTagDescr),
        DefaultValue(null),
        TypeConverter(typeof(StringConverter)),
        ]
        public object Tag {
            get {
                return userData;
            }
            set {
                userData = value;
            }
        }
 
        internal int WidthInternal {
            get {
                return width;
            }
        }
        /// <include file='doc\ColumnHeader.uex' path='docs/doc[@for="ColumnHeader.Width"]/*' />
        /// <devdoc>
        ///     The width of the column in pixels.
        /// </devdoc>
        [
        SRDescription(SR.ColumnWidth),
        Localizable(true),
        DefaultValue(60)
        ]
        public int Width {
            get {
                // Since we can't keep our private width in sync with the real width because
                // we don't get notified when the user changes it, we need to get this info
                // from the underlying control every time we're asked.
                // The underlying control will only report the correct width if it's in Report view
                if (listview != null && listview.IsHandleCreated && !listview.Disposing && listview.View == View.Details) {
                    
                    // Make sure this column has already been added to the ListView, else just return width
                    //
                    IntPtr hwndHdr = UnsafeNativeMethods.SendMessage(new HandleRef(listview, listview.Handle), NativeMethods.LVM_GETHEADER, 0, 0);
                    if (hwndHdr != IntPtr.Zero) {
                        int nativeColumnCount = (int)UnsafeNativeMethods.SendMessage(new HandleRef(listview, hwndHdr), NativeMethods.HDM_GETITEMCOUNT, 0, 0);
                        if (Index < nativeColumnCount) {
                            width = (int)UnsafeNativeMethods.SendMessage(new HandleRef(listview, listview.Handle), NativeMethods.LVM_GETCOLUMNWIDTH, Index, 0);
                        }
                    }
                }
 
                return width;
            }
            set {
                this.width = value;
                if (listview != null)
                    listview.SetColumnWidth(Index, ColumnHeaderAutoResizeStyle.None);
                }
        }
 
        /// <include file='doc\ColumnHeader.uex' path='docs/doc[@for="ColumnHeader.AutoResize"]/*' />
        public void AutoResize(ColumnHeaderAutoResizeStyle headerAutoResize) {
 
            if (headerAutoResize < ColumnHeaderAutoResizeStyle.None || headerAutoResize > ColumnHeaderAutoResizeStyle.ColumnContent) {
                throw new InvalidEnumArgumentException("headerAutoResize", (int)headerAutoResize, typeof(ColumnHeaderAutoResizeStyle));
            }
 
            if (this.listview != null) {
                this.listview.AutoResizeColumn(this.Index, headerAutoResize);
            }
        }
        
 
        /// <include file='doc\ColumnHeader.uex' path='docs/doc[@for="ColumnHeader.Clone"]/*' />
        /// <devdoc>
        ///     Creates an identical ColumnHeader, unattached to any ListView
        /// </devdoc>
        public object Clone() {
            Type clonedType = this.GetType();
            ColumnHeader columnHeader = null;
 
            if (clonedType == typeof(ColumnHeader)) {
                columnHeader = new ColumnHeader();
            }
            else {
                // SECREVIEW : Late-binding does not represent a security thread, see bug#411899 for more info..
                //
                columnHeader = (ColumnHeader)Activator.CreateInstance(clonedType);
            }
 
            columnHeader.text = text;
            columnHeader.Width = width;
            columnHeader.textAlign = TextAlign;
            return columnHeader;
        }
 
        /// <include file='doc\ColumnHeader.uex' path='docs/doc[@for="ColumnHeader.Dispose"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        protected override void Dispose(bool disposing) {
            if (disposing) {
                if (listview != null) {
                    int index = Index;
                    if (index != -1) {
                        listview.Columns.RemoveAt(index);
                    }
                }
            }
            base.Dispose(disposing);
        }
 
	private void ResetText() {
		Text = null;
	}
 
        // Set the display indices of the listview columns
        private void SetDisplayIndices(int[] cols) {
 
            if (this.listview.IsHandleCreated && !this.listview.Disposing) {
  	       UnsafeNativeMethods.SendMessage(new HandleRef(this.listview, this.listview.Handle), NativeMethods.LVM_SETCOLUMNORDERARRAY, cols.Length, cols);
	    }
        }
 
        private bool ShouldSerializeName() {
            return !string.IsNullOrEmpty(this.name);
        }
 
        private bool ShouldSerializeDisplayIndex() {
            return this.DisplayIndex != this.Index;
        }
 
        /// <include file='doc\ColumnHeader.uex' path='docs/doc[@for="ColumnHeader.ShouldPersistText"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        internal bool ShouldSerializeText() {
            return(text != null);
        }
        
        /// <include file='doc\ColumnHeader.uex' path='docs/doc[@for="ColumnHeader.ToString"]/*' />
        /// <devdoc>
        ///     Returns a string representation of this column header
        /// </devdoc>
        public override string ToString() {
            return "ColumnHeader: Text: " + Text;
        }
 
        internal class ColumnHeaderImageListIndexer : ImageList.Indexer {
            private ColumnHeader owner = null;
            public ColumnHeaderImageListIndexer(ColumnHeader ch) {
                owner = ch;
            }
 
            public override ImageList ImageList {
                get {
                    if (owner != null && owner.ListView != null) {
                        return owner.ListView.SmallImageList;
                    }
                    return null;
                }
                set {
                    Debug.Assert(false, "We should never set the image list");
                }
            }
        }
    }
}