//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // //------------------------------------------------------------------------------ [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Scope="type", Target="System.ComponentModel.BindingList`1")] namespace System.ComponentModel { using System; using System.Reflection; using System.Collections; using System.Collections.ObjectModel; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Security.Permissions; using CodeAccessPermission = System.Security.CodeAccessPermission; /// /// /// [HostProtection(SharedState = true)] [Serializable] public class BindingList : Collection, IBindingList, ICancelAddNew, IRaiseItemChangedEvents { private int addNewPos = -1; private bool raiseListChangedEvents = true; private bool raiseItemChangedEvents = false; [NonSerialized()] private PropertyDescriptorCollection itemTypeProperties = null; [NonSerialized()] private PropertyChangedEventHandler propertyChangedEventHandler = null; [NonSerialized()] private AddingNewEventHandler onAddingNew; [NonSerialized()] private ListChangedEventHandler onListChanged; [NonSerialized()] private int lastChangeIndex = -1; private bool allowNew = true; private bool allowEdit = true; private bool allowRemove = true; private bool userSetAllowNew = false; #region Constructors /// /// /// Default constructor. /// public BindingList() : base() { Initialize(); } /// /// /// Constructor that allows substitution of the inner list with a custom list. /// public BindingList(IList list) : base(list) { Initialize(); } private void Initialize() { // Set the default value of AllowNew based on whether type T has a default constructor this.allowNew = ItemTypeHasDefaultConstructor; // Check for INotifyPropertyChanged if (typeof(INotifyPropertyChanged).IsAssignableFrom(typeof(T))) { // Supports INotifyPropertyChanged this.raiseItemChangedEvents = true; // Loop thru the items already in the collection and hook their change notification. foreach (T item in this.Items) { HookPropertyChanged(item); } } } private bool ItemTypeHasDefaultConstructor { get { Type itemType = typeof(T); if (itemType.IsPrimitive) { return true; } if (itemType.GetConstructor(BindingFlags.Public | BindingFlags.Instance | BindingFlags.CreateInstance, null, new Type[0], null) != null) { return true; } return false; } } #endregion #region AddingNew event /// /// /// Event that allows a custom item to be provided as the new item added to the list by AddNew(). /// public event AddingNewEventHandler AddingNew { add { bool allowNewWasTrue = AllowNew; onAddingNew += value; if (allowNewWasTrue != AllowNew) { FireListChanged(ListChangedType.Reset, -1); } } remove { bool allowNewWasTrue = AllowNew; onAddingNew -= value; if (allowNewWasTrue != AllowNew) { FireListChanged(ListChangedType.Reset, -1); } } } /// /// /// Raises the AddingNew event. /// protected virtual void OnAddingNew(AddingNewEventArgs e) { if (onAddingNew != null) { onAddingNew(this, e); } } // Private helper method private object FireAddingNew() { AddingNewEventArgs e = new AddingNewEventArgs(null); OnAddingNew(e); return e.NewObject; } #endregion #region ListChanged event /// /// /// Event that reports changes to the list or to items in the list. /// public event ListChangedEventHandler ListChanged { add { onListChanged += value; } remove { onListChanged -= value; } } /// /// /// Raises the ListChanged event. /// protected virtual void OnListChanged(ListChangedEventArgs e) { if (onListChanged != null) { onListChanged(this, e); } } /// /// /// public void ResetBindings() { FireListChanged(ListChangedType.Reset, -1); } /// /// /// public void ResetItem(int position) { FireListChanged(ListChangedType.ItemChanged, position); } // Private helper method private void FireListChanged(ListChangedType type, int index) { if (this.raiseListChangedEvents) { OnListChanged(new ListChangedEventArgs(type, index)); } } #endregion #region Collection overrides // Collection funnels all list changes through the four virtual methods below. // We override these so that we can commit any pending new item and fire the proper ListChanged events. protected override void ClearItems() { EndNew(addNewPos); if (this.raiseItemChangedEvents) { foreach (T item in this.Items) { UnhookPropertyChanged(item); } } base.ClearItems(); FireListChanged(ListChangedType.Reset, -1); } protected override void InsertItem(int index, T item) { EndNew(addNewPos); base.InsertItem(index, item); if (this.raiseItemChangedEvents) { HookPropertyChanged(item); } FireListChanged(ListChangedType.ItemAdded, index); } protected override void RemoveItem(int index) { // Need to all RemoveItem if this on the AddNew item if (!this.allowRemove && !(this.addNewPos >= 0 && this.addNewPos == index)) { throw new NotSupportedException(); } EndNew(addNewPos); if (this.raiseItemChangedEvents) { UnhookPropertyChanged(this[index]); } base.RemoveItem(index); FireListChanged(ListChangedType.ItemDeleted, index); } protected override void SetItem(int index, T item) { if (this.raiseItemChangedEvents) { UnhookPropertyChanged(this[index]); } base.SetItem(index, item); if (this.raiseItemChangedEvents) { HookPropertyChanged(item); } FireListChanged(ListChangedType.ItemChanged, index); } #endregion #region ICancelAddNew interface /// /// /// If item added using AddNew() is still cancellable, then remove that item from the list. /// public virtual void CancelNew(int itemIndex) { if (addNewPos >= 0 && addNewPos == itemIndex) { RemoveItem(addNewPos); addNewPos = -1; } } /// /// /// If item added using AddNew() is still cancellable, then commit that item. /// public virtual void EndNew(int itemIndex) { if (addNewPos >= 0 && addNewPos == itemIndex) { addNewPos = -1; } } #endregion #region IBindingList interface /// /// /// Adds a new item to the list. Calls to create and add the item. /// /// Add operations are cancellable via the interface. The position of the /// new item is tracked until the add operation is either cancelled by a call to , /// explicitly commited by a call to , or implicitly commmited some other operation /// that changes the contents of the list (such as an Insert or Remove). When an add operation is /// cancelled, the new item is removed from the list. /// public T AddNew() { return (T)((this as IBindingList).AddNew()); } object IBindingList.AddNew() { // Create new item and add it to list object newItem = AddNewCore(); // Record position of new item (to support cancellation later on) addNewPos = (newItem != null) ? IndexOf((T) newItem) : -1; // Return new item to caller return newItem; } private bool AddingNewHandled { get { return onAddingNew != null && onAddingNew.GetInvocationList().Length > 0; } } /// /// /// Creates a new item and adds it to the list. /// /// The base implementation raises the AddingNew event to allow an event handler to /// supply a custom item to add to the list. Otherwise an item of type T is created. /// The new item is then added to the end of the list. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2113:SecureLateBindingMethods")] protected virtual object AddNewCore() { // Allow event handler to supply the new item for us object newItem = FireAddingNew(); // If event hander did not supply new item, create one ourselves if (newItem == null) { Type type = typeof(T); newItem = SecurityUtils.SecureCreateInstance(type); } // Add item to end of list. Note: If event handler returned an item not of type T, // the cast below will trigger an InvalidCastException. This is by design. Add((T) newItem); // Return new item to caller return newItem; } /// /// /// public bool AllowNew { get { //If the user set AllowNew, return what they set. If we have a default constructor, allowNew will be //true and we should just return true. if (userSetAllowNew || allowNew) { return this.allowNew; } //Even if the item doesn't have a default constructor, the user can hook AddingNew to provide an item. //If there's a handler for this, we should allow new. return AddingNewHandled; } set { bool oldAllowNewValue = AllowNew; userSetAllowNew = true; //Note that we don't want to set allowNew only if AllowNew didn't match value, //since AllowNew can depend on onAddingNew handler this.allowNew = value; if (oldAllowNewValue != value) { FireListChanged(ListChangedType.Reset, -1); } } } /* private */ bool IBindingList.AllowNew { get { return AllowNew; } } /// /// /// public bool AllowEdit { get { return this.allowEdit; } set { if (this.allowEdit != value) { this.allowEdit = value; FireListChanged(ListChangedType.Reset, -1); } } } /* private */ bool IBindingList.AllowEdit { get { return AllowEdit; } } /// /// /// public bool AllowRemove { get { return this.allowRemove; } set { if (this.allowRemove != value) { this.allowRemove = value; FireListChanged(ListChangedType.Reset, -1); } } } /* private */ bool IBindingList.AllowRemove { get { return AllowRemove; } } bool IBindingList.SupportsChangeNotification { get { return SupportsChangeNotificationCore; } } protected virtual bool SupportsChangeNotificationCore { get { return true; } } bool IBindingList.SupportsSearching { get { return SupportsSearchingCore; } } protected virtual bool SupportsSearchingCore { get { return false; } } bool IBindingList.SupportsSorting { get { return SupportsSortingCore; } } protected virtual bool SupportsSortingCore { get { return false; } } bool IBindingList.IsSorted { get { return IsSortedCore; } } protected virtual bool IsSortedCore { get { return false; } } PropertyDescriptor IBindingList.SortProperty { get { return SortPropertyCore; } } protected virtual PropertyDescriptor SortPropertyCore { get { return null; } } ListSortDirection IBindingList.SortDirection { get { return SortDirectionCore; } } protected virtual ListSortDirection SortDirectionCore { get { return ListSortDirection.Ascending; } } void IBindingList.ApplySort(PropertyDescriptor prop, ListSortDirection direction) { ApplySortCore(prop, direction); } protected virtual void ApplySortCore(PropertyDescriptor prop, ListSortDirection direction) { throw new NotSupportedException(); } void IBindingList.RemoveSort() { RemoveSortCore(); } protected virtual void RemoveSortCore() { throw new NotSupportedException(); } int IBindingList.Find(PropertyDescriptor prop, object key) { return FindCore(prop, key); } protected virtual int FindCore(PropertyDescriptor prop, object key) { throw new NotSupportedException(); } void IBindingList.AddIndex(PropertyDescriptor prop) { // Not supported } void IBindingList.RemoveIndex(PropertyDescriptor prop) { // Not supported } #endregion #region Property Change Support private void HookPropertyChanged(T item) { INotifyPropertyChanged inpc = (item as INotifyPropertyChanged); // Note: inpc may be null if item is null, so always check. if (null != inpc) { if (propertyChangedEventHandler == null) { propertyChangedEventHandler = new PropertyChangedEventHandler(Child_PropertyChanged); } inpc.PropertyChanged += propertyChangedEventHandler; } } private void UnhookPropertyChanged(T item) { INotifyPropertyChanged inpc = (item as INotifyPropertyChanged); // Note: inpc may be null if item is null, so always check. if (null != inpc && null != propertyChangedEventHandler) { inpc.PropertyChanged -= propertyChangedEventHandler; } } void Child_PropertyChanged(object sender, PropertyChangedEventArgs e) { if (this.RaiseListChangedEvents) { if (sender == null || e == null || string.IsNullOrEmpty(e.PropertyName)) { // Fire reset event (per INotifyPropertyChanged spec) ResetBindings(); } else { // The change event is broken should someone pass an item to us that is not // of type T. Still, if they do so, detect it and ignore. It is an incorrect // and rare enough occurrence that we do not want to slow the mainline path // with "is" checks. T item; try { item = (T)sender; } catch(InvalidCastException) { ResetBindings(); return; } // Find the position of the item. This should never be -1. If it is, // somehow the item has been removed from our list without our knowledge. int pos = lastChangeIndex; if (pos < 0 || pos >= Count || !this[pos].Equals(item)) { pos = this.IndexOf(item); lastChangeIndex = pos; } if (pos == -1) { Debug.Fail("Item is no longer in our list but we are still getting change notifications."); UnhookPropertyChanged(item); ResetBindings(); } else { // Get the property descriptor if (null == this.itemTypeProperties) { // Get Shape itemTypeProperties = TypeDescriptor.GetProperties(typeof(T)); Debug.Assert(itemTypeProperties != null); } PropertyDescriptor pd = itemTypeProperties.Find(e.PropertyName, true); // Create event args. If there was no matching property descriptor, // we raise the list changed anyway. ListChangedEventArgs args = new ListChangedEventArgs(ListChangedType.ItemChanged, pos, pd); // Fire the ItemChanged event OnListChanged(args); } } } } #endregion #region IRaiseItemChangedEvents interface /// /// /// Returns false to indicate that BindingList does NOT raise ListChanged events /// of type ItemChanged as a result of property changes on individual list items /// unless those items support INotifyPropertyChanged /// bool IRaiseItemChangedEvents.RaisesItemChangedEvents { get { return this.raiseItemChangedEvents; } } #endregion } }