【问题标题】:How to I display a sort arrow in the header of a list view column using C#?如何使用 C# 在列表视图列的标题中显示排序箭头?
【发布时间】:2008-10-31 16:35:55
【问题描述】:

如何在列表视图中的已排序列的标题中显示排序箭头,以遵循操作系统的本机外观?

【问题讨论】:

    标签: c# listview


    【解决方案1】:

    您可以使用以下扩展方法将排序箭头设置为特定列:

    [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_SORTDOWNHDF_SORTUP 标志。
    • 最后它使用HDM_SETITEM 消息重新设置信息。

    这就是它的样子:

    【讨论】:

    • 您定义的 LVCOLUMN 实际上是 HDITEM。我意识到,既然你自己定义它并不重要,但是当把它翻译成 C++ 时,它让我有些困惑:)。
    • 我认为代码 sn-p 缺少一些东西:User32.HeaderMessagesSendMessageLVM_GETHEADERHDM_GETITEM,将&amp;= 应用于枚举,尝试将枚举分配给一个整数,...
    • 这是 2016 年的唯一方法吗?!
    • 这个解决方案绝对是荒谬的,在像 Windows Forms 这样的编程环境中是不可接受的。它是列表视图的基本功能,为什么不在 ListView 类本身中实现?似乎微软刚刚忘记了。
    • 使用上述类的语句: using System;使用 System.ComponentModel;使用 System.Runtime.InteropServices;使用 System.Windows.Forms;
    【解决方案2】:

    安德鲁的回答很好。如果有人在这里寻找 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
    

    【讨论】:

    • 运行良好。我确实需要导入一些参考资料。 Imports System.ComponentModel Imports System.Runtime.CompilerServices Imports System.Runtime.InteropServices
    【解决方案3】:

    对于任何其他懒惰的 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;
    }
    

    【讨论】:

      【解决方案4】:

      您可以妥协并使用看起来像箭头的字符,而不是弄乱 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
      

      【讨论】:

      • 这比添加 API 调用和 PInvoke 要好得多。非常简洁的答案。谢谢。
      猜你喜欢
      • 2012-01-18
      • 1970-01-01
      • 2011-12-06
      • 1970-01-01
      • 2013-01-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多