//--------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. All rights reserved. // // // @owner Microsoft // @backupOwner Microsoft //--------------------------------------------------------------------- using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Data.Common; using System.Data.Metadata; using System.Data.Metadata.Edm; using System.Data.Objects.DataClasses; using System.Diagnostics; using System.Reflection; namespace System.Data.Objects { /// /// Manages a list suitable for data binding. /// /// /// The type of elements in the binding list. /// /// /// /// This class provides an implementation of IBindingList that exposes a list of elements to be bound, /// provides a mechanism to change the membership of the list, /// and events to notify interested objects when the membership of the list is modified /// or an element in the list is modified. /// /// /// ObjectView relies on an object that implements IObjectViewData to manage the binding list. /// See the documentation for IObjectViewData for details. /// /// internal class ObjectView : IBindingList, ICancelAddNew, IObjectView { /// /// Specifies whether events handled from an underlying collection or individual bound item /// should result in list change events being fired from this IBindingList. /// True to prevent events from being fired from this IBindingList; /// otherwise false to allow events to propogate. /// private bool _suspendEvent; // Delegate for IBindingList.ListChanged event. private ListChangedEventHandler onListChanged; /// /// Object that listens for underlying collection or individual bound item changes, /// and notifies this object when they occur. /// private ObjectViewListener _listener; /// /// Index of last item added via a call to IBindingList.AddNew. /// private int _addNewIndex = -1; /// /// Object that maintains the underlying bound list, /// and specifies the operations allowed on that list. /// private IObjectViewData _viewData; /// /// Construct a new instance of ObjectView using the supplied IObjectViewData and event data source. /// /// /// Object that maintains the underlying bound list, /// and specifies the operations allowed on that list. /// /// /// Event source to "attach" to in order to listen to collection and item changes. /// internal ObjectView(IObjectViewData viewData, object eventDataSource) { _viewData = viewData; _listener = new ObjectViewListener(this, (IList)_viewData.List, eventDataSource); } private void EnsureWritableList() { if (((IList)this).IsReadOnly) { throw EntityUtil.WriteOperationNotAllowedOnReadOnlyBindingList(); } } private bool IsElementTypeAbstract { get { return typeof(TElement).IsAbstract; } } #region ICancelAddNew implementation /// /// If a new item has been added to the list, and is the position of that item, /// remove it from the list and cancel the add operation. /// /// Index of item to be removed as a result of the cancellation of a previous addition. void ICancelAddNew.CancelNew(int itemIndex) { if (_addNewIndex >= 0 && itemIndex == _addNewIndex) { TElement item = _viewData.List[_addNewIndex]; _listener.UnregisterEntityEvents(item); int oldIndex = _addNewIndex; // Reset the addNewIndex here so that the IObjectView.CollectionChanged method // will not attempt to examine the item being removed. // See IObjectView.CollectionChanged method for details. _addNewIndex = -1; try { _suspendEvent = true; _viewData.Remove(item, true); } finally { _suspendEvent = false; } OnListChanged(ListChangedType.ItemDeleted, oldIndex, -1); } } /// /// Commit a new item to the binding list. /// /// /// Index of item to be committed. /// This index must match the index of the item created by the last call to IBindindList.AddNew; /// otherwise this method is a nop. /// void ICancelAddNew.EndNew(int itemIndex) { if (_addNewIndex >= 0 && itemIndex == _addNewIndex) { _viewData.CommitItemAt(_addNewIndex); _addNewIndex = -1; } } #endregion #region IBindingList implementation bool IBindingList.AllowNew { get { return _viewData.AllowNew && !IsElementTypeAbstract; } } bool IBindingList.AllowEdit { get { return _viewData.AllowEdit; } } object IBindingList.AddNew() { EnsureWritableList(); if (IsElementTypeAbstract) { throw EntityUtil.AddNewOperationNotAllowedOnAbstractBindingList(); } _viewData.EnsureCanAddNew(); ((ICancelAddNew)this).EndNew(_addNewIndex); TElement newItem = (TElement)Activator.CreateInstance(typeof(TElement)); _addNewIndex = _viewData.Add(newItem, true); _listener.RegisterEntityEvents(newItem); OnListChanged(ListChangedType.ItemAdded, _addNewIndex /* newIndex*/, -1 /*oldIndex*/); return newItem; } bool IBindingList.AllowRemove { get { return _viewData.AllowRemove; } } bool IBindingList.SupportsChangeNotification { get { return true; } } bool IBindingList.SupportsSearching { get { return false; } } bool IBindingList.SupportsSorting { get { return false; } } bool IBindingList.IsSorted { get { return false; } } PropertyDescriptor IBindingList.SortProperty { get { throw EntityUtil.NotSupported(); } } ListSortDirection IBindingList.SortDirection { get { throw EntityUtil.NotSupported(); } } public event System.ComponentModel.ListChangedEventHandler ListChanged { add { onListChanged += value; } remove { onListChanged -= value; } } void IBindingList.AddIndex(PropertyDescriptor property) { throw EntityUtil.NotSupported(); } void IBindingList.ApplySort(PropertyDescriptor property, ListSortDirection direction) { throw EntityUtil.NotSupported(); } int IBindingList.Find(PropertyDescriptor property, object key) { throw EntityUtil.NotSupported(); } void IBindingList.RemoveIndex(PropertyDescriptor property) { throw EntityUtil.NotSupported(); } void IBindingList.RemoveSort() { throw EntityUtil.NotSupported(); } #endregion /// /// Get item at the specified index. /// /// /// The zero-based index of the element to get or set. /// /// /// This strongly-typed indexer is used by the data binding in WebForms and ASP.NET /// to determine the Type of elements in the bound list. /// The list of properties available for binding can then be determined from that element Type. /// public TElement this[int index] { get { return _viewData.List[index]; } set { // this represents a ROW basically whole entity, we should not allow any setting throw EntityUtil.CannotReplacetheEntityorRow(); } } #region IList implementation object IList.this[int index] { get { return _viewData.List[index]; } set { // this represents a ROW basically whole entity, we should not allow any setting throw EntityUtil.CannotReplacetheEntityorRow(); } } bool IList.IsReadOnly { get { return !(_viewData.AllowNew || _viewData.AllowRemove); } } bool IList.IsFixedSize { get { return (false); } } int IList.Add(object value) { EnsureWritableList(); EntityUtil.CheckArgumentNull(value, "value"); if (!(value is TElement)) { throw EntityUtil.IncompatibleArgument(); } ((ICancelAddNew)this).EndNew(_addNewIndex); int index = ((IList)this).IndexOf(value); // Add the item if it doesn't already exist in the binding list. if (index == -1) { index = _viewData.Add((TElement)value, false); // Only fire a change event if the IObjectView data doesn't implicitly fire an event itself. if (!_viewData.FiresEventOnAdd) { _listener.RegisterEntityEvents(value); OnListChanged(ListChangedType.ItemAdded, index /*newIndex*/, -1/* oldIndex*/); } } return index; } void IList.Clear() { EnsureWritableList(); ((ICancelAddNew)this).EndNew(_addNewIndex); // Only fire a change event if the IObjectView data doesn't implicitly fire an event itself. if (_viewData.FiresEventOnClear) { _viewData.Clear(); } else { try { // Suspend list changed events during the clear, since the IObjectViewData declared that it wouldn't fire an event. // It's possible the IObjectViewData could implement Clear by repeatedly calling Remove, // and we don't want these events to percolate during the Clear operation. _suspendEvent = true; _viewData.Clear(); } finally { _suspendEvent = false; } OnListChanged(ListChangedType.Reset, -1 /*newIndex*/, -1/* oldIndex*/); // Indexes not used for reset event. } } bool IList.Contains(object value) { bool itemExists; if (value is TElement) { itemExists = _viewData.List.Contains((TElement)value); } else { itemExists = false; } return itemExists; } int IList.IndexOf(object value) { int index; if (value is TElement) { index = _viewData.List.IndexOf((TElement)value); } else { index = -1; } return index; } void IList.Insert(int index, object value) { throw EntityUtil.IndexBasedInsertIsNotSupported(); } void IList.Remove(object value) { EnsureWritableList(); EntityUtil.CheckArgumentNull(value, "value"); if (!(value is TElement)) { throw EntityUtil.IncompatibleArgument(); } Debug.Assert(((IList)this).Contains(value), "Value does not exist in view."); ((ICancelAddNew)this).EndNew(_addNewIndex); TElement item = (TElement)value; int index = _viewData.List.IndexOf(item); bool removed = _viewData.Remove(item, false); // Only fire a change event if the IObjectView data doesn't implicitly fire an event itself. if (removed && !_viewData.FiresEventOnRemove) { _listener.UnregisterEntityEvents(item); OnListChanged(ListChangedType.ItemDeleted, index /* newIndex */, -1 /* oldIndex */); } } void IList.RemoveAt(int index) { ((IList)this).Remove(((IList)this)[index]); } #endregion #region ICollection implementation public int Count { get { return _viewData.List.Count; } } public void CopyTo(Array array, int index) { ((IList)_viewData.List).CopyTo(array, index); } object ICollection.SyncRoot { get { return this; } } bool ICollection.IsSynchronized { get { return false; } } public IEnumerator GetEnumerator() { return _viewData.List.GetEnumerator(); } #endregion private void OnListChanged(ListChangedType listchangedType, int newIndex, int oldIndex) { ListChangedEventArgs changeArgs = new ListChangedEventArgs(listchangedType, newIndex, oldIndex); OnListChanged(changeArgs); } private void OnListChanged(ListChangedEventArgs changeArgs) { // Only fire the event if someone listens to it and it is not suspended. if (onListChanged != null && !_suspendEvent) { onListChanged(this, changeArgs); } } void IObjectView.EntityPropertyChanged(object sender, PropertyChangedEventArgs e) { Debug.Assert(sender is TElement, "Entity should be of type TElement"); int index = ((IList)this).IndexOf((TElement)sender); OnListChanged(ListChangedType.ItemChanged, index /*newIndex*/, index/*oldIndex*/); } /// /// Handle a change in the underlying collection bound by this ObjectView. /// /// The source of the event. /// /// Event arguments that specify the type of modification and the associated item. /// void IObjectView.CollectionChanged(object sender, CollectionChangeEventArgs e) { // If there is a pending edit of a new item in the bound list (indicated by _addNewIndex >= 0) // and the collection membership changed due to an operation external to this ObjectView, // it is possible that the _addNewIndex position will need to be adjusted. // // If the modification was made through this ObjectView, the pending edit would have been implicitly committed, // and there would be no need to examine it here. TElement addNew = default(TElement); if (_addNewIndex >= 0) { addNew = this[_addNewIndex]; } ListChangedEventArgs changeArgs = _viewData.OnCollectionChanged(sender, e, _listener); if (_addNewIndex >= 0) { if (_addNewIndex >= this.Count) { _addNewIndex = ((IList)this).IndexOf(addNew); } else if (!this[_addNewIndex].Equals(addNew)) { _addNewIndex = ((IList)this).IndexOf(addNew); } } if (changeArgs != null) { OnListChanged(changeArgs); } } } }