Jesse Johnston
.NET Dev. Good times with .NET and coffee

ObjectListView Update (1.0.0.9): Debugger Visualizer

Sunday, 11 March 2007 19:46 by jesse

Note: You can download the complete implementation here.

For those of you just tuning in, ObjectListView is my answer to the .NET need for a DataView-like construct for ordinary objects.  With ObjectListView, you can have a sorted and filtered view of any IList or IList<T>.  You can bind this view to controls like the DataGridView.

This new version adds a debugger visualizer and includes a few other small enhancements and bug fixes.

Bug Fixes

Ecki found a bug in the Master/Details demo where I was doing an invalid cast in the New button click handler.  I also found that .NET 1.1 style property change events weren't wired properly for items already in the list at the time an ObjectListView was constructed.  Those two are fixed.

Boris reported that changing a list item property while iterating over ObjectListView with a foreach loop would cause an InvalidOperationException to be thrown.  Indeed!  This is by design; any change to a .NET collection must invalidate all enumerators of the collection.   ObjectListView is really a view over a collection, though, and not a collection itself.  This view presents the appearance of a collection.  This virtual collection does change when items are added, removed, or replaced in the underlying collection.  When the value of a list item property changes, however, the virtual collection only changes in certain circumstances.  Fortunately, these circumstances are well-understood:  if the property is not included in either the ObjectListView sort or filter, the virtual collection does not change.  In this case, there is no need to invalidate any enumeration in progress.  In the updated version, ObjectListView allows enumeration to continue following a list item property change if the property does not belong to the sort or the filter criteria.

Small Changes

I added a ToArray() method, as I find it irritating to have to declare an array and copy into it with CopyTo() in two steps.  By request, OnListChanged(), OnSorted() and OnAddingNew() are now protected virtual, for extensibility.  These methods follow the usual convention of event-raising methods in .NET.  If you override one of them, be sure to call the base version so that the event will in fact be raised.

License

Since more than a few have asked, I've added the license terms to the Readme.txt file of the download.  ObjectListView is free - no strings attached.

The Visualizer

This is the big cool addition in my estimation.  If you're debugging a program that uses ObjectListView, you can see a nice dialog that shows you the current state of the ObjectListView and it's underlying list.  At a breakpoint, move the mouse over the variable that is the ObjectListView, and click the little magnifying glass.  You'll see something like this:

On the left side, you'll see information about the type of objects in the underlying list ("List Item Type"), and below that, information about the list itself ("List Type").  If the ObjectListView is sorted, the sort properties and sort direction of each are listed.  Below that is the current filter expression, if any.  If you're using a filter predicate (as I am in the example), it will tell you that.

The right side shows two tabs.  The one on the top ("View") shows the list items that are currently exposed by the ObjectListView, in the order defined by the current sort, and excluding any items that are filtered out.  This is exactly what you would see in a foreach loop, enumerating the items in the ObjectListView.  The second tab ("List") shows all of the list items in the underlying list, in the order in which they appear in the list.  Cool, huh?

But wait, there's more!  If you're having problems getting ObjectListView to work the way that you expect it to, check out the Analysis button in the lower left.  If you click that, you'll see a synopsis of how the list and list item types work with ObjectListView.  It will tell you about any potential problems, and recommend a solution.  Here's what it looks like:

I hope that the visualizer is helpful.  Debug visualizers are really not very hard to write, and I encourage you to write your own for any complex types that you're coding up.  Take a look at the source in ObjectListViewVisualizer.cs to see how to do it.  The only significant constraint on your code is that the type you're visualizing needs to be serializable.

Enjoy - and please don't hesitate to speak up if you have questions or thoughts about ObjectListView!

kick it on DotNetKicks.com

Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkList

Comments

March 29. 2007 00:07

JT

Hi,

This isn't directly related to the ObjectListView, but since you've dived through the bowels of the IBindingList interface and its cohorts, I was wondering if you could shed some light on some odd behaviour I've noticed.

I have a DataGridView connected to a BindingSource which is in turn connected to a BindingList. When I add a new row to the Grid (on screen), InsertInto is called on the underlying BindingList instead of AddNew. I've dug around till I'm blue in the face, and can't work it out.

If you happen to know why, or you've seen it before (heck, even if you haven't) and you can spare the time, please let me know.

Thanks,
JT

JT

April 1. 2007 11:24

Jesse

Hi James,

Sorry for the late reply - I was in the bay area for a couple of days.

It's true, BindingList<T>.InsertItem() is called in this situation.  It's not as bizarre as you might think, though.

An easy way to understand this behavior is to create a class that derives from BindingList<T>, and override AddNewCore() and InsertItem().  Just use this class in place of BindingList<T>, and put breakpoints in AddNewCore() and InsertItem().  When you add a new row to the DataGridView, the stack trace looks something like this (stopped in InsertItem):

>  MasterDetailDemo.exe!MasterDetailDemo.CustomerList.InsertItem(int index = 0, MasterDetailDemo.Customer item = {MasterDetailDemo.Customer}) Line 15  C#
  mscorlib.dll!System.Collections.ObjectModel.Collection<System.__Canon>.Add(System.__Canon item) + 0x79 bytes  
  System.dll!System.ComponentModel.BindingList<System.__Canon>.AddNewCore() + 0x79 bytes  
  MasterDetailDemo.exe!MasterDetailDemo.CustomerList.AddNewCore() Line 11 + 0x7 bytes  C#
  System.dll!System.ComponentModel.BindingList<MasterDetailDemo.Customer>.System.ComponentModel.IBindingList.AddNew() + 0xe bytes  
  System.Windows.Forms.dll!System.Windows.Forms.BindingSource.AddNew() + 0xb5 bytes  
  System.Windows.Forms.dll!System.Windows.Forms.CurrencyManager.AddNew() + 0x1e bytes  
  System.Windows.Forms.dll!System.Windows.Forms.DataGridView.DataGridViewDataConnection.AddNew() + 0x8b bytes  
  System.Windows.Forms.dll!System.Windows.Forms.DataGridView.DataGridViewDataConnection.OnNewRowNeeded() + 0x24 bytes  
  System.Windows.Forms.dll!System.Windows.Forms.DataGridView.OnRowEnter(ref System.Windows.Forms.DataGridViewCell dataGridViewCell = null, int columnIndex = 0, int rowIndex = 0, bool canCreateNewRow, bool validationFailureOccurred) + 0x1c1 bytes  
  System.Windows.Forms.dll!System.Windows.Forms.DataGridView.SetCurrentCellAddressCore(int columnIndex = 0, int rowIndex = 0, bool setAnchorCellAddress, bool validateCurrentCell, bool throughMouseClick = true) + 0x678 bytes  
  System.Windows.Forms.dll!System.Windows.Forms.DataGridView.OnCellMouseDown(System.Windows.Forms.DataGridView.HitTestInfo hti, bool isShiftDown, bool isControlDown) + 0x1376 bytes  
  System.Windows.Forms.dll!System.Windows.Forms.DataGridView.OnCellMouseDown(System.Windows.Forms.DataGridViewCellMouseEventArgs e) + 0x380 bytes  
  System.Windows.Forms.dll!System.Windows.Forms.DataGridView.OnMouseDown(System.Windows.Forms.MouseEventArgs e) + 0x183 bytes  
What does this tell us?

First of all, the IBindingList implementation of AddNew() is called in our BindingList class, as we would expect.  The call to InsertItem() is just an implementation detail of AddNew().  BindingList<T>.AddNew() results in a call to the base Collection<T>.Add(), which in turn calls the virtual method InsertItem().  Since BindingList<T> overrides InsertItem(), you see it being called in this case.

I hope that clears it up!

Cheers,
Jesse

Jesse

Comments are closed