EDN Admin
Well-known member
There are a lot of different generic collections available, so the wisdom in creating a new one may be questionable right from the start. However, I often find myself wanting the same features from my custom generic lists, but cannot
quite find all of those features in an existing object; namely, I would like to have a generic collection which works like a List or Dictionary (much like the System.Collections.ObjectModel.KeyedCollection(Of TKey, TValue)), but also works like a
BindingSource does when it is bound to an ADO object (that is, it supports full sorting and filtering as well as currency). A feature I almost never need however, is transactional adds (the BindingSources ability to cancel add-new).
Again, with so many existing collections out there, I should take a moment to go over the reasons why none of them quite meet my needs.
There is the System.ComponentModel.BindingList(Of T) which would provide some of the functionality of a BindingSource, but has strict requirements about the type {T} implementing IComparable in order to support sorting, and does not provide filtering (it
is not an IBindingListView). It also only maintains a base item list by index, so does not provide keyed access.
We could use simple databinding with a custom list inheriting from System.Collections.ObjectModel.KeyedCollection(Of TKey, TValue) bound to a BindingSource, but again TValue would have to implement IComparable to support sorting and we would still not have
filter support.
So to get all the features I was after, I decided that the best thing would be to create a new generic collection object which implemented IList, IDictionary, IBindingList, and IBindingListView (along with a few others) and would be capable of serving as
a simple list, or as a binding source while providing full sorting and filtering over any newable object type.
To that end, Ive created the KeyedObjectModel which contains the KeyedBindingList(Of TKey, T) generic collection/binding source. This collection can be used as a typical list, or as a binding source (you do not need an additional BindingSource component
between the collection and UI controls, although you could certainly have one). Various constructors are provided in order to initialize an instance of the collection as either an enhanced list-dictionary, or as a complete BindingSource.
Any object that has a public constructor which takes no arguments can be used at type {T}, providing that the object has a property of type {TKey} which will contain unique values for each entity in a given collection. If the object implements
System.ComponentModel.NotifyPropertyChanged then the collection will also bubble up property changes on the current item to the collections CurrentItemChanged event.
An implementation of IComparable on type {T} is irrelevant because sorting and filtering are performed on the property values of {T} based on the type of the specified property.
Some Notes on Development
The current design of KeyedBindingList(Of TKey, T) is in an alpha stage. The class is mostly complete from a feature implementation standpoint, however, the code has not yet been optimized and the base collections of values, keys, and view are being
maintianed in generic lists of their own, rather than raw arrays. However, even without performance tuning, the collection appears to be performing quite well, up to a few thousand entries, given the amount of flexibility and funcitonality it provides.
At this stage, I would really love to get some peers to try using the compiled DLL and beat up the collection a bit. Once the code has been proven and optimized, I will publish it as an example. It is a fairly complex class though (currently
made up of eleven or so partial classes) so I dont really want to post the code until it has been optimized.
The Class Library & Example Code
The KeyedObjectModel class library is available here: https://skydrive.live.com/self.aspx/.Public/Visual%20Basic/KeyedObjectModel.zip?cid=8357f4a651e4838b&sc=documents
KeyedObjectModel.zip
The follow code is a demo application which can be pasted into a new Windows Forms Application project which is referencing the KeyedObjectModel api:
<div style="color:Black;background-color:White; <pre>
<span style="color:Blue; Public <span style="color:Blue; Class Form1
<span style="color:Blue; Private _MainSplitContainer <span style="color:Blue; As <span style="color:Blue; New SplitContainer
<span style="color:Blue; Private _ViewSplitContainer <span style="color:Blue; As <span style="color:Blue; New SplitContainer
<span style="color:Blue; Private _DataGridView <span style="color:Blue; As <span style="color:Blue; New DataGridView
<span style="color:Blue; Private _CommandPanel <span style="color:Blue; As <span style="color:Blue; New FlowLayoutPanel
<span style="color:Blue; Private _FilterTextBox <span style="color:Blue; As <span style="color:Blue; New TextBox
<span style="color:Blue; Private <span style="color:Blue; WithEvents _LoadButton <span style="color:Blue; As <span style="color:Blue; New Button
<span style="color:Blue; Private <span style="color:Blue; WithEvents _FilterButton <span style="color:Blue; As <span style="color:Blue; New Button
<span style="color:Blue; Private _PositionLabel <span style="color:Blue; As <span style="color:Blue; New Label
<span style="color:Blue; Private _ValueEditBox <span style="color:Blue; As <span style="color:Blue; New TextBox
<span style="color:Blue; Private _EventListBox <span style="color:Blue; As <span style="color:Blue; New ListBox
<span style="color:Blue; Private <span style="color:Blue; WithEvents _TestList <span style="color:Blue; As <span style="color:Blue; New KeyedObjectModel.KeyedBindingList(Of Guid, AnObject)(<span style="color:Blue; Me)
<span style="color:Blue; Private _NextIndex <span style="color:Blue; As <span style="color:Blue; Integer = 1
<span style="color:Blue; Private <span style="color:Blue; Sub Form1_Load(<span style="color:Blue; ByVal sender <span style="color:Blue; As System.Object, <span style="color:Blue; ByVal e <span style="color:Blue; As System.EventArgs) <span style="color:Blue; Handles <span style="color:Blue; MyBase.Load
SetupFormUI()
<span style="color:Blue; End <span style="color:Blue; Sub
<span style="color:Blue; Private <span style="color:Blue; Sub _FilterButton_Click(<span style="color:Blue; ByVal sender <span style="color:Blue; As <span style="color:Blue; Object, <span style="color:Blue; ByVal e <span style="color:Blue; As System.EventArgs) <span style="color:Blue; Handles _FilterButton.Click
<span style="color:Blue; Try
_TestList.Filter = _FilterTextBox.Text
<span style="color:Blue; Catch ex <span style="color:Blue; As Exception
MessageBox.Show(ex.Message, <span style="color:#A31515; "Error Applying Filter", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
_TestList.RemoveFilter()
<span style="color:Blue; End <span style="color:Blue; Try
<span style="color:Blue; End <span style="color:Blue; Sub
<span style="color:Blue; Private <span style="color:Blue; Sub _LoadButton_Click(<span style="color:Blue; ByVal sender <span style="color:Blue; As <span style="color:Blue; Object, <span style="color:Blue; ByVal e <span style="color:Blue; As System.EventArgs) <span style="color:Blue; Handles _LoadButton.Click
LoadTestList(1000)
<span style="color:Blue; End <span style="color:Blue; Sub
<span style="color:Blue; Private <span style="color:Blue; Sub _TestList_AddingNew(<span style="color:Blue; ByVal sender <span style="color:Blue; As <span style="color:Blue; Object, <span style="color:Blue; ByVal e <span style="color:Blue; As System.ComponentModel.AddingNewEventArgs) <span style="color:Blue; Handles _TestList.AddingNew
AppendEventMessage(<span style="color:#A31515; "Adding New " & _NextIndex.ToString)
<span style="color:Blue; End <span style="color:Blue; Sub
<span style="color:Blue; Private <span style="color:Blue; Sub _TestList_BindingComplete(<span style="color:Blue; ByVal sender <span style="color:Blue; As <span style="color:Blue; Object, <span style="color:Blue; ByVal e <span style="color:Blue; As System.Windows.Forms.BindingCompleteEventArgs) <span style="color:Blue; Handles _TestList.BindingComplete
<span style="color:Green; not yet implemented
<span style="color:Blue; End <span style="color:Blue; Sub
<span style="color:Blue; Private <span style="color:Blue; Sub _TestList_CurrentChanged(<span style="color:Blue; ByVal sender <span style="color:Blue; As <span style="color:Blue; Object, <span style="color:Blue; ByVal e <span style="color:Blue; As System.EventArgs) <span style="color:Blue; Handles _TestList.CurrentChanged
AppendEventMessage(<span style="color:#A31515; "List Current Changed")
<span style="color:Blue; End <span style="color:Blue; Sub
<span style="color:Blue; Private <span style="color:Blue; Sub _TestList_CurrentItemChanged(<span style="color:Blue; ByVal sender <span style="color:Blue; As <span style="color:Blue; Object, <span style="color:Blue; ByVal e <span style="color:Blue; As System.EventArgs) <span style="color:Blue; Handles _TestList.CurrentItemChanged
AppendEventMessage(<span style="color:#A31515; "Current Item Changed")
<span style="color:Blue; End <span style="color:Blue; Sub
<span style="color:Blue; Private <span style="color:Blue; Sub _TestList_ItemPropertyChanged(<span style="color:Blue; ByVal sender <span style="color:Blue; As <span style="color:Blue; Object, <span style="color:Blue; ByVal e <span style="color:Blue; As KeyedObjectModel.KeyedBindingListItemPropertyChangedEventArgs(Of AnObject)) <span style="color:Blue; Handles _TestList.ItemPropertyChanged
AppendEventMessage(<span style="color:Blue; String.Format(<span style="color:#A31515; "Items {0} Changed", e.PropertyName))
<span style="color:Blue; End <span style="color:Blue; Sub
<span style="color:Blue; Private <span style="color:Blue; Sub _TestList_ListChanged(<span style="color:Blue; ByVal sender <span style="color:Blue; As <span style="color:Blue; Object, <span style="color:Blue; ByVal e <span style="color:Blue; As System.ComponentModel.ListChangedEventArgs) <span style="color:Blue; Handles _TestList.ListChanged
AppendEventMessage(<span style="color:#A31515; "List Changed " & e.ListChangedType.ToString)
<span style="color:Blue; End <span style="color:Blue; Sub
<span style="color:Blue; Private <span style="color:Blue; Sub _TestList_PositionChanged(<span style="color:Blue; ByVal sender <span style="color:Blue; As <span style="color:Blue; Object, <span style="color:Blue; ByVal e <span style="color:Blue; As System.EventArgs) <span style="color:Blue; Handles _TestList.PositionChanged
_PositionLabel.Text = <span style="color:#A31515; "Position = " & _TestList.Position.ToString
<span style="color:Blue; End <span style="color:Blue; Sub
<span style="color:Blue; Private <span style="color:Blue; Sub AppendEventMessage(<span style="color:Blue; ByVal messageText <span style="color:Blue; As <span style="color:Blue; String)
_EventListBox.Items.Insert(0, messageText)
<span style="color:Blue; End <span style="color:Blue; Sub
<span style="color:Blue; Private <span style="color:Blue; Sub LoadTestList(<span style="color:Blue; ByVal <span style="color:Blue; count <span style="color:Blue; As <span style="color:Blue; Integer)
<span style="color:Blue; Dim valueGenerator <span style="color:Blue; As <span style="color:Blue; New Random
_TestList.SuspendBinding()
<span style="color:Blue; Dim delta <span style="color:Blue; As <span style="color:Blue; Integer = _NextIndex + <span style="color:Blue; count
<span style="color:Blue; While _NextIndex < delta
<span style="color:Blue; Dim createdDate <span style="color:Blue; As <span style="color:Blue; Date = Now.Subtract(TimeSpan.FromHours(_NextIndex))
<span style="color:Blue; Dim item <span style="color:Blue; As <span style="color:Blue; New AnObject(createdDate)
item.Index = _NextIndex
item.Name = <span style="color:Blue; String.Concat(<span style="color:#A31515; "Item Number ", _NextIndex.ToString)
item.Value = valueGenerator.NextDouble()
_TestList.Add(item)
_NextIndex += 1
<span style="color:Blue; End <span style="color:Blue; While
_TestList.ResumeBinding()
<span style="color:Blue; End <span style="color:Blue; Sub
<span style="color:Blue; Private <span style="color:Blue; Sub SetupFormUI()
Size = <span style="color:Blue; New Size(800, 600)
Controls.Add(_MainSplitContainer)
_MainSplitContainer.Dock = DockStyle.Fill
_MainSplitContainer.Orientation = Orientation.Horizontal
_MainSplitContainer.FixedPanel = FixedPanel.Panel1
_MainSplitContainer.Panel2.Controls.Add(_ViewSplitContainer)
_ViewSplitContainer.Dock = DockStyle.Fill
_ViewSplitContainer.Panel1.Controls.Add(_DataGridView)
_DataGridView.Dock = DockStyle.Fill
_DataGridView.DataSource = _TestList
_MainSplitContainer.Panel1.Controls.Add(_CommandPanel)
_CommandPanel.Dock = DockStyle.Fill
_CommandPanel.Controls.Add(_LoadButton)
_LoadButton.Text = <span style="color:#A31515; "Load 1000 Items"
_LoadButton.AutoSize = <span style="color:Blue; True
_CommandPanel.SetFlowBreak(_LoadButton, <span style="color:Blue; True)
_CommandPanel.Controls.Add(_FilterTextBox)
_FilterTextBox.Width = 300
_CommandPanel.Controls.Add(_FilterButton)
_FilterButton.Text = <span style="color:#A31515; "Apply Filter"
_FilterButton.AutoSize = <span style="color:Blue; True
_CommandPanel.Controls.Add(_ValueEditBox)
_ValueEditBox.DataBindings.Add(<span style="color:#A31515; "Text", _TestList, <span style="color:#A31515; "Value")
_CommandPanel.Controls.Add(_PositionLabel)
_PositionLabel.AutoSize = <span style="color:Blue; True
_ViewSplitContainer.Panel2.Controls.Add(_EventListBox)
_EventListBox.Dock = DockStyle.Fill
_EventListBox.IntegralHeight = <span style="color:Blue; False
_MainSplitContainer.SplitterDistance = _FilterButton.Height * 2.5
_ViewSplitContainer.SplitterDistance = _MainSplitContainer.Width * 0.75
<span style="color:Blue; End <span style="color:Blue; Sub
<span style="color:Blue; End <span style="color:Blue; Class
<span style="color:Blue; Public <span style="color:Blue; Class AnObject
<span style="color:Blue; Inherits KeyedObjectModel.ObservableGuidKeyedObject
<span style="color:Blue; Protected _Created <span style="color:Blue; As <span style="color:Blue; Date
<span style="color:Blue; Public <span style="color:Blue; ReadOnly <span style="color:Blue; Property Created <span style="color:Blue; As <span style="color:Blue; Date
<span style="color:Blue; Get
<span style="color:Blue; Return _Created
<span style="color:Blue; End <span style="color:Blue; Get
<span style="color:Blue; End <span style="color:Blue; Property
<span style="color:Blue; Private _Index <span style="color:Blue; As <span style="color:Blue; Integer
<span style="color:Blue; Public <span style="color:Blue; Property Index() <span style="color:Blue; As <span style="color:Blue; Integer
<span style="color:Blue; Get
<span style="color:Blue; Return _Index
<span style="color:Blue; End <span style="color:Blue; Get
<span style="color:Blue; Set(<span style="color:Blue; ByVal value <span style="color:Blue; As <span style="color:Blue; Integer)
<span style="color:Blue; If <span style="color:Blue; Not _Index = value <span style="color:Blue; Then
_Index = value
DoPropertyChanged(<span style="color:#A31515; "Index")
<span style="color:Blue; End <span style="color:Blue; If
<span style="color:Blue; End <span style="color:Blue; Set
<span style="color:Blue; End <span style="color:Blue; Property
<span style="color:Blue; Private _Name <span style="color:Blue; As <span style="color:Blue; String
<span style="color:Blue; Public <span style="color:Blue; Property Name() <span style="color:Blue; As <span style="color:Blue; String
<span style="color:Blue; Get
<span style="color:Blue; Return _Name
<span style="color:Blue; End <span style="color:Blue; Get
<span style="color:Blue; Set(<span style="color:Blue; ByVal value <span style="color:Blue; As <span style="color:Blue; String)
<span style="color:Blue; If <span style="color:Blue; Not _Name = value <span style="color:Blue; Then
_Name = value
DoPropertyChanged(<span style="color:#A31515; "Name")
<span style="color:Blue; End <span style="color:Blue; If
<span style="color:Blue; End <span style="color:Blue; Set
<span style="color:Blue; End <span style="color:Blue; Property
<span style="color:Blue; Private _Value <span style="color:Blue; As <span style="color:Blue; Double
<span style="color:Blue; Public <span style="color:Blue; Property Value() <span style="color:Blue; As <span style="color:Blue; Double
<span style="color:Blue; Get
<span style="color:Blue; Return _Value
<span style="color:Blue; End <span style="color:Blue; Get
<span style="color:Blue; Set(<span style="color:Blue; ByVal value <span style="color:Blue; As <span style="color:Blue; Double)
<span style="color:Blue; If <span style="color:Blue; Not _Value = value <span style="color:Blue; Then
_Value = value
DoPropertyChanged(<span style="color:#A31515; "Value")
<span style="color:Blue; End <span style="color:Blue; If
<span style="color:Blue; End <span style="color:Blue; Set
<span style="color:Blue; End <span style="color:Blue; Property
<span style="color:Blue; Public <span style="color:Blue; Sub <span style="color:Blue; New()
<span style="color:Blue; Me.<span style="color:Blue; New(Now)
<span style="color:Blue; End <span style="color:Blue; Sub
<span style="color:Blue; Public <span style="color:Blue; Sub <span style="color:Blue; New(<span style="color:Blue; ByVal creationDate <span style="color:Blue; As <span style="color:Blue; Date)
_Created = creationDate
<span style="color:Blue; End <span style="color:Blue; Sub
<span style="color:Blue; End <span style="color:Blue; Class
[/code]
<br/>
Some Known Issues:
The BindingComplete event is not yet being raised by the collection. Further interaction with the CurrencyManager is required...
There will be an issue accessing the collection by key if the key property type specified is Integer.
Performance begins to drop noticeably above a few thousand entries, though is still usable at 10,000+. Note however that filtering the view to a small number of entries returns performance even when the underlying list is large.
The control is only partially documented.
Some Notes on Filters:
The filter string can contain multiple criteria. Each criteria should be a space seperated string containing three parts: the property name, the comparison, and the target value. Each criteria is seperated with a space. e.g. "Index
Greater 50" or "Index Greater 50 Name Contains ae"<br/>
The criteria value can be enclosed in "" if it contains a space character.<br/>
The criteria must be parsable into the target property data type.
The FilterBuilder property can be used to programmatically build up a filter from IKeyedBindingListFilterCriteria objects. When built this way, individual criteria can specify a delgates to control the comparing, formatting, and/or parsing of the criteria
value.
Note that setting the Filter string property will remove progammatically built filters.
In Summary
The KeyedBindingList(Of TKey, T) generic collection is a concept object designed to offer a highly flexible, feature-rich collection which can support full databinding and can easily manage any newable object which provides a uniquely keyed property value.
The KeyedObjectModel class library include a few additional classes such as base objects (or interfaces) to be used to define a custom object as uniquely keyed and/or observable. There is also a special LooselyKeyedList(Of TKey, TValue) collection
which provides the hybrid functionality of a list and a dictionary, but does not require that the key value of items be unique.
Please feel free to beat this library up one side and down the other and give an honest critique! <hr class="sig Reed Kimble - "When you do things right, people wont be sure youve done anything at all"
View the full article
quite find all of those features in an existing object; namely, I would like to have a generic collection which works like a List or Dictionary (much like the System.Collections.ObjectModel.KeyedCollection(Of TKey, TValue)), but also works like a
BindingSource does when it is bound to an ADO object (that is, it supports full sorting and filtering as well as currency). A feature I almost never need however, is transactional adds (the BindingSources ability to cancel add-new).
Again, with so many existing collections out there, I should take a moment to go over the reasons why none of them quite meet my needs.
There is the System.ComponentModel.BindingList(Of T) which would provide some of the functionality of a BindingSource, but has strict requirements about the type {T} implementing IComparable in order to support sorting, and does not provide filtering (it
is not an IBindingListView). It also only maintains a base item list by index, so does not provide keyed access.
We could use simple databinding with a custom list inheriting from System.Collections.ObjectModel.KeyedCollection(Of TKey, TValue) bound to a BindingSource, but again TValue would have to implement IComparable to support sorting and we would still not have
filter support.
So to get all the features I was after, I decided that the best thing would be to create a new generic collection object which implemented IList, IDictionary, IBindingList, and IBindingListView (along with a few others) and would be capable of serving as
a simple list, or as a binding source while providing full sorting and filtering over any newable object type.
To that end, Ive created the KeyedObjectModel which contains the KeyedBindingList(Of TKey, T) generic collection/binding source. This collection can be used as a typical list, or as a binding source (you do not need an additional BindingSource component
between the collection and UI controls, although you could certainly have one). Various constructors are provided in order to initialize an instance of the collection as either an enhanced list-dictionary, or as a complete BindingSource.
Any object that has a public constructor which takes no arguments can be used at type {T}, providing that the object has a property of type {TKey} which will contain unique values for each entity in a given collection. If the object implements
System.ComponentModel.NotifyPropertyChanged then the collection will also bubble up property changes on the current item to the collections CurrentItemChanged event.
An implementation of IComparable on type {T} is irrelevant because sorting and filtering are performed on the property values of {T} based on the type of the specified property.
Some Notes on Development
The current design of KeyedBindingList(Of TKey, T) is in an alpha stage. The class is mostly complete from a feature implementation standpoint, however, the code has not yet been optimized and the base collections of values, keys, and view are being
maintianed in generic lists of their own, rather than raw arrays. However, even without performance tuning, the collection appears to be performing quite well, up to a few thousand entries, given the amount of flexibility and funcitonality it provides.
At this stage, I would really love to get some peers to try using the compiled DLL and beat up the collection a bit. Once the code has been proven and optimized, I will publish it as an example. It is a fairly complex class though (currently
made up of eleven or so partial classes) so I dont really want to post the code until it has been optimized.
The Class Library & Example Code
The KeyedObjectModel class library is available here: https://skydrive.live.com/self.aspx/.Public/Visual%20Basic/KeyedObjectModel.zip?cid=8357f4a651e4838b&sc=documents
KeyedObjectModel.zip
The follow code is a demo application which can be pasted into a new Windows Forms Application project which is referencing the KeyedObjectModel api:
<div style="color:Black;background-color:White; <pre>
<span style="color:Blue; Public <span style="color:Blue; Class Form1
<span style="color:Blue; Private _MainSplitContainer <span style="color:Blue; As <span style="color:Blue; New SplitContainer
<span style="color:Blue; Private _ViewSplitContainer <span style="color:Blue; As <span style="color:Blue; New SplitContainer
<span style="color:Blue; Private _DataGridView <span style="color:Blue; As <span style="color:Blue; New DataGridView
<span style="color:Blue; Private _CommandPanel <span style="color:Blue; As <span style="color:Blue; New FlowLayoutPanel
<span style="color:Blue; Private _FilterTextBox <span style="color:Blue; As <span style="color:Blue; New TextBox
<span style="color:Blue; Private <span style="color:Blue; WithEvents _LoadButton <span style="color:Blue; As <span style="color:Blue; New Button
<span style="color:Blue; Private <span style="color:Blue; WithEvents _FilterButton <span style="color:Blue; As <span style="color:Blue; New Button
<span style="color:Blue; Private _PositionLabel <span style="color:Blue; As <span style="color:Blue; New Label
<span style="color:Blue; Private _ValueEditBox <span style="color:Blue; As <span style="color:Blue; New TextBox
<span style="color:Blue; Private _EventListBox <span style="color:Blue; As <span style="color:Blue; New ListBox
<span style="color:Blue; Private <span style="color:Blue; WithEvents _TestList <span style="color:Blue; As <span style="color:Blue; New KeyedObjectModel.KeyedBindingList(Of Guid, AnObject)(<span style="color:Blue; Me)
<span style="color:Blue; Private _NextIndex <span style="color:Blue; As <span style="color:Blue; Integer = 1
<span style="color:Blue; Private <span style="color:Blue; Sub Form1_Load(<span style="color:Blue; ByVal sender <span style="color:Blue; As System.Object, <span style="color:Blue; ByVal e <span style="color:Blue; As System.EventArgs) <span style="color:Blue; Handles <span style="color:Blue; MyBase.Load
SetupFormUI()
<span style="color:Blue; End <span style="color:Blue; Sub
<span style="color:Blue; Private <span style="color:Blue; Sub _FilterButton_Click(<span style="color:Blue; ByVal sender <span style="color:Blue; As <span style="color:Blue; Object, <span style="color:Blue; ByVal e <span style="color:Blue; As System.EventArgs) <span style="color:Blue; Handles _FilterButton.Click
<span style="color:Blue; Try
_TestList.Filter = _FilterTextBox.Text
<span style="color:Blue; Catch ex <span style="color:Blue; As Exception
MessageBox.Show(ex.Message, <span style="color:#A31515; "Error Applying Filter", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
_TestList.RemoveFilter()
<span style="color:Blue; End <span style="color:Blue; Try
<span style="color:Blue; End <span style="color:Blue; Sub
<span style="color:Blue; Private <span style="color:Blue; Sub _LoadButton_Click(<span style="color:Blue; ByVal sender <span style="color:Blue; As <span style="color:Blue; Object, <span style="color:Blue; ByVal e <span style="color:Blue; As System.EventArgs) <span style="color:Blue; Handles _LoadButton.Click
LoadTestList(1000)
<span style="color:Blue; End <span style="color:Blue; Sub
<span style="color:Blue; Private <span style="color:Blue; Sub _TestList_AddingNew(<span style="color:Blue; ByVal sender <span style="color:Blue; As <span style="color:Blue; Object, <span style="color:Blue; ByVal e <span style="color:Blue; As System.ComponentModel.AddingNewEventArgs) <span style="color:Blue; Handles _TestList.AddingNew
AppendEventMessage(<span style="color:#A31515; "Adding New " & _NextIndex.ToString)
<span style="color:Blue; End <span style="color:Blue; Sub
<span style="color:Blue; Private <span style="color:Blue; Sub _TestList_BindingComplete(<span style="color:Blue; ByVal sender <span style="color:Blue; As <span style="color:Blue; Object, <span style="color:Blue; ByVal e <span style="color:Blue; As System.Windows.Forms.BindingCompleteEventArgs) <span style="color:Blue; Handles _TestList.BindingComplete
<span style="color:Green; not yet implemented
<span style="color:Blue; End <span style="color:Blue; Sub
<span style="color:Blue; Private <span style="color:Blue; Sub _TestList_CurrentChanged(<span style="color:Blue; ByVal sender <span style="color:Blue; As <span style="color:Blue; Object, <span style="color:Blue; ByVal e <span style="color:Blue; As System.EventArgs) <span style="color:Blue; Handles _TestList.CurrentChanged
AppendEventMessage(<span style="color:#A31515; "List Current Changed")
<span style="color:Blue; End <span style="color:Blue; Sub
<span style="color:Blue; Private <span style="color:Blue; Sub _TestList_CurrentItemChanged(<span style="color:Blue; ByVal sender <span style="color:Blue; As <span style="color:Blue; Object, <span style="color:Blue; ByVal e <span style="color:Blue; As System.EventArgs) <span style="color:Blue; Handles _TestList.CurrentItemChanged
AppendEventMessage(<span style="color:#A31515; "Current Item Changed")
<span style="color:Blue; End <span style="color:Blue; Sub
<span style="color:Blue; Private <span style="color:Blue; Sub _TestList_ItemPropertyChanged(<span style="color:Blue; ByVal sender <span style="color:Blue; As <span style="color:Blue; Object, <span style="color:Blue; ByVal e <span style="color:Blue; As KeyedObjectModel.KeyedBindingListItemPropertyChangedEventArgs(Of AnObject)) <span style="color:Blue; Handles _TestList.ItemPropertyChanged
AppendEventMessage(<span style="color:Blue; String.Format(<span style="color:#A31515; "Items {0} Changed", e.PropertyName))
<span style="color:Blue; End <span style="color:Blue; Sub
<span style="color:Blue; Private <span style="color:Blue; Sub _TestList_ListChanged(<span style="color:Blue; ByVal sender <span style="color:Blue; As <span style="color:Blue; Object, <span style="color:Blue; ByVal e <span style="color:Blue; As System.ComponentModel.ListChangedEventArgs) <span style="color:Blue; Handles _TestList.ListChanged
AppendEventMessage(<span style="color:#A31515; "List Changed " & e.ListChangedType.ToString)
<span style="color:Blue; End <span style="color:Blue; Sub
<span style="color:Blue; Private <span style="color:Blue; Sub _TestList_PositionChanged(<span style="color:Blue; ByVal sender <span style="color:Blue; As <span style="color:Blue; Object, <span style="color:Blue; ByVal e <span style="color:Blue; As System.EventArgs) <span style="color:Blue; Handles _TestList.PositionChanged
_PositionLabel.Text = <span style="color:#A31515; "Position = " & _TestList.Position.ToString
<span style="color:Blue; End <span style="color:Blue; Sub
<span style="color:Blue; Private <span style="color:Blue; Sub AppendEventMessage(<span style="color:Blue; ByVal messageText <span style="color:Blue; As <span style="color:Blue; String)
_EventListBox.Items.Insert(0, messageText)
<span style="color:Blue; End <span style="color:Blue; Sub
<span style="color:Blue; Private <span style="color:Blue; Sub LoadTestList(<span style="color:Blue; ByVal <span style="color:Blue; count <span style="color:Blue; As <span style="color:Blue; Integer)
<span style="color:Blue; Dim valueGenerator <span style="color:Blue; As <span style="color:Blue; New Random
_TestList.SuspendBinding()
<span style="color:Blue; Dim delta <span style="color:Blue; As <span style="color:Blue; Integer = _NextIndex + <span style="color:Blue; count
<span style="color:Blue; While _NextIndex < delta
<span style="color:Blue; Dim createdDate <span style="color:Blue; As <span style="color:Blue; Date = Now.Subtract(TimeSpan.FromHours(_NextIndex))
<span style="color:Blue; Dim item <span style="color:Blue; As <span style="color:Blue; New AnObject(createdDate)
item.Index = _NextIndex
item.Name = <span style="color:Blue; String.Concat(<span style="color:#A31515; "Item Number ", _NextIndex.ToString)
item.Value = valueGenerator.NextDouble()
_TestList.Add(item)
_NextIndex += 1
<span style="color:Blue; End <span style="color:Blue; While
_TestList.ResumeBinding()
<span style="color:Blue; End <span style="color:Blue; Sub
<span style="color:Blue; Private <span style="color:Blue; Sub SetupFormUI()
Size = <span style="color:Blue; New Size(800, 600)
Controls.Add(_MainSplitContainer)
_MainSplitContainer.Dock = DockStyle.Fill
_MainSplitContainer.Orientation = Orientation.Horizontal
_MainSplitContainer.FixedPanel = FixedPanel.Panel1
_MainSplitContainer.Panel2.Controls.Add(_ViewSplitContainer)
_ViewSplitContainer.Dock = DockStyle.Fill
_ViewSplitContainer.Panel1.Controls.Add(_DataGridView)
_DataGridView.Dock = DockStyle.Fill
_DataGridView.DataSource = _TestList
_MainSplitContainer.Panel1.Controls.Add(_CommandPanel)
_CommandPanel.Dock = DockStyle.Fill
_CommandPanel.Controls.Add(_LoadButton)
_LoadButton.Text = <span style="color:#A31515; "Load 1000 Items"
_LoadButton.AutoSize = <span style="color:Blue; True
_CommandPanel.SetFlowBreak(_LoadButton, <span style="color:Blue; True)
_CommandPanel.Controls.Add(_FilterTextBox)
_FilterTextBox.Width = 300
_CommandPanel.Controls.Add(_FilterButton)
_FilterButton.Text = <span style="color:#A31515; "Apply Filter"
_FilterButton.AutoSize = <span style="color:Blue; True
_CommandPanel.Controls.Add(_ValueEditBox)
_ValueEditBox.DataBindings.Add(<span style="color:#A31515; "Text", _TestList, <span style="color:#A31515; "Value")
_CommandPanel.Controls.Add(_PositionLabel)
_PositionLabel.AutoSize = <span style="color:Blue; True
_ViewSplitContainer.Panel2.Controls.Add(_EventListBox)
_EventListBox.Dock = DockStyle.Fill
_EventListBox.IntegralHeight = <span style="color:Blue; False
_MainSplitContainer.SplitterDistance = _FilterButton.Height * 2.5
_ViewSplitContainer.SplitterDistance = _MainSplitContainer.Width * 0.75
<span style="color:Blue; End <span style="color:Blue; Sub
<span style="color:Blue; End <span style="color:Blue; Class
<span style="color:Blue; Public <span style="color:Blue; Class AnObject
<span style="color:Blue; Inherits KeyedObjectModel.ObservableGuidKeyedObject
<span style="color:Blue; Protected _Created <span style="color:Blue; As <span style="color:Blue; Date
<span style="color:Blue; Public <span style="color:Blue; ReadOnly <span style="color:Blue; Property Created <span style="color:Blue; As <span style="color:Blue; Date
<span style="color:Blue; Get
<span style="color:Blue; Return _Created
<span style="color:Blue; End <span style="color:Blue; Get
<span style="color:Blue; End <span style="color:Blue; Property
<span style="color:Blue; Private _Index <span style="color:Blue; As <span style="color:Blue; Integer
<span style="color:Blue; Public <span style="color:Blue; Property Index() <span style="color:Blue; As <span style="color:Blue; Integer
<span style="color:Blue; Get
<span style="color:Blue; Return _Index
<span style="color:Blue; End <span style="color:Blue; Get
<span style="color:Blue; Set(<span style="color:Blue; ByVal value <span style="color:Blue; As <span style="color:Blue; Integer)
<span style="color:Blue; If <span style="color:Blue; Not _Index = value <span style="color:Blue; Then
_Index = value
DoPropertyChanged(<span style="color:#A31515; "Index")
<span style="color:Blue; End <span style="color:Blue; If
<span style="color:Blue; End <span style="color:Blue; Set
<span style="color:Blue; End <span style="color:Blue; Property
<span style="color:Blue; Private _Name <span style="color:Blue; As <span style="color:Blue; String
<span style="color:Blue; Public <span style="color:Blue; Property Name() <span style="color:Blue; As <span style="color:Blue; String
<span style="color:Blue; Get
<span style="color:Blue; Return _Name
<span style="color:Blue; End <span style="color:Blue; Get
<span style="color:Blue; Set(<span style="color:Blue; ByVal value <span style="color:Blue; As <span style="color:Blue; String)
<span style="color:Blue; If <span style="color:Blue; Not _Name = value <span style="color:Blue; Then
_Name = value
DoPropertyChanged(<span style="color:#A31515; "Name")
<span style="color:Blue; End <span style="color:Blue; If
<span style="color:Blue; End <span style="color:Blue; Set
<span style="color:Blue; End <span style="color:Blue; Property
<span style="color:Blue; Private _Value <span style="color:Blue; As <span style="color:Blue; Double
<span style="color:Blue; Public <span style="color:Blue; Property Value() <span style="color:Blue; As <span style="color:Blue; Double
<span style="color:Blue; Get
<span style="color:Blue; Return _Value
<span style="color:Blue; End <span style="color:Blue; Get
<span style="color:Blue; Set(<span style="color:Blue; ByVal value <span style="color:Blue; As <span style="color:Blue; Double)
<span style="color:Blue; If <span style="color:Blue; Not _Value = value <span style="color:Blue; Then
_Value = value
DoPropertyChanged(<span style="color:#A31515; "Value")
<span style="color:Blue; End <span style="color:Blue; If
<span style="color:Blue; End <span style="color:Blue; Set
<span style="color:Blue; End <span style="color:Blue; Property
<span style="color:Blue; Public <span style="color:Blue; Sub <span style="color:Blue; New()
<span style="color:Blue; Me.<span style="color:Blue; New(Now)
<span style="color:Blue; End <span style="color:Blue; Sub
<span style="color:Blue; Public <span style="color:Blue; Sub <span style="color:Blue; New(<span style="color:Blue; ByVal creationDate <span style="color:Blue; As <span style="color:Blue; Date)
_Created = creationDate
<span style="color:Blue; End <span style="color:Blue; Sub
<span style="color:Blue; End <span style="color:Blue; Class
[/code]
<br/>
Some Known Issues:
The BindingComplete event is not yet being raised by the collection. Further interaction with the CurrencyManager is required...
There will be an issue accessing the collection by key if the key property type specified is Integer.
Performance begins to drop noticeably above a few thousand entries, though is still usable at 10,000+. Note however that filtering the view to a small number of entries returns performance even when the underlying list is large.
The control is only partially documented.
Some Notes on Filters:
The filter string can contain multiple criteria. Each criteria should be a space seperated string containing three parts: the property name, the comparison, and the target value. Each criteria is seperated with a space. e.g. "Index
Greater 50" or "Index Greater 50 Name Contains ae"<br/>
The criteria value can be enclosed in "" if it contains a space character.<br/>
The criteria must be parsable into the target property data type.
The FilterBuilder property can be used to programmatically build up a filter from IKeyedBindingListFilterCriteria objects. When built this way, individual criteria can specify a delgates to control the comparing, formatting, and/or parsing of the criteria
value.
Note that setting the Filter string property will remove progammatically built filters.
In Summary
The KeyedBindingList(Of TKey, T) generic collection is a concept object designed to offer a highly flexible, feature-rich collection which can support full databinding and can easily manage any newable object which provides a uniquely keyed property value.
The KeyedObjectModel class library include a few additional classes such as base objects (or interfaces) to be used to define a custom object as uniquely keyed and/or observable. There is also a special LooselyKeyedList(Of TKey, TValue) collection
which provides the hybrid functionality of a list and a dictionary, but does not require that the key value of items be unique.
Please feel free to beat this library up one side and down the other and give an honest critique! <hr class="sig Reed Kimble - "When you do things right, people wont be sure youve done anything at all"
View the full article