// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections; namespace System.Windows.Forms; public partial class ListView { // Overrides ListViewItemCollection methods and properties to automatically update // the native listview. // internal class ListViewNativeItemCollection : ListViewItemCollection.IInnerList { private readonly ListView _owner; public ListViewNativeItemCollection(ListView owner) { _owner = owner; } public int Count { get { _owner.ApplyUpdateCachedItems(); return _owner.VirtualMode ? _owner.VirtualListSize : _owner._itemCount; } } public bool OwnerIsVirtualListView => _owner.VirtualMode; public bool OwnerIsDesignMode => _owner.DesignMode; public ListViewItem this[int displayIndex] { get => GetItemByIndexInternal(displayIndex, throwInVirtualMode: true)!; set { _owner.ApplyUpdateCachedItems(); if (_owner.VirtualMode) { throw new InvalidOperationException(SR.ListViewCantModifyTheItemCollInAVirtualListView); } ArgumentOutOfRangeException.ThrowIfNegative(displayIndex); ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(displayIndex, _owner._itemCount); if (_owner.ExpectingMouseUp) { _owner.ItemCollectionChangedInMouseDown = true; } RemoveAt(displayIndex); Insert(displayIndex, value); } } public ListViewItem? GetItemByIndex(int index) => GetItemByIndexInternal(index, throwInVirtualMode: false); private ListViewItem? GetItemByIndexInternal(int index, [NotNullWhen(true)] bool throwInVirtualMode) { _owner.ApplyUpdateCachedItems(); if (_owner.VirtualMode) { // If we are showing virtual items, we need to get the item from the user. RetrieveVirtualItemEventArgs rVI = new(index); _owner.OnRetrieveVirtualItem(rVI); if (rVI.Item is null) { return !throwInVirtualMode ? null : throw new InvalidOperationException(SR.ListViewVirtualItemRequired); } rVI.Item.SetItemIndex(_owner, index); return rVI.Item; } else { ArgumentOutOfRangeException.ThrowIfNegative(index); ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, _owner._itemCount); if (_owner.IsHandleCreated && !_owner.ListViewHandleDestroyed) { _owner._listItemsTable.TryGetValue(DisplayIndexToID(index), out ListViewItem? item); return item!; } else { Debug.Assert(_owner._listViewItems is not null, "listItemsArray is null, but the handle isn't created"); return _owner._listViewItems[index]; } } } public ListViewItem Add(ListViewItem value) { if (_owner.VirtualMode) { throw new InvalidOperationException(SR.ListViewCantAddItemsToAVirtualListView); } else { Debug.Assert(!_owner.FlipViewToLargeIconAndSmallIcon || Count == 0, "the FlipView... bit is turned off after adding 1 item."); // PERF. // Get the Checked bit before adding it to the back end. // This saves a call into NativeListView to retrieve the real index. bool valueChecked = value.Checked; _owner.InsertItems(_owner._itemCount, [value], true); if (_owner.IsHandleCreated && !_owner.CheckBoxes && valueChecked) { _owner.UpdateSavedCheckedItems(value, true /*addItem*/); } if (_owner.ExpectingMouseUp) { _owner.ItemCollectionChangedInMouseDown = true; } return value; } } public void AddRange(params ListViewItem[] values) { ArgumentNullException.ThrowIfNull(values); if (_owner.VirtualMode) { throw new InvalidOperationException(SR.ListViewCantAddItemsToAVirtualListView); } IComparer? comparer = _owner._listItemSorter; _owner._listItemSorter = null; Debug.Assert(!_owner.FlipViewToLargeIconAndSmallIcon || Count == 0, "the FlipView... bit is turned off after adding 1 item."); bool[]? checkedValues = null; if (_owner.IsHandleCreated && !_owner.CheckBoxes) { // PERF. // Cache the Checked bit before adding the item to the list view. // This saves a bunch of calls to native list view when we want to UpdateSavedCheckedItems. checkedValues = new bool[values.Length]; for (int i = 0; i < values.Length; i++) { checkedValues[i] = values[i].Checked; } } try { _owner.BeginUpdate(); _owner.InsertItems(_owner._itemCount, values, true); if (_owner.IsHandleCreated && !_owner.CheckBoxes) { for (int i = 0; i < values.Length; i++) { if (checkedValues![i]) { _owner.UpdateSavedCheckedItems(values[i], true /*addItem*/); } } } } finally { _owner._listItemSorter = comparer; _owner.EndUpdate(); } if (_owner.ExpectingMouseUp) { _owner.ItemCollectionChangedInMouseDown = true; } if (comparer is not null || ((_owner.Sorting != SortOrder.None) && !_owner.VirtualMode)) { _owner.Sort(); } } private int DisplayIndexToID(int displayIndex) { Debug.Assert(!_owner.VirtualMode, "in virtual mode, this method does not make any sense"); if (_owner.IsHandleCreated && !_owner.ListViewHandleDestroyed) { // Obtain internal index of the item LVITEMW lvItem = new() { mask = LIST_VIEW_ITEM_FLAGS.LVIF_PARAM, iItem = displayIndex }; PInvokeCore.SendMessage(_owner, PInvoke.LVM_GETITEMW, (WPARAM)0, ref lvItem); return PARAM.ToInt(lvItem.lParam); } else { return this[displayIndex]._id; } } public void Clear() { if (_owner._itemCount <= 0) { return; } _owner.ApplyUpdateCachedItems(); if (_owner.IsHandleCreated && !_owner.ListViewHandleDestroyed) { // Walk the items to see which ones are selected. // We use the LVM_GETNEXTITEM message to see what the next selected item is // so we can avoid checking selection for each one. int count = _owner.Items.Count; int nextSelected = (int)PInvokeCore.SendMessage( _owner, PInvoke.LVM_GETNEXTITEM, (WPARAM)(-1), (LPARAM)PInvoke.LVNI_SELECTED); for (int i = 0; i < count; i++) { ListViewItem item = _owner.Items[i]; Debug.Assert(item is not null, $"Failed to get item at index {i}"); if (item is null) { continue; } // If it's the one we're looking for, ask for the next one. if (i == nextSelected) { item.StateSelected = true; nextSelected = (int)PInvokeCore.SendMessage( _owner, PInvoke.LVM_GETNEXTITEM, (WPARAM)nextSelected, (LPARAM)PInvoke.LVNI_SELECTED); } else { // Otherwise it's false. item.StateSelected = false; } item.UnHost(i, false); } Debug.Assert(_owner._listViewItems is null, "listItemsArray not null, even though handle created"); PInvokeCore.SendMessage(_owner, PInvoke.LVM_DELETEALLITEMS); // There's a problem in the list view that if it's in small icon, it won't pick up the small icon // sizes until it changes from large icon, so we flip it twice here... if (_owner.View == View.SmallIcon) { if (Application.ComCtlSupportsVisualStyles) { _owner.FlipViewToLargeIconAndSmallIcon = true; } else { Debug.Assert(!_owner.FlipViewToLargeIconAndSmallIcon, "we only set this when comctl 6.0 is loaded"); _owner.View = View.LargeIcon; _owner.View = View.SmallIcon; } } } else { int count = _owner.Items.Count; for (int i = 0; i < count; i++) { ListViewItem item = _owner.Items[i]; item?.UnHost(i, true); } Debug.Assert(_owner._listViewItems is not null, "listItemsArray is null, but the handle isn't created"); _owner._listViewItems.Clear(); } _owner._listItemsTable.Clear(); if (_owner.IsHandleCreated && !_owner.CheckBoxes) { _owner._savedCheckedItems = null; } _owner._itemCount = 0; if (_owner.ExpectingMouseUp) { _owner.ItemCollectionChangedInMouseDown = true; } } public bool Contains(ListViewItem item) { _owner.ApplyUpdateCachedItems(); if (_owner.IsHandleCreated && !_owner.ListViewHandleDestroyed) { return _owner._listItemsTable.TryGetValue(item._id, out ListViewItem? itemOut) && itemOut == item; } else { Debug.Assert(_owner._listViewItems is not null, "listItemsArray is null, but the handle isn't created"); return _owner._listViewItems.Contains(item); } } public ListViewItem Insert(int index, ListViewItem item) { int count; if (_owner.VirtualMode) { count = Count; } else { count = _owner._itemCount; } ArgumentOutOfRangeException.ThrowIfNegative(index); ArgumentOutOfRangeException.ThrowIfGreaterThan(index, count); if (_owner.VirtualMode) { throw new InvalidOperationException(SR.ListViewCantAddItemsToAVirtualListView); } Debug.Assert(!_owner.FlipViewToLargeIconAndSmallIcon || Count == 0, "the FlipView... bit is turned off after adding 1 item."); if (index < count) { // if we're not inserting at the end, force the add. _owner.ApplyUpdateCachedItems(); } _owner.InsertItems(index, [item], true); if (_owner.IsHandleCreated && !_owner.CheckBoxes && item.Checked) { _owner.UpdateSavedCheckedItems(item, true /*addItem*/); } if (_owner.ExpectingMouseUp) { _owner.ItemCollectionChangedInMouseDown = true; } return item; } public int IndexOf(ListViewItem item) { Debug.Assert(!_owner.VirtualMode, "in virtual mode, this function does not make any sense"); for (int i = 0; i < Count; i++) { if (item == this[i]) { return i; } } return -1; } public void Remove(ListViewItem item) { int index = _owner.VirtualMode ? Count - 1 : IndexOf(item); Debug.Assert(!_owner.FlipViewToLargeIconAndSmallIcon || Count == 0, "the FlipView... bit is turned off after adding 1 item."); if (_owner.VirtualMode) { throw new InvalidOperationException(SR.ListViewCantRemoveItemsFromAVirtualListView); } if (index != -1) { RemoveAt(index); } } public void RemoveAt(int index) { if (_owner.VirtualMode) { throw new InvalidOperationException(SR.ListViewCantRemoveItemsFromAVirtualListView); } ArgumentOutOfRangeException.ThrowIfNegative(index); ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, _owner._itemCount); Debug.Assert(!_owner.FlipViewToLargeIconAndSmallIcon || Count == 0, "the FlipView... bit is turned off after adding 1 item."); if (_owner.IsHandleCreated && !_owner.CheckBoxes && this[index].Checked) { _owner.UpdateSavedCheckedItems(this[index], addItem: false); } _owner.ApplyUpdateCachedItems(); int itemID = DisplayIndexToID(index); this[index].Focused = false; this[index].UnHost(true); if (_owner.IsHandleCreated) { Debug.Assert(_owner._listViewItems is null, "listItemsArray not null, even though handle created"); if (PInvokeCore.SendMessage(_owner, PInvoke.LVM_DELETEITEM, (WPARAM)index) == 0) { throw new ArgumentOutOfRangeException(nameof(index), index, string.Format(SR.InvalidArgument, nameof(index), index)); } } else { Debug.Assert(_owner._listViewItems is not null, "listItemsArray is null, but the handle isn't created"); _owner._listViewItems.RemoveAt(index); } _owner._itemCount--; _owner._listItemsTable.Remove(itemID); if (_owner.ExpectingMouseUp) { _owner.ItemCollectionChangedInMouseDown = true; } } public void CopyTo(Array dest, int index) { if (_owner._itemCount > 0) { for (int displayIndex = 0; displayIndex < Count; ++displayIndex) { dest.SetValue(this[displayIndex], index++); } } } public IEnumerator GetEnumerator() { ListViewItem[] items = new ListViewItem[_owner._itemCount]; CopyTo(items, 0); return items.GetEnumerator(); } } }