QuickImage – Rev 1
?pathlinks?
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security;
using System.Text;
using System.Threading.Tasks;
using Microsoft.VisualBasic;
using System.Windows.Forms;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using static System.Windows.Forms.AxHost;
using static Microsoft.WindowsAPICodePack.Shell.PropertySystem.SystemProperties.System;
using Message = System.Windows.Forms.Message;
namespace QuickImage.Utilities.Controls
{
/// <summary>
///
/// </summary>
/// <remarks>https://stackoverflow.com/questions/1330050/collapsible-listview</remarks>
/// <remarks>https://stackoverflow.com/questions/41816335/listview-group-header-click-how-to-add-a-context-menu-to-listview-group-header</remarks>
public class ListViewCollapsible : ListView
{
public event EventHandler<ListViewGroup> GroupHeaderClick;
private Dictionary<int, GroupState> groupStates = new Dictionary<int, GroupState>();
private Dictionary<int, GroupState> groupStateMasks = new Dictionary<int, GroupState>();
private const int LVM_FIRST = 0x1000;
// ListView messages
private const int LVM_SETGROUPINFO = LVM_FIRST + 147;
private const int LVM_GETGROUPINFO = LVM_FIRST + 149;
private const int LVM_GETGROUPSTATE = LVM_FIRST + 92;
// ListView messages Setinfo on Group
private const int WM_LBUTTONUP = 0x202;
private const int LVM_HITTEST = LVM_FIRST + 18;
private const int LVM_SUBITEMHITTEST = LVM_FIRST + 57;
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr SendMessage(IntPtr hWnd, int msg, int wParam, int lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, int lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SendMessage(IntPtr hWnd, int msg, int wParam, IntPtr lParam);
[DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
public static extern IntPtr SendMessage(IntPtr hWnd, int msg, int wParam, ref LVHITTESTINFO ht);
[DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
public static extern IntPtr SendMessageString(IntPtr hWnd, int Msg, int wParam, string lParam);
[DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
public static extern IntPtr SendMessageIUnknown(IntPtr hWnd, int msg,
[MarshalAs(UnmanagedType.IUnknown)] object wParam, int lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SendMessage(IntPtr hWnd, int msg, int wParam, ref LVGROUP lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SendMessage(IntPtr hWnd, int msg, int wParam, ref LVGROUP2 lParam);
public ListViewCollapsible()
{
//Activate double buffering
SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true);
//Enable the OnNotifyMessage event so we get a chance to filter out
// Windows messages before they get to the form's WndProc
SetStyle(ControlStyles.EnableNotifyMessage, true);
}
public bool GetCollapsed(ListViewGroup group)
{
//var index = group.ListView.Groups.IndexOf(group);
var index = ExtractID(group);
return GetOneState(this, index, GroupState.LVGS_COLLAPSED);
}
public void SetCollapsed(ListViewGroup group, bool value)
{
//var index = group.ListView.Groups.IndexOf(group);
var index = ExtractID(group);
SetOneState(this, index, value, GroupState.LVGS_COLLAPSED);
}
private GroupState GetState(ListViewCollapsible olv, int groupId)
{
return (GroupState)SendMessage(olv.Handle, LVM_GETGROUPSTATE, groupId, (int)GroupState.LVGS_ALL);
}
private int SetGroupInfo(ListViewCollapsible olv, int groupId, LVGROUP2 group)
{
return (int)SendMessage(olv.Handle, LVM_SETGROUPINFO, groupId, ref group);
}
private int SetState(ListViewCollapsible olv, int groupId, GroupState newState, GroupState mask)
{
LVGROUP2 group = new LVGROUP2();
group.cbSize = ((uint)Marshal.SizeOf(typeof(LVGROUP2)));
group.mask = (uint)GroupMask.LVGF_STATE;
group.state = (uint)newState;
group.stateMask = (uint)mask;
return SetGroupInfo(olv,groupId, group);
}
private void SetOneState(ListViewCollapsible olv, int groupId, bool value, GroupState mask)
{
if (!groupStateMasks.ContainsKey(groupId))
{
groupStateMasks.Add(groupId, GetState(olv, groupId));
}
groupStateMasks[groupId] ^= mask;
if (!groupStates.ContainsKey(groupId))
{
groupStates.Add(groupId, GetState(olv, groupId));
}
if (value)
{
groupStates[groupId] ^= mask;
}
else
{
groupStates[groupId] &= ~mask;
}
if (Created)
SetState(olv, groupId, groupStates[groupId], mask);
}
private bool GetOneState(ListViewCollapsible olv, int groupId, GroupState mask)
{
if (Created)
{
if (!groupStates.ContainsKey(groupId))
{
groupStates.Add(groupId, GetState(olv, groupId));
return (groupStates[groupId] & mask) == mask;
}
groupStates[groupId] = GetState(olv, groupId);
}
return (groupStates[groupId] & mask) == mask;
}
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
var group = TestGroupHit(e);
if (group == null)
{
return;
}
switch (e.Clicks)
{
case 1:
if (GroupHeaderClick != null)
{
GroupHeaderClick(this, group);
}
break;
}
}
private ListViewGroup TestGroupHit(MouseEventArgs e)
{
var ht = new LVHITTESTINFO();
ht.pt.x = e.X;
ht.pt.y = e.Y;
var msg = View == System.Windows.Forms.View.Details ? LVM_SUBITEMHITTEST : LVM_HITTEST;
var value = (int)SendMessage(Handle, msg, -1, ref ht);
if (value != -1 && ht.flags.HasFlag(LVHITTESTFLAGS.LVHT_EX_GROUP_HEADER))
{
return FindGroupById(value);
}
return null;
}
private ListViewGroup FindGroupById(int id)
{
foreach (ListViewGroup group in Groups)
{
if (ExtractID(group) == id)
{
return group;
}
}
return null;
}
public static int ExtractID(ListViewGroup group)
{
try
{
return (int)group
.GetType()
.GetProperty("ID", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(group, new object[0]);
}
catch
{
return -1;
}
}
protected override void OnNotifyMessage(Message m)
{
//Filter out the WM_ERASEBKGND message
if (m.Msg != 0x14)
{
base.OnNotifyMessage(m);
}
}
/// <summary>
/// convert the IntPtr LParam to an Point.
/// </summary>
private static POINT LParamToPoint(IntPtr lparam)
{
return new POINT(lparam.ToInt32() & 0xFFFF, lparam.ToInt32() >> 16);
}
#region Natives
/// <summary>
/// see http://msdn.microsoft.com/en-us/library/bb774754%28v=VS.85%29.aspx
/// </summary>
[Flags]
public enum LVHITTESTFLAGS : uint
{
LVHT_NOWHERE = 0x00000001,
LVHT_ONITEMICON = 0x00000002,
LVHT_ONITEMLABEL = 0x00000004,
LVHT_ONITEMSTATEICON = 0x00000008,
LVHT_ONITEM = (LVHT_ONITEMICON | LVHT_ONITEMLABEL | LVHT_ONITEMSTATEICON),
LVHT_ABOVE = 0x00000008,
LVHT_BELOW = 0x00000010,
LVHT_TORIGHT = 0x00000020,
LVHT_TOLEFT = 0x00000040,
// Vista/Win7+ only
LVHT_EX_GROUP_HEADER = 0x10000000,
LVHT_EX_GROUP_FOOTER = 0x20000000,
LVHT_EX_GROUP_COLLAPSE = 0x40000000,
LVHT_EX_GROUP_BACKGROUND = 0x80000000,
LVHT_EX_GROUP_STATEICON = 0x01000000,
LVHT_EX_GROUP_SUBSETLINK = 0x02000000,
}
/// <summary>
/// see http://msdn.microsoft.com/en-us/library/dd162805%28v=VS.85%29.aspx
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public POINT(int x, int y)
{
this.x = x;
this.y = y;
}
public int x;
public int y;
}
[StructLayout(LayoutKind.Sequential)]
public struct LVGROUP
{
public uint cbSize;
public uint mask;
[MarshalAs(UnmanagedType.LPTStr)]
public string pszHeader;
public int cchHeader;
[MarshalAs(UnmanagedType.LPTStr)]
public string pszFooter;
public int cchFooter;
public int iGroupId;
public uint stateMask;
public uint state;
public uint uAlign;
}
[StructLayout(LayoutKind.Sequential)]
public struct LVGROUP2
{
public uint cbSize;
public uint mask;
[MarshalAs(UnmanagedType.LPTStr)]
public string pszHeader;
public uint cchHeader;
[MarshalAs(UnmanagedType.LPTStr)]
public string pszFooter;
public int cchFooter;
public int iGroupId;
public uint stateMask;
public uint state;
public uint uAlign;
[MarshalAs(UnmanagedType.LPTStr)]
public string pszSubtitle;
public uint cchSubtitle;
[MarshalAs(UnmanagedType.LPTStr)]
public string pszTask;
public uint cchTask;
[MarshalAs(UnmanagedType.LPTStr)]
public string pszDescriptionTop;
public uint cchDescriptionTop;
[MarshalAs(UnmanagedType.LPTStr)]
public string pszDescriptionBottom;
public uint cchDescriptionBottom;
public int iTitleImage;
public int iExtendedImage;
public int iFirstItem; // Read only
public int cItems; // Read only
[MarshalAs(UnmanagedType.LPTStr)]
public string pszSubsetTitle; // NULL if group is not subset
public uint cchSubsetTitle;
}
/// <summary>
/// These values indicate what is the state of the group. These values
/// are taken directly from the SDK and many are not used by ObjectListView.
/// </summary>
[Flags]
public enum GroupState
{
/// <summary>
/// Normal
/// </summary>
LVGS_NORMAL = 0x0,
/// <summary>
/// Collapsed
/// </summary>
LVGS_COLLAPSED = 0x1,
/// <summary>
/// Hidden
/// </summary>
LVGS_HIDDEN = 0x2,
/// <summary>
/// NoHeader
/// </summary>
LVGS_NOHEADER = 0x4,
/// <summary>
/// Can be collapsed
/// </summary>
LVGS_COLLAPSIBLE = 0x8,
/// <summary>
/// Has focus
/// </summary>
LVGS_FOCUSED = 0x10,
/// <summary>
/// Is Selected
/// </summary>
LVGS_SELECTED = 0x20,
/// <summary>
/// Is subsetted
/// </summary>
LVGS_SUBSETED = 0x40,
/// <summary>
/// Subset link has focus
/// </summary>
LVGS_SUBSETLINKFOCUSED = 0x80,
/// <summary>
/// All styles
/// </summary>
LVGS_ALL = 0xFFFF
}
/// <summary>
/// This mask indicates which members of a LVGROUP have valid data. These values
/// are taken directly from the SDK and many are not used by ObjectListView.
/// </summary>
[Flags]
public enum GroupMask
{
/// <summary>
/// No mask
/// </summary>
LVGF_NONE = 0,
/// <summary>
/// Group has header
/// </summary>
LVGF_HEADER = 1,
/// <summary>
/// Group has footer
/// </summary>
LVGF_FOOTER = 2,
/// <summary>
/// Group has state
/// </summary>
LVGF_STATE = 4,
/// <summary>
///
/// </summary>
LVGF_ALIGN = 8,
/// <summary>
///
/// </summary>
LVGF_GROUPID = 0x10,
/// <summary>
/// pszSubtitle is valid
/// </summary>
LVGF_SUBTITLE = 0x00100,
/// <summary>
/// pszTask is valid
/// </summary>
LVGF_TASK = 0x00200,
/// <summary>
/// pszDescriptionTop is valid
/// </summary>
LVGF_DESCRIPTIONTOP = 0x00400,
/// <summary>
/// pszDescriptionBottom is valid
/// </summary>
LVGF_DESCRIPTIONBOTTOM = 0x00800,
/// <summary>
/// iTitleImage is valid
/// </summary>
LVGF_TITLEIMAGE = 0x01000,
/// <summary>
/// iExtendedImage is valid
/// </summary>
LVGF_EXTENDEDIMAGE = 0x02000,
/// <summary>
/// iFirstItem and cItems are valid
/// </summary>
LVGF_ITEMS = 0x04000,
/// <summary>
/// pszSubsetTitle is valid
/// </summary>
LVGF_SUBSET = 0x08000,
/// <summary>
/// readonly, cItems holds count of items in visible subset, iFirstItem is valid
/// </summary>
LVGF_SUBSETITEMS = 0x10000
}
/// <summary>
/// see http://msdn.microsoft.com/en-us/library/bb774754%28v=VS.85%29.aspx
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct LVHITTESTINFO
{
public POINT pt;
public LVHITTESTFLAGS flags;
public int iItem;
public int iSubItem;
// Vista/Win7+
public int iGroup;
}
#endregion
}
}