File: System\Windows\Forms\Controls\ListView\ColumnHeader.cs
Web Access
Project: src\src\System.Windows.Forms\src\System.Windows.Forms.csproj (System.Windows.Forms)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.ComponentModel;
using System.Drawing.Design;
 
namespace System.Windows.Forms;
 
/// <summary>
///  Displays a single column header in a <see cref="Forms.ListView"/> control.
/// </summary>
[DesignTimeVisible(false)]
[DefaultProperty(nameof(Text))]
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
[ToolboxItem(false)]
[TypeConverter(typeof(ColumnHeaderConverter))]
public partial class ColumnHeader : Component, ICloneable
{
    // disable C# compiler warning #0414: field assigned unused value
#pragma warning disable 0414
    internal int _index = -1;
#pragma warning restore 0414
    internal string? _text;
    internal string _name;
    internal int _width = 60;
    internal int _correspondingListViewSubItemIndex = -1;
 
    // Use TextAlign property instead of this member variable, always
    private HorizontalAlignment _textAlign = HorizontalAlignment.Left;
    private bool _textAlignInitialized;
    private AccessibleObject? _accessibilityObject;
    private readonly ColumnHeaderImageListIndexer _imageIndexer;
 
    // We need to send some messages to ListView when it gets initialized.
    internal ListView? OwnerListview
    {
        get
        {
            return ListView;
        }
        set
        {
            int width = Width;
 
            ListView = value;
 
            // The below properties are set into the listview.
            Width = width;
        }
    }
 
    /// <summary>
    ///  Creates a new ColumnHeader object
    /// </summary>
    public ColumnHeader()
    {
        _imageIndexer = new ColumnHeaderImageListIndexer(this);
        _name = string.Empty;
    }
 
    /// <summary>
    ///  Creates a new ColumnHeader object
    /// </summary>
    public ColumnHeader(int imageIndex) : this()
    {
        ImageIndex = imageIndex;
    }
 
    /// <summary>
    ///  Creates a new ColumnHeader object
    /// </summary>
    public ColumnHeader(string imageKey) : this()
    {
        ImageKey = imageKey;
    }
 
    internal AccessibleObject AccessibilityObject
    {
        get
        {
            _accessibilityObject ??= new ListViewColumnHeaderAccessibleObject(this);
 
            return _accessibilityObject;
        }
    }
 
    internal int ActualImageIndex_Internal
    {
        get
        {
            int imgIndex = _imageIndexer.ActualIndex;
            if (ImageList is null || ImageList.Images is null || imgIndex >= ImageList.Images.Count)
            {
                // the ImageIndex equivalent of a ImageKey that does not exist in the ImageList
                return -1;
            }
            else
            {
                return imgIndex;
            }
        }
    }
 
    [Localizable(true)]
    [RefreshProperties(RefreshProperties.Repaint)]
    [SRCategory(nameof(SR.CatBehavior))]
    [SRDescription(nameof(SR.ColumnHeaderDisplayIndexDescr))]
    public int DisplayIndex
    {
        get
        {
            return DisplayIndexInternal;
        }
 
        set
        {
            // When the list is being deserialized we need
            // to take the display index as is. ListView
            // does correctly synchronize the indices.
            if (ListView is null)
            {
                DisplayIndexInternal = value;
                return;
            }
 
            if (value < 0 || value > (ListView.Columns.Count - 1))
            {
                throw new ArgumentOutOfRangeException(nameof(DisplayIndex), SR.ColumnHeaderBadDisplayIndex);
            }
 
            int lowDI = Math.Min(DisplayIndexInternal, value);
            int hiDI = Math.Max(DisplayIndexInternal, value);
            int[] colsOrder = new int[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 > DisplayIndexInternal;
            ColumnHeader? movedHdr = null;
            for (int i = 0; i < ListView.Columns.Count; i++)
            {
                ColumnHeader hdr = ListView.Columns[i];
                if (hdr.DisplayIndex == DisplayIndexInternal)
                {
                    movedHdr = hdr;
                }
                else if (hdr.DisplayIndex >= lowDI && hdr.DisplayIndex <= hiDI)
                {
                    hdr.DisplayIndexInternal -= hdrMovedForward ? 1 : -1;
                }
 
                if (i != Index)
                {
                    colsOrder[hdr.DisplayIndexInternal] = i;
                }
            }
 
            if (movedHdr is not null)
            {
                movedHdr.DisplayIndexInternal = value;
                colsOrder[movedHdr.DisplayIndexInternal] = movedHdr.Index;
            }
 
            SetDisplayIndices(colsOrder);
        }
    }
 
    internal int DisplayIndexInternal { get; set; } = -1;
 
    /// <summary>
    ///  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 order columns if the allowColumnReorder property is true.
    /// </summary>
    [Browsable(false)]
    public int Index
    {
        get
        {
            if (ListView is not null)
            {
                return ListView.GetColumnIndex(this);
            }
 
            return -1;
        }
    }
 
    [DefaultValue(ImageList.Indexer.DefaultIndex)]
    [TypeConverter(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 != ImageList.Indexer.DefaultIndex && ImageList is not null && _imageIndexer.Index >= ImageList.Images.Count)
            {
                return ImageList.Images.Count - 1;
            }
 
            return _imageIndexer.Index;
        }
        set
        {
            ArgumentOutOfRangeException.ThrowIfLessThan(value, ImageList.Indexer.DefaultIndex);
 
            if (_imageIndexer.Index == value && value != ImageList.Indexer.DefaultIndex)
            {
                return;
            }
 
            _imageIndexer.Index = value;
 
            if (ListView is not null && ListView.IsHandleCreated)
            {
                ListView.SetColumnInfo(LVCOLUMNW_MASK.LVCF_IMAGE, this);
            }
        }
    }
 
    [Browsable(false)]
    public ImageList? ImageList
    {
        // we added the ImageList property so that the ImageIndexConverter can find our image list
        get
        {
            return _imageIndexer.ImageList;
        }
    }
 
    [DefaultValue(ImageList.Indexer.DefaultKey)]
    [TypeConverter(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 && !string.Equals(value, ImageList.Indexer.DefaultKey))
            {
                return;
            }
 
            _imageIndexer.Key = value;
 
            if (ListView is not null && ListView.IsHandleCreated)
            {
                ListView.SetColumnInfo(LVCOLUMNW_MASK.LVCF_IMAGE, this);
            }
        }
    }
 
    /// <summary>
    ///  Returns the ListView control that this column is displayed in. May be null
    /// </summary>
    [Browsable(false)]
    public ListView? ListView { get; private set; }
 
    /// <summary>
    ///  The Name of the column header
    /// </summary>
    [Browsable(false)]
    [SRDescription(nameof(SR.ColumnHeaderNameDescr))]
    public string? Name
    {
        get
        {
            return WindowsFormsUtils.GetComponentName(this, _name);
        }
        set
        {
            if (value is null)
            {
                _name = string.Empty;
            }
            else
            {
                _name = value;
            }
 
            if (Site is not null)
            {
                Site.Name = value;
            }
        }
    }
 
    /// <summary>
    ///  The text displayed in the column header
    /// </summary>
    [Localizable(true)]
    [SRDescription(nameof(SR.ColumnCaption))]
    [AllowNull]
    public string Text
    {
        get
        {
            return _text ?? nameof(ColumnHeader);
        }
        set
        {
            if (value is null)
            {
                _text = string.Empty;
            }
            else
            {
                _text = value;
            }
 
            ListView?.SetColumnInfo(LVCOLUMNW_MASK.LVCF_TEXT, this);
        }
    }
 
    /// <summary>
    ///  The horizontal alignment of the text contained in this column
    /// </summary>
    [SRDescription(nameof(SR.ColumnAlignment))]
    [Localizable(true)]
    [DefaultValue(HorizontalAlignment.Left)]
    public HorizontalAlignment TextAlign
    {
        get
        {
            if (!_textAlignInitialized && (ListView is not null))
            {
                _textAlignInitialized = true;
                // See below for an explanation of (Index != 0)
                // Added !IsMirrored
                if ((Index != 0) && (ListView.RightToLeft == RightToLeft.Yes) && !ListView.IsMirrored)
                {
                    _textAlign = HorizontalAlignment.Right;
                }
            }
 
            return _textAlign;
        }
        set
        {
            // valid values are 0x0 to 0x2.
            SourceGenerated.EnumValidator.Validate(value);
 
            _textAlign = value;
 
            // The first column must be left-aligned
            if (Index == 0 && _textAlign != HorizontalAlignment.Left)
            {
                _textAlign = HorizontalAlignment.Left;
            }
 
            if (ListView is not null)
            {
                ListView.SetColumnInfo(LVCOLUMNW_MASK.LVCF_FMT, this);
                ListView.Invalidate();
            }
        }
    }
 
    [SRCategory(nameof(SR.CatData))]
    [Localizable(false)]
    [Bindable(true)]
    [SRDescription(nameof(SR.ControlTagDescr))]
    [DefaultValue(null)]
    [TypeConverter(typeof(StringConverter))]
    public object? Tag { get; set; }
 
    internal int WidthInternal
    {
        get
        {
            return _width;
        }
    }
 
    /// <summary>
    ///  The width of the column in pixels.
    /// </summary>
    [SRDescription(nameof(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 is not null && ListView.IsHandleCreated && !ListView.Disposing && ListView.View == View.Details)
            {
                // Make sure this column has already been added to the ListView, else just return width
                HWND hwndHdr = (HWND)PInvokeCore.SendMessage(ListView, PInvoke.LVM_GETHEADER);
                if (!hwndHdr.IsNull)
                {
                    int nativeColumnCount = (int)PInvokeCore.SendMessage(hwndHdr, PInvoke.HDM_GETITEMCOUNT);
                    if (Index < nativeColumnCount)
                    {
                        _width = (int)PInvokeCore.SendMessage(ListView, PInvoke.LVM_GETCOLUMNWIDTH, (WPARAM)Index);
                    }
                }
            }
 
            return _width;
        }
        set
        {
            _width = value;
            ListView?.SetColumnWidth(Index, ColumnHeaderAutoResizeStyle.None);
        }
    }
 
    public void AutoResize(ColumnHeaderAutoResizeStyle headerAutoResize)
    {
        if (headerAutoResize is < ColumnHeaderAutoResizeStyle.None or > ColumnHeaderAutoResizeStyle.ColumnContent)
        {
            throw new InvalidEnumArgumentException(nameof(headerAutoResize), (int)headerAutoResize, typeof(ColumnHeaderAutoResizeStyle));
        }
 
        ListView?.AutoResizeColumn(Index, headerAutoResize);
    }
 
    /// <summary>
    ///  Creates an identical ColumnHeader, unattached to any ListView
    /// </summary>
    public object Clone()
    {
        Type clonedType = GetType();
        ColumnHeader columnHeader;
 
        if (clonedType == typeof(ColumnHeader))
        {
            columnHeader = new ColumnHeader();
        }
        else
        {
            columnHeader = (ColumnHeader)Activator.CreateInstance(clonedType)!;
        }
 
        columnHeader._text = _text;
        columnHeader.Width = _width;
        columnHeader._textAlign = TextAlign;
        return columnHeader;
    }
 
    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            if (ListView is not null)
            {
                int index = Index;
                if (index != -1)
                {
                    ListView.Columns.RemoveAt(index);
                }
            }
        }
 
        base.Dispose(disposing);
    }
 
    internal void ReleaseUiaProvider()
    {
        PInvoke.UiaDisconnectProvider(_accessibilityObject);
        _accessibilityObject = null;
    }
 
    private void ResetText()
    {
        Text = null;
    }
 
    // Set the display indices of the listview columns
    private unsafe void SetDisplayIndices(int[] cols)
    {
        if (ListView is not null && ListView.IsHandleCreated && !ListView.Disposing)
        {
            fixed (int* pCols = cols)
            {
                PInvokeCore.SendMessage(ListView, PInvoke.LVM_SETCOLUMNORDERARRAY, (WPARAM)cols.Length, (LPARAM)pCols);
            }
        }
    }
 
    private bool ShouldSerializeName()
    {
        return !string.IsNullOrEmpty(Name);
    }
 
    private bool ShouldSerializeDisplayIndex()
    {
        return DisplayIndex != Index;
    }
 
    internal bool ShouldSerializeText()
    {
        return _text is not null;
    }
 
    /// <summary>
    ///  Returns a string representation of this column header
    /// </summary>
    public override string ToString()
    {
        return $"{nameof(ColumnHeader)}: Text: {Text}";
    }
}