File: winforms\Managed\System\WinForms\BindingContext.cs
Project: ndp\fx\src\System.Windows.Forms.csproj (System.Windows.Forms)
//------------------------------------------------------------------------------
// <copyright file="BindingContext.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>                                                                
//------------------------------------------------------------------------------
 
namespace System.Windows.Forms {
 
    using System;
    using Microsoft.Win32;
    using System.ComponentModel;
    using System.Collections;
    using System.Globalization;
    using System.Diagnostics.CodeAnalysis;
 
    /// <include file='doc\BindingContext.uex' path='docs/doc[@for="BindingContext"]/*' />
    /// <devdoc>
    /// <para>Manages the collection of System.Windows.Forms.BindingManagerBase
    /// objects for a Win Form.</para>
    /// </devdoc>
    [DefaultEvent("CollectionChanged")]
    public class BindingContext : ICollection {
 
        private Hashtable listManagers;
 
        /// <include file='doc\BindingContext.uex' path='docs/doc[@for="BindingContext.ICollection.Count"]/*' />
        /// <internalonly/>
        /// <devdoc>
        /// <para>
        /// Gets the total number of System.Windows.Forms.BindingManagerBases
        /// objects.
        /// </para>
        /// </devdoc>
        int ICollection.Count {
            get {
                ScrubWeakRefs();
                return listManagers.Count;
            }
        }
 
        /// <include file='doc\BindingContext.uex' path='docs/doc[@for="BindingContext.ICollection.CopyTo"]/*' />
        /// <internalonly/>
        /// <devdoc>
        /// <para>
        /// Copies the elements of the collection into a specified array, starting
        /// at the collection index.
        /// </para>
        /// </devdoc>
        void ICollection.CopyTo(Array ar, int index)
        {
            IntSecurity.UnmanagedCode.Demand();
            ScrubWeakRefs();
            listManagers.CopyTo(ar, index);
        }
 
        /// <include file='doc\BindingContext.uex' path='docs/doc[@for="BindingContext.IEnumerable.GetEnumerator"]/*' />
        /// <internalonly/>
        /// <devdoc>
        /// <para>
        /// Gets an enumerator for the collection.
        /// </para>
        /// </devdoc>
        IEnumerator IEnumerable.GetEnumerator()
        {
            IntSecurity.UnmanagedCode.Demand();
            ScrubWeakRefs();
            return listManagers.GetEnumerator();
        }
 
        /// <include file='doc\BindingContext.uex' path='docs/doc[@for="BindingContext.IsReadOnly"]/*' />
        /// <internalonly/>
        /// <devdoc>
        ///    <para>
        ///       Gets a value indicating whether the collection is read-only.
        ///    </para>
        /// </devdoc>
        public bool IsReadOnly {
            get {
                return false;
            }
        }
 
        /// <include file='doc\BindingContext.uex' path='docs/doc[@for="BindingContext.ICollection.IsSynchronized"]/*' />
        /// <internalonly/>
        /// <devdoc>
        /// <para>
        /// Gets a value indicating whether the collection is synchronized.
        /// </para>
        /// </devdoc>
        bool ICollection.IsSynchronized {
            get {
                // so the user will know that it has to lock this object
                return false;
            }
        }
 
        /// <include file='doc\BindingContext.uex' path='docs/doc[@for="BindingContext.ICollection.SyncRoot"]/*' />
        /// <internalonly/>
        /// <devdoc>
        /// <para>Gets an object to use for synchronization (thread safety).</para>
        /// </devdoc>
        object ICollection.SyncRoot {
            get {
                return null;
            }
        }
 
 
        /// <include file='doc\BindingContext.uex' path='docs/doc[@for="BindingContext.BindingContext"]/*' />
        /// <devdoc>
        /// <para>Initializes a new instance of the System.Windows.Forms.BindingContext class.</para>
        /// </devdoc>
        public BindingContext() {
            listManagers = new Hashtable();
        }
 
        /// <include file='doc\BindingContext.uex' path='docs/doc[@for="BindingContext.this"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Gets the System.Windows.Forms.BindingManagerBase
        ///       associated with the specified data source.
        ///    </para>
        /// </devdoc>
        public BindingManagerBase this[object dataSource] {
            get {
                return this[dataSource, ""];
            }
        }
 
        /// <include file='doc\BindingContext.uex' path='docs/doc[@for="BindingContext.this1"]/*' />
        /// <devdoc>
        /// <para>Gets the System.Windows.Forms.BindingManagerBase associated with the specified data source and
        ///    data member.</para>
        /// </devdoc>
        public BindingManagerBase this[object dataSource, string dataMember] {
            get {
                return EnsureListManager(dataSource, dataMember);
            }
        }
 
        /// <include file='doc\BindingContext.uex' path='docs/doc[@for="BindingContext.Add"]/*' />
        /// <devdoc>
        /// Adds the listManager to the collection.  An ArgumentNullException is thrown if this listManager
        /// is null.  An exception is thrown if a listManager to the same target and Property as an existing listManager or
        /// if the listManager's column isn't a valid column given this DataSource.Table's schema.
        /// Fires the CollectionChangedEvent.
        /// </devdoc>
        internal protected void Add(object dataSource, BindingManagerBase listManager) {
            /* !!THIS METHOD IS OBSOLETE AND UNUSED!! */
            AddCore(dataSource, listManager);
            OnCollectionChanged(new CollectionChangeEventArgs(CollectionChangeAction.Add, dataSource));
        }
 
        /// <include file='doc\BindingContext.uex' path='docs/doc[@for="BindingContext.AddCore"]/*' />
        /// <devdoc>
        /// </devdoc>
        protected virtual void AddCore(object dataSource, BindingManagerBase listManager) {
            /* !!THIS METHOD IS OBSOLETE AND UNUSED!! */
            if (dataSource == null)
                throw new ArgumentNullException("dataSource");
            if (listManager == null)
                throw new ArgumentNullException("listManager");
 
            // listManagers[dataSource] = listManager;
            listManagers[GetKey(dataSource, "")] = new WeakReference(listManager, false);
        }
 
 
        /// <include file='doc\BindingContext.uex' path='docs/doc[@for="BindingContext.CollectionChanged"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Occurs when the collection has changed.
        ///    </para>
        /// </devdoc>
        [SRDescription(SR.collectionChangedEventDescr), EditorBrowsable(EditorBrowsableState.Never), Browsable(false)]
        public event CollectionChangeEventHandler CollectionChanged {
            /* !!THIS EVENT IS OBSOLETE AND UNUSED!! */
            [SuppressMessage("Microsoft.Performance", "CA1801:AvoidUnusedParameters")]
            add {
                throw new NotImplementedException();
            }
            [SuppressMessage("Microsoft.Performance", "CA1801:AvoidUnusedParameters")]
            remove {
            }
        }
 
        /// <include file='doc\BindingContext.uex' path='docs/doc[@for="BindingContext.Clear"]/*' />
        /// <devdoc>
        /// Clears the collection of any bindings.
        /// Fires the CollectionChangedEvent.
        /// </devdoc>
        internal protected void Clear() {
            /* !!THIS METHOD IS OBSOLETE AND UNUSED!! */
            ClearCore();
            OnCollectionChanged(new CollectionChangeEventArgs(CollectionChangeAction.Refresh, null));
        }
 
        /// <include file='doc\BindingContext.uex' path='docs/doc[@for="BindingContext.ClearCore"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Clears the collection.
        ///    </para>
        /// </devdoc>
        protected virtual void ClearCore() {
            /* !!THIS METHOD IS OBSOLETE AND UNUSED!! */
            listManagers.Clear();
        }
 
        /// <include file='doc\BindingContext.uex' path='docs/doc[@for="BindingContext.Contains"]/*' />
        /// <devdoc>
        /// <para>Gets a value indicating whether the System.Windows.Forms.BindingContext
        /// contains the specified
        /// data source.</para>
        /// </devdoc>
        public bool Contains(object dataSource) {
            return Contains(dataSource, "");
        }
 
        /// <include file='doc\BindingContext.uex' path='docs/doc[@for="BindingContext.Contains1"]/*' />
        /// <devdoc>
        /// <para>Gets a value indicating whether the System.Windows.Forms.BindingContext
        /// contains the specified data source and data member.</para>
        /// </devdoc>
        public bool Contains(object dataSource, string dataMember) {
            return listManagers.ContainsKey(GetKey(dataSource, dataMember));
        }
 
        internal HashKey GetKey(object dataSource, string dataMember) {
            return new HashKey(dataSource, dataMember);
        }
 
        /// <include file='doc\BindingContext.uex' path='docs/doc[@for="BindingContext.HashKey"]/*' />
        /// <internalonly/>
        /// <devdoc>
        /// </devdoc>
        //
        internal class HashKey {
            WeakReference wRef;
            int dataSourceHashCode;
            string dataMember;
 
            internal HashKey(object dataSource, string dataMember) {
                if (dataSource == null)
                    throw new ArgumentNullException("dataSource");
                if (dataMember == null)
                    dataMember = "";
                // The dataMember should be case insensitive.
                // so convert the dataMember to lower case
                //
                this.wRef = new WeakReference(dataSource, false);
                this.dataSourceHashCode = dataSource.GetHashCode();
                this.dataMember = dataMember.ToLower(CultureInfo.InvariantCulture);
            }
 
            /// <include file='doc\BindingContext.uex' path='docs/doc[@for="BindingContext.HashKey.GetHashCode"]/*' />
            /// <internalonly/>
            /// <devdoc>
            /// </devdoc>
            public override int GetHashCode() {
                return dataSourceHashCode * dataMember.GetHashCode();
            }
 
            /// <include file='doc\BindingContext.uex' path='docs/doc[@for="BindingContext.HashKey.Equals"]/*' />
            /// <internalonly/>
            /// <devdoc>
            /// </devdoc>
            public override bool Equals(object target) {
                if (target is HashKey) {
                    HashKey keyTarget = (HashKey)target;
                    return wRef.Target == keyTarget.wRef.Target && dataMember == keyTarget.dataMember;
                }
                return false;
            }
        }
 
        /// <include file='doc\BindingContext.uex' path='docs/doc[@for="BindingContext.OnCollectionChanged"]/*' />
        /// <internalonly/>
        /// <devdoc>
        ///    This method is called whenever the collection changes.  Overriders
        ///    of this method should call the base implementation of this method.
        ///    NOTE: This shipped in Everett, so we need to keep it, but we don't do
        ///    anything here.
        /// </devdoc>
        protected virtual void OnCollectionChanged(CollectionChangeEventArgs ccevent) {
        }
 
        /// <include file='doc\BindingContext.uex' path='docs/doc[@for="BindingContext.Remove"]/*' />
        /// <devdoc>
        /// Removes the given listManager from the collection.
        /// An ArgumentNullException is thrown if this listManager is null.  An ArgumentException is thrown
        /// if this listManager doesn't belong to this collection.
        /// The CollectionChanged event is fired if it succeeds.
        /// </devdoc>
        internal protected void Remove(object dataSource) {
            /* !!THIS METHOD IS OBSOLETE AND UNUSED!! */
            RemoveCore(dataSource);
            OnCollectionChanged(new CollectionChangeEventArgs(CollectionChangeAction.Remove, dataSource));
        }
 
        /// <include file='doc\BindingContext.uex' path='docs/doc[@for="BindingContext.RemoveCore"]/*' />
        /// <internalonly/>
        /// <devdoc>
        /// </devdoc>
        protected virtual void RemoveCore(object dataSource) {
            /* !!THIS METHOD IS OBSOLETE AND UNUSED!! */
            listManagers.Remove(GetKey(dataSource, ""));
        }
 
        /// <include file='doc\BindingContext.uex' path='docs/doc[@for="BindingContext.EnsureListManager"]/*' />
        /// <devdoc>
        ///    Create a suitable binding manager for the specified dataSource/dataMember combination.
        ///    - If one has already been created and cached by this BindingContext, return that instead.
        ///    - If the data source is an ICurrencyManagerProvider, just delegate to the data source.
        /// </devdoc>
        internal BindingManagerBase EnsureListManager(object dataSource, string dataMember) {
            BindingManagerBase bindingManagerBase = null;
 
            if (dataMember == null)
                dataMember = "";
 
            // Check whether data source wants to provide its own binding managers
            // (but fall through to old logic if it fails to provide us with one)
            //
            if (dataSource is ICurrencyManagerProvider) {
                bindingManagerBase = (dataSource as ICurrencyManagerProvider).GetRelatedCurrencyManager(dataMember);
 
                if (bindingManagerBase != null) {
                    return bindingManagerBase;
                }
            }
 
            // Check for previously created binding manager
            //
            HashKey key = GetKey(dataSource, dataMember);
            WeakReference wRef;
            wRef = listManagers[key] as WeakReference;
            if (wRef != null)
                bindingManagerBase = (BindingManagerBase) wRef.Target;
            if (bindingManagerBase != null) {
                return bindingManagerBase;
            }
 
            if (dataMember.Length == 0) {
                // No data member specified, so create binding manager directly on the data source
                //
                if (dataSource is IList || dataSource is IListSource) {
                    // IListSource so we can bind the dataGrid to a table and a dataSet
                    bindingManagerBase = new CurrencyManager(dataSource);
                }
                else {
                    // Otherwise assume simple property binding
                    bindingManagerBase = new PropertyManager(dataSource);
                }
            }
            else {
                // Data member specified, so get data source's binding manager, and hook a 'related' binding manager to it
                //
                int lastDot = dataMember.LastIndexOf(".");            
                string dataPath = (lastDot == -1) ? "" : dataMember.Substring(0, lastDot);
                string dataField = dataMember.Substring(lastDot + 1);
 
                BindingManagerBase formerManager = EnsureListManager(dataSource, dataPath);
 
                PropertyDescriptor prop = formerManager.GetItemProperties().Find(dataField, true);
                if (prop == null)
                    throw new ArgumentException(SR.GetString(SR.RelatedListManagerChild, dataField));
 
                if (typeof(IList).IsAssignableFrom(prop.PropertyType))
                    bindingManagerBase = new RelatedCurrencyManager(formerManager, dataField);
                else
                    bindingManagerBase = new RelatedPropertyManager(formerManager, dataField);
            }
 
            // if wRef == null, then it is the first time we want this bindingManagerBase: so add it
            // if wRef != null, then the bindingManagerBase was GC'ed at some point: keep the old wRef and change its target
            if (wRef == null)
                listManagers.Add(key, new WeakReference(bindingManagerBase, false));
            else
                wRef.Target = bindingManagerBase;
 
            IntSecurity.UnmanagedCode.Demand();
            ScrubWeakRefs();
            // Return the final binding manager
            return bindingManagerBase;
        }
 
        // may throw
        private static void CheckPropertyBindingCycles(BindingContext newBindingContext, Binding propBinding) {
            if (newBindingContext == null || propBinding == null)
                return;
            if (newBindingContext.Contains(propBinding.BindableComponent, "")) {
                // this way we do not add a bindingManagerBase to the
                // bindingContext if there isn't one already
                BindingManagerBase bindingManagerBase = newBindingContext.EnsureListManager(propBinding.BindableComponent, "");
                for (int i = 0; i < bindingManagerBase.Bindings.Count; i++) {
                    Binding binding = bindingManagerBase.Bindings[i];
                    if (binding.DataSource == propBinding.BindableComponent) {
                        if (propBinding.BindToObject.BindingMemberInfo.BindingMember.Equals(binding.PropertyName))
                            throw new ArgumentException(SR.GetString(SR.DataBindingCycle, binding.PropertyName), "propBinding");
                    } else if (propBinding.BindToObject.BindingManagerBase is PropertyManager)
                        CheckPropertyBindingCycles(newBindingContext, binding);
                }
            }
        }
 
        private void ScrubWeakRefs() {
            ArrayList cleanupList = null;
            foreach (DictionaryEntry de in listManagers) {
                WeakReference wRef = (WeakReference) de.Value;
                if (wRef.Target == null) {
                    if (cleanupList == null) {
                        cleanupList = new ArrayList();
                    }
                    cleanupList.Add(de.Key);
                }
            }
 
            if (cleanupList != null) {
                foreach (object o in cleanupList) {
                    listManagers.Remove(o);
                }
            }
        }
 
        /// <include file='doc\BindingContext.uex' path='docs/doc[@for="BindingContext.UpdateBinding"]/*' />
        /// <devdoc>
        ///     Associates a Binding with a different BindingContext. Intended for use by components that support
        ///     IBindableComponent, to update their Bindings when the value of IBindableComponent.BindingContext
        ///     is changed.
        /// </devdoc>
        public static void UpdateBinding(BindingContext newBindingContext, Binding binding) {
            BindingManagerBase oldManager = binding.BindingManagerBase;
            if (oldManager != null) {
                oldManager.Bindings.Remove(binding);
            }
 
            if (newBindingContext != null) {
                // we need to first check for cycles before adding this binding to the collection
                // of bindings.
                if (binding.BindToObject.BindingManagerBase is PropertyManager)
                    CheckPropertyBindingCycles(newBindingContext, binding);
 
                BindToObject bindTo = binding.BindToObject;
                BindingManagerBase newManager = newBindingContext.EnsureListManager(bindTo.DataSource, bindTo.BindingMemberInfo.BindingPath);
                newManager.Bindings.Add(binding);
            }
        }
    }
}