ListView with Checkboxes

Yesterday I fought with getting the ListView to have CheckBoxes that didn’t “automagically” toggle whenever you tapped an item, but only when the checkbox itself was clicked.  Well, not to be outdone, I’ve got it working (rather well actually).  It was a 3 hour pain in my ass, facilitated by my earlier work with the IMessageFilter and ApplicationEx classes, and it shouldn’t have required this much effort, but hey, that’s what makes our jobs interesting.


So here’s the scoop.  First you need an IMessageFilter implementation.  This can be expanded to the other mouse events, I’ve only added the MouseDown cause that’s all I care about right now.  Feel free to add your own (submit it back if you do, please):


using System;
using System.Windows.Forms;
using System.Collections;


namespace OpenNETCF.Windows.Forms
{
 public delegate void MouseEvent(MouseEventArgs e);


 /// <summary>
 /// Summary description for MouseEventFilter.
 /// </summary>
 public class MouseEventFilter : OpenNETCF.Windows.Forms.IMessageFilter
 {
  private const int WM_MOUSEFIRST                   = 0x0200;
  private const int WM_MOUSEMOVE                    = 0x0200;
  private const int WM_LBUTTONDOWN                  = 0x0201;
  private const int WM_LBUTTONUP                    = 0x0202;
  private const int WM_LBUTTONDBLCLK                = 0x0203;
  private const int WM_RBUTTONDOWN                  = 0x0204;
  private const int WM_RBUTTONUP                    = 0x0205;
  private const int WM_RBUTTONDBLCLK                = 0x0206;
  private const int WM_MBUTTONDOWN                  = 0x0207;
  private const int WM_MBUTTONUP                    = 0x0208;
  private const int WM_MBUTTONDBLCLK                = 0x0209;
  private const int WM_MOUSELAST                    = 0x0209;
  private const int MK_LBUTTON        = 0x0001;
  private const int MK_RBUTTON        = 0x0002;
  private const int MK_SHIFT         = 0x0004;
  private const int MK_CONTROL        = 0x0008;
  private const int MK_MBUTTON        = 0x0010;


  public event MouseEvent MouseDown;
  public event MouseEvent MouseUp;
  public event MouseEvent MouseMove;


  private MouseButtons btns = 0;
  private short x = 0;
  private short y = 0;
  private byte[] pos;


  private bool m_enabled = true;
  private ArrayList m_hwnds;


  public MouseEventFilter()
  {
   m_hwnds = new ArrayList(1);
  }


  public bool Enabled
  {
   get { return m_enabled; }
   set { m_enabled = value; }
  }


  public ArrayList FilterHandles
  {
   get { return m_hwnds; }
   set { m_hwnds = value; }
  }


  #region IMessageFilter Members


  public bool PreFilterMessage(ref Microsoft.WindowsCE.Forms.Message m)
  {
   if(m_enabled)
   {
    // filter if the filter list is empty (filter all hWnds) or if it’s marked for filtering
    if((m_hwnds.Count == 0) || (m_hwnds.Contains(m.HWnd)))
    {
     switch(m.Msg)
     {
      case WM_LBUTTONDOWN:
       if(MouseDown != null)
       {
        btns = MouseButtons.None;


        btns |= ((m.WParam.ToInt32() & MK_LBUTTON) > 0) ? MouseButtons.Left : 0;
        btns |= ((m.WParam.ToInt32() & MK_RBUTTON) > 0) ? MouseButtons.Right : 0;
  
        pos = BitConverter.GetBytes(m.LParam.ToInt32());


        x = BitConverter.ToInt16(pos, 0); // LOWORD
        y = BitConverter.ToInt16(pos, 2); // HIWORD


        foreach(MouseEvent me in MouseDown.GetInvocationList())
        {


         me(new MouseEventArgs(btns, 0, x, y, 0));
        }
       }
       break;
     }
    }
   }
   return false;
  }


  #endregion
 }
}


Next you use ApplicationEx to add it to your app, something like this:


static void Main()
{
 eventFilter = new MouseEventFilter();
 myForm = new MyForm();
 ApplicationEx.AddMessageFilter(eventFilter);
 ApplicationEx.Run(myForm);
}

Then in your Form with the ListView, you add your hWnd to the Filter (for better perf):


lvwMyList.Focus();
hwndList = GetFocus();  // use P/Invoke
eventFilter.FilterHandles.Add(hwndList);


And then add a mousedown handler:


eventFilter.MouseDown += new MouseEvent(eventFilter_MouseDown);


Add implementation:


private void eventFilter_MouseDown(MouseEventArgs e)
{
 // determine which item index was clicked
 int topindex = SendMessageInt(hwndList, LVM_GETTOPINDEX, 0, 0);
 int index = topindex + (e.Y / itemheight);


 if(e.X <= 17)
 {
  // on a check
  lstReports.Items[index].ImageIndex = (lstReports.Items[index].ImageIndex == AppGlobal.ICON_CHECKED) ? AppGlobal.ICON_UNCHECKED : AppGlobal.ICON_CHECKED;
 }
 else
 {
  // navigate
  MessageBox.Show(“Navigate”);
 }
}

And voila – fucking magic.  IMHO, this is a good example of why you pay consultants consultant rates.  How long would it have taken someone with less experience to come up with that?  Today it’s a freebie.

3 thoughts on “ListView with Checkboxes”

  1. I’ve found this works quite well:-

    listView1.Activation = ItemActivation.OneClick;

    On the desktop this is available through the designer but not in .NETCF for some reason even though the property is supported.

    Like

  2. Actually no. Since Peter’s method worked for what I was after I tossed the code except for the IMessageFilter implementation (which I improved and put into the SDF).

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s