tags:

views:

319

answers:

1

In Windows Explorer in XP and Vista, when you sort by a column when the ListView is in Details mode, the background of the column is highlighted to indicate that the column is being sorted. An arrow for the direction of the sort is also displayed in the column header. What are the best practices to achieving these effects in your own Windows Forms application?

+2  A: 

This method emulates "Windows XP" style of selection. Vista and Win7 use extended functionality of ListViews, which is not (yet?) present in .NET. If needed, that can be done using Interop.

Here is how to do that:

  1. Create an ImageList which contains the following images: 0: empty, 1: "up" and 2: "down"
  2. Assign this ImageList to your ListView.SmallImageList property
  3. Every time you add an item into the ListView, make sure that its UseItemStyleForSubItems is set to 'false'
  4. Add the sorting code to your application, which is described here
  5. In the code which you've just added, replace the ColumnClick handler with the following code (the ListView instance here is called "_List"):

    void _List_ColumnClick(object sender, ColumnClickEventArgs e) {
        _List.SuspendLayout();
        if (e.Column == lvwColumnSorter.SortColumn) { // the column previously sorted will be flipped
            if (lvwColumnSorter.Order == SortOrder.Ascending) {
                lvwColumnSorter.Order = SortOrder.Descending;
            } else {
                lvwColumnSorter.Order = SortOrder.Ascending;
            }
        } else {    // new column will be sorted
            _List.Columns[lvwColumnSorter.SortColumn].ImageIndex = 0;
            foreach (ListViewItem lvi in _List.Items) {
                lvi.SubItems[lvwColumnSorter.SortColumn].BackColor = SystemColors.Window;
            }
            lvwColumnSorter.SortColumn = e.Column;
            lvwColumnSorter.Order = SortOrder.Ascending;
        }
        _List.Sort();
        _List.Columns[e.Column].ImageIndex = lvwColumnSorter.Order == SortOrder.Ascending ? 1 : 2;
        foreach (ListViewItem lvi in _List.Items) {
            lvi.SubItems[e.Column].BackColor = SystemColors.Info; // this is the color used for tracking
        }
        _List.ResumeLayout();
    }
    


Edit:

Below is the interop approach:

    [DllImport("user32.dll")]
    public static extern int SendMessage(IntPtr hWnd, uint message, UIntPtr wParam, IntPtr lParam);
    [DllImport("user32.dll")]
    public static extern int SendMessage(IntPtr hWnd, uint message, UIntPtr wParam, ref HDITEM lParam);

    public enum WM {
        LVM_FIRST = 0x1000,      // ListView messages
        // ...
        LVM_GETHEADER = LVM_FIRST + 31,
        // ...
        HDM_FIRST = 0x1200,      // Header messages
        // ...
        HDM_SETITEM = HDM_FIRST + 12,
        // ...
        HDM_SETFOCUSEDITEM = HDM_FIRST + 28,
    }
    public enum HDI {
        HDI_WIDTH = 0x0001,
        HDI_HEIGHT = HDI_WIDTH,
        HDI_TEXT = 0x0002,
        HDI_FORMAT = 0x0004,
        HDI_LPARAM = 0x0008,
        HDI_BITMAP = 0x0010,
        HDI_IMAGE = 0x0020,
        HDI_DI_SETITEM = 0x0040,
        HDI_ORDER = 0x0080,
        HDI_FILTER = 0x0100,
        HDI_STATE = 0x0200
    }
    public enum HDF {
        HDF_LEFT                = 0x0000,
        HDF_RIGHT               = 0x0001,
        HDF_CENTER              = 0x0002,
        HDF_JUSTIFYMASK         = 0x0003,
        HDF_RTLREADING          = 0x0004,
        HDF_BITMAP              = 0x2000,
        HDF_STRING              = 0x4000,
        HDF_OWNERDRAW           = 0x8000,
        HDF_IMAGE               = 0x0800,
        HDF_BITMAP_ON_RIGHT     = 0x1000,
        HDF_SORTUP              = 0x0400,
        HDF_SORTDOWN            = 0x0200,
        HDF_CHECKBOX            = 0x0040,
        HDF_CHECKED             = 0x0080,
        HDF_FIXEDWIDTH          = 0x0100,
        HDF_SPLITBUTTON      = 0x1000000
    }
    [StructLayout(LayoutKind.Sequential)]
    public struct HDITEM {
        public uint mask;
        public int cxy;
        public String pszText;
        public IntPtr hbm;
        public int cchTextMax;
        public int fmt;
        public IntPtr lParam;
        public int iImage;
        public int iOrder;
        public uint type;
        public IntPtr pvFilter; // this is void*
        public uint state;
    }


    void _List_ColumnClick(object sender, ColumnClickEventArgs e) {
        _List.SuspendLayout();
        HDITEM hdi = new HDITEM();
        hdi.mask = (uint)HDI.HDI_FORMAT;
        hdi.fmt = (int)HDF.HDF_STRING;
        IntPtr hHeader = (IntPtr)SendMessage(_List.Handle, (uint)WM.LVM_GETHEADER, (UIntPtr)0, (IntPtr)0);
        if (e.Column == lvwColumnSorter.SortColumn) { // the column previously sorted will be flipped
            if (lvwColumnSorter.Order == SortOrder.Ascending) {
                lvwColumnSorter.Order = SortOrder.Descending;
            } else {
                lvwColumnSorter.Order = SortOrder.Ascending;
            }
        } else {    // new column will be sorted
            SendMessage((IntPtr)hHeader, (uint)WM.HDM_SETITEM, (UIntPtr)lvwColumnSorter.SortColumn, ref hdi);
            foreach (ListViewItem lvi in _List.Items) {
                lvi.SubItems[lvwColumnSorter.SortColumn].BackColor = SystemColors.Window;
            }
            lvwColumnSorter.SortColumn = e.Column;
            lvwColumnSorter.Order = SortOrder.Ascending;
        }
        _List.Sort();
        hdi.fmt |= (lvwColumnSorter.Order == SortOrder.Ascending ? (int)HDF.HDF_SORTDOWN : (int)HDF.HDF_SORTUP);
        SendMessage((IntPtr)hHeader, (uint)WM.HDM_SETITEM, (UIntPtr)e.Column, ref hdi);
        foreach (ListViewItem lvi in _List.Items) {
            lvi.SubItems[e.Column].BackColor = SystemColors.Info; // this is the color used for tracking
        }
        _List.ResumeLayout();
    }
Rom
Do you have the Interop approach on hand?
Daniel Henry
Now I do: added to the answer. The code sample shows how the Interop is done, and the messages and constants are taken from the SDK header files, with some help from MSDN and the search engine.
Rom