ListView 内联编辑






4.89/5 (10投票s)
一种在 .NET 中支持 ListView 控件内联(子项)编辑的快速简便方法。
引言
在 Windows 编程中,ListView
是一个很棒的控件!特别是它支持各种视图。但是,Visual Studio 提供的原始 ListView
控件非常有限,如果不进行核心级别编程,就无法利用其无限的可能性。编辑 ListView
控件列中的文本是为最终用户提供这种基本功能所必须完成的困难任务之一。
背景
许多 ListView
控件程序员的基本需求是允许最终用户在运行时编辑控件的内容。但是,ListView
只允许在“详细信息”视图中编辑第一列。其他列怎么办?
互联网上有许多现成的免费控件,它们提供了强大的功能并打破了 ListView
控件的限制。一个值得注意的控件是 ObjectListView 控件
。但是,我总觉得为什么只为了在 ListView
控件中实现如此简单的功能,却要使用这样一个难以编程的复杂控件。
因此,这里提供了一种简单的编码技术,允许最终用户编辑 ListView
控件的列,而无需使用任何第三方控件。它所做的只是监视键盘和鼠标点击,并使用一个简单的 TextBox
控件进行编辑。
尽管本文的重点是启用 ListView
控件的内联编辑,但我的示例项目还展示了基本的 XML 文档处理过程,例如如何在 XML 中加载、查询和保存数据。
Using the Code
要开始制作此类应用程序,请在 Visual Studio 中创建一个新的 Windows 窗体应用程序。我们将需要原始的 ListView
控件和一个 TextBox
控件。将这两个控件放在窗体上,并将 TextBox
的可见性设置为 False
。
在我的示例中,我将通过检查鼠标点击和键盘按键来观察用户与 ListView
控件的交互。用户可以双击 SubItem
开始编辑,或者也可以按 F2 等快捷键开始编辑。
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
通过使用 ListView
的 MouseDoubleClick
事件,我们可以灵活地查看单击了哪个 ListViewItem
,并从中检测单击了哪个 SubItem
。
在这里,您有机会决定要将哪些列设置为“只读”。例如,您可能不希望用户编辑某些列。在我的示例中,我只允许在运行时编辑第 2 列和第 3 列。其余列将是“只读”。(请参阅 Select-Case 代码,它决定允许编辑哪些列。)
最后,我们在双击的 SubItem
上方显示一个 TextBox
控件(它已存在于窗体上,但设置为不可见)。我们将 TextBox
调整大小以覆盖该 SubItem
的整个区域。
当用户双击 SubItem
时,会触发上述代码。现在我们还需要监听键盘敲击。以下代码允许我们实现此目的
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
如您在上述代码中看到的,我们正在监听 ListView
的 KeyDown
事件中的 F2 键。一旦 F2 键被按下,我们必须手动启动 SubItem
编辑。手动的意思是再次执行在 MouseDoubleClick
中执行的所有代码。由于我的编程风格是不重复相同的代码,我将通过 KeyDown
事件模拟鼠标双击。您实际上不需要这样做。您可以提出更好、更高效的编码。一个好的程序员总是通过尽可能地重用代码来最小化代码,而不是一遍又一遍地编写相同的代码。无论如何,回到主题……
BeginEditListItem
是一个子程序,它模拟鼠标双击以触发 ListView
的 MouseDoubleClick
事件并开始编辑 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
在上述代码中,我们模拟鼠标双击以启动 SubItem
的编辑。请记住,此 Sub
是从 KeyDown
事件调用的,即当按下 F2 时。
所以……此时,我们已经完成了启动 ListView
控件中 ListItem
的 SubItem
编辑的编码。我们已经捕获了鼠标双击和键盘的 F2 键击。现在我们必须弄清楚用户何时完成了 SubItem
文本的编辑。
为了检测用户何时完成 SubItem
的编辑,使用以下代码
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
上述代码监视 TextBox
中的两种按键,回车键和 Esc 键。回车键表示编辑完成,Esc 键当然表示取消编辑。是否保存更改,它保存在 bCancelEdit
变量中,然后 TextBox
再次设置为不可见。通过隐藏 TextBox
,它会在 TextBox
上触发 LostFocus
事件。从那里,我们将捕获用户输入的新文本以及是否更新 SubItem
或放弃更改。
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
您还可以在此处设置对输入的文本进行验证。例如,您可能只希望在该列中输入数字值,因此您可以检查 TextBox
的 Text
属性并在接受新输入的文本之前向用户显示适当的消息。
就是这样。这就是我们启用 SubItem
编辑所需的所有操作。但是,我们必须克服一个小障碍……
障碍
ListView
默认只允许编辑第一列。我们做了上述所有代码,使其也适用于其 SubItem
。目前,在 MouseDoubleClick
事件中,我们只编写了代码来检测 SubItem
上的鼠标双击。如果用户单击了第一列怎么办?这实际上不是问题,只是您无法像获取 SubItem
的 Bounds
一样获取第一列的 Bounds
。如果您尝试检查第一列的 Bounds
属性,它将返回 ListItem
整个行的尺寸,而不仅仅是第一列。而如果您检查除第一列之外的任何 SubItem
的 Bounds
属性,它将只告诉您该特定列的尺寸。
因此,如果我们在第一列上显示 TextBox
并使用第一列的 Bounds
属性调整其大小,它最终会扩散到整个行并覆盖所有 SubItem
。那么我们如何克服这个障碍呢?简单!我们使用 ListView
原本支持的标签编辑。我们所要做的就是检查用户是否双击了第一列,如果是这种情况,我们中止所有复杂的编码并通过调用 BeginEdit
方法启动 ListView
自己的标签编辑。为此,我们必须向 ListView
的 MouseDoubleClick
事件添加另一段代码
' 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
此代码插入在我们调整 TextBox
控件大小之前。如您所见,我们检查单击的 SubItem
的索引是否为零,即第一列。如果是,我们中止代码并对选定的 ListItem
调用 BeginEdit
。当然,这要求在运行此代码之前将 ListView
的 LabelEdit
属性设置为 True
。最好在设计时设置。
示例项目
下载并查看示例项目。请注意,此项目是使用 Visual Studio 10 和 .NET Framework 2.0 制作的。如果您的系统上未安装此框架,您可以切换到其他框架。(打开项目属性,转到左侧的“编译”选项卡,单击“高级编译选项”,然后选择您的目标框架。)
希望它能帮助您提高界面设计技能 :-)