概要
標準コントロールのリストビューはWin32 APIのリストビュー(コモンコントロール)を元に作成されています。したがって、ListView.Handle
を使えばWin32 APIのリストビューと同様の手順でソートマークやスプリットボタンを表示できます。
手順はおおよそ以下の通りです。なお、ドロップダウン(スプリットボタンのクリック)などに応答するためにはウィンドウプロシージャーの処理が必要となりますが、ここでは対応していません。
ListView
を作成してコンテナへ追加する。- ※追加は以下の操作後でも可能ですが、スプリットボタンのみ先に追加しないとリセットされるようです。
SendMessage
とLVM_GETHEADER
でヘッダーのウィンドウハンドルを取得する。SendMessage
とHDM_GETITEMW
/HDM_SETITEMW
でヘッダーのアイテムを操作する。このときHD_ITEMW
構造体にHDI_FORMAT
を使用して操作対象をヘッダーのフォーマットに限定する。
実際のソースコードは以下の通りです。上記の操作を'ListViewHeaderUtility.cs'のListViewHeaderUtility
静的クラスにまとめています。プロジェクトはC# .NET 5.0のWindowsアプリケーションとして作成してください。
Program.cs
static class Program { [STAThread] static void Main() { Application.SetHighDpiMode(HighDpiMode.SystemAware); Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); // フォームとリストビューの作成 var form = new Form(); var listView1 = new ListView(); listView1.View = View.Details; listView1.Dock = DockStyle.Fill; listView1.Columns.Add("Column0"); listView1.Columns.Add("Column1"); listView1.Columns.Add("Column2"); listView1.Columns.Add("Column3"); listView1.Columns.Add("Column4"); listView1.Columns.Add("Column5"); // 最初のカラムだけソートマークの切り替え対応 listView1.ColumnClick += (sender, e) => { if (ListViewHeaderUtility.GetSortDown(listView1, 0)) { ListViewHeaderUtility.SetSortDown(listView1, 0, false); ListViewHeaderUtility.SetSortUp(listView1, 0, true); } else { ListViewHeaderUtility.SetSortDown(listView1, 0, true); ListViewHeaderUtility.SetSortUp(listView1, 0, false); } }; form.Controls.Add(listView1); // ヘッダーフォーマットの変更 // ソートマークやスプリットボタンの追加 ListViewHeaderUtility.SetSortDown(listView1, 0, true); ListViewHeaderUtility.SetSortUp(listView1, 1, true); ListViewHeaderUtility.SetSortUp(listView1, 2, true); ListViewHeaderUtility.SetSortDown(listView1, 2, true); ListViewHeaderUtility.SetSplitButton(listView1, 3, true); ListViewHeaderUtility.SetSplitButton(listView1, 4, true); ListViewHeaderUtility.SetSortDown(listView1, 4, true); ListViewHeaderUtility.SetSplitButton(listView1, 5, true); ListViewHeaderUtility.SetSortUp(listView1, 5, true); // カラムサイズの調整 // そのまま表示するとスプリットボタンが隠れる。 listView1.AutoResizeColumns(ColumnHeaderAutoResizeStyle.HeaderSize); Application.Run(form); } }
ListViewHeaderUtility.cs
using System.Runtime.InteropServices; using System.Runtime.Versioning; [SupportedOSPlatform("Windows")] static class ListViewHeaderUtility { public static IntPtr GetHeaderWindowHandle(ListView listView) { if (listView == null) throw new ArgumentNullException(nameof(listView)); return NativeMethods.SendMessage(listView.Handle, Constants.LVM_GETHEADER, 0, 0); } public static int GetFormat(ListView listView, int column) { if (listView == null) throw new ArgumentNullException(nameof(listView)); if (!(0 <= column && column < listView.Columns.Count)) throw new ArgumentOutOfRangeException(nameof(column)); HD_ITEMW item = default; item.mask = Constants.HDI_FORMAT; var ret = NativeMethods.SendMessage( GetHeaderWindowHandle(listView), Constants.HDM_GETITEMW, column, ref item); if (ret == 0) Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); return item.fmt; } public static void SetFormat(ListView listView, int column, int format) { if (listView == null) throw new ArgumentNullException(nameof(listView)); if (!(0 <= column && column < listView.Columns.Count)) throw new ArgumentOutOfRangeException(nameof(column)); HD_ITEMW item = default; item.mask = Constants.HDI_FORMAT; item.fmt = format; var ret = NativeMethods.SendMessage( GetHeaderWindowHandle(listView), 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 static bool GetSortUp(ListView listView, int column) => HasFlag(GetFormat(listView, column), Constants.HDF_SORTUP); public static void SetSortUp(ListView listView, int column, bool flag) => SetFormat(listView, column, SetFlags(GetFormat(listView, column), Constants.HDF_SORTUP, flag)); public static bool GetSortDown(ListView listView, int column) => HasFlag(GetFormat(listView, column), Constants.HDF_SORTDOWN); public static void SetSortDown(ListView listView, int column, bool flag) => SetFormat(listView, column, SetFlags(GetFormat(listView, column), Constants.HDF_SORTDOWN, flag)); public static bool GetFixedWidth(ListView listView, int column) => HasFlag(GetFormat(listView, column), Constants.HDF_FIXEDWIDTH); public static void SetFixedWidth(ListView listView, int column, bool flag) => SetFormat(listView, column, SetFlags(GetFormat(listView, column), Constants.HDF_FIXEDWIDTH, flag)); public static bool GetSplitButton(ListView listView, int column) => HasFlag(GetFormat(listView, column), Constants.HDF_SPLITBUTTON); public static void SetSplitButton(ListView listView, int column, bool flag) => SetFormat(listView, column, SetFlags(GetFormat(listView, column), Constants.HDF_SPLITBUTTON, flag)); 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_GETITEMW = HDM_FIRST + 11; public const int HDM_SETITEMW = HDM_FIRST + 12; public const uint HDI_FORMAT = 0x0004; // Windows XP以降 public const int HDF_SORTUP = 0x0400; public const int HDF_SORTDOWN = 0x0200; // Windows Vista以降 public const int HDF_FIXEDWIDTH = 0x0100; public const int HDF_SPLITBUTTON = 0x1000000; } 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); } [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; } }
参考