SortedBindingList bound to DataGridView ArgumentOutOfRangeException

SortedBindingList bound to DataGridView ArgumentOutOfRangeException

Old forum URL: forums.lhotka.net/forums/t/12158.aspx


Michael posted on Monday, September 23, 2013

We recently upgraded from CSLA 4.3 to 4.5.30.

We use a lot of SortedBindingLists bound to DataGridViews. The business objects usually have a simple int SortOrder property. The collection inherits from BusinessBindingListBase<T, C>. New objects are added to the collection via bindingSource.AddNew(). Inside AddNewCore() when any biz object property is set, the following exception is thrown:

System.ArgumentOutOfRangeException: Specified argument was out of the range of valid values.
Parameter name: rowIndex
   at System.Windows.Forms.DataGridView.InvalidateCell(Int32 columnIndex, Int32 rowIndex)
   at System.Windows.Forms.DataGridView.DataGridViewDataConnection.ProcessListChanged(ListChangedEventArgs e)
   at System.Windows.Forms.DataGridView.DataGridViewDataConnection.currencyManager_ListChanged(Object sender, ListChangedEventArgs e)
   at System.Windows.Forms.CurrencyManager.List_ListChanged(Object sender, ListChangedEventArgs e)
   at System.Windows.Forms.BindingSource.InnerList_ListChanged(Object sender, ListChangedEventArgs e)
   at Csla.SortedBindingList`1.SourceChanged(Object sender, ListChangedEventArgs e)
   at System.ComponentModel.ListChangedEventHandler.Invoke(Object sender, ListChangedEventArgs e)
   at System.ComponentModel.BindingList`1.Child_PropertyChanged(Object sender, PropertyChangedEventArgs e)
   at System.ComponentModel.PropertyChangedEventHandler.Invoke(Object sender, PropertyChangedEventArgs e)
   at Csla.Core.BindableBase.OnPropertyChanged(String propertyName)
   at Csla.Core.BusinessBase.Csla.Rules.IHostRules.AllRulesComplete()

...

All of our forms using SortedBindingList are now broken. I can't see any changes in SortedBindingList.cs from 4.3 to 4.5.30.

Thanks in advance.

—Michael

JonnyBee replied on Monday, September 23, 2013

Hi,

Could it be changes in the BusinessBase that affects your application. Like OnPropertyChanged happening at wrong timings?

Have you set CslaPropertyChangedMode to "Windows" in your application?

You now get PropertyChanged on the meta properties (IsDirty, IsValid etc) when CslaPropertyChangedMode is Xaml (the default value).

Ex:

    protected virtual void MetaPropertyHasChanged(string name)
    {
      if (ApplicationContext.PropertyChangedMode != ApplicationContext.PropertyChangedModes.Windows)
        OnPropertyChanged(name);
    }

and

    protected virtual void CheckPropertyRules(IPropertyInfo property)
    {
      var propertyNames = BusinessRules.CheckRules(property);
      if (ApplicationContext.PropertyChangedMode == ApplicationContext.PropertyChangedModes.Windows)
        OnPropertyChanged(property);
      else
        foreach (var name in propertyNames)
          OnPropertyChanged(name);
    }

Michael replied on Monday, September 23, 2013

Thanks for the reply, Jonny.

I must have missed the memo about the PropertyChangedMode, but it hasn't fixed the problem, unfortunately.

I have uploaded a sample solution showing the problem. I would really appreciate it if you could take a look.

7 zip (2.6 MB): http://www.softbiz.com.au/temp/SortedBindingListSample.7z

OR

Zip (6.5 MB): http://www.softbiz.com.au/temp/SortedBindingListSample.zip

Kind regards
Michael

JonnyBee replied on Tuesday, September 24, 2013

Hi,

I have tried with several version back to CSLA 4.2.2 with the same result - Exception.

Actually - it is in the way SortedBindingList is coded that suppress the ItemAdded event until AFTER AddNewCore has completed. When you set a property on the object after Add in AddNewCore the events come in wrong sequence to the UI control and it has been so for quite some time. 

From SortedBindingList:

    /// <summary>
    /// Implemented by IList source object.
    /// </summary>
    public object AddNew()
    {
      object result;
      if (_supportsBinding)
      {
        _initiatedLocally = true;
        result = _bindingList.AddNew();
        _initiatedLocally = false;
        OnListChanged(
          new ListChangedEventArgs(
          ListChangedType.ItemAdded, _bindingList.Count - 1));
      }
      else
        result = null;
 
      return result;
    }

So when your code call OnPropertyChanged inside AddNewCore - after the Add - it is out of sequence.

I removed the _initiatedLocally and all if tests that suppress ListChanged event and removed the OnListChanged inside AddNew and your code runs just fine. I'm not sure why this was added at some time. One easy way to test is to add SortedBindingList in your own code and modify / try out changes. This class is quite "standalone" and you can update your code to use YOUR version (and any help to make this work perfect with FilteredBindingList and BindingList is greatly appreciated). 

JonnyBee replied on Tuesday, September 24, 2013

Try this version of SortedBindingList:

//-----------------------------------------------------------------------
// <copyright file="SortedBindingList.cs" company="Marimer LLC">
//     Copyright (c) Marimer LLC. All rights reserved.
//     Website: http://www.lhotka.net/cslanet/
// </copyright>
// <summary>Provides a sorted view into an existing IList(Of T).</summary>
//-----------------------------------------------------------------------
 
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using Csla.Properties;
 
namespace SortedBindingListSample
{
 
  /// <summary>
  /// Provides a sorted view into an existing IList(Of T).
  /// </summary>
  /// <typeparam name="T">
  /// Type of child object contained by
  /// the original list or collection.
  /// </typeparam>
  public class SortedBindingList<T> :
    IList<T>, IBindingListIEnumerable<T>, ICancelAddNew
  {
 
    #region ListItem class
 
    private class ListItem : IComparable<ListItem>
    {
 
      private object _key;
      private int _baseIndex;
 
      public object Key
      {
        get { return _key; }
      }
 
      public int BaseIndex
      {
        get { return _baseIndex; }
        set { _baseIndex = value; }
      }
 
      public ListItem(object key, int baseIndex)
      {
        _key = key;
        _baseIndex = baseIndex;
      }
 
      public int CompareTo(ListItem other)
      {
        object target = other.Key;
 
        if (Key is IComparable)
          return ((IComparable)Key).CompareTo(target);
 
        else
        {
          if (Key == null)
          {
            if (target == null)
              return 0;
            else
              return 1;
          }
          else if (Key.Equals(target))
            return 0;
 
          else if (target == null)
            return 1;
 
          return Key.ToString().CompareTo(target.ToString());
        }
      }
 
      public override string ToString()
      {
        return Key.ToString();
      }
 
    }
 
    #endregion
 
    #region Sorted enumerator
 
    private class SortedEnumerator : IEnumerator<T>
    {
      private IList<T> _list;
      private List<ListItem> _sortIndex;
      private ListSortDirection _sortOrder;
      private int _index;
 
      public SortedEnumerator(
        IList<T> list,
        List<ListItem> sortIndex,
        ListSortDirection direction)
      {
        _list = list;
        _sortIndex = sortIndex;
        _sortOrder = direction;
        Reset();
      }
 
      public T Current
      {
        get { return _list[_sortIndex[_index].BaseIndex]; }
      }
 
      Object System.Collections.IEnumerator.Current
      {
        get { return _list[_sortIndex[_index].BaseIndex]; }
      }
 
      public bool MoveNext()
      {
        if (_sortOrder == ListSortDirection.Ascending)
        {
          if (_index < _sortIndex.Count - 1)
          {
            _index++;
            return true;
          }
          else
            return false;
        }
        else
        {
          if (_index > 0)
          {
            _index--;
            return true;
          }
          else
            return false;
        }
      }
 
      public void Reset()
      {
        if (_sortOrder == ListSortDirection.Ascending)
          _index = -1;
        else
          _index = _sortIndex.Count;
      }
 
      #region IDisposable Support
 
      private bool _disposedValue = false// To detect redundant calls.
 
      // IDisposable
      protected virtual void Dispose(bool disposing)
      {
        if (!_disposedValue)
        {
          if (disposing)
          {
            // free unmanaged resources when explicitly called
          }
          // free shared unmanaged resources
        }
        _disposedValue = true;
      }
 
      // this code added to correctly implement the disposable pattern.
      public void Dispose()
      {
        // Do not change this code.  Put cleanup code in Dispose(bool disposing) above.
        Dispose(true);
        GC.SuppressFinalize(this);
      }
 
      /// <summary>
      /// Allows an <see cref="T:System.Object"/> to attempt to free resources and perform other cleanup operations before the <see cref="T:System.Object"/> is reclaimed by garbage collection.
      /// </summary>
      ~SortedEnumerator()
      {
        Dispose(false);
      }
 
      #endregion
 
    }
 
    #endregion
 
    #region Sort/Unsort
 
    private void DoSort()
    {
      int index = 0;
      _sortIndex.Clear();
 
      if (_sortBy == null)
      {
        foreach (T obj in _list)
        {
          _sortIndex.Add(new ListItem(obj, index));
          index++;
        }
      }
      else
      {
        foreach (T obj in _list)
        {
          _sortIndex.Add(new ListItem(_sortBy.GetValue(obj), index));
          index++;
        }
      }
 
      _sortIndex.Sort();
      _sorted = true;
 
      OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, 0));
 
    }
 
    private void UndoSort()
    {
      _sortIndex.Clear();
      _sortBy = null;
      _sortOrder = ListSortDirection.Ascending;
      _sorted = false;
 
      OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, 0));
 
    }
 
    #endregion
 
    #region IEnumerable<T>
 
    /// <summary>
    /// Returns an enumerator for the list, honoring
    /// any sort that is active at the time.
    /// </summary>
    public IEnumerator<T> GetEnumerator()
    {
      if (_sorted)
        return new SortedEnumerator(_list, _sortIndex, _sortOrder);
      else
        return _list.GetEnumerator();
    }
 
    #endregion
 
    #region IBindingList, IList<T>
 
    /// <summary>
    /// Implemented by IList source object.
    /// </summary>
    /// <param name="property">Property on which
    /// to build the index.</param>
    public void AddIndex(PropertyDescriptor property)
    {
      if (_supportsBinding)
        _bindingList.AddIndex(property);
    }
 
    /// <summary>
    /// Implemented by IList source object.
    /// </summary>
    public object AddNew()
    {
      object result;
      if (_supportsBinding)
      {
        result = _bindingList.AddNew();
      }
      else
        result = null;
 
      return result;
    }
 
    /// <summary>
    /// Implemented by IList source object.
    /// </summary>
    public bool AllowEdit
    {
      get
      {
        if (_supportsBinding)
          return _bindingList.AllowEdit;
        else
          return false;
      }
    }
 
    /// <summary>
    /// Implemented by IList source object.
    /// </summary>
    public bool AllowNew
    {
      get
      {
        if (_supportsBinding)
          return _bindingList.AllowNew;
        else
          return false;
      }
    }
 
    /// <summary>
    /// Implemented by IList source object.
    /// </summary>
    public bool AllowRemove
    {
      get
      {
        if (_supportsBinding)
          return _bindingList.AllowRemove;
        else
          return false;
      }
    }
 
    /// <summary>
    /// Applies a sort to the view.
    /// </summary>
    /// <param name="propertyName">The text name of the property on which to sort.</param>
    /// <param name="direction">The direction to sort the data.</param>
    public void ApplySort(string propertyName, ListSortDirection direction)
    {
      _sortBy = GetPropertyDescriptor(propertyName);
 
      ApplySort(_sortBy, direction);
    }
 
    /// <summary>
    /// Applies a sort to the view.
    /// </summary>
    /// <param name="property">A PropertyDescriptor for the property on which to sort.</param>
    /// <param name="direction">The direction to sort the data.</param>
    public void ApplySort(
      PropertyDescriptor property, ListSortDirection direction)
    {
      _sortBy = property;
      _sortOrder = direction;
      DoSort();
    }
 
    /// <summary>
    /// Finds an item in the view
    /// </summary>
    /// <param name="propertyName">Name of the property to search</param>
    /// <param name="key">Value to find</param>
    public int Find(string propertyName, object key)
    {
      PropertyDescriptor findProperty = GetPropertyDescriptor(propertyName);
 
      return Find(findProperty, key);
 
    }
 
    private static PropertyDescriptor GetPropertyDescriptor(string propertyName)
    {
      PropertyDescriptor property = null;
 
      if (!String.IsNullOrEmpty(propertyName))
      {
        Type itemType = typeof(T);
        foreach (PropertyDescriptor prop in TypeDescriptor.GetProperties(itemType))
        {
          if (prop.Name == propertyName)
          {
            property = prop;
            break;
          }
        }
 
        // throw exception if propertyDescriptor could not be found
        if (property == null)
          throw new ArgumentException(string.Format(Resources.SortedBindingListPropertyNameNotFound, propertyName), propertyName);
      }
 
      return property;
    }
 
    /// <summary>
    /// Implemented by IList source object.
    /// </summary>
    /// <param name="key">Key value for which to search.</param>
    /// <param name="property">Property to search for the key
    /// value.</param>
    public int Find(PropertyDescriptor property, object key)
    {
      if (_supportsBinding)
      {
        var originalIndex = _bindingList.Find(property, key);
        return originalIndex > -1 ? SortedIndex(originalIndex) : -1;
      }
      else
        return -1;
    }
 
    /// <summary>
    /// Gets a value indicating whether the view is currently sorted.
    /// </summary>
    public bool IsSorted
    {
      get { return _sorted; }
    }
 
    /// <summary>
    /// Raised to indicate that the list's data has changed.
    /// </summary>
    /// <remarks>
    /// This event is raised if the underling IList object's data changes
    /// (assuming the underling IList also implements the IBindingList
    /// interface). It is also raised if the sort property or direction
    /// is changed to indicate that the view's data has changed. See
    /// Chapter 5 for details.
    /// </remarks>
    public event ListChangedEventHandler ListChanged;
 
    /// <summary>
    /// Raises the <see cref="ListChanged"/> event.
    /// </summary>
    /// <param name="e">Event arguments.</param>
    protected void OnListChanged(ListChangedEventArgs e)
    {
      if (ListChanged != null)
        ListChanged(this, e);
    }
 
    /// <summary>
    /// Implemented by IList source object.
    /// </summary>
    /// <param name="property">Property for which the
    /// index should be removed.</param>
    public void RemoveIndex(PropertyDescriptor property)
    {
      if (_supportsBinding)
        _bindingList.RemoveIndex(property);
    }
 
    /// <summary>
    /// Removes any sort currently applied to the view.
    /// </summary>
    public void RemoveSort()
    {
      UndoSort();
    }
 
    /// <summary>
    /// Returns the direction of the current sort.
    /// </summary>
    public ListSortDirection SortDirection
    {
      get { return _sortOrder; }
    }
 
    /// <summary>
    /// Returns the PropertyDescriptor of the current sort.
    /// </summary>
    public PropertyDescriptor SortProperty
    {
      get { return _sortBy; }
    }
 
    /// <summary>
    /// Returns <see langword="true"/> since this object does raise the
    /// ListChanged event.
    /// </summary>
    public bool SupportsChangeNotification
    {
      get { return true; }
    }
 
    /// <summary>
    /// Implemented by IList source object.
    /// </summary>
    public bool SupportsSearching
    {
      get
      {
        if (_supportsBinding)
          return _bindingList.SupportsSearching;
        else
          return false;
      }
    }
 
    /// <summary>
    /// Returns <see langword="true"/>. Sorting is supported.
    /// </summary>
    public bool SupportsSorting
    {
      get { return true; }
    }
 
    /// <summary>
    /// Implemented by IList source object.
    /// </summary>
    /// <param name="array">Array to receive the data.</param>
    /// <param name="arrayIndex">Starting array index.</param>
    public void CopyTo(T[] array, int arrayIndex)
    {
      int pos = arrayIndex;
      foreach (T child in this)
      {
        array[pos] = child;
        pos++;
      }
    }
 
    void System.Collections.ICollection.CopyTo(System.Array array, int index)
    {
      T[] tmp = new T[array.Length];
      CopyTo(tmp, index);
      Array.Copy(tmp, 0, array, index, array.Length);
    }
 
    /// <summary>
    /// Implemented by IList source object.
    /// </summary>
    public int Count
    {
      get { return _list.Count; }
    }
 
    bool System.Collections.ICollection.IsSynchronized
    {
      get { return false; }
    }
 
    object System.Collections.ICollection.SyncRoot
    {
      get { return _list; }
    }
 
    IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
      return GetEnumerator();
    }
 
    /// <summary>
    /// Implemented by IList source object.
    /// </summary>
    /// <param name="item">Item to add to the list.</param>
    public void Add(T item)
    {
      _list.Add(item);
    }
 
    int System.Collections.IList.Add(object value)
    {
      Add((T)value);
      return SortedIndex(_list.Count - 1);
    }
 
    /// <summary>
    /// Implemented by IList source object.
    /// </summary>
    public void Clear()
    {
      _list.Clear();
    }
 
    /// <summary>
    /// Implemented by IList source object.
    /// </summary>
    /// <param name="item">Item for which to search.</param>
    public bool Contains(T item)
    {
      return _list.Contains(item);
    }
 
    bool System.Collections.IList.Contains(object value)
    {
      return Contains((T)value);
    }
 
    /// <summary>
    /// Implemented by IList source object.
    /// </summary>
    /// <param name="item">Item for which to search.</param>
    public int IndexOf(T item)
    {
      return SortedIndex(_list.IndexOf(item));
    }
 
    int System.Collections.IList.IndexOf(object value)
    {
      return IndexOf((T)value);
    }
 
    /// <summary>
    /// Implemented by IList source object.
    /// </summary>
    /// <param name="index">Index at
    /// which to insert the item.</param>
    /// <param name="item">Item to insert.</param>
    public void Insert(int index, T item)
    {
      _list.Insert(index, item);
    }
 
    void System.Collections.IList.Insert(int index, object value)
    {
      Insert(index, (T)value);
    }
 
    bool System.Collections.IList.IsFixedSize
    {
      get { return false; }
    }
 
    /// <summary>
    /// Implemented by IList source object.
    /// </summary>
    public bool IsReadOnly
    {
      get { return _list.IsReadOnly; }
    }
 
    object System.Collections.IList.this[int index]
    {
      get
      {
        return this[index];
      }
      set
      {
        this[index] = (T)value;
      }
    }
 
    /// <summary>
    /// Implemented by IList source object.
    /// </summary>
    /// <param name="item">Item to be removed.</param>
    public bool Remove(T item)
    {
      return _list.Remove(item);
    }
 
    void System.Collections.IList.Remove(object value)
    {
      Remove((T)value);
    }
 
    /// <summary>
    /// Removes the child object at the specified index
    /// in the list, resorting the display as needed.
    /// </summary>
    /// <param name="index">The index of the object to remove.</param>
    /// <remarks>
    /// See Chapter 5 for details on how and why the list is
    /// altered during the remove process.
    /// </remarks>
    public void RemoveAt(int index)
    {
      if (_sorted)
      {
        int baseIndex = OriginalIndex(index);
        // remove the item from the source list
        _list.RemoveAt(baseIndex);
      }
      else
        _list.RemoveAt(index);
    }
 
    /// <summary>
    /// Gets the child item at the specified index in the list,
    /// honoring the sort order of the items.
    /// </summary>
    /// <param name="index">The index of the item in the sorted list.</param>
    public T this[int index]
    {
      get
      {
        if (_sorted)
          return _list[OriginalIndex(index)];
        else
          return _list[index];
      }
      set
      {
        if (_sorted)
          _list[OriginalIndex(index)] = value;
        else
          _list[index] = value;
      }
    }
 
    #endregion
 
    #region SourceList
 
    /// <summary>
    /// Gets the source list over which this
    /// SortedBindingList is a view.
    /// </summary>
    [EditorBrowsable(EditorBrowsableState.Advanced)]
    public IList<T> SourceList
    {
      get
      {
        return _list;
      }
    }
 
    #endregion
 
    private IList<T> _list;
    private bool _supportsBinding;
    private IBindingList _bindingList;
    private bool _sorted;
    private PropertyDescriptor _sortBy;
    private ListSortDirection _sortOrder =
      ListSortDirection.Ascending;
    private List<ListItem> _sortIndex =
      new List<ListItem>();
 
 
    /// <summary>
    /// Creates a new view based on the provided IList object.
    /// </summary>
    /// <param name="list">The IList (collection) containing the data.</param>
    public SortedBindingList(IList<T> list)
    {
      _list = list;
 
      if (_list is IBindingList)
      {
        _supportsBinding = true;
        _bindingList = (IBindingList)_list;
        _bindingList.ListChanged += SourceChanged;
      }
    }
 
    private void SourceChanged(object sender, ListChangedEventArgs e)
    {
      if (_sorted)
      {
        switch (e.ListChangedType)
        {
          case ListChangedType.ItemAdded:
            T newItem = _list[e.NewIndex];
            if (e.NewIndex == _list.Count - 1)
            {
              object newKey;
              if (_sortBy != null)
                newKey = _sortBy.GetValue(newItem);
              else
                newKey = newItem;
 
              if (_sortOrder == ListSortDirection.Ascending)
                _sortIndex.Add(
                  new ListItem(newKey, e.NewIndex));
              else
                _sortIndex.Insert(0,
                  new ListItem(newKey, e.NewIndex));
               OnListChanged(
                  new ListChangedEventArgs(
                  ListChangedType.ItemAdded,
                  SortedIndex(e.NewIndex)));
            }
            else
              DoSort();
            break;
 
          case ListChangedType.ItemChanged:
            // an item changed - just relay the event with
            // a translated index value
            OnListChanged(
              new ListChangedEventArgs(
              ListChangedType.ItemChanged, SortedIndex(e.NewIndex), e.PropertyDescriptor));
            break;
 
          case ListChangedType.ItemDeleted:
            var internalIndex = InternalIndex(e.NewIndex);
            var sortedIndex = internalIndex;
            if ((_sorted) && (_sortOrder == ListSortDirection.Descending))
              sortedIndex = _sortIndex.Count - 1 - internalIndex;
 
            // remove from internal list 
            _sortIndex.RemoveAt(internalIndex);
 
            // now fix up all index pointers in the sort index
            foreach (ListItem item in _sortIndex)
              if (item.BaseIndex > e.NewIndex)
                item.BaseIndex -= 1;
 
            OnListChanged(
               new ListChangedEventArgs(
              ListChangedType.ItemDeleted, sortedIndex, e.PropertyDescriptor));
            break;
 
          default:
            // for anything other than add, delete or change
            // just re-sort the list
              DoSort();
            break;
        }
      }
      else
      {
          OnListChanged(e);
      }
    }
 
    private int OriginalIndex(int sortedIndex)
    {
      if (sortedIndex == -1) return -1;
 
      if (_sorted)
      {
        if (_sortOrder == ListSortDirection.Ascending)
          return _sortIndex[sortedIndex].BaseIndex;
        else
          return _sortIndex[_sortIndex.Count - 1 - sortedIndex].BaseIndex;
      }
      else
        return sortedIndex;
    }
 
    private int SortedIndex(int originalIndex)
    {
      if (originalIndex == -1) return -1;
 
      int result = 0;
      if (_sorted)
      {
        for (int index = 0; index < _sortIndex.Count; index++)
        {
          if (_sortIndex[index].BaseIndex == originalIndex)
          {
            result = index;
            break;
          }
        }
        if (_sortOrder == ListSortDirection.Descending)
          result = _sortIndex.Count - 1 - result;
      }
      else
        result = originalIndex;
      return result;
    }
 
    private int InternalIndex(int originalIndex)
    {
      int result = 0;
      if (_sorted)
      {
        for (int index = 0; index < _sortIndex.Count; index++)
        {
          if (_sortIndex[index].BaseIndex == originalIndex)
          {
            result = index;
            break;
          }
        }
      }
      else
        result = originalIndex;
      return result;
    }
 
 
    #region ICancelAddNew Members
 
    void ICancelAddNew.CancelNew(int itemIndex)
    {
      if (itemIndex > -1) return;
 
      ICancelAddNew can = _list as ICancelAddNew;
      if (can != null)
        can.CancelNew(OriginalIndex(itemIndex));
      else
        _list.RemoveAt(OriginalIndex(itemIndex));
    }
 
    void ICancelAddNew.EndNew(int itemIndex)
    {
      ICancelAddNew can = _list as ICancelAddNew;
      if (can != null)
        can.EndNew(OriginalIndex(itemIndex));
    }
 
    #endregion
 
    #region ToArray
 
    /// <summary>
    /// Get an array containing all items in the list.
    /// </summary>
    public T[] ToArray()
    {
      List<T> result = new List<T>();
      foreach (T item in this)
        result.Add(item);
      return result.ToArray();
    }
 
    #endregion
 
  }
}

Michael replied on Tuesday, September 24, 2013

Thank you so much, Jonny. I will try it out and get back to you.

Michael replied on Wednesday, September 25, 2013

The modified code seems to be working fine.

I know the SortedBindingList and FilteredBindingList have supposedly been superseded by LINQ, but we have used them both to implement some very useful controls in WinForms.

The main problem I have had with SortedBindingList was getting the dreaded EditLevelMismatchException when it was hooked up to an editable DataGridView. (I asked for help here.) My solution was to inherit from SortedBindingList, and expose a SortChanging event:

public event EventHandler SortChanging;
 
private void OnSortChanging()
{
    if (SortChanging != null)
        SortChanging(thisEventArgs.Empty);
}

and raise the event in the ApplySort() and RemoveSort() methods:

public override void ApplySort(PropertyDescriptor property, ListSortDirection direction)
{
    OnSortChanging();
 
    base.ApplySort(property, direction);
}
 
public override void ApplySort(string propertyName, ListSortDirection direction)
{
    OnSortChanging();
 
    base.ApplySort(propertyName, direction);
}
 
public override void RemoveSort()
{
    OnSortChanging();
 
    base.RemoveSort();
}

In the UI, we handle the event and call EndEdit() on the relevant binding source(s) as used in the RootChildGrandchild example.

I've also got my own version of FilteredBindingList which exposes exactly the same SortChanging event, and also exposes FilterChanging and FilterChanged events:

public event EventHandler FilterChanging;
 
private void OnFilterChanging()
{
    if (FilterChanging != null)
        FilterChanging(thisEventArgs.Empty);
}
 
public event EventHandler FilterChanged;
 
private void OnFilterChanged()
{
    if (FilterChanged != null)
        FilterChanged(thisEventArgs.Empty);
}

and raise the events in the ApplyFilter() and RemoveFilter() methods:

public override void ApplyFilter(string propertyName, object filter)
{
    OnFilterChanging();
 
    base.ApplyFilter(propertyName, filter);
 
    OnFilterChanged();
}
 
public override void RemoveFilter()
{
    OnFilterChanging();
 
    base.RemoveFilter();
 
    OnFilterChanged();
}

In the UI, the code to handle the FilterChanging event is identical to handling the SortChanging event, so I normally just make one event handler, e.g. OnFilterOrSortChanging.

The methods above are not actually virtual in standard CSLA, but it would be helpful if they were. The FilterChanged event was not necessary to solve the bug. I needed it to implement an Excel-style auto-filter control, which uses a FilteredBindingList.

If these changes were incorporated into CSLA, I'm sure it would be helpful to others using SortedBindingList or FilteredBindingList.

Thanks again for your help in solving this, Jonny.

—Michael

Copyright (c) Marimer LLC