リストビュー(ListView
)から派生してソートマークやスプリットボタン(ドロップダウン用)に対応したリストビューコントロールのコードです。
Program.cs
using System; using System.Drawing; using System.Windows.Forms; static class Program { [STAThread] static void Main() { Application.SetHighDpiMode(HighDpiMode.SystemAware); Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); var form = new Form(); var listView1 = new ListViewEx(); listView1.View = View.Details; listView1.Dock = DockStyle.Fill; listView1.Columns.Add("Column0"); listView1.Columns.Add("Column1"); listView1.Columns.Add("Column2"); form.Controls.Add(listView1); listView1.SetColumnSortDown(0, true); listView1.SetColumnSortUp(1, true); listView1.SetColumnSplitButton(2, true); // 1番目のカラムはソートマークの切り替え対応 listView1.ColumnClick += (sender, e) => { if (e.Column == 0) { if (listView1.GetColumnSortDown(0)) { listView1.SetColumnSortDown(0, false); listView1.SetColumnSortUp(0, true); } else { listView1.SetColumnSortDown(0, true); listView1.SetColumnSortUp(0, false); } } }; // 3番目のカラムはドロップダウン(スプリットボタンのクリック)に対応 listView1.ColumnDropDown += (sender, e) => { if (e.Column == 2) { var columnBounds = form.RectangleToClient( listView1.RectangleToScreen(listView1.GetColumnBounds(2))); var panel = new Panel(); panel.Size = columnBounds.Size; panel.Location = new Point(columnBounds.Left, columnBounds.Bottom); panel.Capture = true; panel.MouseDown += (sender, e) => form.Controls.Remove(panel); form.Controls.Add(panel); form.Controls.SetChildIndex(panel, 0); // 一番上に移動 } }; // カラムサイズの調整 // そのまま表示するとスプリットボタンが隠れる。 listView1.AutoResizeColumns(ColumnHeaderAutoResizeStyle.HeaderSize); Application.Run(form); } }
ListViewEx.cs
using System; using System.Drawing; using System.Runtime.InteropServices; using System.Windows.Forms; using System.ComponentModel; class ListViewEx : ListView { [Category("Action")] public event EventHandler<ListViewColumnDropDownEventArgs> ColumnDropDown; public ListViewEx() : base() { } public IntPtr HeaderWindowHandle { get => NativeMethods.SendMessage(Handle, Constants.LVM_GETHEADER, 0, 0); } private int GetFormat(int column) { if (!(0 <= column && column < Columns.Count)) throw new ArgumentOutOfRangeException(nameof(column)); HD_ITEMW item = default; item.mask = Constants.HDI_FORMAT; var ret = NativeMethods.SendMessage( HeaderWindowHandle, Constants.HDM_GETITEMW, column, ref item); if (ret == 0) Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); return item.fmt; } private void SetFormat(int column, int format) { if (!(0 <= column && column < Columns.Count)) throw new ArgumentOutOfRangeException(nameof(column)); HD_ITEMW item = default; item.mask = Constants.HDI_FORMAT; item.fmt = format; var ret = NativeMethods.SendMessage( HeaderWindowHandle, Constants.HDM_SETITEMW, column, ref item); if (ret == 0) Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); } private static bool HasFlag(int flags, int flag) => (flags & flag) == flag; private static int SetFlags(int flags, int flag, bool on) { if (on) flags |= flag; else flags &= ~flag; return flags; } public bool GetColumnSortUp(int column) => HasFlag(GetFormat(column), Constants.HDF_SORTUP); public void SetColumnSortUp(int column, bool flag) => SetFormat(column, SetFlags(GetFormat(column), Constants.HDF_SORTUP, flag)); public bool GetColumnSortDown(int column) => HasFlag(GetFormat(column), Constants.HDF_SORTDOWN); public void SetColumnSortDown(int column, bool flag) => SetFormat(column, SetFlags(GetFormat(column), Constants.HDF_SORTDOWN, flag)); public bool GetColumnFixedWidth(int column) => HasFlag(GetFormat(column), Constants.HDF_FIXEDWIDTH); public void SetColumnFixedWidth(int column, bool flag) => SetFormat(column, SetFlags(GetFormat(column), Constants.HDF_FIXEDWIDTH, flag)); public bool GetColumnSplitButton(int column) { ThrowIfParentIsNull(); return HasFlag(GetFormat(column), Constants.HDF_SPLITBUTTON); } public void SetColumnSplitButton(int column, bool flag) { ThrowIfParentIsNull(); SetFormat(column, SetFlags(GetFormat(column), Constants.HDF_SPLITBUTTON, flag)); } public Rectangle GetColumnBounds(int column) { if (!(0 <= column && column < Columns.Count)) throw new ArgumentOutOfRangeException(nameof(column)); var ret = NativeMethods.SendMessage( HeaderWindowHandle, Constants.HDM_GETITEMRECT, column, out var rc); if (ret == 0) Marshal.ThrowExceptionForHR(Marshal.GetLastWin32Error()); return rc.ToRectangle(); } public Rectangle GetDropDownColumnBounds(int column) { if (!(0 <= column && column < Columns.Count)) throw new ArgumentOutOfRangeException(nameof(column)); var ret = NativeMethods.SendMessage( HeaderWindowHandle, Constants.HDM_GETITEMDROPDOWNRECT, column, out var rc); if (ret == 0) Marshal.ThrowExceptionForHR(Marshal.GetLastWin32Error()); return rc.ToRectangle(); } private void ThrowIfParentIsNull() { if (Parent == null) throw new InvalidOperationException("この操作はコンテナへの追加後に実行してください。"); } public class ListViewColumnDropDownEventArgs : EventArgs { public int Column; } protected override void WndProc(ref Message m) { if (m.Msg != Constants.WM_REFLECT + Constants.WM_NOTIFY) { base.WndProc(ref m); return; } var nmlv = Marshal.PtrToStructure<NMLISTVIEW>(m.LParam); if (nmlv.hdr.code != Constants.LVN_COLUMNDROPDOWN) { base.WndProc(ref m); return; } var e = new ListViewColumnDropDownEventArgs(); e.Column = nmlv.iSubItem; ColumnDropDown?.Invoke(this, e); } private static class Constants { public const int LVM_FIRST = 0x1000; public const int LVM_GETHEADER = LVM_FIRST + 31; public const int HDM_FIRST = 0x1200; public const int HDM_GETITEMRECT = HDM_FIRST + 7; public const int HDM_GETITEMW = HDM_FIRST + 11; public const int HDM_SETITEMW = HDM_FIRST + 12; public const int HDM_GETITEMDROPDOWNRECT = HDM_FIRST + 25; public const uint HDI_FORMAT = 0x0004; public const int HDF_SORTUP = 0x0400; public const int HDF_SORTDOWN = 0x0200; public const int HDF_FIXEDWIDTH = 0x0100; public const int HDF_SPLITBUTTON = 0x1000000; public const int WM_NOTIFY = 0x004E; public const int WM_REFLECT = 0x2000; public const uint LVN_COLUMNDROPDOWN = unchecked((uint)(-100 - 64)); } private static class NativeMethods { [DllImport("user32.dll")] public static extern nint SendMessage(IntPtr hWnd, uint Msg, nint wParam, nint lParam); [DllImport("user32.dll")] public static extern nint SendMessage(IntPtr hWnd, uint Msg, nint wParam, ref HD_ITEMW lParam); [DllImport("user32.dll")] public static extern nint SendMessage(IntPtr hWnd, uint Msg, nint wParam, out RECT lParam); } [StructLayout(LayoutKind.Sequential)] private struct RECT { public int Left; public int Top; public int Right; public int Bottom; public Rectangle ToRectangle() { return Rectangle.FromLTRB(Left, Top, Right, Bottom); } } [StructLayout(LayoutKind.Sequential)] private struct HD_ITEMW { public uint mask; public int cxy; public IntPtr pszText; public IntPtr hbm; public int cchTextMax; public int fmt; public nint lParam; public int iImage; public int iOrder; public IntPtr type; public IntPtr pvFilter; public uint state; } [StructLayout(LayoutKind.Sequential)] private struct NMHDR { public IntPtr hwndFrom; public nuint idFrom; public uint code; } [StructLayout(LayoutKind.Sequential)] private struct NMLISTVIEW { public NMHDR hdr; public int iItem; public int iSubItem; public uint uNewState; public uint uOldState; public uint uChanged; public Point ptAction; public IntPtr lParam; } }