【问题标题】:VB.NET Listview Multiple Column FilterVB.NET Listview 多列过滤器
【发布时间】:2012-12-20 16:33:37
【问题描述】:

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


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

Private originalListItems As New List(Of ListViewItem)

这里填满了所有项目(超过 6000 个)。

然后我将根据创建的五个文本框(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

这是我的方法 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

这是我的方法 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

这是我的搜索示例:

tbSearchArticleNo.Text = "33"

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

tbSearchDescription.Text = "混音器"

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


实际的过滤器工作正常 - 我唯一的问题是每当我删除某些内容时,例如“Mixer”(虽然 articleNo 中仍然有“33”),但它不会返回包含“33”的 articleNo 的结果。 ..相反,它不会改变我的搜索结果。可能有更好的方法来搜索这个?

【问题讨论】:

    标签: vb.net listview search


    【解决方案1】:

    处理此问题的另一种方式是使用 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
    

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

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

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

    【讨论】:

    • 这听起来很有趣,尽管到目前为止我还没有做过任何 LINQ。我对如何调用此函数感到困惑:GetFilter(lwArticles.Items, tbSearchArticleNo.Text, tbSearchDesc.Text, tbPartNum.Text, tbManufacturer.Text, tbCost.Text) 给我错误:Impossibleto cast object of type 'ListViewItemCollection'键入“System.Collections.Generic.IEnumerable`1[System.Windows.Forms.ListViewItem]”。
    • 您是否尝试过我的建议,即使用 originalListItems 调用它而不是尝试传入列表框的项目集合?
    • 是的,我做到了,但没有收到错误。尽管似乎什么都没有整理好。我使用函数 GetFilter(originalListItems, tbSearchArticleNo.Text, tbSearchDesc.Text, tbPartNum.Text, tbManufacturer.Text, tbCost.Text)。当我这样做时没有错误,但它似乎没有触发任何过滤。无论如何,我创建了一个长的冗余选择案例,现在可以使用。另外,非常感谢您的帮助!
    • 该函数不编辑列表,它返回一个对象(如列表的副本),您可以使用该对象用过滤结果重新填充列表框。您需要在 GetFilter(...) 中使用 ForEach i 之类的代码:lwArticles.Items.Add(i):Next
    • @Alex 我添加了一个示例,说明我希望如何使用该函数。
    【解决方案2】:

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

    1. 从每个过滤器文本框的 TextChanged(或 Validated)事件中调用单个“过滤器”函数。
    2. 在 Filter 函数中,首先将 originalListItems 重新复制到 lwArticles 中。
    3. 为每个具有序列值的文本框应用过滤器。 (为每个文本框调用 SearchCurrentList,传入 lwArticles。每一步都从上一步进一步过滤列表,但只对具有值的文本框起作用。)

    【讨论】:

      【解决方案3】:

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

      然后就可以对数据源进行搜索了,非常简单:

      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
      

      【讨论】:

        【解决方案4】:

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

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

        tbSearchArticleNo.Tag = 1

        tbSearchDesc.Tag = 2

        tbPartNum.Tag = 4

        tbManufacturer.Tag = 8

        这并不优雅,因为随着您添加文本框,您将呈指数级增长。 (注意)根据输入的字段计算总计,然后由我的 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
        

        这是我的方法:

        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
        

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

        【讨论】:

          猜你喜欢
          • 2013-03-16
          • 2014-05-28
          • 2016-08-17
          • 2013-03-17
          • 1970-01-01
          • 1970-01-01
          • 2016-05-09
          • 1970-01-01
          • 2018-09-24
          相关资源
          最近更新 更多