using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Windows.Forms;
using Vanara.PInvoke;
using static Vanara.PInvoke.ComCtl32;
using static Vanara.PInvoke.User32;
namespace Vanara.Extensions;
/// Extension methods for .
public static partial class ListViewExtension
{
private static PropertyInfo? GroupIdProperty;
/// Gets the items in a list view as a sequence.
/// The items.
///
public static IEnumerable AsEnumerable(this ListView.ListViewItemCollection items)
{
foreach (ListViewItem item in items.Cast())
yield return item;
}
/// Gets the groups in a list view as a sequence.
/// The items.
///
public static IEnumerable AsEnumerable(this ListViewGroupCollection items)
{
foreach (ListViewGroup item in items.Cast())
yield return item;
}
/// Performs an action on every item in a list view.
/// The items.
/// The action.
public static void ForEach(this ListView.ListViewItemCollection items, Action action)
{
foreach (ListViewItem item in items.Cast()) action(item);
}
/// Gets the collapsed state of the list view group.
/// The list view group.
///
///
public static bool GetCollapsed(this ListViewGroup group)
{
if (group == null)
throw new ArgumentNullException();
return GetState(group, ListViewGroupState.LVGS_NORMAL | ListViewGroupState.LVGS_COLLAPSED);
}
/// Determines if the list view group is collapsible.
/// The group.
///
///
public static bool GetCollapsible(this ListViewGroup group)
{
if (group == null)
throw new ArgumentNullException();
return IsWinVista() && GetState(group, ListViewGroupState.LVGS_COLLAPSIBLE);
}
/// Gets the handle of the list view header.
/// The list view.
///
public static IntPtr GetHeaderHandle(this ListView listView)
{
if (listView.IsHandleCreated)
return SendMessage(listView.Handle, (uint)ListViewMessage.LVM_GETHEADER, IntPtr.Zero, IntPtr.Zero);
return IntPtr.Zero;
}
/// Invalidates the header.
/// The list view.
public static void InvalidateHeader(this ListView listView)
{
if (listView.IsHandleCreated && listView.View == View.Details)
InvalidateRect(GetHeaderHandle(listView), null, true);
}
/// Sets the collapsed state of the list view group.
/// The group.
/// if set to [value].
///
public static void SetCollapsed(this ListViewGroup group, bool value)
{
if (group == null)
throw new ArgumentNullException();
SetState(group, ListViewGroupState.LVGS_NORMAL | ListViewGroupState.LVGS_COLLAPSED, value);
}
/// Sets a value that determines if the list view group is collapsible.
/// The group.
/// if set to [value].
///
///
public static void SetCollapsible(this ListViewGroup group, bool value)
{
if (group == null)
throw new ArgumentNullException();
if (!IsWinVista())
throw new PlatformNotSupportedException();
SetState(group, ListViewGroupState.LVGS_COLLAPSIBLE, value);
}
/// Sets a list view header column to display as a split button or not.
/// The list view.
/// Index of the column.
/// if set to show the column header as a split button.
/// columnIndex
public static void SetColumnDropDown(this ListView listView, int columnIndex, bool enable)
{
if (columnIndex < 0 || columnIndex >= 0 && listView.Columns == null || columnIndex >= listView.Columns.Count)
throw new ArgumentOutOfRangeException(nameof(columnIndex));
if (listView.IsHandleCreated)
{
var lvc = new LVCOLUMN(ListViewColumMask.LVCF_FMT);
var hr = listView.Handle;
SendLVMsg(hr, ListViewMessage.LVM_GETCOLUMN, columnIndex, lvc);
lvc.Format = lvc.Format.SetFlags(ListViewColumnFormat.LVCFMT_SPLITBUTTON, enable);
SendLVMsg(hr, ListViewMessage.LVM_SETCOLUMN, columnIndex, lvc);
listView.InvalidateHeader();
}
}
/// Enables or disables the Explorer theme on a list view.
/// The list view.
/// if set to [on].
public static void SetExplorerTheme(this ListView listView, bool on = true)
{
if (IsWinVista())
{
listView.SetWindowTheme(on ? "explorer" : null);
if (!on) return;
SendMessage(listView.Handle, (uint)ListViewMessage.LVM_SETEXTENDEDLISTVIEWSTYLE, (IntPtr)ListViewStyleEx.LVS_EX_DOUBLEBUFFER, (IntPtr)ListViewStyleEx.LVS_EX_DOUBLEBUFFER);
}
}
/// Sets the footer text and alignment for a list view group.
/// The group.
/// The footer text.
/// The footer alignment.
public static void SetFooter(this ListViewGroup group, string? footer = null, HorizontalAlignment footerAlignment = HorizontalAlignment.Left)
{
var groupId = GetGroupId(group);
if (groupId >= 0 && group.ListView != null)
{
using var lvgroup = new LVGROUP { Footer = footer, Alignment = MakeAlignment(group.HeaderAlignment, footerAlignment) };
SendLVMsg(group.ListView.Handle, ListViewMessage.LVM_SETGROUPINFO, groupId, lvgroup);
}
}
/// Sets the image list to use for a list view group.
/// The group.
/// The image list.
///
public static void SetGroupImageList(this ListViewGroup group, ImageList imageList)
{
if (group.ListView is null || !group.ListView.IsHandleCreated)
throw new InvalidOperationException();
var lparam = imageList?.Handle ?? IntPtr.Zero;
SendMessage(group.ListView.Handle, (uint)ListViewMessage.LVM_SETIMAGELIST, (IntPtr)3, lparam);
}
/// Sets the image index from the image list to use for a list view group.
/// The group.
/// Index of the title image.
/// The description top.
/// The description bottom.
public static void SetImage(this ListViewGroup group, int titleImageIndex, string? descriptionTop = null, string? descriptionBottom = null)
{
var groupId = GetGroupId(group);
if (groupId >= 0 && group.ListView != null)
{
using var lvgroup = new LVGROUP { TitleImageIndex = titleImageIndex };
if (descriptionBottom != null)
lvgroup.DescriptionBottom = descriptionBottom;
if (descriptionTop != null)
lvgroup.DescriptionTop = descriptionTop;
SendLVMsg(group.ListView.Handle, ListViewMessage.LVM_SETGROUPINFO, groupId, lvgroup);
}
}
/// Sets the overlay image for an item in a list view.
/// The list view item.
/// Index of the image.
/// imageIndex
/// lvi - ListViewItem must be attached to a valid ListView.
///
public static void SetOverlayImage(this ListViewItem lvi, int imageIndex)
{
if (imageIndex is < 1 or > 15)
throw new ArgumentOutOfRangeException(nameof(imageIndex));
if (lvi.ListView == null)
throw new ArgumentNullException(nameof(lvi), @"ListViewItem must be attached to a valid ListView.");
var nItem = new LVITEM(lvi.Index) { OverlayImageIndex = (uint)imageIndex };
if (SendLVMsg(lvi.ListView.Handle, ListViewMessage.LVM_SETITEM, 0, nItem).ToInt32() == 0)
throw new Win32Exception();
}
/// Sets the sort icon displayed on a list view header column.
/// The list view.
/// Index of the column.
/// The sort order icon to display.
public static void SetSortIcon(this ListView listView, int columnIndex, SortOrder order)
{
var columnHeader = (HWND)SendMessage(listView.Handle, (uint)ListViewMessage.LVM_GETHEADER);
for (var columnNumber = 0; columnNumber <= listView.Columns.Count - 1; columnNumber++)
{
// Get current ListView column info
var lvcol = new LVCOLUMN(ListViewColumMask.LVCF_FMT);
SendLVMsg(listView.Handle, ListViewMessage.LVM_GETCOLUMN, columnNumber, lvcol);
// Get current header info
var hditem = new HDITEM(HeaderItemMask.HDI_FORMAT | HeaderItemMask.HDI_DI_SETITEM);
SendHdrMsg(columnHeader, HeaderMessage.HDM_GETITEM, columnNumber, hditem);
// Update header with column info
hditem.Format |= (HeaderItemFormat)((uint)lvcol.Format & 0x1001803);
if ((lvcol.Format & ListViewColumnFormat.LVCFMT_NO_TITLE) == 0)
hditem.ShowText = true;
// Set header image info
if (order != SortOrder.None && columnNumber == columnIndex)
hditem.ImageDisplay = (order == SortOrder.Descending) ? HeaderItemImageDisplay.DownArrow : HeaderItemImageDisplay.UpArrow;
else
hditem.ImageDisplay = HeaderItemImageDisplay.None;
// Update header
SendHdrMsg(columnHeader, HeaderMessage.HDM_SETITEM, columnNumber, hditem);
}
}
/// Sets the task link for a list view group.
/// The group.
/// The task link.
public static void SetTask(this ListViewGroup group, string taskLink)
{
var groupId = GetGroupId(group);
if (groupId >= 0 && group.ListView != null)
{
using var lvgroup = new LVGROUP { TaskLink = taskLink };
SendLVMsg(group.ListView.Handle, ListViewMessage.LVM_SETGROUPINFO, groupId, lvgroup);
}
}
private static void ApplyGroupingSet(this ListView listView, ListViewGroupingSet set, string? nonMatchingGroupName = "Other") where T : class
{
var vm = listView.VirtualMode;
listView.BeginUpdate();
if (vm)
listView.VirtualMode = false;
listView.Groups.Clear();
listView.Groups.AddRange(set.Groups);
var other = new List();
foreach (ListViewItem i in listView.Items.Cast())
{
var found = false;
for (var r = 0; r < set.Conditions.Length; r++)
{
if (set.Conditions[r](i))
{
listView.Groups[r].Items.Add(i);
found = true;
break;
}
}
if (!found)
other.Add(i);
}
if (other.Count > 0 && !string.IsNullOrEmpty(nonMatchingGroupName))
{
var og = listView.Groups.Add(nonMatchingGroupName, nonMatchingGroupName);
og.Items.AddRange(other.ToArray());
}
if (vm)
listView.VirtualMode = true;
listView.ShowGroups = true;
listView.EndUpdate();
}
private static int GetGroupId(ListViewGroup group)
{
GroupIdProperty ??= typeof(ListViewGroup).GetProperty("ID", BindingFlags.NonPublic | BindingFlags.Instance);
return (int?)GroupIdProperty?.GetValue(group, null) ?? -1;
}
private static bool GetState(ListViewGroup group, ListViewGroupState state)
{
var groupId = GetGroupId(group);
if (groupId < 0 || group.ListView is null)
return false;
return (SendMessage(group.ListView.Handle, (uint)ListViewMessage.LVM_GETGROUPSTATE, (IntPtr)groupId, new IntPtr((int)state)).ToInt32() & (int)state) != 0;
}
private static bool IsWinVista() => Environment.OSVersion.Version.Major >= 6;
private static ListViewGroupAlignment MakeAlignment(HorizontalAlignment hdr, HorizontalAlignment ftr)
{
var h = hdr == HorizontalAlignment.Left ? ListViewGroupAlignment.LVGA_HEADER_LEFT : (hdr == HorizontalAlignment.Center ? ListViewGroupAlignment.LVGA_HEADER_CENTER : ListViewGroupAlignment.LVGA_HEADER_RIGHT);
return h | (ftr == HorizontalAlignment.Left ? ListViewGroupAlignment.LVGA_FOOTER_LEFT : (ftr == HorizontalAlignment.Center ? ListViewGroupAlignment.LVGA_FOOTER_CENTER : ListViewGroupAlignment.LVGA_FOOTER_RIGHT));
}
private static IntPtr SendHdrMsg(HWND hwnd, HeaderMessage msg, int wParam, TLP lParam) where TLP : class
{
using var lpin = new PinnedObject(lParam);
return SendMessage(hwnd, (uint)msg, (IntPtr)wParam, lpin);
}
private static IntPtr SendLVMsg(HWND hwnd, ListViewMessage msg, int wParam, TLP lParam) where TLP : class
{
using var lpin = new PinnedObject(lParam);
return SendMessage(hwnd, (uint)msg, (IntPtr)wParam, lpin);
}
private static void SetState(ListViewGroup group, ListViewGroupState state, bool value)
{
var groupId = GetGroupId(group);
if (groupId >= 0 && group.ListView != null)
{
var lvgroup = new LVGROUP(ListViewGroupMask.LVGF_STATE);
{
lvgroup.SetState(state, value);
SendLVMsg(group.ListView.Handle, ListViewMessage.LVM_SETGROUPINFO, groupId, lvgroup);
}
}
}
}
/// Takes a list of groups and matching predicates to be used by the ApplyGroupingSet extension method.
/// Type of the item that represents and can convert to a .
public class ListViewGroupingSet where T : class
{
private readonly Converter converter;
private readonly List>> list = new();
/// Initializes a new instance of the class.
///
/// The converter to take the and convert it to a . If
/// is not , an exception is thrown.
///
/// Generic type T must be convertible to a ListViewGroup.
public ListViewGroupingSet(Converter? converter = null)
{
if (converter != null)
this.converter = converter;
else
{
if (typeof(T) == typeof(ListViewGroup))
this.converter = a => (ListViewGroup)(object)a;
else
{
var tc = TypeDescriptor.GetConverter(typeof(T));
if (!tc.CanConvertTo(typeof(ListViewGroup)))
throw new InvalidCastException("Generic type T must be convertible to a ListViewGroup.");
this.converter = t => (ListViewGroup)(tc.ConvertTo(t, typeof(ListViewGroup)) ?? throw new InvalidOperationException("Unable to create a converter for the list view group."));
}
}
}
internal Predicate[] Conditions => list.ConvertAll(kvp => kvp.Value).ToArray();
internal ListViewGroup[] Groups => list.ConvertAll(kvp => converter(kvp.Key)).ToArray();
/// Adds the specified group and matching condition to the set.
/// The group.
/// The condition for instances to be added to the group.
public void Add(T group, Predicate condition) => list.Add(new KeyValuePair>(group, condition));
}