sergiu reznicencu sergiu reznicencu - 4 months ago 10
Vb.net Question

Search BindingList by property

I have in my program a bindingList to which I want to add some elements. The elements are some instances of a class NameValue_Client which contains three properties. I want to search through the list using any property I want.

This is the class:

Public Class NameValue_Client
Implements INotifyPropertyChanged

Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged

Private _assigned_db_name As String, _assigned_tcp_name As String
Private _val_obj As Client
Private _key_obj As Integer


Public WriteOnly Property DB_Name As String
Set(ByVal value As String)
_assigned_db_name = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("DB_Name"))
End Set
End Property

Public Property Value As Client
Get
Return _val_obj
End Get
Set(ByVal value As Client)
_val_obj = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("Value"))
End Set
End Property

Public Property Key As Integer
Get
Return _key_obj
End Get
Set(ByVal value As Integer)
_key_obj = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("Key"))
End Set
End Property

Public ReadOnly Property Name_Identifier As String
Get
Return String.Format("{0} : {1}", _assigned_db_name, _assigned_tcp_name)
End Get
End Property



Sub New(ByVal Key As Integer, ByVal DB_Name As String)
_assigned_db_name = DB_Name
_key_obj = Key
End Sub

Private Sub changed(ByVal sender As Object, ByVal e As PropertyChangedEventArgs) Handles Me.PropertyChanged
If e.PropertyName = "Value" Then
If _val_obj IsNot Nothing Then
_assigned_tcp_name = _val_obj.Details.Computer_Name
End If
End If
End Sub

Public Overrides Function ToString() As String
If _val_obj IsNot Nothing Then
Return String.Format("Db_Name:{0} Tcp_Name:{1} {2}", _assigned_db_name, _assigned_tcp_name, _val_obj.ToString)
Else
Return String.Format("Db_Name:{0} Tcp_Name:{1} Nothing", _assigned_db_name, _assigned_tcp_name)
End If
End Function
End Class


I found this on MSDN and it looks it's the solution but it only serches for one property and I don't want to put a select case . It has to be a better way.

This is the code I found:

Public Class MyFontList
Inherits BindingList(Of Font)

Protected Overrides ReadOnly Property SupportsSearchingCore() As Boolean
Get
Return True
End Get
End Property

Protected Overrides Function FindCore(ByVal prop As PropertyDescriptor, _
ByVal key As Object) As Integer
' Ignore the prop value and search by family name.<--That's why
Dim i As Integer
While i < Count
If Items(i).FontFamily.Name.ToLower() = CStr(key).ToLower() Then
Return i
End If
i += 1
End While

Return -1
End Function
End Class


And I don't know how to implement this 'child' class (never worked with this type).




This is my code(until now):

Public Class NameValue_Client
Implements INotifyPropertyChanged

Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged

Private _assigned_db_name As String, _assigned_tcp_name As String
Private _val_obj As Client
Private _key_obj As Integer


Public WriteOnly Property DB_Name As String
Set(ByVal value As String)
_assigned_db_name = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("DB_Name"))
End Set
End Property

Public Property Value As Client
Get
Return _val_obj
End Get
Set(ByVal value As Client)
_val_obj = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("Value"))
End Set
End Property

Public Property Key As Integer
Get
Return _key_obj
End Get
Set(ByVal value As Integer)
_key_obj = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("Key"))
End Set
End Property

Public ReadOnly Property Name_Identifier As String
Get
Return String.Format("{0} : {1}", _assigned_db_name, _assigned_tcp_name)
End Get
End Property



Sub New(ByVal Key As Integer, ByVal DB_Name As String)
_assigned_db_name = DB_Name
_key_obj = Key
End Sub

Private Sub changed(ByVal sender As Object, ByVal e As PropertyChangedEventArgs) Handles Me.PropertyChanged
If e.PropertyName = "Value" Then
If _val_obj IsNot Nothing Then
_assigned_tcp_name = _val_obj.Details.Computer_Name
End If
End If
End Sub

Public Overrides Function ToString() As String
If _val_obj IsNot Nothing Then
Return String.Format("Db_Name:{0} Tcp_Name:{1} {2}", _assigned_db_name, _assigned_tcp_name, _val_obj.ToString)
Else
Return String.Format("Db_Name:{0} Tcp_Name:{1} Nothing", _assigned_db_name, _assigned_tcp_name)
End If
End Function
End Class

Public Class Interface_NameValue
Inherits BindingList(Of NameValue_Client)

Protected Overrides ReadOnly Property SupportsSearchingCore() As Boolean
Get
Return True
End Get
End Property

Protected Overrides Function FindCore(ByVal prop As PropertyDescriptor, _
ByVal key As Object) As Integer

' Ignore the prop value and search by family name.
Dim i As Integer

While i < Count
''Old-fashion way
Select Case prop.Name
Case "Value"
Case "Key"
Case "Name_Identifier"
End Select
i += 1
End While

Return -1
End Function
End Class


And now what am I supposed to do with this new class? How can I implement this?

Answer

I want to search through the list using any property I want...and I don't want to put a select case .

The MyFontList class from MSDN is showing part of what you would do to implement a collection class - notice the Inherits BindingList(Of Font) - rather than modifying the item class (NameValue_Client). Your code would them use that class in place of the BindingList(of NameValue_Client) collection variable.

There is an easier way.

First, though there is no reason for your item class to subscribe to its own PropertyChanged event. Just change the Value setter to this:

Set(ByVal value As Client)
    _val_obj = value
    If _val_obj IsNot Nothing Then
        _assigned_tcp_name = _val_obj.ComputerName
    End If
    RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("Value"))
End Set

Updating _assigned_tcp_name before you raise the event will allow the new value to be visible to anything subscribing to the event and perhaps using Name_Identifier. But you should consider getting rid of _assigned_tcp_name since it allows the possibility of it being "stale":

Public ReadOnly Property Name_Identifier As String
    Get
        Dim tcpname = If(Value IsNot Nothing, Value.ComputerName, "")
        Return String.Format("{0} : {1}", _assigned_db_name, tcpname)
    End Get
End Property

It also seems very odd for a KeyValuePair type class where the Value is basically optional (apparently it is manually set). It need not even exist at all, making it possible to have a Key with no Value.


When it comes to searching, there is really only one property on the item class: Key. The Value is a type which means you may want one of any number of properties on Client. DB_Name is WriteOnly (?!), so it cannot be searched; I guess you might search for Name_Identifier but that is a compound, 'artificial' property.

We can only see 1 property on Client so I added some for illustration purposes:

Public Class Client
    Public Property ComputerName As String
    Public Property Foo As String
    Public Property Bar As Int32

    Public Sub New(n As String)
        ComputerName = n
    End Sub
End Class

You can use linq to find whatever you want:

Dim Clients As New BindingList(Of NameValue_Client)

Dim c = New Client("ziggy") With {.Bar = 9, .Foo = "9"}
Dim NVC = New NameValue_Client(1, "Alpha")
NVC.Value = c
Clients.Add(NVC)

c = New Client("zoey") With {.Bar = 7, .Foo = "Q"}
NVC = New NameValue_Client(2, "Beta")  With {.Value = c}
Clients.Add(NVC)

' find a key:
NVC = Clients.FirstOrDefault(Function(q) q.Key = 1)
' find a client object:
NVC = Clients.FirstOrDefault(Function(q) q.Value Is c)
' find a client.computername:
NVC = Clients.FirstOrDefault(Function(q) q.Value.ComputerName = "Mine")
' find a client.foo value
NVC = Clients.FirstOrDefault(Function(q) q.Value.Foo = "Q")

' return is Nothing if not found:
If NVC Is Nothing Then
    ' NOT FOUND!
Else
    ' found
    Console.WriteLine(NVC.Name_Identifier)
End If

What you search for can be a variable, but of course searching for a Key (int) requires an int var where other properties might require a string or DateTime variable.

As noted, if what you want cannot be found, FirstOrDefault will be nothing. Searching for a Client object as in the second example would require that c be the exact same object as in the list. A different client object even with the same property values will not match. If you need that, you could implement your own comparer.

Since the Value can be Nothing, any query involving it ought to take that into account:

NVC = Clients.
        FirstOrDefault(Function(q) q.Value IsNot Nothing AndAlso
                                 q.Value.Foo = "Q")

Some of the code in your item class ought to also allow for that, or change it so that an item cannot be created without a Client object.

Comments