The Labs \ Source Viewer \ SSCLI \ System.ComponentModel \ BindingList

  1. //------------------------------------------------------------------------------
  2. // <copyright file="BindingList.cs" company="Microsoft">
  3. //
  4. // Copyright (c) 2006 Microsoft Corporation. All rights reserved.
  5. //
  6. // The use and distribution terms for this software are contained in the file
  7. // named license.txt, which can be found in the root of this distribution.
  8. // By using this software in any fashion, you are agreeing to be bound by the
  9. // terms of this license.
  10. //
  11. // You must not remove this notice, or any other, from this software.
  12. //
  13. // </copyright>
  14. //------------------------------------------------------------------------------
  15. [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Scope = "type", Target = "System.ComponentModel.BindingList`1")]
  16. namespace System.ComponentModel
  17. {
  18.     using System;
  19.     using System.Reflection;
  20.     using System.Collections;
  21.     using System.Collections.ObjectModel;
  22.     using System.Collections.Generic;
  23.     using System.ComponentModel;
  24.     using System.Diagnostics;
  25.     using System.Security.Permissions;
  26.     using CodeAccessPermission = System.Security.CodeAccessPermission;
  27.    
  28.     /// <include file='doc\BindingList.uex' path='docs/doc[@for="BindingList"]/*' />
  29.     /// <devdoc>
  30.     /// </devdoc>
  31.     [HostProtection(SharedState = true)]
  32.     [Serializable()]
  33.     public class BindingList<T> : Collection<T>, IBindingList, ICancelAddNew, IRaiseItemChangedEvents
  34.     {
  35.         private int addNewPos = -1;
  36.         private bool raiseListChangedEvents = true;
  37.         private bool raiseItemChangedEvents = false;
  38.        
  39.         [NonSerialized()]
  40.         private PropertyDescriptorCollection itemTypeProperties = null;
  41.        
  42.         [NonSerialized()]
  43.         private PropertyChangedEventHandler propertyChangedEventHandler = null;
  44.        
  45.         [NonSerialized()]
  46.         private AddingNewEventHandler onAddingNew;
  47.        
  48.         [NonSerialized()]
  49.         private ListChangedEventHandler onListChanged;
  50.        
  51.         [NonSerialized()]
  52.         private int lastChangeIndex = -1;
  53.        
  54.         private bool allowNew = true;
  55.         private bool allowEdit = true;
  56.         private bool allowRemove = true;
  57.         private bool userSetAllowNew = false;
  58.        
  59.         #region Constructors
  60.        
  61.         /// <include file='doc\BindingList.uex' path='docs/doc[@for="BindingList.BindingList"]/*' />
  62.         /// <devdoc>
  63.         /// Default constructor.
  64.         /// </devdoc>
  65.         public BindingList() : base()
  66.         {
  67.             Initialize();
  68.         }
  69.        
  70.         /// <include file='doc\BindingList.uex' path='docs/doc[@for="BindingList.BindingList1"]/*' />
  71.         /// <devdoc>
  72.         /// Constructor that allows substitution of the inner list with a custom list.
  73.         /// </devdoc>
  74.         public BindingList(IList<T> list) : base(list)
  75.         {
  76.             Initialize();
  77.         }
  78.        
  79.         private void Initialize()
  80.         {
  81.             // Set the default value of AllowNew based on whether type T has a default constructor
  82.             this.allowNew = ItemTypeHasDefaultConstructor;
  83.            
  84.             // Check for INotifyPropertyChanged
  85.             if (typeof(INotifyPropertyChanged).IsAssignableFrom(typeof(T))) {
  86.                 // Supports INotifyPropertyChanged
  87.                 this.raiseItemChangedEvents = true;
  88.                
  89.                 // Loop thru the items already in the collection and hook their change notification.
  90.                 foreach (T item in this.Items) {
  91.                     HookPropertyChanged(item);
  92.                 }
  93.             }
  94.         }
  95.        
  96.         private bool ItemTypeHasDefaultConstructor {
  97.             get {
  98.                 Type itemType = typeof(T);
  99.                
  100.                 if (itemType.IsPrimitive) {
  101.                     return true;
  102.                 }
  103.                
  104.                 if (itemType.GetConstructor(BindingFlags.Public | BindingFlags.Instance | BindingFlags.CreateInstance, null, new Type[0], null) != null) {
  105.                     return true;
  106.                 }
  107.                
  108.                 return false;
  109.             }
  110.         }
  111.        
  112.         #endregion
  113.        
  114.         #region AddingNew event
  115.        
  116.         /// <include file='doc\BindingList.uex' path='docs/doc[@for="BindingList.AddingNew"]/*' />
  117.         /// <devdoc>
  118.         /// Event that allows a custom item to be provided as the new item added to the list by AddNew().
  119.         /// </devdoc>
  120.         public event AddingNewEventHandler AddingNew {
  121.             add {
  122.                 bool allowNewWasTrue = AllowNew;
  123.                 onAddingNew += value;
  124.                 if (allowNewWasTrue != AllowNew) {
  125.                     FireListChanged(ListChangedType.Reset, -1);
  126.                 }
  127.             }
  128.             remove {
  129.                 bool allowNewWasTrue = AllowNew;
  130.                 onAddingNew -= value;
  131.                 if (allowNewWasTrue != AllowNew) {
  132.                     FireListChanged(ListChangedType.Reset, -1);
  133.                 }
  134.             }
  135.         }
  136.        
  137.         /// <include file='doc\BindingList.uex' path='docs/doc[@for="BindingList.OnAddingNew"]/*' />
  138.         /// <devdoc>
  139.         /// Raises the AddingNew event.
  140.         /// </devdoc>
  141.         protected virtual void OnAddingNew(AddingNewEventArgs e)
  142.         {
  143.             if (onAddingNew != null) {
  144.                 onAddingNew(this, e);
  145.             }
  146.         }
  147.        
  148.         // Private helper method
  149.         private object FireAddingNew()
  150.         {
  151.             AddingNewEventArgs e = new AddingNewEventArgs(null);
  152.             OnAddingNew(e);
  153.             return e.NewObject;
  154.         }
  155.        
  156.         #endregion
  157.        
  158.         #region ListChanged event
  159.        
  160.         /// <include file='doc\BindingList.uex' path='docs/doc[@for="BindingList.ListChanged"]/*' />
  161.         /// <devdoc>
  162.         /// Event that reports changes to the list or to items in the list.
  163.         /// </devdoc>
  164.         public event ListChangedEventHandler ListChanged {
  165.             add { onListChanged += value; }
  166.             remove { onListChanged -= value; }
  167.         }
  168.        
  169.         /// <include file='doc\BindingList.uex' path='docs/doc[@for="BindingList.OnListChanged"]/*' />
  170.         /// <devdoc>
  171.         /// Raises the ListChanged event.
  172.         /// </devdoc>
  173.         protected virtual void OnListChanged(ListChangedEventArgs e)
  174.         {
  175.             if (onListChanged != null) {
  176.                 onListChanged(this, e);
  177.             }
  178.         }
  179.        
  180.         /// <include file='doc\BindingList.uex' path='docs/doc[@for="BindingList.RaiseListChangedEvents"]/* />
  181.         public bool RaiseListChangedEvents {
  182.             get { return this.raiseListChangedEvents; }
  183.            
  184.             set {
  185.                 if (this.raiseListChangedEvents != value) {
  186.                     this.raiseListChangedEvents = value;
  187.                 }
  188.             }
  189.         }
  190.        
  191.         /// <include file='doc\BindingList.uex' path='docs/doc[@for="BindingList.ResetBindings"]/*' />
  192.         /// <devdoc>
  193.         /// </devdoc>
  194.         public void ResetBindings()
  195.         {
  196.             FireListChanged(ListChangedType.Reset, -1);
  197.         }
  198.        
  199.         /// <include file='doc\BindingList.uex' path='docs/doc[@for="BindingList.ResetItem"]/*' />
  200.         /// <devdoc>
  201.         /// </devdoc>
  202.         public void ResetItem(int position)
  203.         {
  204.             FireListChanged(ListChangedType.ItemChanged, position);
  205.         }
  206.        
  207.         // Private helper method
  208.         private void FireListChanged(ListChangedType type, int index)
  209.         {
  210.             if (this.raiseListChangedEvents) {
  211.                 OnListChanged(new ListChangedEventArgs(type, index));
  212.             }
  213.         }
  214.        
  215.         #endregion
  216.        
  217.         #region Collection overrides
  218.        
  219.         // Collection<T> funnels all list changes through the four virtual methods below.
  220.         // We override these so that we can commit any pending new item and fire the proper ListChanged events.
  221.        
  222.         protected override void ClearItems()
  223.         {
  224.             EndNew(addNewPos);
  225.            
  226.             if (this.raiseItemChangedEvents) {
  227.                 foreach (T item in this.Items) {
  228.                     UnhookPropertyChanged(item);
  229.                 }
  230.             }
  231.            
  232.             base.ClearItems();
  233.             FireListChanged(ListChangedType.Reset, -1);
  234.         }
  235.        
  236.         protected override void InsertItem(int index, T item)
  237.         {
  238.             EndNew(addNewPos);
  239.             base.InsertItem(index, item);
  240.            
  241.             if (this.raiseItemChangedEvents) {
  242.                 HookPropertyChanged(item);
  243.             }
  244.            
  245.             FireListChanged(ListChangedType.ItemAdded, index);
  246.         }
  247.        
  248.         protected override void RemoveItem(int index)
  249.         {
  250.             // Need to all RemoveItem if this on the AddNew item
  251.             if (!this.allowRemove && !(this.addNewPos >= 0 && this.addNewPos == index)) {
  252.                 throw new NotSupportedException();
  253.             }
  254.            
  255.             EndNew(addNewPos);
  256.            
  257.             if (this.raiseItemChangedEvents) {
  258.                 UnhookPropertyChanged(this[index]);
  259.             }
  260.            
  261.             base.RemoveItem(index);
  262.             FireListChanged(ListChangedType.ItemDeleted, index);
  263.         }
  264.        
  265.         protected override void SetItem(int index, T item)
  266.         {
  267.            
  268.             if (this.raiseItemChangedEvents) {
  269.                 UnhookPropertyChanged(this[index]);
  270.             }
  271.            
  272.             base.SetItem(index, item);
  273.            
  274.             if (this.raiseItemChangedEvents) {
  275.                 HookPropertyChanged(item);
  276.             }
  277.            
  278.             FireListChanged(ListChangedType.ItemChanged, index);
  279.         }
  280.        
  281.         #endregion
  282.        
  283.         #region ICancelAddNew interface
  284.        
  285.         /// <include file='doc\BindingList.uex' path='docs/doc[@for="BindingList.CancelNew"]/*' />
  286.         /// <devdoc>
  287.         /// If item added using AddNew() is still cancellable, then remove that item from the list.
  288.         /// </devdoc>
  289.         public virtual void CancelNew(int itemIndex)
  290.         {
  291.             if (addNewPos >= 0 && addNewPos == itemIndex) {
  292.                 RemoveItem(addNewPos);
  293.                 addNewPos = -1;
  294.             }
  295.         }
  296.        
  297.         /// <include file='doc\BindingList.uex' path='docs/doc[@for="BindingList.EndNew"]/*' />
  298.         /// <devdoc>
  299.         /// If item added using AddNew() is still cancellable, then commit that item.
  300.         /// </devdoc>
  301.         public virtual void EndNew(int itemIndex)
  302.         {
  303.             if (addNewPos >= 0 && addNewPos == itemIndex) {
  304.                 addNewPos = -1;
  305.             }
  306.         }
  307.        
  308.         #endregion
  309.        
  310.         #region IBindingList interface
  311.        
  312.         /// <include file='doc\BindingList.uex' path='docs/doc[@for="BindingList.AddNew"]/*' />
  313.         /// <devdoc>
  314.         /// Adds a new item to the list. Calls <see cref='AddNewCore'> to create and add the item.
  315.         ///
  316.         /// Add operations are cancellable via the <see cref='ICancelAddNew'> interface. The position of the
  317.         /// new item is tracked until the add operation is either cancelled by a call to <see cref='CancelNew'>,
  318.         /// explicitly commited by a call to <see cref='EndNew'>, or implicitly commmited some other operation
  319.         /// that changes the contents of the list (such as an Insert or Remove). When an add operation is
  320.         /// cancelled, the new item is removed from the list.
  321.         /// </devdoc>
  322.         public T AddNew()
  323.         {
  324.             return (T)((this as IBindingList).AddNew());
  325.         }
  326.        
  327.         object IBindingList.AddNew()
  328.         {
  329.             // Create new item and add it to list
  330.             object newItem = AddNewCore();
  331.            
  332.             // Record position of new item (to support cancellation later on)
  333.             addNewPos = (newItem != null) ? IndexOf((T)newItem) : -1;
  334.            
  335.             // Return new item to caller
  336.             return newItem;
  337.         }
  338.        
  339.         private bool AddingNewHandled {
  340.             get { return onAddingNew != null && onAddingNew.GetInvocationList().Length > 0; }
  341.         }
  342.        
  343.         /// <include file='doc\BindingList.uex' path='docs/doc[@for="BindingList.AddNewCore"]/*' />
  344.         /// <devdoc>
  345.         /// Creates a new item and adds it to the list.
  346.         ///
  347.         /// The base implementation raises the AddingNew event to allow an event handler to
  348.         /// supply a custom item to add to the list. Otherwise an item of type T is created.
  349.         /// The new item is then added to the end of the list.
  350.         /// </devdoc>
  351.         [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2113:SecureLateBindingMethods")]
  352.         protected virtual object AddNewCore()
  353.         {
  354.             // Allow event handler to supply the new item for us
  355.             object newItem = FireAddingNew();
  356.            
  357.             // If event hander did not supply new item, create one ourselves
  358.             if (newItem == null) {
  359.                
  360.                 Type type = typeof(T);
  361.                 newItem = SecurityUtils.SecureCreateInstance(type);
  362.             }
  363.            
  364.             // Add item to end of list. Note: If event handler returned an item not of type T,
  365.             // the cast below will trigger an InvalidCastException. This is by design.
  366.             Add((T)newItem);
  367.            
  368.             // Return new item to caller
  369.             return newItem;
  370.         }
  371.        
  372.         /// <include file='doc\BindingList.uex' path='docs/doc[@for="BindingList.AllowNew"]/*' />
  373.         /// <devdoc>
  374.         /// </devdoc>
  375.         public bool AllowNew {
  376.             get {
  377.                 //If the user set AllowNew, return what they set. If we have a default constructor, allowNew will be
  378.                 //true and we should just return true.
  379.                 if (userSetAllowNew || allowNew) {
  380.                     return this.allowNew;
  381.                 }
  382.                 //Even if the item doesn't have a default constructor, the user can hook AddingNew to provide an item.
  383.                 //If there's a handler for this, we should allow new.
  384.                 return AddingNewHandled;
  385.             }
  386.             set {
  387.                 bool oldAllowNewValue = AllowNew;
  388.                 userSetAllowNew = true;
  389.                 //Note that we don't want to set allowNew only if AllowNew didn't match value,
  390.                 //since AllowNew can depend on onAddingNew handler
  391.                 this.allowNew = value;
  392.                 if (oldAllowNewValue != value) {
  393.                     FireListChanged(ListChangedType.Reset, -1);
  394.                 }
  395.             }
  396.         }
  397.        
  398. /* private */        bool IBindingList.AllowNew {
  399.             get { return AllowNew; }
  400.         }
  401.        
  402.         /// <include file='doc\BindingList.uex' path='docs/doc[@for="BindingList.AllowEdit"]/*' />
  403.         /// <devdoc>
  404.         /// </devdoc>
  405.         public bool AllowEdit {
  406.             get { return this.allowEdit; }
  407.             set {
  408.                 if (this.allowEdit != value) {
  409.                     this.allowEdit = value;
  410.                     FireListChanged(ListChangedType.Reset, -1);
  411.                 }
  412.             }
  413.         }
  414.        
  415. /* private */        bool IBindingList.AllowEdit {
  416.             get { return AllowEdit; }
  417.         }
  418.        
  419.         /// <include file='doc\BindingList.uex' path='docs/doc[@for="BindingList.AllowRemove"]/*' />
  420.         /// <devdoc>
  421.         /// </devdoc>
  422.         public bool AllowRemove {
  423.             get { return this.allowRemove; }
  424.             set {
  425.                 if (this.allowRemove != value) {
  426.                     this.allowRemove = value;
  427.                     FireListChanged(ListChangedType.Reset, -1);
  428.                 }
  429.             }
  430.         }
  431.        
  432. /* private */        bool IBindingList.AllowRemove {
  433.             get { return AllowRemove; }
  434.         }
  435.        
  436.         bool IBindingList.SupportsChangeNotification {
  437.             get { return SupportsChangeNotificationCore; }
  438.         }
  439.        
  440.         protected virtual bool SupportsChangeNotificationCore {
  441.             get { return true; }
  442.         }
  443.        
  444.         bool IBindingList.SupportsSearching {
  445.             get { return SupportsSearchingCore; }
  446.         }
  447.        
  448.         protected virtual bool SupportsSearchingCore {
  449.             get { return false; }
  450.         }
  451.        
  452.         bool IBindingList.SupportsSorting {
  453.             get { return SupportsSortingCore; }
  454.         }
  455.        
  456.         protected virtual bool SupportsSortingCore {
  457.             get { return false; }
  458.         }
  459.        
  460.         bool IBindingList.IsSorted {
  461.             get { return IsSortedCore; }
  462.         }
  463.        
  464.         protected virtual bool IsSortedCore {
  465.             get { return false; }
  466.         }
  467.        
  468.         PropertyDescriptor IBindingList.SortProperty {
  469.             get { return SortPropertyCore; }
  470.         }
  471.        
  472.         protected virtual PropertyDescriptor SortPropertyCore {
  473.             get { return null; }
  474.         }
  475.        
  476.         ListSortDirection IBindingList.SortDirection {
  477.             get { return SortDirectionCore; }
  478.         }
  479.        
  480.         protected virtual ListSortDirection SortDirectionCore {
  481.             get { return ListSortDirection.Ascending; }
  482.         }
  483.        
  484.         void IBindingList.ApplySort(PropertyDescriptor prop, ListSortDirection direction)
  485.         {
  486.             ApplySortCore(prop, direction);
  487.         }
  488.        
  489.         protected virtual void ApplySortCore(PropertyDescriptor prop, ListSortDirection direction)
  490.         {
  491.             throw new NotSupportedException();
  492.         }
  493.        
  494.         void IBindingList.RemoveSort()
  495.         {
  496.             RemoveSortCore();
  497.         }
  498.        
  499.         protected virtual void RemoveSortCore()
  500.         {
  501.             throw new NotSupportedException();
  502.         }
  503.        
  504.         int IBindingList.Find(PropertyDescriptor prop, object key)
  505.         {
  506.             return FindCore(prop, key);
  507.         }
  508.        
  509.         protected virtual int FindCore(PropertyDescriptor prop, object key)
  510.         {
  511.             throw new NotSupportedException();
  512.         }
  513.        
  514.         void IBindingList.AddIndex(PropertyDescriptor prop)
  515.         {
  516.             // Not supported
  517.         }
  518.        
  519.         void IBindingList.RemoveIndex(PropertyDescriptor prop)
  520.         {
  521.             // Not supported
  522.         }
  523.        
  524.         #endregion
  525.        
  526.         #region Property Change Support
  527.        
  528.         private void HookPropertyChanged(T item)
  529.         {
  530.             INotifyPropertyChanged inpc = (item as INotifyPropertyChanged);
  531.            
  532.             // Note: inpc may be null if item is null, so always check.
  533.             if (null != inpc) {
  534.                 if (propertyChangedEventHandler == null) {
  535.                     propertyChangedEventHandler = new PropertyChangedEventHandler(Child_PropertyChanged);
  536.                 }
  537.                 inpc.PropertyChanged += propertyChangedEventHandler;
  538.             }
  539.         }
  540.        
  541.         private void UnhookPropertyChanged(T item)
  542.         {
  543.             INotifyPropertyChanged inpc = (item as INotifyPropertyChanged);
  544.            
  545.             // Note: inpc may be null if item is null, so always check.
  546.             if (null != inpc && null != propertyChangedEventHandler) {
  547.                 inpc.PropertyChanged -= propertyChangedEventHandler;
  548.             }
  549.         }
  550.        
  551.         void Child_PropertyChanged(object sender, PropertyChangedEventArgs e)
  552.         {
  553.             if (this.RaiseListChangedEvents) {
  554.                 if (sender == null || e == null || string.IsNullOrEmpty(e.PropertyName)) {
  555.                     // Fire reset event (per INotifyPropertyChanged spec)
  556.                     ResetBindings();
  557.                 }
  558.                 else {
  559.                     T item;
  560.                    
  561.                     try {
  562.                         item = (T)sender;
  563.                     }
  564.                     catch (InvalidCastException) {
  565.                         ResetBindings();
  566.                         return;
  567.                     }
  568.                    
  569.                     // Find the position of the item. This should never be -1. If it is,
  570.                     // somehow the item has been removed from our list without our knowledge.
  571.                     int pos = lastChangeIndex;
  572.                    
  573.                     if (pos < 0 || pos >= Count || !this[pos].Equals(item)) {
  574.                         pos = this.IndexOf(item);
  575.                         lastChangeIndex = pos;
  576.                     }
  577.                    
  578.                     if (pos == -1) {
  579.                         Debug.Fail("Item is no longer in our list but we are still getting change notifications.");
  580.                         UnhookPropertyChanged(item);
  581.                         ResetBindings();
  582.                     }
  583.                     else {
  584.                         // Get the property descriptor
  585.                         if (null == this.itemTypeProperties) {
  586.                             // Get Shape
  587.                             itemTypeProperties = TypeDescriptor.GetProperties(typeof(T));
  588.                             Debug.Assert(itemTypeProperties != null);
  589.                         }
  590.                        
  591.                         PropertyDescriptor pd = itemTypeProperties.Find(e.PropertyName, true);
  592.                        
  593.                         // Create event args. If there was no matching property descriptor,
  594.                         // we raise the list changed anyway.
  595.                         ListChangedEventArgs args = new ListChangedEventArgs(ListChangedType.ItemChanged, pos, pd);
  596.                        
  597.                         // Fire the ItemChanged event
  598.                         OnListChanged(args);
  599.                     }
  600.                 }
  601.             }
  602.         }
  603.        
  604.         #endregion
  605.        
  606.         #region IRaiseItemChangedEvents interface
  607.        
  608.         /// <include file='doc\BindingList.uex' path='docs/doc[@for="BindingList.RaisesItemChangedEvents"]/*' />
  609.         /// <devdoc>
  610.         /// Returns false to indicate that BindingList<T> does NOT raise ListChanged events
  611.         /// of type ItemChanged as a result of property changes on individual list items
  612.         /// unless those items support INotifyPropertyChanged
  613.         /// </devdoc>
  614.         bool IRaiseItemChangedEvents.RaisesItemChangedEvents {
  615.             get { return this.raiseItemChangedEvents; }
  616.         }
  617.        
  618.         #endregion
  619.        
  620.     }
  621. }

Developer Fusion