6

I have a ListView with the columns 'Name', 'Expected', 'Total', and I want to add another column saying 'Recount' at the end. The 'Recount' column will ideally have a checkbox only if the 'Expected' value is larger than the 'Total' value.

So far I have got the ListView with columns and can add a check box on the left hand side, but that check box is not under a column heading (though I can probably put another column with no values in there to work around that) and it is on all of the records.

Anyone have any ideas what else I can do?

Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
Harold
  • 1,068
  • 4
  • 14
  • 35

2 Answers2

7

This is actually relatively simple to implement, provided that you're willing to endure the drudgery of P/Invoke to access functionality built into the native Windows control, but not exposed by the .NET FW.

I demonstrate in my answer here how this exact same thing can be done with a TreeView control, and considering how similar a ListView is to a TreeView, it should not be particularly surprising that this can be done in very much the same way with a ListView.

Here's all the code that is required (make sure that you've added an Imports declaration for the System.Runtime.InteropServices namespace):

' P/Invoke declarations
Private Const LVIF_STATE As Integer = &H8
Private Const LVIS_STATEIMAGEMASK As Integer = &HF000
Private Const LVM_FIRST As Integer = &H1000
Private Const LVM_SETITEM As Integer = LVM_FIRST + 76

<StructLayout(LayoutKind.Sequential, Pack:=8, CharSet:=CharSet.Auto)> _
Private Structure LVITEM
   Public mask As Integer
   Public iItem As Integer
   Public iSubItem As Integer
   Public state As Integer
   Public stateMask As Integer
   <MarshalAs(UnmanagedType.LPTStr)> _
   Public lpszText As String
   Public cchTextMax As Integer
   Public iImage As Integer
   Public lParam As IntPtr
End Structure

<DllImport("user32.dll", CharSet:=CharSet.Auto)> _
Private Shared Function SendMessage(ByVal hWnd As IntPtr, ByVal Msg As Integer, ByVal wParam As IntPtr, ByRef lParam As LVITEM) As IntPtr
End Function

''' <summary>
''' Hides the checkbox for the specified item in a ListView control.
''' </summary>
Private Sub HideCheckBox(ByVal lvw As ListView, ByVal item As ListViewItem)
   Dim lvi As LVITEM = New LVITEM()
   lvi.iItem = item.Index
   lvi.mask = LVIF_STATE
   lvi.stateMask = LVIS_STATEIMAGEMASK
   lvi.state = 0
   SendMessage(lvw.Handle, LVM_SETITEM, IntPtr.Zero, lvi)
End Sub

And then you can simply call the above method like this:

Private Sub btnHideCheckForSelected_Click(ByVal sender As Object, ByVal e As EventArgs)
   ' Hide the checkbox next to the currently selected ListViewItem
   HideCheckBox(myListView, myListView.SelectedItems(0))
End Sub

Producing something that looks a bit like this (after clicking the "Hide Check" button for both the tomato and the cucumber items):

     Sample ListView, with checkboxes removed from individual items

Community
  • 1
  • 1
Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
  • That looks great but I'm getting the error `can't find pinvoke dll user32.dll` and can't find a user32.dll file to reference. Is there something else I'm meant to reference instead? And thanks, by the way :) – Harold May 09 '11 at 10:17
  • @Harold: Hrm... Are you running on Mono in Linux? The code assumes that you're running Windows. – Cody Gray - on strike May 09 '11 at 10:19
  • I'm running on the compact mobile framework on windows 7, visual studio 2008 – Harold May 09 '11 at 10:20
  • @Harold: I see. I don't know anything about the compact framework. I would have thought that `user32.dll` would still exist, but I can't really say for sure. Since the compact framework targets different devices, it doesn't support everything that the full framework does for a full version of Windows. – Cody Gray - on strike May 09 '11 at 10:25
  • Actually, [according to this page](http://www.christec.co.nz/blog/archives/193), it looks like the compact framework renames `user32.dll` to `coredll.dll`. For whatever absurd reason. You could try changing the P/Invoke declaration to that, instead. But there's still no guarantee it'll work. The ListView control might be different under the hood. I'm assuming details of the full Win32 API. – Cody Gray - on strike May 09 '11 at 10:26
  • That's brilliant, thank you sir. You are a gentleman and a scholar – Harold May 09 '11 at 10:54
  • @Harold: You're welcome! In the end, we're all chumps at something. I just got lucky guessing at the magic sauce for the Compact Framework. Glad it's working for you. – Cody Gray - on strike May 09 '11 at 10:55
4

C# version below.

    private void HideCheckbox(ListView lvw, ListViewItem item)
    {
        var lviItem = new LVITEM();
        lviItem.iItem = item.Index;
        lviItem.mask = LVIF_STATE;
        lviItem.stateMask = LVIS_STATEIMAGEMASK;
        lviItem.state = 0;
        SendMessage(lvw.Handle, LVM_SETITEM, IntPtr.Zero, ref lviItem);
    }

    private const int LVIF_STATE = 0x8;
    private const int LVIS_STATEIMAGEMASK = 0xF000;
    private const int LVM_FIRST = 0x1000;
    private const int LVM_SETITEM = LVM_FIRST + 76;

    // suppress warnings for interop
#pragma warning disable 0649
    private struct LVITEM
    {
        public int mask;
        public int iItem;
        public int iSubItem;
        public int state;
        public int stateMask;
        [MarshalAs(UnmanagedType.LPTStr)]
        public String lpszText;
        public int cchTextMax;
        public int iImage;
        public IntPtr iParam;
    }
#pragma warning restore 0649

    [DllImport("user32.dll")]
    private static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam,  ref LVITEM lParam);

    private void button1_Click(object sender, EventArgs e)
    {
        HideCheckbox(listView1, listView1.SelectedItems[0]);
    }    
Jamie Ide
  • 48,427
  • 16
  • 81
  • 117