DataBindEngine.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ 4.0 / 4.0 / untmp / DEVDIV_TFS / Dev10 / Releases / RTMRel / wpf / src / Framework / MS / Internal / Data / DataBindEngine.cs / 1305600 / DataBindEngine.cs

                            //---------------------------------------------------------------------------- 
//
// 
//    Copyright (C) Microsoft Corporation.  All rights reserved.
//  
//
// Description: Data binding engine. 
// 
//---------------------------------------------------------------------------
 
using System;
using System.Collections.Generic;   // Dictionary
using System.ComponentModel;
using System.Diagnostics; 
using System.Collections;
using System.Collections.Specialized; 
using System.Globalization; 
using System.Windows.Threading;
using System.Security;              // [SecurityCritical,SecurityTreatAsSafe] 
using System.Threading;

using System.Windows;
using System.Windows.Data; 
using System.Windows.Markup;
using MS.Internal.Data; 
using MS.Internal;          // Invariant.Assert 

namespace MS.Internal.Data 
{
    internal enum TaskOps
    {
        TransferValue, 
        UpdateValue,
        AttachToContext, 
        VerifySourceReference, 
        RaiseTargetUpdatedEvent,
    } 

    internal interface IDataBindEngineClient
    {
        void TransferValue(); 
        void UpdateValue();
        bool AttachToContext(bool lastChance); 
        void VerifySourceReference(bool lastChance); 
        void OnTargetUpdated();
        DependencyObject TargetElement { get; } 
    }

    internal class DataBindEngine : DispatcherObject
    { 
        //-----------------------------------------------------
        // 
        //  Nested classes 
        //
        //----------------------------------------------------- 

        // The task list is represented by a singly linked list of Tasks
        // connected via the Next pointer.  The variables _head and _tail
        // point to the beginning and end of the list.  The head is always 
        // a dummy Task that is never used for anything else - this makes
        // the code for adding to the list simpler. 
        // 
        // In addition, all tasks for a particular client are linked in
        // reverse order of arrival by the PreviousForClient back pointer. 
        // The heads of these lists are kept in the _mostRecentForClient
        // hashtable.  This allows rapid cancellation of all tasks pending
        // for a particular client - we only need to look at the tasks that
        // are actually affected, rather than the entire list.  This avoids 
        // an O(n^2) algorithm (bug 1366032).
 
        private class Task 
        {
            public enum Status { Pending, Running, Completed, Retry, Cancelled }; 
            public IDataBindEngineClient client;
            public TaskOps op;
            public Status status;
            public Task Next; 
            public Task PreviousForClient;
 
            public Task(IDataBindEngineClient c, TaskOps o, Task previousForClient) 
            {
                client = c; 
                op = o;
                PreviousForClient = previousForClient;
                status = Status.Pending;
            } 

            public void Run(bool lastChance) 
            { 
                status = Status.Running;
                Status newStatus = Status.Completed; 
                switch (op)
                {
                    case TaskOps.TransferValue:
                        client.TransferValue(); 
                        break;
 
                    case TaskOps.UpdateValue: 
                        client.UpdateValue();
                        break; 

                    case TaskOps.RaiseTargetUpdatedEvent:
                        client.OnTargetUpdated();
                        break; 

                    case TaskOps.AttachToContext: 
                        bool succeeded = client.AttachToContext(lastChance); 
                        if (!succeeded && !lastChance)
                            newStatus = Status.Retry; 
                        break;

                    case TaskOps.VerifySourceReference:
                        client.VerifySourceReference(lastChance); 
                        break;
                } 
                status = newStatus; 
            }
        } 

        //------------------------------------------------------
        //
        //  Constructors 
        //
        //----------------------------------------------------- 
 
        /// 
        ///     Critical: This code calls into Link demanded methods to attach handlers 
        ///     TreatAsSafe: This code does not take any parameter or return state.
        ///     It simply attaches private call back.
        /// 
        [SecurityCritical,SecurityTreatAsSafe] 
        private DataBindEngine()
        { 
            // Set up the final cleanup 
            DataBindEngineShutDownListener listener = new DataBindEngineShutDownListener(this);
 
            // initialize the task list
            _head = new Task(null, TaskOps.TransferValue, null);
            _tail = _head;
            _mostRecentTask = new HybridDictionary(); 
        }
 
        //------------------------------------------------------ 
        //
        //  Internal Properties 
        //
        //------------------------------------------------------

        internal PathParser PathParser { get { return _pathParser; } } 
        internal ValueConverterContext ValueConverterContext { get { return _valueConverterContext; } }
        internal AccessorTable AccessorTable { get { return _accessorTable; } } 
        internal bool IsShutDown { get { return (_viewManager == null); } } 

        internal bool CleanupEnabled 
        {
            get { return _cleanupEnabled; }
            set
            { 
                _cleanupEnabled = value;
                WeakEventManager.SetCleanupEnabled(value); 
            } 
        }
 
        internal IAsyncDataDispatcher AsyncDataDispatcher
        {
            get
            { 
                // lazy construction of async dispatcher
                if (_defaultAsyncDataDispatcher == null) 
                    _defaultAsyncDataDispatcher = new DefaultAsyncDataDispatcher(); 

                return _defaultAsyncDataDispatcher; 
            }
        }

        ///  
        /// Return the DataBindEngine for the current thread
        ///  
        internal static DataBindEngine CurrentDataBindEngine 
        {
            get 
            {
                // _currentEngine is [ThreadStatic], so there's one per thread
                if (_currentEngine == null)
                { 
                    _currentEngine = new DataBindEngine();
                } 
 
                return _currentEngine;
            } 
        }


        //----------------------------------------------------- 
        //
        //  Internal Methods 
        // 
        //------------------------------------------------------
 
        internal void AddTask(IDataBindEngineClient c, TaskOps op)
        {
            // ignore requests that arrive after shutdown
            if (_mostRecentTask == null) 
                return;
 
            // if we're adding to an empty list, request that the list be processed 
            if (_head == _tail)
            { 
                RequestRun();
            }

            // link a new task into the list 
            Task recentTask = (Task)_mostRecentTask[c];
            Task newTask = new Task(c, op, recentTask); 
            _tail.Next = newTask; 
            _tail = newTask;
            _mostRecentTask[c] = newTask; 

            // if the task is AttachToContext and the target is a UIElement,
            // register for the LayoutUpdated event, and run the task list from the
            // event handler.  This avoids flashing, at the expense of lots more 
            // events and handlers (bug 1019232)
            if (op == TaskOps.AttachToContext && 
                _layoutElement == null && 
                (_layoutElement = c.TargetElement as UIElement) != null)
            { 
                _layoutElement.LayoutUpdated += new EventHandler(OnLayoutUpdated);
            }
        }
 
        internal void CancelTask(IDataBindEngineClient c, TaskOps op)
        { 
            // ignore requests that arrive after shutdown 
            if (_mostRecentTask == null)
                return; 

            for (Task task = (Task)_mostRecentTask[c];  task != null;  task = task.PreviousForClient)
            {
                if (task.op == op && task.status == Task.Status.Pending) 
                {
                    task.status = Task.Status.Cancelled; 
                    break; 
                }
            } 
        }

        internal void CancelTasks(IDataBindEngineClient c)
        { 
            // ignore requests that arrive after shutdown
            if (_mostRecentTask == null) 
                return; 

            // cancel pending tasks for the given client 
            for (Task task = (Task)_mostRecentTask[c];  task != null;  task = task.PreviousForClient)
            {
                Invariant.Assert(task.client == c, "task list is corrupt");
                if (task.status == Task.Status.Pending) 
                {
                    task.status = Task.Status.Cancelled; 
                } 
            }
 
            // no need to look at these tasks ever again
            _mostRecentTask.Remove(c);
        }
 
        internal object Run(object arg)
        { 
            bool lastChance = (bool)arg; 
            Task retryHead = lastChance ? null : new Task(null, TaskOps.TransferValue, null);
            Task retryTail = retryHead; 

            // unregister the LayoutUpdated event - we only need to be called once
            if (_layoutElement != null)
            { 
                _layoutElement.LayoutUpdated -= new EventHandler(OnLayoutUpdated);
                _layoutElement = null; 
            } 

            // iterate through the task list 
            Task nextTask = null;
            for (Task task = _head.Next;  task != null;  task = nextTask)
            {
                // sever the back pointer - older tasks are no longer needed 
                task.PreviousForClient = null;
 
                // run pending tasks 
                if (task.status == Task.Status.Pending)
                { 
                    task.Run(lastChance);

                    // fetch the next task _after_ the current task has
                    // run (in case the current task causes new tasks to be 
                    // added to the list, as in bug 1938866), but _before_
                    // moving the current task to the retry list (which overwrites 
                    // the Next pointer) 
                    nextTask = task.Next;
 
                    if (task.status == Task.Status.Retry && !lastChance)
                    {
                        // the task needs to be retried - add it to the list
                        task.status = Task.Status.Pending; 
                        retryTail.Next = task;
                        retryTail = task; 
                        retryTail.Next = null; 
                    }
                } 
                else
                {
                    nextTask = task.Next;
                } 
            }
 
            // return the list to its empty state 
            _head.Next = null;
            _tail = _head; 
            _mostRecentTask.Clear();

            // repost the tasks that need to be retried
            if (!lastChance) 
            {
                // there is already a dispatcher request to call Run, so change 
                // _head temporarily so that AddTask does not make another request 
                Task headSave = _head;
                _head = null; 

                for (Task task = retryHead.Next;  task != null;  task = task.Next)
                {
                    AddTask(task.client, task.op); 
                }
 
                _head = headSave; 
            }
 
            return null;
        }

        internal ViewRecord GetViewRecord(object collection, CollectionViewSource key, Type collectionViewType, bool createView) 
        {
            if (IsShutDown) 
                return null; 

            ViewRecord record = _viewManager.GetViewRecord(collection, key, collectionViewType, createView); 

            // lacking any definitive event on which to trigger a cleanup pass,
            // we use a heuristic, namely the creation of a new view.  This suggests
            // that there is new activity, which often means that old content is 
            // being replaced.  So perhaps the view table now has stale entries.
            if (record != null && !record.IsInitialized) 
            { 
                ScheduleCleanup();
            } 

            return record;
        }
 
        // cache of default converters (so that all uses of string-to-int can
        // share the same converter) 
        internal IValueConverter GetDefaultValueConverter(Type sourceType, 
                                                        Type targetType,
                                                        bool targetToSource) 
        {
            IValueConverter result = _valueConverterTable[sourceType, targetType, targetToSource];

            if (result == null) 
            {
                result = DefaultValueConverter.Create(sourceType, targetType, targetToSource, this); 
                if (result != null) 
                    _valueConverterTable.Add(sourceType, targetType, targetToSource, result);
            } 

            return result;
        }
 
        // make an async request to the scheduler that handles requests for the given target
        internal void AddAsyncRequest(DependencyObject target, AsyncDataRequest request) 
        { 
            if (target == null)
                return; 

            // get the appropriate scheduler
            IAsyncDataDispatcher asyncDispatcher = AsyncDataDispatcher;
            /* AsyncDataDispatcher property is cut (task 41079) 
            IAsyncDataDispatcher asyncDispatcher = Binding.GetAsyncDataDispatcher(target);
            if (asyncDispatcher == null) 
            { 
                asyncDispatcher = AsyncDataDispatcher;
            } 
            */

            // add it to the list of schedulers that need cleanup
            if (_asyncDispatchers == null) 
            {
                _asyncDispatchers = new HybridDictionary(1);    // lazy instantiation 
            } 
            _asyncDispatchers[asyncDispatcher] = null;  // the value is unused
 
            // make the request
            asyncDispatcher.AddRequest(request);
        }
 

        // 
 

 

        // retrieve the value, using the cache if necessary
        internal object GetValue(object item, PropertyDescriptor pd, bool indexerIsNext)
        { 
            return _valueTable.GetValue(item, pd, indexerIsNext);
        } 
 
        // give the value cache first chance at handling property changes
        internal void RegisterForCacheChanges(object item, object descriptor) 
        {
            PropertyDescriptor pd = descriptor as PropertyDescriptor;
            if (item != null && pd != null && ValueTable.ShouldCache(item, pd))
            { 
                _valueTable.RegisterForChanges(item, pd, this);
            } 
        } 

        // schedule a cleanup pass.  This can be called from any thread. 
        internal void ScheduleCleanup()
        {
            // only the first request after a previous cleanup should schedule real work
            if (Interlocked.Increment(ref _cleanupRequests) == 1) 
            {
                Dispatcher.BeginInvoke(DispatcherPriority.ContextIdle, new DispatcherOperationCallback(CleanupOperation), null); 
            } 
        }
 
        // return true if something was actually cleaned up
        internal bool Cleanup()
        {
            bool foundDirt = false; 

            foundDirt = _viewManager.Purge() || foundDirt; 
 
            foundDirt = WeakEventManager.Cleanup() || foundDirt;
 
            foundDirt = _valueTable.Purge() || foundDirt;

            return foundDirt;
        } 

        // Event registration 
 

        //----------------------------------------------------- 
        //
        //  Private Methods
        //
        //----------------------------------------------------- 

        private void RequestRun() 
        { 
            // Run tasks before layout, to front load as much layout work as possible
            Dispatcher.BeginInvoke(DispatcherPriority.DataBind, new DispatcherOperationCallback(Run), false); 

            // Run tasks (especially re-tried AttachToContext tasks) again after
            // layout as the last chance.  Any failures in AttachToContext will
            // be treated as an error. 
            Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new DispatcherOperationCallback(Run), true);
        } 
 
        // run a cleanup pass
        private object CleanupOperation(object arg) 
        {
            // allow new requests, even if cleanup is disabled
            Interlocked.Exchange(ref _cleanupRequests, 0);
 
            if (!_cleanupEnabled)
                return null; 
 
            Cleanup();
 
            return null;
        }

        // do the final cleanup when the Dispatcher or AppDomain is shut down 
        private void OnShutDown()
        { 
            _viewManager = null; 
            _valueConverterTable = null;
            _mostRecentTask = null; 
            _head = _tail = null;

            // notify all the async dispatchers we've ever talked to
            // The InterlockedExchange makes sure we only do this once 
            // (in case Dispatcher and AppDomain are being shut down simultaneously
            // on two different threads) 
            HybridDictionary asyncDispatchers = (HybridDictionary)Interlocked.Exchange(ref _asyncDispatchers, null); 
            if (asyncDispatchers != null)
            { 
                foreach (object o in asyncDispatchers.Keys)
                {
                    IAsyncDataDispatcher dispatcher = o as IAsyncDataDispatcher;
                    if (dispatcher != null) 
                    {
                        dispatcher.CancelAllRequests(); 
                    } 
                }
            } 

            _defaultAsyncDataDispatcher = null;

            // Note: the engine is still held in TLS.  This maintains the 1-1 relationship 
            // between the thread and the engine.  However the engine is basically
            // dead - _mostRecentTask is null, and most operations are now no-ops or illegal. 
            // This imitates the behavior of the thread's Dispatcher. 
        }
 
        // A UIElement with pending AttachToContext task(s) has raised the
        // LayoutUpdated event.  Run the task list.
        private void OnLayoutUpdated(object sender, EventArgs e)
        { 
            Run(false);
        } 
 
        //-----------------------------------------------------
        // 
        //  Private Types
        //
        //------------------------------------------------------
 
        // cache of default value converters (so that all uses of string-to-int can
        // share the same converter) 
        class ValueConverterTable : Hashtable 
        {
            struct Key 
            {
                Type _sourceType, _targetType;
                bool _targetToSource;
 
                public Key(Type sourceType, Type targetType, bool targetToSource)
                { 
                    _sourceType = sourceType; 
                    _targetType = targetType;
                    _targetToSource = targetToSource; 
                }

                public override int GetHashCode()
                { 
                    return _sourceType.GetHashCode() + _targetType.GetHashCode();
                } 
 
                public override bool Equals(object o)
                { 
                    if (o is Key)
                    {
                        return (this == (Key)o);
                    } 
                    return false;
                } 
 
                public static bool operator==(Key k1, Key k2)
                { 
                    return  k1._sourceType == k2._sourceType &&
                            k1._targetType == k2._targetType &&
                            k1._targetToSource == k2._targetToSource;
                } 

                public static bool operator!=(Key k1, Key k2) 
                { 
                    return !(k1 == k2);
                } 
            }

            public IValueConverter this[Type sourceType, Type targetType, bool targetToSource]
            { 
                get
                { 
                    Key key = new Key(sourceType, targetType, targetToSource); 
                    object value = base[key];
                    return (IValueConverter)value; 
                }
            }

            public void Add(Type sourceType, Type targetType, bool targetToSource, IValueConverter value) 
            {
                base.Add(new Key(sourceType, targetType, targetToSource), value); 
            } 
        }
 
        private sealed class DataBindEngineShutDownListener : ShutDownListener
        {
            /// 
            ///     Critical: accesses AppDomain.DomainUnload event 
            ///     TreatAsSafe: This code does not take any parameter or return state.
            ///                  It simply attaches private callbacks. 
            ///  
            [SecurityCritical,SecurityTreatAsSafe]
            public DataBindEngineShutDownListener(DataBindEngine target) : base(target) 
            {
            }

            internal override void OnShutDown(object target, object sender, EventArgs e) 
            {
                DataBindEngine table = (DataBindEngine)target; 
                table.OnShutDown(); 
            }
        } 

        //-----------------------------------------------------
        //
        //  Private Fields 
        //
        //------------------------------------------------------ 
 
        private HybridDictionary _mostRecentTask;           // client --> Task
        Task                _head; 
        Task                _tail;
        private UIElement   _layoutElement;
        private ViewManager _viewManager = new ViewManager();
        private ValueConverterTable _valueConverterTable = new ValueConverterTable(); 
        private PathParser  _pathParser = new PathParser();
        private IAsyncDataDispatcher _defaultAsyncDataDispatcher; 
        private HybridDictionary _asyncDispatchers; 
        private ValueConverterContext _valueConverterContext = new ValueConverterContext();
 
        private bool        _cleanupEnabled = true;

        private ValueTable  _valueTable = new ValueTable();
        private AccessorTable _accessorTable = new AccessorTable(); 
        private int         _cleanupRequests;
 
        [ThreadStatic] 
        private static DataBindEngine   _currentEngine; // one engine per thread
    } 

}

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.
                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK