File: winforms\Managed\System\WinForms\ListViewGroup.cs
Project: ndp\fx\src\System.Windows.Forms.csproj (System.Windows.Forms)
//-----------------------------------------------------------------------------
// <copyright file="ListViewGroup.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>                                                                
//------------------------------------------------------------------------------
 
 
using System.Collections;
using System.ComponentModel;
using System.Runtime.Serialization;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Win32;
using System.Globalization;
using System.Security.Permissions;
 
namespace System.Windows.Forms {
                               
    /// <include file='doc\ListViewGroup.uex' path='docs/doc[@for="ListViewGroup"]/*' />
    /// <devdoc>
    ///    <para>
    ///         Represents a group within a ListView.
    ///
    ///    </para>
    /// </devdoc>
    [
    TypeConverterAttribute(typeof(ListViewGroupConverter)),
    ToolboxItem(false),
    DesignTimeVisible(false),
    DefaultProperty("Header"),
    Serializable
    ]
    public sealed class ListViewGroup : ISerializable {
 
        private ListView listView;              
        private int id;
 
        private string header;
        private HorizontalAlignment headerAlignment = HorizontalAlignment.Left;
 
        private ListView.ListViewItemCollection items;        
 
        private static int nextID;
 
        private static int nextHeader = 1;
 
        private object userData;
 
        private string name;
 
        /// <include file='doc\ListViewGroup.uex' path='docs/doc[@for="ListViewGroup.ListViewGroup"]/*' />
        /// <devdoc>
        ///     Creates a ListViewGroup.
        /// </devdoc>
        public ListViewGroup() : this(SR.GetString(SR.ListViewGroupDefaultHeader, nextHeader++))
        {
        }
 
        /// <include file='doc\ListViewGroup.uex' path='docs/doc[@for="ListViewGroup.ListViewGroup1"]/*' />
        /// <devdoc>
        ///     Creates a ListViewItem object from an Stream.
        /// </devdoc>
        private ListViewGroup(SerializationInfo info, StreamingContext context) : this() {
            Deserialize(info, context);
        }
 
        /// <include file='doc\ListViewGroup.uex' path='docs/doc[@for="ListViewGroup.ListViewGroup2"]/*' />
        /// <devdoc>
        ///     Creates a ListViewItem object from a Key and a Name
        /// </devdoc>
        public ListViewGroup(string key, string headerText) : this() {
            this.name = key;
            this.header = headerText;
        }
 
        /// <include file='doc\ListViewGroup.uex' path='docs/doc[@for="ListViewGroup.ListViewGroup2"]/*' />
        /// <devdoc>
        ///     Creates a ListViewGroup.
        /// </devdoc>
        public ListViewGroup(string header) 
        {
            this.header = header;
            this.id = nextID++;
        }
 
        /// <include file='doc\ListViewGroup.uex' path='docs/doc[@for="ListViewGroup.ListViewGroup3"]/*' />
        /// <devdoc>
        ///     Creates a ListViewGroup.
        /// </devdoc>
    	public ListViewGroup(string header, HorizontalAlignment headerAlignment) : this(header) {        
            this.headerAlignment = headerAlignment;
        }    	                
 
        /// <include file='doc\ListViewGroup.uex' path='docs/doc[@for="ListViewGroup.Header"]/*' />
        /// <devdoc>
        ///     The text displayed in the group header.
        /// </devdoc>
        [SRCategory(SR.CatAppearance)]
        public string Header {
            get
            {
                return header == null ? "" : header;
            }
            set
            {
                if (header != value) {
                    header = value;
 
                    if (listView != null) {
                        listView.RecreateHandleInternal();
                    }
                }
            }
        }
 
        /// <include file='doc\ListViewGroup.uex' path='docs/doc[@for="ListViewGroup.HeaderAlignment"]/*' />
        /// <devdoc>
        ///     The alignment of the group header.
        /// </devdoc>
        [
            DefaultValue(HorizontalAlignment.Left),
            SRCategory(SR.CatAppearance)
        ]
        public HorizontalAlignment HeaderAlignment {
            get
            {
                return headerAlignment;
            }
            set
            {
                // VSWhidbey 144699: Verify that the value is within the enum's range.
                //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));
                }
                if (headerAlignment != value) {
                    headerAlignment = value;
                    UpdateListView();
                }
            }
        }
 
        internal int ID {
            get
            {
                return id;
            }
        }
 
        /// <include file='doc\ListViewGroup.uex' path='docs/doc[@for="ListViewGroup.Items"]/*' />
        /// <devdoc>
        ///     The items that belong to this group.
        /// </devdoc>
        [Browsable(false)]
        public ListView.ListViewItemCollection Items {
            get
            {
                if (items == null) {
                    items = new ListView.ListViewItemCollection(new ListViewGroupItemCollection(this));
                }
                return items;
            }
        }
 
        [
            Browsable(false),
            DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
        ]
        public ListView ListView
        {
            get
            {
                return listView;
            }
        }        
 
        internal ListView ListViewInternal
        {
            get
            {
                return listView;
            }
            set
            {
                if (listView != value)
                {
                    listView = value;
                }
            }
        }
 
        /// <include file='doc\ListViewGroup.uex' path='docs/doc[@for="ListViewGroup.Name"]/*' />
        [
        SRCategory(SR.CatBehavior),
        SRDescription(SR.ListViewGroupNameDescr),
        Browsable(true),
        DefaultValue("")
        ]
        public string Name
        {
            get
            {
                return this.name;
            }
            set
            {
                this.name = value;
            }
        }
 
 
        /// <include file='doc\ListViewGroup.uex' path='docs/doc[@for="ListViewGroup.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;
            }
        }
 
 
        /// <include file='doc\ListViewGroup.uex' path='docs/doc[@for="ListViewGroup.Deserialize"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        private void Deserialize(SerializationInfo info, StreamingContext context) {
 
            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 == "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++) {
                    // SEC 
                    items[i] = (ListViewItem)info.GetValue("Item" + i, typeof(ListViewItem));
                }
                Items.AddRange(items);
            }
        }
 
        /// <include file='doc\ListViewGroup.uex' path='docs/doc[@for="ListViewGroup.ToString"]/*' />
        public override string ToString() {
            return Header;
        }
 
        private void UpdateListView() {
            if (listView != null && listView.IsHandleCreated) {
                listView.UpdateGroupNative(this);                
            }
        }                
 
        /// <include file='doc\ListViewGroup.uex' path='docs/doc[@for="ListViewGroup.GetObjectData"]/*' />
        /// <devdoc>
        ///     To be supplied.
        /// </devdoc>        
        [SecurityPermissionAttribute(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.SerializationFormatter)] 
        void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) {
            info.AddValue("Header", this.Header);
            info.AddValue("HeaderAlignment", this.HeaderAlignment);
            info.AddValue("Tag", this.Tag);
            if (!String.IsNullOrEmpty(this.Name)) {
                info.AddValue("Name", this.Name);
            }
            if (items != null && items.Count > 0) {
                info.AddValue("ItemsCount", this.Items.Count);
                for (int i = 0; i < Items.Count; i ++) {
                    info.AddValue("Item" + i.ToString(CultureInfo.InvariantCulture), Items[i], typeof(ListViewItem));
                }
            }
        }
    }
        
    /// <include file='doc\ListViewGroup.uex' path='docs/doc[@for="ListViewGroup.ListViewGroupCollection"]/*' />
    /// <devdoc>
    ///    <para>
    ///         A collection of listview groups.
    ///    </para>
    /// </devdoc>
    [ListBindable(false)]
    public class ListViewGroupCollection : IList {
 
        private ListView listView;
 
        private ArrayList list;
 
        internal ListViewGroupCollection(ListView listView) {
            this.listView = listView;
        }
 
        /// <include file='doc\ListViewGroup.uex' path='docs/doc[@for="ListViewGroup.ListViewGroupCollection.Count"]/*' />
        /// <devdoc>
        ///     To be supplied.
        /// </devdoc>
        public int Count
        {
            get
            {
                return this.List.Count;
            }
        }
 
        /// <include file='doc\ListViewGroup.uex' path='docs/doc[@for="ListViewGroup.ListViewGroupCollection.ICollection.SyncRoot"]/*' />
        /// <internalonly/>
        object ICollection.SyncRoot {
            get {
                return this;
            }
        }
 
        /// <include file='doc\ListViewGroup.uex' path='docs/doc[@for="ListViewGroup.ListViewGroupCollection.ICollection.IsSynchronized"]/*' />
        /// <internalonly/>
        bool ICollection.IsSynchronized {
            get {
                return true;
            }
        }
        
        /// <include file='doc\ListViewGroup.uex' path='docs/doc[@for="ListViewGroup.ListViewGroupCollection.IList.IsFixedSize"]/*' />
        /// <internalonly/>
        bool IList.IsFixedSize {
            get {
                return false;
            }
        }
        
        /// <include file='doc\ListViewGroup.uex' path='docs/doc[@for="ListViewGroup.ListViewGroupCollection.IList.IsReadOnly"]/*' />
        /// <internalonly/>
        bool IList.IsReadOnly {
            get {
                return false;
            }
        }
 
        private ArrayList List
        {
            get
            {
                if (list == null) {
                    list = new ArrayList();
                }
                return list;
            }
        }
                
        /// <include file='doc\ListViewGroup.uex' path='docs/doc[@for="ListViewGroup.ListViewGroupCollection.this"]/*' />
        /// <devdoc>
        ///     To be supplied.
        /// </devdoc>
        public ListViewGroup this[int index] {
            get
            {
                return (ListViewGroup)this.List[index];
            }
            set
            {
                if (this.List.Contains(value)) {
                    return;
                }
                this.List[index] = value;
            }
        }
                
        /// <include file='doc\ListViewGroup.uex' path='docs/doc[@for="ListViewGroup.ListViewGroupCollection.this2"]/*' />
        /// <devdoc>
        ///     To be supplied.
        /// </devdoc>
        public ListViewGroup this[string key] {
            get {
 
                if (list == null) {
                    return null;
                }
 
                for (int i = 0; i < list.Count; i ++) {
                    if (String.Compare(key, this[i].Name, false /*case insensitive*/, System.Globalization.CultureInfo.CurrentCulture) == 0) {
                        return this[i];
                    }
                }
 
                return null;
            }
            set {
                int index = -1;
 
                if (this.list == null) {
                    return; // nothing to do
                }
 
                for (int i = 0; i < this.list.Count; i ++) {
                    if (String.Compare(key, this[i].Name, false /*case insensitive*/, System.Globalization.CultureInfo.CurrentCulture) ==0) {
                        index = i;
                        break;
                    }
                }
 
                if (index != -1) {
                    this.list[index] = value;
                }
            }
        }
 
        /// <include file='doc\ListViewGroup.uex' path='docs/doc[@for="ListViewGroup.ListViewGroupCollection.IList.this"]/*' />
        /// <internalonly/>
        object IList.this[int index] {
            get
            {
                return this[index];
            }
            set
            {
                if (value is ListViewGroup) {
                    this[index] = (ListViewGroup)value;
                }
            }
        }
        
        /// <include file='doc\ListViewGroup.uex' path='docs/doc[@for="ListViewGroup.ListViewGroupCollection.Add"]/*' />
        /// <devdoc>
        ///     To be supplied.
        /// </devdoc>
        public int Add(ListViewGroup group)
        {
            if (this.Contains(group)) {
                return -1;
            }
 
            // Will throw InvalidOperationException if group contains items which are parented by another listView.
            CheckListViewItems(group);
            group.ListViewInternal = this.listView;
            int index = this.List.Add(group);
            if (listView.IsHandleCreated) {
                listView.InsertGroupInListView(this.List.Count, group);
                MoveGroupItems(group);
            }            
            return index;
        }        
        
        /// <include file='doc\ListViewGroup.uex' path='docs/doc[@for="ListViewGroup.ListViewGroupCollection.Add1"]/*' />
        /// <devdoc>
        ///     To be supplied.
        /// </devdoc>
        public ListViewGroup Add(string key, string headerText)
        {
            ListViewGroup group = new ListViewGroup(key, headerText);
            this.Add(group);
            return group;
        }
 
        /// <include file='doc\ListViewGroup.uex' path='docs/doc[@for="ListViewGroup.ListViewGroupCollection.IList.Add"]/*' />
        /// <internalonly/>
        [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")]
        [
            SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters") // "value" is the name of the param passed in.
                                                                                                        // So we don't have to localize it.
        ]
        int IList.Add(object value) {
            if (value is ListViewGroup) {
                return Add((ListViewGroup)value);
            }
            throw new ArgumentException("value");
        }
                
        /// <include file='doc\ListViewGroup.uex' path='docs/doc[@for="ListViewGroup.ListViewGroupCollection.AddRange"]/*' />
        /// <devdoc>
        ///     To be supplied.
        /// </devdoc>
        public void AddRange(ListViewGroup[] groups)
        {
            for(int i=0; i < groups.Length; i++) {
                Add(groups[i]);
            }
        }
 
        /// <include file='doc\ListViewGroup.uex' path='docs/doc[@for="ListViewGroup.ListViewGroupCollection.AddRange2"]/*' />
        /// <devdoc>
        ///     To be supplied.
        /// </devdoc>
        public void AddRange(ListViewGroupCollection groups)
        {
            for(int i=0; i < groups.Count; i++) {
                Add(groups[i]);
            }
        }
 
        private void CheckListViewItems(ListViewGroup group) {
            for (int i = 0; i < group.Items.Count; i ++) {
                ListViewItem item = group.Items[i];
                if (item.ListView != null && item.ListView != this.listView) {
                    throw new ArgumentException(SR.GetString(SR.OnlyOneControl, item.Text));
                }
            }
        }
 
        /// <include file='doc\ListViewGroup.uex' path='docs/doc[@for="ListViewGroup.ListViewGroupCollection.Clear"]/*' />
        /// <devdoc>
        ///     To be supplied.
        /// </devdoc>
        public void Clear() {
            if (listView.IsHandleCreated) {
                for(int i=0; i < Count; i++) {
                    listView.RemoveGroupFromListView(this[i]);
                }
            }
            // Dissociate groups from the ListView
            //
            for(int i=0; i < Count; i++) {
                this[i].ListViewInternal = null;
            }
            this.List.Clear();
 
            // we have to tell the listView that there are no more groups
            // so the list view knows to remove items from the default group
            this.listView.UpdateGroupView();
        }
        
        /// <include file='doc\ListViewGroup.uex' path='docs/doc[@for="ListViewGroup.ListViewGroupCollection.Contains"]/*' />
        /// <devdoc>
        ///     To be supplied.
        /// </devdoc>
        public bool Contains(ListViewGroup value) {
            return this.List.Contains(value);
        }
 
        /// <include file='doc\ListViewGroup.uex' path='docs/doc[@for="ListViewGroup.ListViewGroupCollection.IList.Contains"]/*' />
        /// <internalonly/>
        bool IList.Contains(object value)
        {
            if (value is ListViewGroup) {
                return Contains((ListViewGroup)value);
            }
            return false;
        }
 
        /// <include file='doc\ListViewGroup.uex' path='docs/doc[@for="ListViewGroup.ListViewGroupCollection.CopyTo"]/*' />
        /// <devdoc>
        ///     To be supplied.
        /// </devdoc>
        public void CopyTo(Array array, int index) {
            this.List.CopyTo(array, index);
        }
 
        /// <include file='doc\ListViewGroup.uex' path='docs/doc[@for="ListViewGroup.ListViewGroupCollection.GetEnumerator"]/*' />
        /// <devdoc>
        ///     To be supplied.
        /// </devdoc>
        public IEnumerator GetEnumerator()
        {
            return this.List.GetEnumerator();
        }
 
        /// <include file='doc\ListViewGroup.uex' path='docs/doc[@for="ListViewGroup.ListViewGroupCollection.IndexOf"]/*' />
        /// <devdoc>
        ///     To be supplied.
        /// </devdoc>
        public int IndexOf(ListViewGroup value) {
            return this.List.IndexOf(value);
        }
 
        /// <include file='doc\ListViewGroup.uex' path='docs/doc[@for="ListViewGroup.ListViewGroupCollection.IList.IndexOf"]/*' />
        /// <internalonly/>
        int IList.IndexOf(object value) {
            if (value is ListViewGroup) {
                return IndexOf((ListViewGroup)value);
            }
            return -1;
        }
 
        /// <include file='doc\ListViewGroup.uex' path='docs/doc[@for="ListViewGroup.ListViewGroupCollection.Insert"]/*' />
        /// <devdoc>
        ///     To be supplied.
        /// </devdoc>
        public void Insert(int index, ListViewGroup group) {
            if (Contains(group)) {
                return;
            }
            group.ListViewInternal = this.listView;
            this.List.Insert(index, group);
            if (listView.IsHandleCreated) {
                listView.InsertGroupInListView(index, group);
                MoveGroupItems(group);
            }            
        }       
 
        /// <include file='doc\ListViewGroup.uex' path='docs/doc[@for="ListViewGroup.ListViewGroupCollection.IList.Insert"]/*' />
        /// <internalonly/>
        void IList.Insert(int index, object value) {
            if (value is ListViewGroup) {
                Insert(index, (ListViewGroup)value);
            }            
        }
 
        private void MoveGroupItems(ListViewGroup group) {
            Debug.Assert(listView.IsHandleCreated, "MoveGroupItems pre-condition: listView handle must be created");
 
            foreach(ListViewItem item in group.Items) {
                if (item.ListView == this.listView) {
                    item.UpdateStateToListView(item.Index);                    
                }
            }
        }
       
        /// <include file='doc\ListViewGroup.uex' path='docs/doc[@for="ListViewGroup.ListViewGroupCollection.Remove"]/*' />
        /// <devdoc>
        ///     To be supplied.
        /// </devdoc>
        public void Remove(ListViewGroup group) {
            group.ListViewInternal = null;            
            this.List.Remove(group);
 
            if (listView.IsHandleCreated) {
                listView.RemoveGroupFromListView(group);
            }
        }
 
        /// <include file='doc\ListViewGroup.uex' path='docs/doc[@for="ListViewGroup.ListViewGroupCollection.IList.Remove"]/*' />
        /// <internalonly/>
        void IList.Remove(object value)
        {
            if (value is ListViewGroup) {
                Remove((ListViewGroup)value);
            }
        }
 
        /// <include file='doc\ListViewGroup.uex' path='docs/doc[@for="ListViewGroup.ListViewGroupCollection.RemoveAt"]/*' />
        /// <devdoc>
        ///     To be supplied.
        /// </devdoc>
        public void RemoveAt(int index) {
            Remove(this[index]);
        }        
    }        
}