Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

ListView in-line editing

0.00/5 (No votes)
18 Jan 2012 1  
A quick and easy way to support in-line (subitems) editing on the ListView control in .NET.

Introduction

In Windows programming, ListView is a great control! Especially with the variety of views it supports. But alas, the original ListView control supplied by Visual Studio is very limited and its endless possibilities cannot be exploited without going through core level programming. Editing the text in the columns of the ListView control is one of the difficult tasks that one has to do to allow such basic facility to end-users.

Background

The basic need of many programmers of ListView control is to allow users to edit the content of the control during runtime by end-users. ListView however only allows the first column to be edited in the 'Detailed' view. What about other columns?

There are many off-the-shelf free controls available on the internet that allow great facilities and bend the limits of the ListView control. One noteworthy control is the ObjectListView control. But somehow I feel why use such a difficult control, which is difficult to program as well, just to allow such a simple feature in the ListView control.

So here's a simple coding technique that allows your end-users to edit columns of the ListView control without using any third-party controls. All it does is monitor the keyboard and mouse clicks and uses a simple TextBox control to allow editing.

Although my focus in this article is to enable in-line editing on the ListView control, my sample project also shows basic XML document handling procedures, like how to load, query, and save data in XML.

Using the Code

To begin making such an application, create a new Windows Forms Application in Visual Studio. We're going to need the original ListView control and a TextBox control. Place these two controls on your form and set the TextBox's visibility to False.

In my example, I will be looking at the user's interaction with the ListView control by checking the mouse clicks and keyboard keys. A user can double-click on a SubItem to begin editing, or can also press a shortcut key like F2 on keyboard to begin editing.

Private Sub ListView1_MouseDoubleClick(sender As Object, _
        e As System.Windows.Forms.MouseEventArgs) Handles ListView1.MouseDoubleClick
    ' This subroutine checks where the double-clicking was performed and
    ' initiates in-line editing if user double-clicked on the right subitem

    ' check where clicked
    CurrentItem = ListView1.GetItemAt(e.X, e.Y)     ' which listviewitem was clicked
    If CurrentItem Is Nothing Then Exit Sub
    CurrentSB = CurrentItem.GetSubItemAt(e.X, e.Y)  ' which subitem was clicked

    ' See which column has been clicked

    ' NOTE: This portion is important. Here you can define your own
    '       rules as to which column can be edited and which cannot.
    Dim iSubIndex As Integer = CurrentItem.SubItems.IndexOf(CurrentSB)
    Select Case iSubIndex
        Case 2, 3
            ' These two columns are allowed to be edited. So continue the code
        Case Else
            ' In my example I have defined that only "Runs"
            ' and "Wickets" columns can be edited by user
            Exit Sub
    End Select

    Dim lLeft = CurrentSB.Bounds.Left + 2
    Dim lWidth As Integer = CurrentSB.Bounds.Width
    With TextBox1
        .SetBounds(lLeft + ListView1.Left, CurrentSB.Bounds.Top + _
                   ListView1.Top, lWidth, CurrentSB.Bounds.Height)
        .Text = CurrentSB.Text
        .Show()
        .Focus()
    End With
End Sub

By using ListView's MouseDoubleClick event, we have the flexibility to look at which ListViewItem was clicked, and from there we can detect which SubItem was clicked.

Here you have the opportunity to decide which column you want to make 'read-only'. For example, you may not want some columns to be edited by users. In my example, I've only allowed column number 2 and 3 to be edited during runtime. Rest of the columns are to be 'read-only'. (See the Select-Case coding, it determines which columns to allow editing.)

Finally, we display a TextBox control (which is already present on the form, but set as invisible) on top of the SubItem which was double-clicked. We resize the TextBox to cover the entire area of that SubItem.

The above code is triggered when the user double-clicks on the SubItem. Now we also need to listen to keyboard strokes. The following code allows us to achieve this:

Private Sub LV_KeyDown(sender As Object, _
        e As System.Windows.Forms.KeyEventArgs) Handles ListView1.KeyDown
    ' This subroutine is for starting editing when keyboard shortcut is pressed (e.g. F2 key)
    If ListView1.SelectedItems.Count = 0 Then Exit Sub

    Select Case e.KeyCode
        Case Keys.F2    ' F2 key is pressed. Initiate editing
            e.Handled = True
            BeginEditListItem(ListView1.SelectedItems(0), 2)
    End Select
End Sub

As you can see in the above code, we're listening for the F2 key in ListView's KeyDown event. Once the F2 key is pressed, we have to manually initiate SubItem editing. By manually I mean performing all that code again which was performed in MouseDoubleClick. Since it's not my programming style not to repeat the same piece of code over and over again, I am going to fake a mouse double-click through the KeyDown event. You don't need to do this really. You could come up with a better and more efficient piece of coding. A good programmer always minimizes his code by making it reusable as much as possible, and not write the same code over and over again. Anyway, back to the topic...

BeginEditListItem is a subroutine that fakes a mouse double-click in order to trigger the ListView's MouseDoubleClick event and commence editing a SubItem.

Private Sub BeginEditListItem(iTm As ListViewItem, SubItemIndex As Integer)
    ' This subroutine is for manually initiating editing instead of mouse double-clicks

    Dim pt As Point = iTm.SubItems(SubItemIndex).Bounds.Location
    Dim ee As New System.Windows.Forms.MouseEventArgs(Windows.Forms.MouseButtons.Left, 2, pt.X, pt.Y, 0)
    Call ListView1_MouseDoubleClick(ListView1, ee)
End Sub

In the above code, we fake a mouse double-click to initiate editing of a SubItem. Remember this Sub is called from the KeyDown event, i.e., when F2 is pressed.

So.. at this point, we're done with coding for initiating the editing of a SubItem of a ListItem in a ListView control. We've captured the mouse-double click and the keyboard's F2 keystroke. Now we have to figure out when the user has finished editing the SubItem text.

To detect when the user has done editing a SubItem, the following code is used:

Private Sub TextBox1_KeyPress(sender As Object, _
            e As System.Windows.Forms.KeyPressEventArgs) Handles TextBox1.KeyPress
    ' This subroutine closes the text box

    Select Case e.KeyChar

        Case Microsoft.VisualBasic.ChrW(Keys.Return)    ' Enter key is pressed
            bCancelEdit = False ' editing completed
            e.Handled = True
            TextBox1.Hide()

        Case Microsoft.VisualBasic.ChrW(Keys.Escape)    ' Escape key is pressed
            bCancelEdit = True  ' editing was cancelled
            e.Handled = True
            TextBox1.Hide()

    End Select

End Sub

The above code monitors two types of keystrokes in the TextBox, the Enter key and the Escape key. The Enter key means the editing is finished, and the Escape key of course means cancel the editing. Whether to save the changes or not, it is saved in the bCancelEdit variable and then the TextBox is set to invisible again. By hiding the TextBox, it triggers a LostFocus event on the TextBox. From there, we will capture what new text was entered by the user and whether to update the SubItem or discard the changes.

Private Sub TextBox1_LostFocus(sender As Object, e As System.EventArgs) Handles TextBox1.LostFocus

    TextBox1.Hide()

    If bCancelEdit = False Then

        ' update listviewitem
        CurrentSB.Text = CInt(TextBox1.Text).ToString("#,###0")


    Else

        ' Editing was cancelled by user
        bCancelEdit = False

    End If

    ListView1.Focus()

End Sub

Here you can also set up validations on the text entered. For example, you might want only numeric values to be entered in that column, so you can check the Text property of the TextBox and show appropriate messages to the user before accepting the newly entered text.

That's just about it. That's all we need to do to enable editing on SubItems. However, there's a slight obstacle that we have to overcome...

Obstacle

The ListView by default allows editing of the first column only. We did all the above code to make it work on its SubItems as well. At the moment, in the MouseDoubleClick event, we've only coded for detecting mouse double-clicks on SubItems. What if the user had clicked on the first column? It is really no issue except that you cannot get the Bounds of the first column like you can get for SubItems. If you try to check the Bounds property of the first column, it will return you the size of the entire row of the ListItem and not just the first column. Whereas if you check the Bounds property of any SubItem other than the first column, it'll tell you only the size of that particular column.

So if we show our TextBox on the first column and resize it using the first column's Bounds property, it ends up spreading all over the entire row and covers up all the SubItems. So how do we overcome this obstacle? Easy..! We use the ListView's originally supported label editing. All we have to do is to check if the user double-clicked on the first column, and if that's the case, we abort all our tricky coding and initiate the ListView's own label editing by calling the BeginEdit method. For this, we have to add another piece of code to the ListView's MouseDoubleClick event:

' Check if the first subitem is being edited
If iSubIndex = 0 Then
    ' There's a slight coding difficulty here. If the first item is to be edited
    ' then when you get the Bounds of the first subitem, it returns the Bounds of
    ' the entire ListViewItem. Hence the Textbox looks very wierd. In order to allow
    ' editing on the first column, we use the built-in editing method.

    CurrentItem.BeginEdit()     ' make sure the LabelEdit is set to True
    Exit Sub
End If

This code is inserted right before we resize our TextBox control. As you can see, we check whether the index of the SubItem clicked was zero, i.e., the first column. If so, we abort the code and call BeginEdit on the selected ListItem. Of course, this requires that ListView's LabelEdit property is set to True prior to running into this code. Preferably at design-time.

Sample Project

Download and see the sample project. Please note, this project has been made with .NET Framework 2.0 using Visual Studio 10. You can switch to another framework in case this framework is not installed on your system. (Open project properties, go to Compile tab on the left, click Advanced Compile Options, and select your target framework.)

Hope it helps improve your interface designing skills :-)

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here