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:
- Create an ImageList which contains the following images: 0: empty, 1: "up" and 2: "down"
- Assign this ImageList to your ListView.SmallImageList property
- Every time you add an item into the ListView, make sure that its UseItemStyleForSubItems is set to 'false'
- Add the sorting code to your application, which is described here
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
2009-06-25 03:29:23
Do you have the Interop approach on hand?
Daniel Henry
2009-06-25 23:57:56
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
2009-06-26 05:31:20