File: winforms\Managed\System\WinForms\CurrencyManager.cs
Project: ndp\fx\src\System.Windows.Forms.csproj (System.Windows.Forms)
//------------------------------------------------------------------------------
// <copyright file="CurrencyManager.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>                                                                
//------------------------------------------------------------------------------
 
namespace System.Windows.Forms {
 
    using System;
    using Microsoft.Win32;
    using System.Diagnostics;    
    using System.Diagnostics.CodeAnalysis;
    using System.ComponentModel;
    using System.Collections;
    using System.Reflection;
    using System.Globalization;
 
    /// <include file='doc\ListManager.uex' path='docs/doc[@for="CurrencyManager"]/*' />
    /// <devdoc>
    ///    <para>Manages the position and bindings of a
    ///       list.</para>
    /// </devdoc>
    public class CurrencyManager : BindingManagerBase {
 
        private Object dataSource;
        private IList list;
        
        private bool bound = false;
        private bool shouldBind = true;
        
        /// <include file='doc\ListManager.uex' path='docs/doc[@for="CurrencyManager.listposition"]/*' />
        /// <internalonly/>
        /// <devdoc>
        /// </devdoc>
        [
            SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields") // We can't make CurrencyManager.listposition internal
                                                                                            // because it would be a breaking change.
        ]
        protected int listposition = -1;
 
        private int lastGoodKnownRow = -1;
        private bool pullingData = false;
 
        private bool inChangeRecordState = false;
        private bool suspendPushDataInCurrentChanged = false;
        // private bool onItemChangedCalled = false;
        // private EventHandler onCurrentChanged;
        // private CurrentChangingEventHandler onCurrentChanging;
        private ItemChangedEventHandler onItemChanged;
        private ListChangedEventHandler onListChanged;
        private ItemChangedEventArgs resetEvent = new ItemChangedEventArgs(-1);
        private EventHandler onMetaDataChangedHandler;
 
        /// <include file='doc\ListManager.uex' path='docs/doc[@for="CurrencyManager.finalType"]/*' />
        /// <devdoc>
        ///    <para>Gets the type of the list.</para>
        /// </devdoc>
        protected Type finalType;
 
        /// <include file='doc\ListManager.uex' path='docs/doc[@for="CurrencyManager.ItemChanged"]/*' />
        /// <devdoc>
        ///    <para>Occurs when the
        ///       current item has been
        ///       altered.</para>
        /// </devdoc>
        [SRCategory(SR.CatData)]
        public event ItemChangedEventHandler ItemChanged {
            add {
                onItemChanged += value;
            }
            remove {
                onItemChanged -= value;
            }
        }
 
        /// <include file='doc\ListManager.uex' path='docs/doc[@for="CurrencyManager.ListChanged"]/*' />
        public event ListChangedEventHandler ListChanged {
            add {
                onListChanged += value;
            }
            remove {
                onListChanged -= value;
            }
        }
 
        /// <include file='doc\ListManager.uex' path='docs/doc[@for="CurrencyManager.CurrencyManager"]/*' />
        /// <devdoc>
        /// </devdoc>
        [
            SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")  // If the constructor does not set the dataSource
                                                                                                    // it would be a breaking change.
        ]
        internal CurrencyManager(Object dataSource) {
            SetDataSource(dataSource);
        }
 
        /// <devdoc>
        ///    <para>Gets a value indicating
        ///       whether items can be added to the list.</para>
        /// </devdoc>
        internal bool AllowAdd {
            get {
                if (list is IBindingList) {
                    return ((IBindingList)list).AllowNew;
                }
                if (list == null)
                    return false;
                return !list.IsReadOnly && !list.IsFixedSize;
            }
        }
        
        /// <devdoc>
        ///    <para>Gets a value
        ///       indicating whether edits to the list are allowed.</para>
        /// </devdoc>
        internal bool AllowEdit {
            get {
                if (list is IBindingList) {
                    return ((IBindingList)list).AllowEdit;
                }
                if (list == null)
                    return false;
                return !list.IsReadOnly;
            }
        }
        
        /// <devdoc>
        ///    <para>Gets a value indicating whether items can be removed from the list.</para>
        /// </devdoc>
        internal bool AllowRemove {
            get {
                if (list is IBindingList) {
                    return ((IBindingList)list).AllowRemove;
                }
                if (list == null)
                    return false;
                return !list.IsReadOnly && !list.IsFixedSize;
            }
        }
        
        /// <include file='doc\ListManager.uex' path='docs/doc[@for="CurrencyManager.Count"]/*' />
        /// <devdoc>
        ///    <para>Gets the number of items in the list.</para>
        /// </devdoc>
        public override int Count {
            get {
                if (list == null)
                    return 0;
                else
                    return list.Count;
            }
        }
        
        /// <include file='doc\ListManager.uex' path='docs/doc[@for="CurrencyManager.Current"]/*' />
        /// <devdoc>
        ///    <para>Gets the current item in the list.</para>
        /// </devdoc>
        public override Object Current {
            get {
                return this[Position];
            }
        }
 
        internal override Type BindType {
            get {
                return ListBindingHelper.GetListItemType(this.List);
            }
        }
        
        /// <devdoc>
        ///    <para>Gets the data source of the list.</para>
        /// </devdoc>
        internal override Object DataSource {
            get {
                return dataSource;
            }
        }
 
        internal override void SetDataSource(Object dataSource) {
            if (this.dataSource != dataSource) {
                Release();
                this.dataSource = dataSource;
                this.list = null;
                this.finalType = null;
                
                Object tempList = dataSource;
                if (tempList is Array) {
                    finalType = tempList.GetType();
                    tempList = (Array)tempList;
                }
                
                if (tempList is IListSource) {
                    tempList = ((IListSource)tempList).GetList();                    
                }
            
                if (tempList is IList) {
                    if (finalType == null) {
                        finalType = tempList.GetType();
                    }
                    this.list = (IList)tempList;
                    WireEvents(list);
                    if (list.Count > 0 )
                        listposition = 0;
                    else
                        listposition = -1;
                    OnItemChanged(resetEvent);
                    OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1, -1));
                    UpdateIsBinding();
                }
                else {
                    if (tempList == null) {
                        throw new ArgumentNullException("dataSource");
                    }
                    throw new ArgumentException(SR.GetString(SR.ListManagerSetDataSource, tempList.GetType().FullName), "dataSource");
                }
 
            }
        }
 
        /// <include file='doc\ListManager.uex' path='docs/doc[@for="CurrencyManager.IsBinding"]/*' />
        /// <internalonly/>
        /// <devdoc>
        ///    <para>Gets a value indicating whether the list is bound to a data source.</para>
        /// </devdoc>
        internal override bool IsBinding {
            get {
                return bound;
            }
        }
 
        // The DataGridView needs this.
        internal bool ShouldBind {
            get {
                return shouldBind;
            }
        }
 
        /// <include file='doc\ListManager.uex' path='docs/doc[@for="CurrencyManager.List"]/*' />
        /// <devdoc>
        ///    <para>Gets the list as an object.</para>
        /// </devdoc>
        public IList List {
            get {
                // NOTE: do not change this to throw an exception if the list is not IBindingList.
                // doing this will cause a major performance hit when wiring the 
                // dataGrid to listen for MetaDataChanged events from the IBindingList
                // (basically we would have to wrap all calls to CurrencyManager::List with
                // a try/catch block.)
                //
                return list;
            }
        }
 
        /// <include file='doc\ListManager.uex' path='docs/doc[@for="CurrencyManager.Position"]/*' />
        /// <devdoc>
        ///    <para> Gets or sets the position you are at within the list.</para>
        /// </devdoc>
        public override int Position {
            get {
                return listposition;
            }
            set {
                if (listposition == -1)
                    return;
 
                if (value < 0)
                    value = 0;
                int count = list.Count;
                if (value >= count)
                    value = count - 1;
 
                ChangeRecordState(value, listposition != value, true, true, false);       // true for endCurrentEdit
                                                                                          // true for firingPositionChange notification
                                                                                          // data will be pulled from controls anyway.
            }
        }
 
        /// <devdoc>
        ///    <para>Gets or sets the object at the specified index.</para>
        /// </devdoc>
        internal Object this[int index] {
            get {
                if (index < 0 || index >= list.Count) {
                    throw new IndexOutOfRangeException(SR.GetString(SR.ListManagerNoValue, index.ToString(CultureInfo.CurrentCulture)));
                }
                return list[index];
            }
            set {
                if (index < 0 || index >= list.Count) {
                    throw new IndexOutOfRangeException(SR.GetString(SR.ListManagerNoValue, index.ToString(CultureInfo.CurrentCulture)));
                }
                list[index] = value;
            }
        }
        
        /// <include file='doc\ListManager.uex' path='docs/doc[@for="CurrencyManager.AddNew"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public override void AddNew() {
            IBindingList ibl = list as IBindingList;
            if (ibl != null) {
                ibl.AddNew();
            }
            else {
                // If the list is not IBindingList, then throw an exception:
                throw new NotSupportedException(SR.GetString(SR.CurrencyManagerCantAddNew));
            }
 
            ChangeRecordState(list.Count - 1, (Position != list.Count - 1), (Position != list.Count - 1), true, true);  // true for firingPositionChangeNotification
                                                                                                                    // true for pulling data from the controls
        }
 
        /// <include file='doc\ListManager.uex' path='docs/doc[@for="CurrencyManager.CancelCurrentEdit"]/*' />
        /// <devdoc>
        ///    <para>Cancels the current edit operation.</para>
        /// </devdoc>
        public override void CancelCurrentEdit() {
            if (Count > 0) {
                Object item = (Position >= 0 && Position < list.Count) ? list[Position] : null;
 
                // onItemChangedCalled = false;
 
                IEditableObject iEditableItem = item as IEditableObject;
                if (iEditableItem != null) {
                    iEditableItem.CancelEdit();
                }
 
                ICancelAddNew iListWithCancelAddNewSupport = list as ICancelAddNew;
                if (iListWithCancelAddNewSupport != null) {
                    iListWithCancelAddNewSupport.CancelNew(this.Position);
                }
 
                OnItemChanged(new ItemChangedEventArgs(Position));
                if (this.Position != -1) {
                    OnListChanged(new ListChangedEventArgs(ListChangedType.ItemChanged, this.Position));
                }
            }
        }
 
        private void ChangeRecordState(int newPosition, bool validating, bool endCurrentEdit, bool firePositionChange, bool pullData) {
            if (newPosition == -1 && list.Count == 0) {
                if (listposition != -1) {
                    this.listposition = -1;
                    OnPositionChanged(EventArgs.Empty);
                }
                return;
            }
            
            if ((newPosition < 0 || newPosition >= Count) && this.IsBinding) {
                throw new IndexOutOfRangeException(SR.GetString(SR.ListManagerBadPosition));
            }
 
            // if PushData fails in the OnCurrentChanged and there was a lastGoodKnownRow
            // then the position does not change, so we should not fire the OnPositionChanged
            // event;
            // this is why we have to cache the old position and compare that w/ the position that
            // the user will want to navigate to
            int oldPosition = listposition;
            if (endCurrentEdit) {
                // Do not PushData when pro. See ASURT 65095.
                inChangeRecordState = true;
                try {
                    EndCurrentEdit();
                } finally {
                    inChangeRecordState = false;
                }
            }
 
            // we pull the data from the controls only when the ListManager changes the list. when the backEnd changes the list we do not 
            // pull the data from the controls
            if (validating && pullData) {
                CurrencyManager_PullData();
            }
 
            // vsWhidbey 425961: EndCurrentEdit or PullData can cause the list managed by the CurrencyManager to shrink.
            this.listposition = Math.Min(newPosition, Count - 1);
 
            if (validating) {
                OnCurrentChanged(EventArgs.Empty);
            }
                
            bool positionChanging = (oldPosition != listposition);
            if (positionChanging && firePositionChange) {
                OnPositionChanged(EventArgs.Empty);
            }                
        }
 
        /// <include file='doc\ListManager.uex' path='docs/doc[@for="CurrencyManager.CheckEmpty"]/*' />
        /// <devdoc>
        ///    <para>Throws an exception if there is no list.</para>
        /// </devdoc>
        protected void CheckEmpty() {
            if (dataSource == null || list == null || list.Count == 0) {
                throw new InvalidOperationException(SR.GetString(SR.ListManagerEmptyList));
            }
        }
 
        // will return true if this function changes the position in the list
        private bool CurrencyManager_PushData() {
            if (pullingData)
                return false;
 
            int initialPosition = listposition;
            if (lastGoodKnownRow == -1) {
                try {
                    PushData();
                }
                catch (Exception ex) {
                    OnDataError(ex);
 
                    // get the first item in the list that is good to push data
                    // for now, we assume that there is a row in the backEnd
                    // that is good for all the bindings.
                    FindGoodRow();
                }
                lastGoodKnownRow = listposition;
            } else {
                try {
                    PushData();
                }
                catch (Exception ex) {
                    OnDataError(ex);
 
                    listposition = lastGoodKnownRow;
                    PushData();
                }
                lastGoodKnownRow = listposition;
            }
 
            return initialPosition != listposition;
        }
 
        private bool CurrencyManager_PullData() {
            bool success = true;
            pullingData = true;
 
            try {
                PullData(out success);
            } finally {
                pullingData = false;
            }
 
            return success;
        }
 
        /// <include file='doc\ListManager.uex' path='docs/doc[@for="CurrencyManager.RemoveAt"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public override void RemoveAt(int index) {
            list.RemoveAt(index);
        }
 
        /// <include file='doc\ListManager.uex' path='docs/doc[@for="CurrencyManager.EndCurrentEdit"]/*' />
        /// <devdoc>
        ///    <para>Ends the current edit operation.</para>
        /// </devdoc>
        public override void EndCurrentEdit() {
            if (Count > 0) {
                bool success = CurrencyManager_PullData();
 
                if (success) {
                    Object item = (Position >= 0 && Position < list.Count) ? list[Position] : null;
 
                    IEditableObject iEditableItem = item as IEditableObject;
                    if (iEditableItem != null) {
                        iEditableItem.EndEdit();
                    }
 
                    ICancelAddNew iListWithCancelAddNewSupport = list as ICancelAddNew;
                    if (iListWithCancelAddNewSupport != null) {
                        iListWithCancelAddNewSupport.EndNew(this.Position);
                    }
                }
            }
        }
 
        private void FindGoodRow() {
            int rowCount = this.list.Count;
            for (int i = 0; i < rowCount; i++) {
                listposition = i;
                try {
                    PushData();
                }
                catch (Exception ex) {
                    OnDataError(ex);
                    continue;
                }
                listposition = i;
                return;
            }
            // if we got here, the list did not contain any rows suitable for the bindings
            // suspend binding and throw an exception
            SuspendBinding();
            throw new InvalidOperationException(SR.GetString(SR.DataBindingPushDataException));
        }
 
        /// <devdoc>
        ///    <para>Sets the column to sort by, and the direction of the sort.</para>
        /// </devdoc>
        internal void SetSort(PropertyDescriptor property, ListSortDirection sortDirection) {
            if (list is IBindingList && ((IBindingList)list).SupportsSorting) {
                ((IBindingList)list).ApplySort(property, sortDirection);
            }
        }
        
        /// <devdoc>
        /// <para>Gets a <see cref='System.ComponentModel.PropertyDescriptor'/> for a CurrencyManager.</para>
        /// </devdoc>
        internal PropertyDescriptor GetSortProperty() {
            if ((list is IBindingList) && ((IBindingList)list).SupportsSorting) {
                return ((IBindingList)list).SortProperty;
            }
            return null;
        }
 
        /// <devdoc>
        ///    <para>Gets the sort direction of a list.</para>
        /// </devdoc>
        internal ListSortDirection GetSortDirection() {
            if ((list is IBindingList) && ((IBindingList)list).SupportsSorting) {
                return ((IBindingList)list).SortDirection;
            }
            return ListSortDirection.Ascending;
        }
                
        /// <devdoc>
        ///    <para>Find the position of a desired list item.</para>
        /// </devdoc>
        internal int Find(PropertyDescriptor property, Object key, bool keepIndex) {
            if (key == null)
                throw new ArgumentNullException("key");
 
            if (property != null && (list is IBindingList) && ((IBindingList)list).SupportsSearching) {
                return ((IBindingList)list).Find(property, key);
            }
 
            if (property != null) {
                for (int i = 0; i < list.Count; i++) {
                    object value = property.GetValue(list[i]);
                    if (key.Equals(value)) {
                        return i;
                    }
                }
            }
 
            return -1;
        }
 
        /// <devdoc>
        ///    <para>Gets the name of the list.</para>
        /// </devdoc>
        internal override string GetListName() {
            if (list is ITypedList) {
                return ((ITypedList)list).GetListName(null);
            }
            else {
                return finalType.Name;
            }
        }
 
        /// <include file='doc\ListManager.uex' path='docs/doc[@for="CurrencyManager.GetListName1"]/*' />
        /// <devdoc>
        ///    <para>Gets the name of the specified list.</para>
        /// </devdoc>
        protected internal override string GetListName(ArrayList listAccessors) {
            if (list is ITypedList) {
                PropertyDescriptor[] properties = new PropertyDescriptor[listAccessors.Count];
                listAccessors.CopyTo(properties, 0);
                return ((ITypedList)list).GetListName(properties);
            }
            return "";            
        }
        
        /// <devdoc>
        /// </devdoc>
        internal override PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors) {
            return ListBindingHelper.GetListItemProperties(this.list, listAccessors);
        }
 
        /// <include file='doc\ListManager.uex' path='docs/doc[@for="CurrencyManager.GetItemProperties"]/*' />
        /// <devdoc>
        /// <para>Gets the <see cref='T:System.ComponentModel.PropertyDescriptorCollection'/> for
        ///    the list.</para>
        /// </devdoc>
        public override PropertyDescriptorCollection GetItemProperties() {
            return GetItemProperties(null);
        }
 
        /// <devdoc>
        /// <para>Gets the <see cref='T:System.ComponentModel.PropertyDescriptorCollection'/> for the specified list.</para>
        /// </devdoc>
        private void List_ListChanged(Object sender, System.ComponentModel.ListChangedEventArgs e) {
            // If you change the assert below, better change the 
            // code in the OnCurrentChanged that deals w/ firing the OnCurrentChanged event
            Debug.Assert(lastGoodKnownRow == -1 || lastGoodKnownRow == listposition, "if we have a valid lastGoodKnownRow, then it should equal the position in the list");
 
            // bug 139627: when the data view fires an ItemMoved event where the old position is negative, that really means
            // that a row was added
            // bug 156236: when the data view fires an ItemMoved event where the new position is negative, that really means
            // that a row was deleted
            // dbe is our DataBindingEvent
            //
            ListChangedEventArgs dbe;
 
            if (e.ListChangedType == ListChangedType.ItemMoved && e.OldIndex < 0) {
                dbe = new ListChangedEventArgs(ListChangedType.ItemAdded, e.NewIndex, e.OldIndex);
            } else if (e.ListChangedType == ListChangedType.ItemMoved && e.NewIndex < 0) {
                dbe = new ListChangedEventArgs(ListChangedType.ItemDeleted, e.OldIndex, e.NewIndex);
            } else {
                dbe = e;
            }
 
            int oldposition = listposition;
 
            UpdateLastGoodKnownRow(dbe);
            UpdateIsBinding();
 
            if (list.Count == 0) {
                listposition = -1;
 
                if (oldposition != -1) {
                    // if we used to have a current row, but not any more, then report current as changed
                    OnPositionChanged(EventArgs.Empty);
                    OnCurrentChanged(EventArgs.Empty);
                }
 
                if (dbe.ListChangedType == System.ComponentModel.ListChangedType.Reset && e.NewIndex == -1) {
                    // if the list is reset, then let our users know about it.
                    OnItemChanged(resetEvent);
                }
 
                if (dbe.ListChangedType == System.ComponentModel.ListChangedType.ItemDeleted) {
                    // if the list is reset, then let our users know about it.
                    OnItemChanged(resetEvent);
                }
 
                // we should still fire meta data change notification even when the list is empty
                if (e.ListChangedType == System.ComponentModel.ListChangedType.PropertyDescriptorAdded ||
                    e.ListChangedType == System.ComponentModel.ListChangedType.PropertyDescriptorDeleted ||
                    e.ListChangedType == System.ComponentModel.ListChangedType.PropertyDescriptorChanged)
                    OnMetaDataChanged(EventArgs.Empty);
 
                // 
 
 
                OnListChanged(dbe);
                return;
            }
            
            suspendPushDataInCurrentChanged = true;
            try {
                switch (dbe.ListChangedType) {
                    case System.ComponentModel.ListChangedType.Reset:
                        Debug.WriteLineIf(CompModSwitches.DataCursor.TraceVerbose, "System.ComponentModel.ListChangedType.Reset Position: " + Position + " Count: " + list.Count);
                        if (listposition == -1 && list.Count > 0)
                            ChangeRecordState(0, true, false, true, false);     // last false: we don't pull the data from the control when DM changes
                        else 
                            ChangeRecordState(Math.Min(listposition,list.Count - 1), true, false, true, false);
                        UpdateIsBinding(/*raiseItemChangedEvent:*/ false);
                        OnItemChanged(resetEvent);
                        break;
                    case System.ComponentModel.ListChangedType.ItemAdded:
                        Debug.WriteLineIf(CompModSwitches.DataCursor.TraceVerbose, "System.ComponentModel.ListChangedType.ItemAdded " + dbe.NewIndex.ToString(CultureInfo.InvariantCulture));
                        if (dbe.NewIndex <= listposition && listposition < list.Count - 1) {
                            // this means the current row just moved down by one.
                            // the position changes, so end the current edit
                            ChangeRecordState(listposition + 1, true, true, listposition != list.Count - 2, false);
                            UpdateIsBinding();
                            // 85426: refresh the list after we got the item added event
                            OnItemChanged(resetEvent);
                            // 84460: when we get the itemAdded, and the position was at the end
                            // of the list, do the right thing and notify the positionChanged after refreshing the list
                            if (listposition == list.Count - 1)
                                OnPositionChanged(EventArgs.Empty);
                            break;
                        } else if (dbe.NewIndex == this.listposition && this.listposition == list.Count - 1 && this.listposition != -1) {
                            // The CurrencyManager has a non-empty list.
                            // The position inside the currency manager is at the end of the list and the list still fired an ItemAdded event.
                            // This could be the second ItemAdded event that the DataView fires to signal that the AddNew operation was commited.
                            // We need to fire CurrentItemChanged event so that relatedCurrencyManagers update their lists.
                            OnCurrentItemChanged(System.EventArgs.Empty);
                        }
 
                        if (listposition == -1) {
                            // do not call EndEdit on a row that was not there ( position == -1)
                            ChangeRecordState(0, false, false, true, false);
                        }
                        UpdateIsBinding();
                        // put the call to OnItemChanged after setting the position, so the
                        // controls would use the actual position.
                        // if we have a control bound to a dataView, and then we add a row to a the dataView, 
                        // then the control will use the old listposition to get the data. and this is bad.
                        //
                        OnItemChanged(resetEvent);
                        break;
                    case System.ComponentModel.ListChangedType.ItemDeleted:
                        Debug.WriteLineIf(CompModSwitches.DataCursor.TraceVerbose, "System.ComponentModel.ListChangedType.ItemDeleted " + dbe.NewIndex.ToString(CultureInfo.InvariantCulture));
                        if (dbe.NewIndex == listposition) {
                            // this means that the current row got deleted.
                            // cannot end an edit on a row that does not exist anymore
                            ChangeRecordState(Math.Min(listposition, Count - 1), true, false, true, false);
                            // put the call to OnItemChanged after setting the position
                            // in the currencyManager, so controls will use the actual position
                            OnItemChanged(resetEvent);
                            break;
                           
                        }
                        if (dbe.NewIndex < listposition) {
                            // this means the current row just moved up by one.
                            // cannot end an edit on a row that does not exist anymore
                            ChangeRecordState(listposition - 1, true, false, true, false);
                            // put the call to OnItemChanged after setting the position
                            // in the currencyManager, so controls will use the actual position
                            OnItemChanged(resetEvent);
                            break;
                        }
                        OnItemChanged(resetEvent);
                        break;
                    case System.ComponentModel.ListChangedType.ItemChanged:
                        Debug.WriteLineIf(CompModSwitches.DataCursor.TraceVerbose, "System.ComponentModel.ListChangedType.ItemChanged " + dbe.NewIndex.ToString(CultureInfo.InvariantCulture));
                        // the current item changed
                        if (dbe.NewIndex == this.listposition) {
                            OnCurrentItemChanged(EventArgs.Empty);
                        }
 
                        OnItemChanged(new ItemChangedEventArgs(dbe.NewIndex));
                        break;
                    case System.ComponentModel.ListChangedType.ItemMoved:
                        Debug.WriteLineIf(CompModSwitches.DataCursor.TraceVerbose, "System.ComponentModel.ListChangedType.ItemMoved " + dbe.NewIndex.ToString(CultureInfo.InvariantCulture));
                        if (dbe.OldIndex == listposition) { // current got moved.
                            // the position changes, so end the current edit. Make sure there is something that we can end edit...
                            ChangeRecordState(dbe.NewIndex, true, this.Position > -1 && this.Position < list.Count, true, false);
                        }
                        else if (dbe.NewIndex == listposition) { // current was moved
                            // the position changes, so end the current edit. Make sure there is something that we can end edit
                            ChangeRecordState(dbe.OldIndex, true, this.Position > -1 && this.Position < list.Count, true, false);
                        }
                        OnItemChanged(resetEvent);
                        break;
                    case System.ComponentModel.ListChangedType.PropertyDescriptorAdded:
                    case System.ComponentModel.ListChangedType.PropertyDescriptorDeleted:
                    case System.ComponentModel.ListChangedType.PropertyDescriptorChanged:
                        // reset lastGoodKnownRow because it was computed against property descriptors which changed
                        this.lastGoodKnownRow = -1;
 
                        // In Everett, metadata changes did not alter current list position. In Whidbey, this behavior
                        // preserved - except that we will now force the position to stay in valid range if necessary.
                        if (listposition == -1 && list.Count > 0)
                            ChangeRecordState(0, true, false, true, false);
                        else if (listposition > list.Count - 1)
                            ChangeRecordState(list.Count - 1, true, false, true, false);
 
                        // fire the MetaDataChanged event
                        OnMetaDataChanged(EventArgs.Empty);
                        break;
                }
                // send the ListChanged notification after the position changed in the list
                // 
 
 
                OnListChanged(dbe);
            } finally {
                suspendPushDataInCurrentChanged = false;
            }
            Debug.Assert(lastGoodKnownRow == -1 || listposition == lastGoodKnownRow, "how did they get out of sync?");
        }
 
        /// <include file='doc\ListManager.uex' path='docs/doc[@for="CurrencyManager.MetaDataChanged"]/*' />
        [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly")] //Exists in Everett
        [SRCategory(SR.CatData)]
        public event EventHandler MetaDataChanged {
            add {
                onMetaDataChangedHandler += value;
            }
            remove {
                onMetaDataChangedHandler -= value;
            }
        }
 
        /// <include file='doc\ListManager.uex' path='docs/doc[@for="CurrencyManager.OnCurrentChanged"]/*' />
        /// <devdoc>
        /// <para>Causes the CurrentChanged event to occur. </para>
        /// </devdoc>
        internal protected override void OnCurrentChanged(EventArgs e) {
            if (!inChangeRecordState) {
                Debug.WriteLineIf(CompModSwitches.DataView.TraceVerbose, "OnCurrentChanged() " + e.ToString());
                int curLastGoodKnownRow = lastGoodKnownRow;
                bool positionChanged = false;
                if (!suspendPushDataInCurrentChanged)
                    positionChanged = CurrencyManager_PushData();
                if (Count > 0) {
                    Object item = list[Position];
                    if (item is IEditableObject) {
                        ((IEditableObject)item).BeginEdit();
                    }
                }
                try {
                    // if currencyManager changed position then we have two cases:
                    // 1. the previous lastGoodKnownRow was valid: in that case we fell back so do not fire onCurrentChanged
                    // 2. the previous lastGoodKnownRow was invalid: we have two cases:
                    //      a. FindGoodRow actually found a good row, so it can't be the one before the user changed the position: fire the onCurrentChanged
                    //      b. FindGoodRow did not find a good row: we should have gotten an exception so we should not even execute this code
                    if (!positionChanged ||(positionChanged && curLastGoodKnownRow != -1)) {
                        if (onCurrentChangedHandler != null) {
                            onCurrentChangedHandler(this, e);
                        }
 
                        // we fire OnCurrentItemChanged event every time we fire the CurrentChanged + when a property of the Current item changed
                        if (onCurrentItemChangedHandler != null) {
                            onCurrentItemChangedHandler(this, e);
                        }
                    }
                }
                catch (Exception ex) {
                    OnDataError(ex);
                }                    
            }
        }
 
        // this method should only be called when the currency manager receives the ListChangedType.ItemChanged event
        // and when the index of the ListChangedEventArgs == the position in the currency manager
        /// <include file='doc\ListManager.uex' path='docs/doc[@for="CurrencyManager.OnCurrentItemChanged"]/*' />
        internal protected override void OnCurrentItemChanged(EventArgs e) {
            if (onCurrentItemChangedHandler != null) {
                onCurrentItemChangedHandler(this, e);
            }
        }
        
        /// <include file='doc\ListManager.uex' path='docs/doc[@for="CurrencyManager.OnItemChanged"]/*' />
        /// <devdoc>
        /// </devdoc>
        protected virtual void OnItemChanged(ItemChangedEventArgs e) {
            // It is possible that CurrencyManager_PushData will change the position
            // in the list. in that case we have to fire OnPositionChanged event
            bool positionChanged = false;
 
            // We should not push the data when we suspend the changeEvents.
            // but we should still fire the OnItemChanged event that we get when processing the EndCurrentEdit method.
            if ((e.Index == listposition || (e.Index == -1 && Position < Count)) && !inChangeRecordState)
                positionChanged = CurrencyManager_PushData();
            Debug.WriteLineIf(CompModSwitches.DataView.TraceVerbose, "OnItemChanged(" + e.Index.ToString(CultureInfo.InvariantCulture) + ") " + e.ToString());
            try {
                if (onItemChanged != null)
                    onItemChanged(this, e);
            }
            catch (Exception ex) {
                OnDataError(ex);
            }
 
            if (positionChanged)
                OnPositionChanged(EventArgs.Empty);
            // onItemChangedCalled = true;
        }
 
        private void OnListChanged(ListChangedEventArgs e) {
            if (onListChanged != null)
                onListChanged(this, e);
        }
 
        [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly")] //Exists in Everett
        internal protected void OnMetaDataChanged(EventArgs e)
        {
            if (onMetaDataChangedHandler != null)
                onMetaDataChangedHandler(this,e);
        }
 
        /// <include file='doc\ListManager.uex' path='docs/doc[@for="CurrencyManager.OnPositionChanged"]/*' />
        /// <devdoc>
        /// </devdoc>
        protected virtual void OnPositionChanged(EventArgs e) {
            // if (!inChangeRecordState) {
                Debug.WriteLineIf(CompModSwitches.DataView.TraceVerbose, "OnPositionChanged(" + listposition.ToString(CultureInfo.InvariantCulture) + ") " + e.ToString());
                try {
                    if (onPositionChangedHandler != null)
                        onPositionChangedHandler(this, e);
                }
                catch (Exception ex) {
                    OnDataError(ex);
                }
            // }
        }
 
        /// <include file='doc\ListManager.uex' path='docs/doc[@for="CurrencyManager.Refresh"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Forces a repopulation of the CurrencyManager
        ///    </para>
        /// </devdoc>
        public void Refresh() { 
            if (list.Count > 0 ) {
                if (listposition >= list.Count) {
                    lastGoodKnownRow = -1;
                    listposition = 0;
                }
            } else {
                listposition = -1;
            }
            List_ListChanged(list, new System.ComponentModel.ListChangedEventArgs(System.ComponentModel.ListChangedType.Reset, -1));
        }
 
        internal void Release() {
            UnwireEvents(list);
        }
 
        /// <include file='doc\ListManager.uex' path='docs/doc[@for="CurrencyManager.ResumeBinding"]/*' />
        /// <internalonly/>
        /// <devdoc>
        ///    <para>Resumes binding of component properties to list items.</para>
        /// </devdoc>
        public override void ResumeBinding() {
            lastGoodKnownRow = -1;
            try {
                if (!shouldBind) {
                    shouldBind = true;
                    // we need to put the listPosition at the beginning of the list if the list is not empty
                    this.listposition = (this.list != null && this.list.Count != 0) ? 0:-1;
                    UpdateIsBinding();
                }
            }
            catch {
                shouldBind = false;
                UpdateIsBinding();
                throw;
            }
        }
 
        /// <include file='doc\ListManager.uex' path='docs/doc[@for="CurrencyManager.SuspendBinding"]/*' />
        /// <internalonly/>
        /// <devdoc>
        ///    <para>Suspends binding.</para>
        /// </devdoc>
        public override void SuspendBinding() {
            lastGoodKnownRow = -1;
            if (shouldBind) {
                shouldBind = false;
                UpdateIsBinding();
            }
        }
 
        internal void UnwireEvents(IList list) {
            if ((list is IBindingList) && ((IBindingList)list).SupportsChangeNotification) {
                ((IBindingList)list).ListChanged -= new System.ComponentModel.ListChangedEventHandler(List_ListChanged);
                /*
                ILiveList liveList = (ILiveList) list;
                liveList.TableChanged -= new TableChangedEventHandler(List_TableChanged);
                */
            }
        }
        
        /// <include file='doc\ListManager.uex' path='docs/doc[@for="CurrencyManager.UpdateIsBinding"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        protected override void UpdateIsBinding() {
            UpdateIsBinding(true);
        }
 
        private void UpdateIsBinding(bool raiseItemChangedEvent) {
            bool newBound = list != null && list.Count > 0 && shouldBind && listposition != -1;
            if (list != null)
            if (bound != newBound) {
                // we will call end edit when moving from bound state to unbounded state
                //
                //bool endCurrentEdit = bound && !newBound;
                bound = newBound;
                int newPos = newBound ? 0 : -1;
                ChangeRecordState(newPos, bound, (Position != newPos), true, false);
                int numLinks = Bindings.Count;
                for (int i = 0; i < numLinks; i++) {
                    Bindings[i].UpdateIsBinding();
                }
 
                if (raiseItemChangedEvent) {
                    OnItemChanged(resetEvent);
                }
            }
        }
 
        private void UpdateLastGoodKnownRow(System.ComponentModel.ListChangedEventArgs e) {
            switch (e.ListChangedType) {
                case System.ComponentModel.ListChangedType.ItemDeleted:
                    if (e.NewIndex == lastGoodKnownRow)
                        lastGoodKnownRow = -1;
                    break;
                case System.ComponentModel.ListChangedType.Reset:
                    lastGoodKnownRow = -1;
                    break;
                case System.ComponentModel.ListChangedType.ItemAdded:
                    if (e.NewIndex <= lastGoodKnownRow && lastGoodKnownRow < this.List.Count - 1)
                        lastGoodKnownRow ++;
                    break;
                case System.ComponentModel.ListChangedType.ItemMoved:
                    if (e.OldIndex == lastGoodKnownRow)
                        lastGoodKnownRow = e.NewIndex;
                    break;
                case System.ComponentModel.ListChangedType.ItemChanged:
                    if (e.NewIndex == lastGoodKnownRow)
                        lastGoodKnownRow = -1;
                    break;
            }
        }
 
        internal void WireEvents(IList list) {
            if ((list is IBindingList) && ((IBindingList)list).SupportsChangeNotification) {
                ((IBindingList)list).ListChanged += new System.ComponentModel.ListChangedEventHandler(List_ListChanged);
                /*
                ILiveList liveList = (ILiveList) list;
                liveList.TableChanged += new TableChangedEventHandler(List_TableChanged);
                */
            }
        }
    }
}