ListView Custom Drawing in .NETCF

In this article I would like to demonstrate how to do custom drawing in the ListView control that the .NET Compact Framework provides. I'll be extending the code I published last year in the article entitled ListView Extended Styles in .NETCF

This is normally a very tedious and frustrating task to do and to accomplish this task we'll have to take advantage of the custom drawing service Windows CE provides for certain controls. A very good reference for custom drawing is an MSDN article called Customizing a Control's Appearance using Custom Draw. Before going any further, I may have to warn you about the extensive interop code involved in this task.

We'll have to handle the ListView windows messages ourselves, and we accomplish this by subclassing this ListView. Subclassing a window means that we assign a new window procedure for messages that are meant for the ListView. This can be done through the SetWindowLong() method with the GWL_WNDPROC parameter. When subclassing, the developer is responsible for choosing which messages they want to handle, which to ignore, and which they let operating system handle. To have the operating system handle the message, a call to CallWindowProc() is done using a pointer to original window procedure.

Before setting the new window procedure its important to get a pointer to the original one in case the developer wishes to let the operating system handle the message. This is done through GetWindowLong()

Let's get started...


First we need to define the interop structures for custom drawing

    struct RECT
    {
        public int left;
        public int top;
        public int right;
        public int bottom;
    }
 
    struct NMHDR
    {
        public IntPtr hwndFrom;
        public IntPtr idFrom;
        public int code;
    }
 
    struct NMCUSTOMDRAW
    {
        public NMHDR nmcd;
        public int dwDrawStage;
        public IntPtr hdc;
        public RECT rc;
        public int dwItemSpec;
        public int uItemState;
        public IntPtr lItemlParam;
    }
 
    struct NMLVCUSTOMDRAW
    {
        public NMCUSTOMDRAW nmcd;
        public int clrText;
        public int clrTextBk;
        public int iSubItem;
        public int dwItemType;
        public int clrFace;
        public int iIconEffect;
        public int iIconPhase;
        public int iPartId;
        public int iStateId;
        public RECT rcText;
        public uint uAlign;
    }

Note: In C# (and VB and C++), the StructLayout is Sequencial by default, hence I didn't state it


The P/Invoke declarations we need are the following:

    [DllImport("coredll.dll")]
    static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
 
    [DllImport("coredll")]
    static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, ref RECT lParam);
 
    [DllImport("coredll.dll")]
    static extern uint SendMessage(IntPtr hwnd, uint msg, uint wparam, uint lparam);
 
    [DllImport("coredll.dll", SetLastError = true)]
    static extern int SetWindowLong(IntPtr hWnd, int nIndex, WndProcDelegate newProc);
 
    [DllImport("coredll.dll", SetLastError = true)]
    static extern IntPtr GetWindowLong(IntPtr hWnd, int nIndex);


And to make life a bit easier, I created some extension methods to the RECT struct we just defined.

    static class RectangleExtensions
    {
        public static Rectangle ToRectangle(this RECT rectangle)
        {
            return Rectangle.FromLTRB(rectangle.left, rectangle.top, rectangle.right, rectangle.bottom);
        }
 
        public static RectangleF ToRectangleF(this RECT rectangle)
        {
            return new RectangleF(rectangle.left, rectangle.top, rectangle.right, rectangle.bottom);
        }
    }


We'll need the following constants defined in the windows platform SDK

    const int GWL_WNDPROC = -4;
    const int WM_NOTIFY = 0x4E;
    const int NM_CUSTOMDRAW = (-12);
    const int CDRF_NOTIFYITEMDRAW = 0x00000020;
    const int CDRF_NOTIFYSUBITEMDRAW = CDRF_NOTIFYITEMDRAW;
    const int CDRF_NOTIFYPOSTPAINT = 0x00000010;
    const int CDRF_SKIPDEFAULT = 0x00000004;
    const int CDRF_DODEFAULT = 0x00000000;
    const int CDDS_PREPAINT = 0x00000001;
    const int CDDS_POSTPAINT = 0x00000002;
    const int CDDS_ITEM = 0x00010000;
    const int CDDS_ITEMPREPAINT = (CDDS_ITEM | CDDS_PREPAINT);
    const int CDDS_SUBITEM = 0x00020000;
    const int CDIS_SELECTED = 0x0001;
    const int LVM_GETSUBITEMRECT = (0x1000 + 56);


Custom drawing in the ListView will only work in the Details view mode. To ensure this, I set the View to View.Details in the constructor method. Since I'm extending my old ListViewEx (Enables ListView Extended Styles) I'm gonna enable Double buffering, Grid lines, and the Gradient background. I'm gonna enable subclassing on the ListView only when the parent is changed, this is because I need to receive messages sent to the parent control of the ListView. We also need a delegate for the new window procedure and a pointer to the original window procedure. And last but not the least we need the actual window procedure method.

    delegate IntPtr WndProcDelegate(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
    IntPtr lpPrevWndFunc;
 
    public ListViewCustomDraw()
    {
        View = View.Details;
        DoubleBuffering = true;
        GridLines = true;
        Gradient = true;
 
        ParentChanged += delegate
        {
            lpPrevWndFunc = GetWindowLong(Parent.Handle, GWL_WNDPROC);
            SetWindowLong(Parent.Handle, GWL_WNDPROC, WndProc);
        };
    }
 
    private IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
    {
        if (msg == WM_NOTIFY)
        {
            var nmhdr = (NMHDR)Marshal.PtrToStructure(lParam, typeof(NMHDR));
            if (nmhdr.hwndFrom == Handle && nmhdr.code == NM_CUSTOMDRAW)
                return CustomDraw(hWnd, msg, wParam, lParam);
 
        }
 
        return CallWindowProc(lpPrevWndFunc, hWnd, msg, wParam, lParam);
    }

In the new window procedure, we are only really interested in the WM_NOTIFY message, because this is what the NM_CUSTOMDRAW message is sent through. The LPARAM parameter of the message will contain the NMHDR which then contains the NM_CUSTOMDRAW message. The LPARAM also contains the NMLVCUSTOMDRAW which provide state and information about the ListView.

The trickiest part in performing custom drawing in the ListView is handling the drawing stage. We create a method called CustomDraw to handle the different drawing stages of the ListView

    private IntPtr CustomDraw(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
    {
        int result;
        var nmlvcd = (NMLVCUSTOMDRAW)Marshal.PtrToStructure(lParam, typeof(NMLVCUSTOMDRAW));
        switch (nmlvcd.nmcd.dwDrawStage)
        {
            case CDDS_PREPAINT:
                result = CDRF_NOTIFYITEMDRAW;
                break;
 
            case CDDS_ITEMPREPAINT:
                var itemBounds = nmlvcd.nmcd.rc.ToRectangle();
                if ((nmlvcd.nmcd.uItemState & CDIS_SELECTED) != 0)
                {
                    using (var brush = new SolidBrush(SystemColors.Highlight))
                    using (var graphics = Graphics.FromHdc(nmlvcd.nmcd.hdc))
                        graphics.FillRectangle(brush, itemBounds);
                }
 
                result = CDRF_NOTIFYSUBITEMDRAW;
                break;
 
            case CDDS_SUBITEM | CDDS_ITEMPREPAINT:
                var index = nmlvcd.nmcd.dwItemSpec;
                var rect = new RECT();
                rect.top = nmlvcd.iSubItem;
                SendMessage(Handle, LVM_GETSUBITEMRECT, index, ref rect);
                rect.left += 2;
 
                Color textColor;
                if ((nmlvcd.nmcd.uItemState & CDIS_SELECTED) != 0)
                    textColor = SystemColors.HighlightText;
                else
                    textColor = SystemColors.ControlText;
 
                using (var brush = new SolidBrush(textColor))
                using (var graphics = Graphics.FromHdc(nmlvcd.nmcd.hdc))
                    graphics.DrawString(Items[index].SubItems[nmlvcd.iSubItem].Text,
                                        Font,
                                        brush,
                                        rect.ToRectangleF());
 
                result = CDRF_SKIPDEFAULT | CDRF_NOTIFYSUBITEMDRAW;
                break;
 
            default:
                result = CDRF_DODEFAULT;
                break;
        }
 
        return (IntPtr)result;
    }

In the first stage we handle is the CDDS_PREPAINT. Here we return CDRF_NOTIFYITEMDRAW to tell that we want to handle drawing of the row ourselves. After this we receive the CDDS_ITEMPREPAINT where we can draw the entire row.

We check if the row is selected through the uItemState field of NMCUSTOMDRAW, if this field has the CDIS_SELECTED flag then it means the item is selected, hence we draw a fill rectangle. After handling the CDDS_ITEMPREPAINT, we return CDRF_NOTIFYSUBITEMDRAW to tell that we want to draw the sub items ourselves.

For drawing the sub items we need to handle CDDS_SUBITEM | CDDS_ITEMPREPAINT. We can get the position index of the item through the dwItemSpec field of NMCUSTOMDRAW. To get the bounds of the current sub item we send the LVM_GETSUBITEMRECT message to the ListView and pass a pointer to RECT as the LPARAM. Before sending this message, set the "top" field of the RECT to the index of the sub item (retrieved from iSubItem field of NMLVCUSTOMDRAW. After drawing the sub item we return CDRF_SKIPDEFAULT | CDRF_NOTIFYSUBITEMDRAW to tell that we only care about handling the next sub item.

Well I hope you guys find this interesting and helpful. To keep things simple, I only demonstrated displaying plan text and a plain rectangle for the selection.

If you're interested in the full source code then you can grab it here.



How to enumerate running programs in .NETCF

I've been helping someone out in the MSDN Smart Device Forums lately with enumerating running programs. I thought I should share it since I don't much results from internet search engines on the topic. So here we go...

To enumerate top-level windows we use the EnumWindows method. To enumerate the running programs the same way the Task manager in Windows Mobile does, we just filter out what we don't need. What we don't want are windows that:

1. Have a parent window - We check by calling GetParent()
2. Is not visible - We check by calling IsWindowVisible()
3. Tool windows - We check by getting the current extended style (through GetWindowLong() of the window and checking if WS_EX_TOOLWINDOW is set

Here's a simple smart device console application the loads and displays a list of running programs:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
 
namespace EnumWindowsNETCF
{
    class Program
    {
        static Dictionary<string, IntPtr> visibleWindows = new Dictionary<string, IntPtr>();
        static StringBuilder lpString;
        static bool visible;
        static bool hasOwner;
        static bool isToolWindow;
 
        delegate int WNDENUMPROC(IntPtr hwnd, uint lParam);
        const int GWL_EXSTYLE = -20;
        const uint WS_EX_TOOLWINDOW = 0x0080;
 
        [DllImport("coredll.dll")]
        static extern int EnumWindows(WNDENUMPROC lpEnumWindow, uint lParam);
 
        [DllImport("coredll.dll")]
        static extern bool IsWindowVisible(IntPtr hwnd);
 
        [DllImport("coredll.dll")]
        static extern IntPtr GetParent(IntPtr hwnd);
 
        [DllImport("coredll.dll")]
        static extern bool GetWindowText(IntPtr hwnd, StringBuilder lpString, int nMaxCount);
 
        [DllImport("coredll.dll")]
        static extern int GetWindowLong(IntPtr hwnd, int nIndex);
 
        static int Callback(IntPtr hwnd, uint lParam)
        {
            hasOwner = GetParent(hwnd) != IntPtr.Zero;
            visible = IsWindowVisible(hwnd);
            isToolWindow = (GetWindowLong(hwnd, GWL_EXSTYLE) & WS_EX_TOOLWINDOW) != 0;
            lpString.Remove(0, lpString.Length);
            GetWindowText(hwnd, lpString, 1024);
 
            string key = lpString.ToString();
            if (!hasOwner &&
                visible &&
                !isToolWindow &&
                !string.IsNullOrEmpty(key) &&
                !visibleWindows.ContainsKey(key))
            {
                visibleWindows.Add(key, hwnd);
            }
 
            return 1;
        }
 
        static void Main()
        {
            lpString = new StringBuilder(1024);
            EnumWindows(Callback, 0);
 
            foreach (var key in visibleWindows.Keys)
            {
                IntPtr hwnd = visibleWindows[key];
                visible = IsWindowVisible(hwnd);
                lpString.Remove(0, lpString.Length);
                GetWindowText(hwnd, lpString, 1024);
 
                Debug.WriteLine("Handle: " + hwnd + "; Is Visible: " + visible + "; Text: " + lpString);
            }
        }
    }
}

I hope you find this useful, otherwise it can always be an addition to your utility library.



A long break...

Wow, I haven't noticed its been almost a year since I last wrote anything. Although I still try to take part in the community, I haven't written an article in ages. I think its time to get back into that habit and hope that I can help as much people as possible with their mobile development troubles.



Source Code Download

I've published a few articles and quite a deal of code in my blog. I realized that I never properly published the source code as Visual Studio solutions.

I will upload all source code as Visual Studio solutions to this URL:
http://cid-ca531e7fb4762c70.skydrive.live.com/browse.aspx/Code%20Samples

Special thanks to Caspar de Zinger for porting my Transparent Controls solution to VB.NET



ListView Extended Styles in .NETCF

In this article I would like to demonstrate how to extend the ListView control in the .NET Compact Framework. We will focus on enabling some of the ListView Extended Styles. If we take a look at the Windows Mobile 5.0 Pocket PC SDK we will see that there are certain features of ListView that aren't provided by the .NET Compact Framework.

An example of the ListView extended styles is displaying gridlines around items and subitems, double buffering, and drawing a gradient background. These extended styles can be enabled in native code by using the ListView_SetExtendedListViewStyle macro or by sending LVM_SETEXTENDEDLISTVIEWSTYLE messages to the ListView.

Send Message

We will be using a lot of P/Invoking so let's start with creating an internal static class called NativeMethods. We need a P/Invoke declaration for SendMessage(HWND, UINT, UINT, UINT).

internal static class NativeMethods
{
  [DllImport("coredll.dll")]
  public static extern uint SendMessage(IntPtr hwnd, uint msg, uint wparam, uint lparam);
}

Enabling and Disabling Extended Styles

Now that we have our SendMessage P/Invoke declaration in place, we can begin extending the ListView control. Let's start off with creating a class called ListViewEx that inherits from ListView. We need to look into the native header files of the Pocket PC SDK to get the ListView Messages. For now we will only need LVM_[GET/SET]EXTENDEDLISTVIEWSTYLE message which will be the main focus of all the examples. I will declare my class as a partial class and create all the pieces one by one for each example. Let's create a private method called SetStyle(), this method will enable/disable extended styles for the ListView

public partial class ListViewEx : ListView
{
  private const uint LVM_FIRST = 0x1000;
  private const uint LVM_SETEXTENDEDLISTVIEWSTYLE = LVM_FIRST + 54;
  private const uint LVM_GETEXTENDEDLISTVIEWSTYLE = LVM_FIRST + 55;

  private void SetStyle(uint style, bool enable)
  {
    uint currentStyle = NativeMethods.SendMessage(
      Handle,
      LVM_GETEXTENDEDLISTVIEWSTYLE,
      0,
      0);

    if (enable)
      NativeMethods.SendMessage(
        Handle,
        LVM_SETEXTENDEDLISTVIEWSTYLE,
        0,
        currentStyle | style);
    else
      NativeMethods.SendMessage(
        Handle,
        LVM_SETEXTENDEDLISTVIEWSTYLE,
        0,
        currentStyle & ~style);
  }
}

Grid Lines

For my first example, let's enable GridLines in the ListView control. We can do this by using LVS_EX_GRIDLINES. This displays gridlines around items and sub-items and is available only in conjunction with the Details mode.

public partial class ListViewEx : ListView
{
  private const uint LVS_EX_GRIDLINES = 0x00000001;

  private bool gridLines = false;
  public bool GridLines
  {
    get { return gridLines; }
    set
    {
      gridLines = value;
      SetStyle(LVS_EX_GRIDLINES, gridLines);
    }
  }
}

What the code above did was add the LVS_EX_GRIDLINES style to the existing extended styles by using the SetStyle() helper method we first created.

An interesting discovery to this is that the Design Time attributes of the Compact Framework ListView control includes the GridLines property. Now that we created the property in the code, when we open the Visual Studio Properties Window for our ListViewEx we will notice that GridLines property we created falls immediately under the "Appearance" category and even includes a description :)

Double Buffering

Do you notice that when you populate a ListView control with a lot of items, the drawing flickers a lot when you scroll up and down the list? Although it is not in the Pocket PC documentation for Windows Mobile 5.0, the ListView actually has an extended style called LVS_EX_DOUBLEBUFFER. Enabling the LVS_EX_DOUBLEBUFFER solves the flickering issue and gives the user a more smooth scrolling experience.

public partial class ListViewEx : ListView
{
  private const uint LVS_EX_DOUBLEBUFFER = 0x00010000;

  private bool doubleBuffering = false;
  public bool DoubleBuffering
  {
    get { return doubleBuffering; }
    set
    {
      doubleBuffering = value;
      SetStyle(LVS_EX_DOUBLEBUFFER, doubleBuffering);
    }
  }
}

Gradient Background

Another cool extended style is the LVS_EX_GRADIENT. This extended style draws a gradient background similar to the one found in Pocket Outlook. It uses the system colors and fades from right to left. But what is really cool about this is that this is done by the OS. All we had to do was enable the style.

public partial class ListViewEx : ListView
{
  private const uint LVS_EX_GRADIENT = 0x20000000;

  private bool gradient = false;
  public bool Gradient
  {
    get { return gradient; }
    set
    {
      gradient = value;
      SetStyle(LVS_EX_GRADIENT, gradient);
    }
  }
}

If you want to look more into extended styles then I suggest you check out the Pocket PC Platform SDK documentation. There a few other extended styles that I did not discuss that might be useful for you. You can get the definitions in a file called commctrl.h in your Windows Mobile SDK "INCLUDE" directory.