web-dev-qa-db-ja.com

C#を使用してリストビューの列のヘッダーに並べ替え矢印を表示する方法

オペレーティングシステムのネイティブな外観に従うリストビューの並べ替えられた列のヘッダーに並べ替え矢印を表示するにはどうすればよいですか?

42
Andrew Moore

次の拡張メソッドを使用して、ソート矢印を特定の列に設定できます。

[EditorBrowsable(EditorBrowsableState.Never)]
public static class ListViewExtensions
{
    [StructLayout(LayoutKind.Sequential)]
    public struct HDITEM
    {
        public Mask mask;
        public int cxy;
        [MarshalAs(UnmanagedType.LPTStr)] public string pszText;
        public IntPtr hbm;
        public int cchTextMax;
        public Format fmt;
        public IntPtr lParam;
        // _WIN32_IE >= 0x0300 
        public int iImage;
        public int iOrder;
        // _WIN32_IE >= 0x0500
        public uint type;
        public IntPtr pvFilter;
        // _WIN32_WINNT >= 0x0600
        public uint state;

        [Flags]
        public enum Mask
        {
            Format = 0x4,       // HDI_FORMAT
        };

        [Flags]
        public enum Format
        {
            SortDown = 0x200,   // HDF_SORTDOWN
            SortUp = 0x400,     // HDF_SORTUP
        };
    };

    public const int LVM_FIRST = 0x1000;
    public const int LVM_GETHEADER = LVM_FIRST + 31;

    public const int HDM_FIRST = 0x1200;
    public const int HDM_GETITEM = HDM_FIRST + 11;
    public const int HDM_SETITEM = HDM_FIRST + 12;

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern IntPtr SendMessage(IntPtr hWnd, UInt32 msg, IntPtr wParam, IntPtr lParam);

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern IntPtr SendMessage(IntPtr hWnd, UInt32 msg, IntPtr wParam, ref HDITEM lParam);

    public static void SetSortIcon(this ListView listViewControl, int columnIndex, SortOrder order)
    {
        IntPtr columnHeader = SendMessage(listViewControl.Handle, LVM_GETHEADER, IntPtr.Zero, IntPtr.Zero);
        for (int columnNumber = 0; columnNumber <= listViewControl.Columns.Count - 1; columnNumber++)
        {
            var columnPtr = new IntPtr(columnNumber);
            var item = new HDITEM
                {
                    mask = HDITEM.Mask.Format
                };

            if (SendMessage(columnHeader, HDM_GETITEM, columnPtr, ref item) == IntPtr.Zero)
            {
                throw new Win32Exception();
            }

            if (order != SortOrder.None && columnNumber == columnIndex)
            {
                switch (order)
                {
                    case SortOrder.Ascending:
                        item.fmt &= ~HDITEM.Format.SortDown;
                        item.fmt |= HDITEM.Format.SortUp;
                        break;
                    case SortOrder.Descending:
                        item.fmt &= ~HDITEM.Format.SortUp;
                        item.fmt |= HDITEM.Format.SortDown;
                        break;
                }
            }
            else
            {
                item.fmt &= ~HDITEM.Format.SortDown & ~HDITEM.Format.SortUp;
            }

            if (SendMessage(columnHeader, HDM_SETITEM, columnPtr, ref item) == IntPtr.Zero)
            {
                throw new Win32Exception();
            }
        }
    }
}

次に、次のように拡張メソッドを呼び出すことができます。

myListView.SetSortIcon(0, SortOrder.Ascending);

これは、P/Invokeを使用して次のように機能します。

  • LVM_GETHEADER メッセージを使用して、リストビューのヘッダーコントロールへのハンドルを取得します。
  • HDM_GETITEM メッセージを使用して、ヘッダー列に関する情報を取得します。
  • 次に、fmtを変更して、返された [〜#〜] hditem [〜#〜] 構造体のHDF_SORTDOWNフラグとHDF_SORTUPフラグを設定/クリアします。
  • 最後に、 HDM_SETITEM メッセージを使用して情報をリセットします。

これはどのように見えるかです:

Arrows on a list view column

67
Andrew Moore

アンドリューによる素晴らしい答え。誰かがここで同等のVB.netを探しているなら、それは次のとおりです。

Public Module ListViewExtensions
    Public Enum SortOrder
        None
        Ascending
        Descending
    End Enum

    <StructLayout(LayoutKind.Sequential)>
    Public Structure HDITEM
        Public theMask As Mask
        Public cxy As Integer
        <MarshalAs(UnmanagedType.LPTStr)>
        Public pszText As String
        Public hbm As IntPtr
        Public cchTextMax As Integer
        Public fmt As Format
        Public lParam As IntPtr
        ' _WIN32_IE >= 0x0300 
        Public iImage As Integer
        Public iOrder As Integer
        ' _WIN32_IE >= 0x0500
        Public type As UInteger
        Public pvFilter As IntPtr
        ' _WIN32_WINNT >= 0x0600
        Public state As UInteger

        <Flags()>
        Public Enum Mask
            Format = &H4       ' HDI_FORMAT
        End Enum


        <Flags()>
        Public Enum Format
            SortDown = &H200 ' HDF_SORTDOWN
            SortUp = &H400     ' HDF_SORTUP
        End Enum
    End Structure

    Public Const LVM_FIRST As Integer = &H1000
    Public Const LVM_GETHEADER As Integer = LVM_FIRST + 31

    Public Const HDM_FIRST As Integer = &H1200
    Public Const HDM_GETITEM As Integer = HDM_FIRST + 11
    Public Const HDM_SETITEM As Integer = HDM_FIRST + 12

    <DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
    Public Function SendMessage(hWnd As IntPtr, msg As UInt32, wParam As IntPtr, lParam As IntPtr) As IntPtr
    End Function

    <DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
    Public Function SendMessage(hWnd As IntPtr, msg As UInt32, wParam As IntPtr, ByRef lParam As HDITEM) As IntPtr
    End Function

    <Extension()>
    Public Sub SetSortIcon(listViewControl As ListView, columnIndex As Integer, order As SortOrder)
        Dim columnHeader As IntPtr = SendMessage(listViewControl.Handle, LVM_GETHEADER, IntPtr.Zero, IntPtr.Zero)
        For columnNumber As Integer = 0 To listViewControl.Columns.Count - 1

            Dim columnPtr As New IntPtr(columnNumber)
            Dim item As New HDITEM

            item.theMask = HDITEM.Mask.Format

            If SendMessage(columnHeader, HDM_GETITEM, columnPtr, item) = IntPtr.Zero Then Throw New Win32Exception

            If order <> SortOrder.None AndAlso columnNumber = columnIndex Then
                Select Case order
                    Case SortOrder.Ascending
                        item.fmt = item.fmt And Not HDITEM.Format.SortDown
                        item.fmt = item.fmt Or HDITEM.Format.SortUp
                    Case SortOrder.Descending
                        item.fmt = item.fmt And Not HDITEM.Format.SortUp
                        item.fmt = item.fmt Or HDITEM.Format.SortDown
                End Select
            Else
                item.fmt = item.fmt And Not HDITEM.Format.SortDown And Not HDITEM.Format.SortUp
            End If

            If SendMessage(columnHeader, HDM_SETITEM, columnPtr, item) = IntPtr.Zero Then Throw New Win32Exception
        Next
    End Sub
End Module
7
Jesse

他の怠惰なC++プログラマー(私のような)の場合:

// possible sorting header icons / indicators
enum class ListViewSortArrow { None, Ascending, Descending };

BOOL LVHeader_SetSortArrow(HWND hHeader, int nColumn, ListViewSortArrow sortArrow)
{
    ASSERT(hHeader);

    HDITEM hdrItem = { 0 };
    hdrItem.mask = HDI_FORMAT;
    if (Header_GetItem(hHeader, nColumn, &hdrItem))
    {
        switch (sortArrow)
        {
        default:
            ASSERT(false);
        case ListViewSortArrow::None:
            hdrItem.fmt = hdrItem.fmt & ~(HDF_SORTDOWN | HDF_SORTUP);
            break;
        case ListViewSortArrow::Ascending:
            hdrItem.fmt = (hdrItem.fmt & ~HDF_SORTDOWN) | HDF_SORTUP;
            break;
        case ListViewSortArrow::Descending:
            hdrItem.fmt = (hdrItem.fmt & ~HDF_SORTUP) | HDF_SORTDOWN;
            break;
        }

        return Header_SetItem(hHeader, nColumn, &hdrItem);
    }

    return FALSE;
}

BOOL ListView_SetSortArrow(HWND hListView, int nColumn, ListViewSortArrow sortArrow)
{
    ASSERT(hListView);

    if (HWND hHeader = ListView_GetHeader(hListView))
        return LVHeader_SetSortArrow(hHeader, nColumn, sortArrow);

    return FALSE;
}
3
Mordachai

windows APIをいじる代わりに、矢印のように見える文字を妥協して使用することができます(charmapを使用してそれらを選択しました)

private void SetSortArrow(ColumnHeader head, SortOrder order)
{
    const string ascArrow = " ▲";
    const string descArrow = " ▼";

    // remove arrow
    if(head.Text.EndsWith(ascArrow) || head.Text.EndsWith(descArrow))
        head.Text = head.Text.Substring(0, head.Text.Length-2);

    // add arrow
    switch (order)
    {
        case SortOrder.Ascending: head.Text += ascArrow; break;
        case  SortOrder.Descending: head.Text += descArrow; break;
    }
}

SetSortArrow(listView1.Columns[0], SortOrder.None);       // remove arrow from first column if present
SetSortArrow(listView1.Columns[1], SortOrder.Ascending);  // set second column arrow to ascending
SetSortArrow(listView1.Columns[1], SortOrder.Descending); // set second column arrow to descending
1
symbiont