| File: System\Windows\Forms\Controls\ListView\ListViewGroup.cs | Web Access |
| Project: src\winforms\src\System.Windows.Forms\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.Runtime.Serialization; namespace System.Windows.Forms; /// <summary> /// Represents a group within a ListView. /// </summary> [TypeConverter(typeof(ListViewGroupConverter))] [ToolboxItem(false)] [DesignTimeVisible(false)] [DefaultProperty(nameof(Header))] [Serializable] // This type is participating in resx serialization scenarios. public sealed partial class ListViewGroup : ISerializable { private string? _header; private HorizontalAlignment _headerAlignment = HorizontalAlignment.Left; private string? _footer; private HorizontalAlignment _footerAlignment = HorizontalAlignment.Left; private ListViewGroupCollapsedState _collapsedState = ListViewGroupCollapsedState.Default; private string? _subtitle; private string? _taskLink; private ListView.ListViewItemCollection? _items; private static int s_nextID; private static int s_nextHeader = 1; private ListViewGroupImageIndexer? _imageIndexer; private AccessibleObject? _accessibilityObject; /// <summary> /// Creates a ListViewGroup. /// </summary> public ListViewGroup() : this(string.Format(SR.ListViewGroupDefaultHeader, s_nextHeader++)) { } /// <summary> /// Creates a ListViewItem object from an Stream. /// </summary> private ListViewGroup(SerializationInfo info, StreamingContext context) : this() { Deserialize(info); } /// <summary> /// Creates a ListViewItem object from a Key and a Name /// </summary> public ListViewGroup(string? key, string? headerText) : this() { Name = key; _header = headerText; } /// <summary> /// Creates a ListViewGroup. /// </summary> public ListViewGroup(string? header) { _header = header; ID = s_nextID++; } /// <summary> /// Creates a ListViewGroup. /// </summary> public ListViewGroup(string? header, HorizontalAlignment headerAlignment) : this(header) { _headerAlignment = headerAlignment; } internal AccessibleObject? AccessibilityObject { get { if (_accessibilityObject is not null) { return _accessibilityObject; } // Using an item from the group to obtain the ListView because the group might not yet be associated // with the ListView. See https://github.com/dotnet/winforms/issues/4019. var listView = ListView ?? (Items.Count > 0 ? Items[0].ListView : null); if (listView is not null) { _accessibilityObject = new ListViewGroupAccessibleObject(this, listView, owningGroupIsDefault: !listView.Groups.Contains(this)); } return _accessibilityObject; } } /// <summary> /// The text displayed in the group header. /// </summary> [AllowNull] [DefaultValue("")] [SRCategory(nameof(SR.CatAppearance))] public string Header { get => _header ?? string.Empty; set { if (_header == value) { return; } _header = value; UpdateListView(); } } /// <summary> /// The alignment of the group header. /// </summary> [DefaultValue(HorizontalAlignment.Left)] [SRCategory(nameof(SR.CatAppearance))] public HorizontalAlignment HeaderAlignment { get => _headerAlignment; set { SourceGenerated.EnumValidator.Validate(value); if (_headerAlignment == value) { return; } _headerAlignment = value; UpdateListView(); } } /// <summary> /// The text displayed in the group footer. /// </summary> [AllowNull] [DefaultValue("")] [SRCategory(nameof(SR.CatAppearance))] public string Footer { get => _footer ?? string.Empty; set { if (_footer == value) { return; } _footer = value; UpdateListView(); } } /// <summary> /// The alignment of the group footer. /// </summary> /// <value> /// One of the <see cref="HorizontalAlignment"/> values that specifies the alignment of the footer text. /// The default is <see cref="HorizontalAlignment.Left"/>. /// </value> /// <exception cref="InvalidEnumArgumentException"> /// The specified value when setting this property is not a valid <see cref="HorizontalAlignment"/> value. /// </exception> [DefaultValue(HorizontalAlignment.Left)] [SRCategory(nameof(SR.CatAppearance))] public HorizontalAlignment FooterAlignment { get => _footerAlignment; set { SourceGenerated.EnumValidator.Validate(value); if (_footerAlignment == value) { return; } _footerAlignment = value; UpdateListView(); } } internal bool Focused { get; set; } /// <summary> /// Controls which <see cref="ListViewGroupCollapsedState"/> the group will appear as. /// </summary> /// <value> /// One of the <see cref="ListViewGroupCollapsedState"/> values that specifies how the group is displayed. /// The default is <see cref="ListViewGroupCollapsedState.Default"/>. /// </value> /// <exception cref="InvalidEnumArgumentException"> /// The specified value when setting this property is not a valid <see cref="ListViewGroupCollapsedState"/> value. /// </exception> [DefaultValue(ListViewGroupCollapsedState.Default)] [SRCategory(nameof(SR.CatAppearance))] public ListViewGroupCollapsedState CollapsedState { get => _collapsedState; set { SourceGenerated.EnumValidator.Validate(value); if (_collapsedState == value) { return; } _collapsedState = value; UpdateListView(); } } /// <summary> /// The text displayed in the group subtitle. /// </summary> [AllowNull] [DefaultValue("")] [SRCategory(nameof(SR.CatAppearance))] public string Subtitle { get => _subtitle ?? string.Empty; set { if (_subtitle == value) { return; } _subtitle = value; UpdateListView(); } } /// <summary> /// The name of the task link displayed in the group header. /// </summary> [AllowNull] [DefaultValue("")] [SRCategory(nameof(SR.CatAppearance))] public string TaskLink { get => _taskLink ?? string.Empty; set { if (value == _taskLink) { return; } _taskLink = value; UpdateListView(); } } /// <summary> /// Gets or sets the index of the image that is displayed for the group. /// </summary> /// <value> /// The zero-based index of the image in the ImageList that is displayed for the group. The default is -1. /// </value> /// <exception cref="ArgumentOutOfRangeException"> /// The value specified is less than -1. /// </exception> [DefaultValue(-1)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [Editor($"System.Windows.Forms.Design.ImageIndexEditor, {Assemblies.SystemDesign}", typeof(Drawing.Design.UITypeEditor))] [Localizable(true)] [RefreshProperties(RefreshProperties.Repaint)] [RelatedImageList("ListView.GroupImageList")] [SRCategory(nameof(SR.CatBehavior))] [TypeConverter(typeof(NoneExcludedImageIndexConverter))] public int TitleImageIndex { get { ImageList? imageList = ImageIndexer.ImageList; return imageList is null || ImageIndexer.Index < imageList.Images.Count ? ImageIndexer.Index : imageList.Images.Count - 1; } set { ArgumentOutOfRangeException.ThrowIfLessThan(value, ImageList.Indexer.DefaultIndex); if (ImageIndexer.Index == value && value != ImageList.Indexer.DefaultIndex) { return; } ImageIndexer.Index = value; UpdateListView(); } } /// <summary> /// Gets or sets the key of the image that is displayed for the group. /// </summary> /// <value> /// The key for the image that is displayed for the group. /// </value> [DefaultValue("")] [TypeConverter(typeof(ImageKeyConverter))] [Editor($"System.Windows.Forms.Design.ImageIndexEditor, {Assemblies.SystemDesign}", typeof(Drawing.Design.UITypeEditor))] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [RefreshProperties(RefreshProperties.Repaint)] [RelatedImageList("ListView.GroupImageList")] [SRCategory(nameof(SR.CatBehavior))] [Localizable(true)] public string TitleImageKey { get => ImageIndexer.Key; set { if (ImageIndexer.Key == value && value != ImageList.Indexer.DefaultKey) { return; } ImageIndexer.Key = value; UpdateListView(); } } internal ListViewGroupImageIndexer ImageIndexer => _imageIndexer ??= new ListViewGroupImageIndexer(this); internal int ID { get; } /// <summary> /// The items that belong to this group. /// </summary> [Browsable(false)] public ListView.ListViewItemCollection Items => _items ??= new ListView.ListViewItemCollection(new ListViewGroupItemCollection(this)); [Browsable(false)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public ListView? ListView { get; internal set; } [SRCategory(nameof(SR.CatBehavior))] [SRDescription(nameof(SR.ListViewGroupNameDescr))] [Browsable(true)] [DefaultValue("")] public string? Name { get; set; } [SRCategory(nameof(SR.CatData))] [Localizable(false)] [Bindable(true)] [SRDescription(nameof(SR.ControlTagDescr))] [DefaultValue(null)] [TypeConverter(typeof(StringConverter))] public object? Tag { get; set; } private void Deserialize(SerializationInfo info) { int count = 0; foreach (SerializationEntry entry in info) { if (entry.Name == "Header") { Header = (string)entry.Value!; } else if (entry.Name == "HeaderAlignment") { HeaderAlignment = (HorizontalAlignment)entry.Value!; } else if (entry.Name == "Footer") { Footer = (string)entry.Value!; } else if (entry.Name == "FooterAlignment") { FooterAlignment = (HorizontalAlignment)entry.Value!; } else if (entry.Name == "Tag") { Tag = entry.Value; } else if (entry.Name == "ItemsCount") { count = (int)entry.Value!; } else if (entry.Name == "Name") { Name = (string)entry.Value!; } } if (count > 0) { ListViewItem[] items = new ListViewItem[count]; for (int i = 0; i < count; i++) { items[i] = (ListViewItem)info.GetValue($"Item{i}", typeof(ListViewItem))!; } Items.AddRange(items); } } internal ListViewGroupCollapsedState GetNativeCollapsedState() { if (ListView is null) { throw new InvalidOperationException(nameof(ListView)); } if (!ListView.GroupsEnabled) { return ListViewGroupCollapsedState.Default; } LIST_VIEW_GROUP_STATE_FLAGS state = (LIST_VIEW_GROUP_STATE_FLAGS)(uint)PInvokeCore.SendMessage( ListView, PInvoke.LVM_GETGROUPSTATE, (WPARAM)ID, (LPARAM)(uint)(LIST_VIEW_GROUP_STATE_FLAGS.LVGS_COLLAPSIBLE | LIST_VIEW_GROUP_STATE_FLAGS.LVGS_COLLAPSED)); if (!state.HasFlag(LIST_VIEW_GROUP_STATE_FLAGS.LVGS_COLLAPSIBLE)) { return ListViewGroupCollapsedState.Default; } return state.HasFlag(LIST_VIEW_GROUP_STATE_FLAGS.LVGS_COLLAPSED) ? ListViewGroupCollapsedState.Collapsed : ListViewGroupCollapsedState.Expanded; } internal void ReleaseUiaProvider() { if (OsVersion.IsWindows8OrGreater() && _accessibilityObject is ListViewGroupAccessibleObject accessibleObject) { PInvoke.UiaDisconnectProvider(accessibleObject, skipOSCheck: true); } _accessibilityObject = null; } // Should be used for the cases when sending the message `PInvoke.LVM_SETGROUPINFO` isn't required // (for example, collapsing/expanding groups with keyboard is performed inside the native control already, // so this message isn't needed) internal void SetCollapsedStateInternal(ListViewGroupCollapsedState state) { SourceGenerated.EnumValidator.Validate(state); if (_collapsedState == state) { return; } _collapsedState = state; } public override string ToString() => Header; private void UpdateListView() { if (ListView is not null && ListView.IsHandleCreated) { ListView.UpdateGroupNative(this); } } void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) { ArgumentNullException.ThrowIfNull(info); info.AddValue(nameof(Header), Header); info.AddValue(nameof(HeaderAlignment), HeaderAlignment); info.AddValue(nameof(Footer), Footer); info.AddValue(nameof(FooterAlignment), FooterAlignment); info.AddValue(nameof(Tag), Tag); if (!string.IsNullOrEmpty(Name)) { info.AddValue(nameof(Name), Name); } if (_items is not null && _items.Count > 0) { info.AddValue("ItemsCount", Items.Count); for (int i = 0; i < Items.Count; i++) { info.AddValue($"Item{i}", Items[i], typeof(ListViewItem)); } } } }