|
//------------------------------------------------------------------------------
// <copyright file="BindingList.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
[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;
/// <include file='doc\BindingList.uex' path='docs/doc[@for="BindingList"]/*' />
/// <devdoc>
/// </devdoc>
[HostProtection(SharedState = true)]
[Serializable]
public class BindingList<T> : Collection<T>, 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
/// <include file='doc\BindingList.uex' path='docs/doc[@for="BindingList.BindingList"]/*' />
/// <devdoc>
/// Default constructor.
/// </devdoc>
public BindingList() : base() {
Initialize();
}
/// <include file='doc\BindingList.uex' path='docs/doc[@for="BindingList.BindingList1"]/*' />
/// <devdoc>
/// Constructor that allows substitution of the inner list with a custom list.
/// </devdoc>
public BindingList(IList<T> 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
/// <include file='doc\BindingList.uex' path='docs/doc[@for="BindingList.AddingNew"]/*' />
/// <devdoc>
/// Event that allows a custom item to be provided as the new item added to the list by AddNew().
/// </devdoc>
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);
}
}
}
/// <include file='doc\BindingList.uex' path='docs/doc[@for="BindingList.OnAddingNew"]/*' />
/// <devdoc>
/// Raises the AddingNew event.
/// </devdoc>
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
/// <include file='doc\BindingList.uex' path='docs/doc[@for="BindingList.ListChanged"]/*' />
/// <devdoc>
/// Event that reports changes to the list or to items in the list.
/// </devdoc>
public event ListChangedEventHandler ListChanged {
add {
onListChanged += value;
}
remove {
onListChanged -= value;
}
}
/// <include file='doc\BindingList.uex' path='docs/doc[@for="BindingList.OnListChanged"]/*' />
/// <devdoc>
/// Raises the ListChanged event.
/// </devdoc>
protected virtual void OnListChanged(ListChangedEventArgs e) {
if (onListChanged != null) {
onListChanged(this, e);
}
}
/// <include file='doc\BindingList.uex' path='docs/doc[@for="BindingList.RaiseListChangedEvents"]/* />
public bool RaiseListChangedEvents {
get {
return this.raiseListChangedEvents;
}
set {
if (this.raiseListChangedEvents != value) {
this.raiseListChangedEvents = value;
}
}
}
/// <include file='doc\BindingList.uex' path='docs/doc[@for="BindingList.ResetBindings"]/*' />
/// <devdoc>
/// </devdoc>
public void ResetBindings() {
FireListChanged(ListChangedType.Reset, -1);
}
/// <include file='doc\BindingList.uex' path='docs/doc[@for="BindingList.ResetItem"]/*' />
/// <devdoc>
/// </devdoc>
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<T> overrides
// Collection<T> 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
/// <include file='doc\BindingList.uex' path='docs/doc[@for="BindingList.CancelNew"]/*' />
/// <devdoc>
/// If item added using AddNew() is still cancellable, then remove that item from the list.
/// </devdoc>
public virtual void CancelNew(int itemIndex)
{
if (addNewPos >= 0 && addNewPos == itemIndex) {
RemoveItem(addNewPos);
addNewPos = -1;
}
}
/// <include file='doc\BindingList.uex' path='docs/doc[@for="BindingList.EndNew"]/*' />
/// <devdoc>
/// If item added using AddNew() is still cancellable, then commit that item.
/// </devdoc>
public virtual void EndNew(int itemIndex)
{
if (addNewPos >= 0 && addNewPos == itemIndex) {
addNewPos = -1;
}
}
#endregion
#region IBindingList interface
/// <include file='doc\BindingList.uex' path='docs/doc[@for="BindingList.AddNew"]/*' />
/// <devdoc>
/// Adds a new item to the list. Calls <see cref='AddNewCore'> to create and add the item.
///
/// Add operations are cancellable via the <see cref='ICancelAddNew'> interface. The position of the
/// new item is tracked until the add operation is either cancelled by a call to <see cref='CancelNew'>,
/// explicitly commited by a call to <see cref='EndNew'>, 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.
/// </devdoc>
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;
}
}
/// <include file='doc\BindingList.uex' path='docs/doc[@for="BindingList.AddNewCore"]/*' />
/// <devdoc>
/// 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.
/// </devdoc>
[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;
}
/// <include file='doc\BindingList.uex' path='docs/doc[@for="BindingList.AllowNew"]/*' />
/// <devdoc>
/// </devdoc>
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;
}
}
/// <include file='doc\BindingList.uex' path='docs/doc[@for="BindingList.AllowEdit"]/*' />
/// <devdoc>
/// </devdoc>
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;
}
}
/// <include file='doc\BindingList.uex' path='docs/doc[@for="BindingList.AllowRemove"]/*' />
/// <devdoc>
/// </devdoc>
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
/// <include file='doc\BindingList.uex' path='docs/doc[@for="BindingList.RaisesItemChangedEvents"]/*' />
/// <devdoc>
/// Returns false to indicate that BindingList<T> does NOT raise ListChanged events
/// of type ItemChanged as a result of property changes on individual list items
/// unless those items support INotifyPropertyChanged
/// </devdoc>
bool IRaiseItemChangedEvents.RaisesItemChangedEvents {
get {
return this.raiseItemChangedEvents;
}
}
#endregion
}
}
|