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
CurrentItem = ListView1.GetItemAt(e.X, e.Y) If CurrentItem Is Nothing Then Exit Sub
CurrentSB = CurrentItem.GetSubItemAt(e.X, e.Y)
Dim iSubIndex As Integer = CurrentItem.SubItems.IndexOf(CurrentSB)
Select Case iSubIndex
Case 2, 3
Case Else
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
If ListView1.SelectedItems.Count = 0 Then Exit Sub
Select Case e.KeyCode
Case Keys.F2 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)
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
Select Case e.KeyChar
Case Microsoft.VisualBasic.ChrW(Keys.Return) bCancelEdit = False e.Handled = True
TextBox1.Hide()
Case Microsoft.VisualBasic.ChrW(Keys.Escape) bCancelEdit = True 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
CurrentSB.Text = CInt(TextBox1.Text).ToString("#,###0")
Else
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 SubItem
s. 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 SubItem
s as well. At the moment,
in the MouseDoubleClick
event, we've only coded for detecting mouse double-clicks on SubItem
s. 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 SubItem
s. 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 SubItem
s. 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:
If iSubIndex = 0 Then
CurrentItem.BeginEdit() 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 :-)