Monday, March 16, 2009

RollingBindingList

This is a class I've found handy a couple of times when writing viewers for streaming data.  In these cases, we want a collection that has the following unique behaviors:

  • Items are added to the top of the list.
  • Only N items are allowed in the list at once.
  • Once N items are reached, items are removed from the bottom of the list.
  • Can be databound to a DataGrid and not have to write any special UI code to do the above.

So in TDD fashion, I've written the following tests:

[TestFixture]
   public class RollingBindingListTests
   {
    
       [Test]
       public void Should_Accept_Max_In_Constructor()
       {
           var target = new RollingBindingList<string>(10);
           Assert.AreEqual(10, target.MaximumItemCount);
       }
 
       [Test]
       public void Items_Should_Add_To_Top_Of_List()
       {
           var target = new RollingBindingList<string>(10);
           target.Add("test1");
           target.Add("test2");
           target.Add("test3");
           Assert.AreEqual("test3", target.First());
       }
 
       [Test]
       public void Should_Only_Allow_Max_Items()
       {
           var target = new RollingBindingList<string>(5);
           target.Add("test1");
           target.Add("test2");
           target.Add("test3");
           target.Add("test4");
           target.Add("test5");
           target.Add("test6");
           Assert.AreEqual(5, target.Count());
           Assert.AreEqual("test2", target.Last());
           Assert.AreEqual("test6", target.First());
       }
   }

Which the following class passes:

public class RollingBindingList<T> : BindingList<T>
    {
        public int MaximumItemCount { get; set; }
 
        public RollingBindingList(int maximumItemCount)
        {
            MaximumItemCount = maximumItemCount;
        }
 
        public new void Add(T item)
        {
            this.Insert(0, item);
            if (this.Count > MaximumItemCount) this.RemoveAt(MaximumItemCount);
        }
    }

When bound to a DataGridView, the behavior is as you would expect - items get added to the top, and "fall out" automatically as more are added.  One quirk this brings out in the DataGridView is that it automatically scrolls to the bottom when an item is added or removed.  I'm still looking for a better workaround for this, but the following works with a little bit of flicker:

allEvents = new RollingBindingList<LogEventArgs>(25);
this.dataGridView1.DataSource = allEvents;
//...
private void dataGridView1_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
    dataGridView1.FirstDisplayedScrollingRowIndex = 1;
}

Some work remains to be done.  Some thread-safety needs to be added, and default constructors and so on- plus I imagine a little work to make it play well in WPF.  But hopefully this will be useful to you!

No comments: