VB.NET Listview 多列过滤器

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/13976557/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-17 11:37:19  来源:igfitidea点击:

VB.NET Listview Multiple Column Filter

vb.netlistviewsearch

提问by Alex

My goal is to develop a search using multiple textboxes. I have five columns (ArticleNo, Description, PartNum, Manufacturer and Cost) each of which will have a textbox over them.

我的目标是使用多个文本框开发搜索。我有五列(ArticleNo、Description、PartNum、Manufacturer 和 Cost),每一列上面都有一个文本框。



I keep track of the original list items using:

我使用以下方法跟踪原始列表项:

Private originalListItems As New List(Of ListViewItem)

This is filled with all the items (over 6000).

这充满了所有项目(超过 6000)。

Then I will have five "text changed" events occuring based on the five textboxes created (tbSearchArticleNo, tbSearchDescription, tbSearchPartNum ... etc)

然后我将根据创建的五个文本框(tbSearchArticleNo、tbSearchDescription、tbSearchPartNum ...等)发生五个“文本更改”事件

Private Sub tbSearchArticleNo_TextChanged(sender As Object, e As System.EventArgs) Handles tbSearchArticleNo.TextChanged
    If tbSearchDesc.Text <> "" Or tbPartNum.Text <> "" Or tbManufacturer.Text <> "" Or tbCost.Text <> "" Then
        SearchCurrentList(lwArticles, tbSearchArticleNo.Text, 0, False)
    Else
        SearchListView(lwArticles, tbSearchArticleNo.Text, 0, False)
    End If
End Sub

Here is my method SearchCurrentList:

这是我的方法 SearchCurrentList:

Private Sub SearchCurrentList(ByVal listview As ListView, ByVal search As String, ByVal colIndex As Integer, ByVal upperCase As Boolean)
    If upperCase Then
        search = search.ToUpper()
    End If

    listview.BeginUpdate()

    'Clear listview
    lwArticles.Items.Clear()

    'Other textbox has information in it, concatenate both results
    For Each item In currentListItems
        Dim itemToUpper = item.SubItems.Item(colIndex).Text
        If upperCase Then
            itemToUpper = item.SubItems.Item(colIndex).Text.ToUpper()
        End If
        If itemToUpper.Contains(search) Then
            lwArticles.Items.Add(item)
        End If
    Next

    'Reupdate the current list of items
    currentListItems.Clear()
    For Each item In lwArticles.Items
        currentListItems.Add(item)
    Next

    listview.EndUpdate()
End Sub

And here is my method SearchListView:

这是我的方法 SearchListView:

Private Sub SearchListView(ByVal listview As ListView, ByVal search As String, ByVal colIndex As Integer, ByVal upperCase As Boolean)
    'Upper case parameter determines if you're searching a string, if so, it is better to compare everything by uppercase
    If upperCase Then
        search = search.ToUpper()
    End If

    listview.BeginUpdate()

    If search.Trim().Length = 0 Then
        'Clear listview
        listview.Items.Clear()

        'Clear currentListItems
        currentListItems.Clear()

        'If nothing is in the textbox make all items appear
        For Each item In originalListItems
            listview.Items.Add(item)
        Next

    Else
        'Clear listview
        listview.Items.Clear()

        'Clear currentListItems
        currentListItems.Clear()

        'Go through each item in the original list and only add the ones which contain the search text
        For Each item In originalListItems
            Dim currItem = item.SubItems.Item(colIndex).Text
            If upperCase Then
                currItem = currItem.ToUpper()
            End If
            If currItem.Contains(search) Then
                currentListItems.Add(item)
                listview.Items.Add(item)
            End If
        Next
    End If

    listview.EndUpdate()
End Sub

Here's an example of my search:

这是我的搜索示例:

tbSearchArticleNo.Text = "33"

tbSearchArticleNo.Text = "33"

This will match every articleNo that contains "33" in the string. Now I want to add another filter:

这将匹配字符串中包含“33”的每个 articleNo。现在我想添加另一个过滤器:

tbSearchDescription.Text = "Mixer"

tbSearchDescription.Text = "混合器"

This should match everything that contains 33 in the article number as well as "mixer" in the description. And so on and so fourth.

这应该与商品编号中包含 33 的所有内容以及描述中的“mixer”相匹配。依此类推,第四。



The actual filters are working correctly - my only problem is whenever I erase something, such as "Mixer" (while still having "33" in the articleNo) it doesn't return the results of the articleNo containing "33" ... Instead it doesn't change the results of my search. There might be a better way of searching through this?

实际的过滤器工作正常 - 我唯一的问题是每当我擦除某些东西时,例如“Mixer”(虽然文章编号中仍然有“33”),它不会返回包含“33”的文章编号的结果......相反它不会改变我的搜索结果。可能有更好的搜索方式吗?

采纳答案by BlueMonkMN

A somewhat different way of handling this is to use LINQ. The following function could be used to return an object that enumerates the provided collection including only those items that fit the filter. You could use this enumerator to re-populate your list. If you used originalListItems each time you called GetFilter, you would always have every item included for consideration in the latest filter.

处理此问题的一种稍微不同的方法是使用 LINQ。以下函数可用于返回一个对象,该对象枚举所提供的集合,仅包括那些适合过滤器的项目。您可以使用此枚举器重新填充您的列表。如果您每次调用 GetFilter 时都使用 originalListItems,那么您将始终将每个项目都包含在最新的过滤器中以供考虑。

Function GetFilter(source As IEnumerable(Of ListViewItem), articleNo As String, description As String,
                  partNum As String, prop4 As String, prop5 As String) As IQueryable(Of ListViewItem)
  GetFilter = source.AsQueryable

  Dim articleFilter As Expressions.Expression(Of Func(Of ListViewItem, Boolean)) = _
     Function(i As ListViewItem) i.SubItems(0).Text.IndexOf(articleNo, StringComparison.InvariantCultureIgnoreCase) >= 0
  Dim descFilter As Expressions.Expression(Of Func(Of ListViewItem, Boolean)) = _
     Function(i As ListViewItem) i.SubItems(1).Text.IndexOf(description, StringComparison.InvariantCultureIgnoreCase) >= 0
  Dim partFilter As Expressions.Expression(Of Func(Of ListViewItem, Boolean)) = _
     Function(i As ListViewItem) i.SubItems(2).Text.IndexOf(partNum, StringComparison.InvariantCultureIgnoreCase) >= 0
  Dim prop4Filter As Expressions.Expression(Of Func(Of ListViewItem, Boolean)) = _
     Function(i As ListViewItem) i.SubItems(3).Text.IndexOf(prop4, StringComparison.InvariantCultureIgnoreCase) >= 0
  Dim prop5Filter As Expressions.Expression(Of Func(Of ListViewItem, Boolean)) = _
     Function(i As ListViewItem) i.SubItems(4).Text.IndexOf(prop5, StringComparison.InvariantCultureIgnoreCase) >= 0

  If Not String.IsNullOrEmpty(articleNo) Then GetFilter = Queryable.Where(GetFilter, articleFilter)
  If Not String.IsNullOrEmpty(description) Then GetFilter = Queryable.Where(GetFilter, descFilter)
  If Not String.IsNullOrEmpty(partNum) Then GetFilter = Queryable.Where(GetFilter, partFilter)
  If Not String.IsNullOrEmpty(prop4) Then GetFilter = Queryable.Where(GetFilter, prop4Filter)
  If Not String.IsNullOrEmpty(prop5) Then GetFilter = Queryable.Where(GetFilter, prop5Filter)
End Function

Better yet, with a little more thought, you could probably make articleNo and the other parameters into variables with a larger scope, and adjust the function to embed IsNullOrEmpty checking into the Queryable expression, and then you wouldn't need to even re-generate the filter when a field value changes. You could just set the variable to the new textbox value and re-evaluate the already-generated filter expression, which will consider the new values in the variables thus yielding newly filtered results.

更好的是,稍加思索,或许可以将 articleNo 和其他参数变成更大范围的变量,并调整函数,将 IsNullOrEmpty 检查嵌入到 Queryable 表达式中,然后您甚至不需要重新生成字段值更改时的过滤器。您可以将变量设置为新的文本框值并重新评估已经生成的过滤器表达式,它将考虑变量中的新值,从而产生新的过滤结果。

Here's how I would expect it to be used:

这是我期望它的使用方式:

lwArticles.Items.Clear()
For Each i In GetFilter(originalListItems, tbSearchArticleNo.Text, tbSearchDesc.Text, tbPartNum.Text, tbManufacturer.Text, tbCost.Text)
   lwArticles.Items.Add(i)
Next

回答by BlueMonkMN

Instead of only filtering one column at a time and passing the current list in to further filter the results, how about having a single function that filters based on all the specified values at once:

与其一次只过滤一列并传入当前列表以进一步过滤结果,不如使用一个函数一次基于所有指定值进行过滤:

  1. Call a single "Filter" function from the TextChanged (or Validated) event of every filter Textbox.
  2. In the Filter function, start by re-copying originalListItems into lwArticles.
  3. Apply filters for each Textbox that has a value in sequence. (Call SearchCurrentList for each textbox, passing in lwArticles. Each step filters down the list further from the previous, but only doing work for textboxes that have values.)
  1. 从每个过滤器文本框的 TextChanged(或 Validated)事件调用单个“过滤器”函数。
  2. 在 Filter 函数中,首先将 originalListItems 重新复制到 lwArticles 中。
  3. 按顺序为每个具有值的文本框应用过滤器。(为每个文本框调用 SearchCurrentList,传入 lwArticles。每一步都从前一步过滤掉列表,但只对具有值的文本框起作用。)

回答by Steve

Its hard to follow what you are trying to do, but i suggest if you are dealing with 6k items in a listview and want to filter them, perhaps you should use a databound gridview instead.

很难遵循您要执行的操作,但我建议如果您在列表视图中处理 6k 项并想要过滤它们,也许您应该改用数据绑定网格视图。

Then you can perform the search on the datasource, very simply:

然后你可以在数据源上执行搜索,非常简单:

Public Class Form1
Private _articleList As List(Of Article)

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
    'populate _itemList somehow, for example from a database. Manually here for example purposes:
    _articleList = New List(Of Article) From {
        New Article("jenny cooks fish", "cooking"),
        New Article("a better sales team", "sales")}
    DataGridView1.DataSource = _articleList
End Sub


Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
    Dim filtered As List(Of Article) = _articleList.Where(Function(x) x.Title.Contains("cook") AndAlso x.Category = "cooking").ToList
    DataGridView1.DataSource = filtered

End Sub
End Class

Public Class Article
Property Title As String
Property Category As String
'etc etc

Public Sub New(ByVal title As String, ByVal category As String)
    _Title = title
    _Category = category
End Sub
End Class

回答by Alex

I managed to get it to work. For those wondering how, it's not that elegant - but it works!

我设法让它工作。对于那些想知道如何做的人来说,它不是那么优雅 - 但它确实有效!

I set tags on the GUI of the four textboxes (i omitted the cost textbox after further talk with the engineers). So..

我在四个文本框的 GUI 上设置了标签(在与工程师进一步交谈后,我省略了成本文本框)。所以..

tbSearchArticleNo.Tag = 1

tbSearchArticleNo.Tag = 1

tbSearchDesc.Tag = 2

tbSearchDesc.Tag = 2

tbPartNum.Tag = 4

tbPartNum.Tag = 4

tbManufacturer.Tag = 8

tbManufacturer.Tag = 8

This isn't elegant because you will exponentially grow as you add textboxes. (be careful) Depending on the fields entered, a total is calculated which will then be treated by my FilterOriginalList(total)

这并不优雅,因为随着您添加文本框,您将呈指数增长。(小心)根据输入的字段,计算总数,然后由我的 FilterOriginalList(total) 处理

Private Sub tbSearchArticleNo_TextChanged(sender As Object, e As System.EventArgs) Handles tbSearchArticleNo.TextChanged, tbSearchDesc.TextChanged, tbPartNum.TextChanged, tbManufacturer.TextChanged
    Dim tag1 As Integer = 0
    Dim tag2 As Integer = 0
    Dim tag3 As Integer = 0
    Dim tag4 As Integer = 0

    If tbSearchArticleNo.Text <> "" Then
        tag1 = tbSearchArticleNo.Tag
    End If

    If tbSearchDesc.Text <> "" Then
        tag2 = tbSearchDesc.Tag
    End If

    If tbPartNum.Text <> "" Then
        tag3 = tbPartNum.Tag
    End If

    If tbManufacturer.Text <> "" Then
        tag4 = tbManufacturer.Tag
    End If

    FilterOriginalList(tag1 + tag2 + tag3 + tag4)
End Sub

Here is my method:

这是我的方法:

Private Sub FilterOriginalList(ByRef tagCounter As Integer)
    Dim field1 As String = tbSearchArticleNo.Text
    Dim field2 As String = tbSearchDesc.Text.ToUpper()
    Dim field4 As String = tbPartNum.Text.ToUpper()
    Dim field8 As String = tbManufacturer.Text.ToUpper()

    lwArticles.BeginUpdate()

    'Clear listview
    lwArticles.Items.Clear()

    For Each item In originalListItems
        Dim currField1 = item.SubItems.Item(0).Text
        Dim currField2 = item.SubItems.Item(1).Text.ToUpper
        Dim currField4 = item.SubItems.Item(2).Text.ToUpper
        Dim currField8 = item.SubItems.Item(3).Text.ToUpper

        Select Case (tagCounter)
            Case 0
                lwArticles.Items.Add(item)
            Case 1
                If currField1.Contains(field1) Then
                    lwArticles.Items.Add(item)
                End If
            Case 2
                If currField2.Contains(field2) Then
                    lwArticles.Items.Add(item)
                End If
            Case 3
                If currField1.Contains(field1) And currField2.Contains(field2) Then
                    lwArticles.Items.Add(item)
                End If
            Case 4
                If currField4.Contains(field4) Then
                    lwArticles.Items.Add(item)
                End If
            Case 5
                If currField1.Contains(field1) And currField4.Contains(field4) Then
                    lwArticles.Items.Add(item)
                End If
            Case 6
                If currField2.Contains(field2) And currField4.Contains(field4) Then
                    lwArticles.Items.Add(item)
                End If
            Case 7
                If currField1.Contains(field1) And currField2.Contains(field2) And currField4.Contains(field4) Then
                    lwArticles.Items.Add(item)
                End If
            Case 8
                If currField8.Contains(field8) Then
                    lwArticles.Items.Add(item)
                End If
            Case 9
                If currField1.Contains(field1) And currField8.Contains(field8) Then
                    lwArticles.Items.Add(item)
                End If
            Case 10
                If currField2.Contains(field2) And currField8.Contains(field8) Then
                    lwArticles.Items.Add(item)
                End If
            Case 11
                If currField1.Contains(field1) And currField2.Contains(field2) And currField8.Contains(field8) Then
                    lwArticles.Items.Add(item)
                End If
            Case 12
                If currField4.Contains(field4) And currField8.Contains(field8) Then
                    lwArticles.Items.Add(item)
                End If
            Case 13
                If currField1.Contains(field1) And currField4.Contains(field4) And currField8.Contains(field8) Then
                    lwArticles.Items.Add(item)
                End If
            Case 14
                If currField2.Contains(field2) And currField4.Contains(field4) And currField8.Contains(field8) Then
                    lwArticles.Items.Add(item)
                End If
            Case 15
                If currField1.Contains(field1) And currField2.Contains(field2) And currField4.Contains(field4) And currField8.Contains(field8) Then
                    lwArticles.Items.Add(item)
                End If
        End Select
    Next
    lwArticles.EndUpdate()
End Sub

Thanks for everyone who helped. Here's the solution I found - there's probably something much easier that I will be looking into in the futur!

感谢所有帮助过的人。这是我找到的解决方案 - 我将来可能会研究更简单的方法!