private PropertyPathWorker(PropertyPath path, DataBindEngine engine)
        {
            _parent = path;
            _arySVS = new SourceValueState[path.Length];
            _engine = engine;

            // initialize each level to NullDataItem, so that the first real
            // item will force a change
            for (int i=_arySVS.Length-1; i>=0; --i)
            {
                _arySVS[i].item = BindingExpression.CreateReference(BindingExpression.NullDataItem);
            }
        }
        // replace the item at level k with the given item, or with an item obtained from the given parent
        private void ReplaceItem(int k, object newO, object parent)
        {
            bool isExtendedTraceEnabled = IsExtendedTraceEnabled(TraceDataLevel.ReplaceItem);
            SourceValueState svs = new SourceValueState();

            object oldO = BindingExpression.GetReference(_arySVS[k].item);

            // stop listening to old item
            if (IsDynamic && SVI[k].type != SourceValueType.Direct)
            {
                INotifyPropertyChanged oldPC;
                DependencyProperty oldDP;
                PropertyInfo oldPI;
                PropertyDescriptor oldPD;
                DynamicObjectAccessor oldDOA;
                PropertyPath.DowncastAccessor(_arySVS[k].info, out oldDP, out oldPI, out oldPD, out oldDOA);

                if (newO == BindingExpression.StaticSource)
                {
                    Type declaringType = (oldPI != null) ? oldPI.DeclaringType
                                        : (oldPD != null) ? oldPD.ComponentType
                                        : null;
                    if (declaringType != null)
                    {
                        StaticPropertyChangedEventManager.RemoveHandler(declaringType, OnStaticPropertyChanged, SVI[k].propertyName);
                    }
                }
                else if (oldDP != null)
                {
                    _dependencySourcesChanged = true;
                }
                else if ((oldPC = oldO as INotifyPropertyChanged) != null)
                {
                    PropertyChangedEventManager.RemoveHandler(oldPC, OnPropertyChanged, SVI[k].propertyName);
                }
                else if (oldPD != null && oldO != null)
                {
                    ValueChangedEventManager.RemoveHandler(oldO, OnValueChanged, oldPD);
                }
            }

            // extra work at the last level
            if (_host != null && k == Length-1)
            {
                // handle INotifyDataErrorInfo
                if (IsDynamic && _host.ValidatesOnNotifyDataErrors)
                {
                    INotifyDataErrorInfo indei = oldO as INotifyDataErrorInfo;
                    if (indei != null)
                    {
                        ErrorsChangedEventManager.RemoveHandler(indei, OnErrorsChanged);
                    }
                }
            }

            // clear the IsDBNullValid cache
            _isDBNullValidForUpdate = null;

            if (newO == null ||
                parent == DependencyProperty.UnsetValue ||
                parent == BindingExpression.NullDataItem ||
                parent == BindingExpressionBase.DisconnectedItem)
            {
                _arySVS[k].item = BindingExpression.ReplaceReference(_arySVS[k].item, newO);

                if (parent == DependencyProperty.UnsetValue ||
                    parent == BindingExpression.NullDataItem ||
                    parent == BindingExpressionBase.DisconnectedItem)
                {
                    _arySVS[k].collectionView = null;
                }

                if (isExtendedTraceEnabled)
                {
                    TraceData.Trace(TraceEventType.Warning,
                                        TraceData.ReplaceItemShort(
                                            TraceData.Identify(_host.ParentBindingExpression),
                                            k,
                                            TraceData.Identify(newO)));
                }

                return;
            }

            // obtain the new item and its access info
            if (newO != BindingExpression.NullDataItem)
            {
                parent = newO;              // used by error reporting
                GetInfo(k, newO, ref svs);
                svs.collectionView = _arySVS[k].collectionView;
            }
            else
            {
                // Note: if we want to support binding to HasValue and/or Value
                // properties of nullable types, we need a way to find out if
                // the rawvalue is Nullable and pass that information here.

                DrillIn drillIn = SVI[k].drillIn;
                ICollectionView view = null;

                // first look for info on the parent
                if (drillIn != DrillIn.Always)
                {
                    GetInfo(k, parent, ref svs);
                }

                // if that fails, look for information on the view itself
                if (svs.info == null)
                {
                    view = CollectionViewSource.GetDefaultCollectionView(parent, TreeContext,
                        (x) =>
                        {
                            return BindingExpression.GetReference((k == 0) ? _rootItem : _arySVS[k-1].item);
                        });

                    if (view != null && drillIn != DrillIn.Always)
                    {
                        if (view != parent)             // don't duplicate work
                            GetInfo(k, view, ref svs);
                    }
                }

                // if that fails, drill in to the current item
                if (svs.info == null && drillIn != DrillIn.Never && view != null)
                {
                    newO = view.CurrentItem;
                    if (newO != null)
                    {
                        GetInfo(k, newO, ref svs);
                        svs.collectionView = view;
                    }
                    else
                    {
                        // no current item: use previous info (if known)
                        svs = _arySVS[k];
                        svs.collectionView = view;

                        // if there's no current item because parent is an empty
                        // XmlDataCollection, treat it as a path error (the XPath
                        // didn't return any nodes)
                        if (!SystemXmlHelper.IsEmptyXmlDataCollection(parent))
                        {
                            // otherwise it's not an error - currency is simply
                            // off the collection
                            svs.item = BindingExpression.ReplaceReference(svs.item, BindingExpression.NullDataItem);
                            if (svs.info == null)
                                svs.info = DependencyProperty.UnsetValue;
                        }
                    }
                }
            }

            // update info about new item
            if (svs.info == null)
            {
                svs.item = BindingExpression.ReplaceReference(svs.item, BindingExpression.NullDataItem);
                _arySVS[k] = svs;
                _status = PropertyPathStatus.PathError;
                ReportNoInfoError(k, parent);
                return;
            }

            _arySVS[k] = svs;
            newO = BindingExpression.GetReference(svs.item);

            if (isExtendedTraceEnabled)
            {
                TraceData.Trace(TraceEventType.Warning,
                                    TraceData.ReplaceItemLong(
                                        TraceData.Identify(_host.ParentBindingExpression),
                                        k,
                                        TraceData.Identify(newO),
                                        TraceData.IdentifyAccessor(svs.info)));
            }

            // start listening to new item
            if (IsDynamic && SVI[k].type != SourceValueType.Direct)
            {
                Engine.RegisterForCacheChanges(newO, svs.info);

                INotifyPropertyChanged newPC;
                DependencyProperty newDP;
                PropertyInfo newPI;
                PropertyDescriptor newPD;
                DynamicObjectAccessor newDOA;
                PropertyPath.DowncastAccessor(svs.info, out newDP, out newPI, out newPD, out newDOA);

                if (newO == BindingExpression.StaticSource)
                {
                    Type declaringType = (newPI != null) ? newPI.DeclaringType
                                        : (newPD != null) ? newPD.ComponentType
                                        : null;
                    if (declaringType != null)
                    {
                        StaticPropertyChangedEventManager.AddHandler(declaringType, OnStaticPropertyChanged, SVI[k].propertyName);
                    }
                }
                else if (newDP != null)
                {
                    _dependencySourcesChanged = true;
                }
                else if ((newPC = newO as INotifyPropertyChanged) != null)
                {
                    PropertyChangedEventManager.AddHandler(newPC, OnPropertyChanged, SVI[k].propertyName);
                }
                else if (newPD != null && newO != null)
                {
                    ValueChangedEventManager.AddHandler(newO, OnValueChanged, newPD);
                }
            }

            // extra work at the last level
            if (_host != null && k == Length-1)
            {
                // set up the default transformer
                _host.SetupDefaultValueConverter(svs.type);

                // check for request to update a read-only property
                if (_host.IsReflective)
                {
                    CheckReadOnly(newO, svs.info);
                }

                // handle INotifyDataErrorInfo
                if (_host.ValidatesOnNotifyDataErrors)
                {
                    INotifyDataErrorInfo indei= newO as INotifyDataErrorInfo;
                    if (indei != null)
                    {
                        if (IsDynamic)
                        {
                            ErrorsChangedEventManager.AddHandler(indei, OnErrorsChanged);
                        }

                        _host.OnDataErrorsChanged(indei, SourcePropertyName);
                    }
                }
            }
        }
        // look for property/indexer on the given item
        private void GetInfo(int k, object item, ref SourceValueState svs)
        {
#if DEBUG
            bool checkCacheResult = false;
#endif
            object oldItem = BindingExpression.GetReference(_arySVS[k].item);
            bool isExtendedTraceEnabled = IsExtendedTraceEnabled(TraceDataLevel.GetInfo);

            // optimization - only change info if the type changed
            // exception - if the info is a PropertyDescriptor, it might depend
            // on the item itself (not just the type), so we have to re-fetch
            Type oldType = ReflectionHelper.GetReflectionType(oldItem);
            Type newType = ReflectionHelper.GetReflectionType(item);
            Type sourceType = null;

            if (newType == oldType && oldItem != BindingExpression.NullDataItem &&
                !(_arySVS[k].info is PropertyDescriptor))
            {
                svs = _arySVS[k];
                svs.item = BindingExpression.ReplaceReference(svs.item, item);

                if (isExtendedTraceEnabled)
                {
                    TraceData.Trace(TraceEventType.Warning,
                                        TraceData.GetInfo_Reuse(
                                            TraceData.Identify(_host.ParentBindingExpression),
                                            k,
                                            TraceData.IdentifyAccessor(svs.info)));
                }
                return;
            }

            // if the new item is null, we won't find a property/indexer on it
            if (newType == null && SVI[k].type != SourceValueType.Direct)
            {
                svs.info = null;
                svs.args = null;
                svs.type = null;
                svs.item = BindingExpression.ReplaceReference(svs.item, item);

                if (isExtendedTraceEnabled)
                {
                    TraceData.Trace(TraceEventType.Warning,
                                        TraceData.GetInfo_Null(
                                            TraceData.Identify(_host.ParentBindingExpression),
                                            k));
                }
                return;
            }

            // optimization - see if we've cached the answer
            int index;
            bool cacheAccessor = !PropertyPath.IsParameterIndex(SVI[k].name, out index);
            if (cacheAccessor)
            {
                AccessorInfo accessorInfo = Engine.AccessorTable[SVI[k].type, newType, SVI[k].name];
                if (accessorInfo != null)
                {
                    svs.info = accessorInfo.Accessor;
                    svs.type = accessorInfo.PropertyType;
                    svs.args = accessorInfo.Args;

                    if (PropertyPath.IsStaticProperty(svs.info))
                        item = BindingExpression.StaticSource;

                    svs.item = BindingExpression.ReplaceReference(svs.item, item);

                    if (IsDynamic && SVI[k].type == SourceValueType.Property && svs.info is DependencyProperty)
                    {
                        _dependencySourcesChanged = true;
                    }

                    if (isExtendedTraceEnabled)
                    {
                        TraceData.Trace(TraceEventType.Warning,
                                            TraceData.GetInfo_Cache(
                                                TraceData.Identify(_host.ParentBindingExpression),
                                                k,
                                                newType.Name,
                                                SVI[k].name,
                                                TraceData.IdentifyAccessor(svs.info)));
                    }

#if DEBUG   // compute the answer the old-fashioned way, and compare
                    checkCacheResult = true;
#else
                    return;
#endif
                }
            }

            object info = null;
            object[] args = null;

            switch (SVI[k].type)
            {
            case SourceValueType.Property:
                info = _parent.ResolvePropertyName(k, item, newType, TreeContext);

                if (isExtendedTraceEnabled)
                {
                    TraceData.Trace(TraceEventType.Warning,
                                        TraceData.GetInfo_Property(
                                            TraceData.Identify(_host.ParentBindingExpression),
                                            k,
                                            newType.Name,
                                            SVI[k].name,
                                            TraceData.IdentifyAccessor(info)));
                }

                DependencyProperty dp;
                PropertyInfo pi1;
                PropertyDescriptor pd;
                DynamicObjectAccessor doa;
                PropertyPath.DowncastAccessor(info, out dp, out pi1, out pd, out doa);

                if (dp != null)
                {
                    sourceType = dp.PropertyType;
                    if (IsDynamic)
                    {
#if DEBUG
                        if (checkCacheResult)
                            Debug.Assert(_dependencySourcesChanged, "Cached accessor didn't change sources");
#endif
                        _dependencySourcesChanged = true;
                    }
                    break;
                }
                else if (pi1 != null)
                {
                    sourceType = pi1.PropertyType;
                }
                else if (pd != null)
                {
                    sourceType = pd.PropertyType;
                }
                else if (doa != null)
                {
                    sourceType = doa.PropertyType;
#if DEBUG
                    checkCacheResult = false;      // not relevant for dynamic objects
#endif
                }
                break;

            case SourceValueType.Indexer:
                IndexerParameterInfo[] aryInfo = _parent.ResolveIndexerParams(k, TreeContext);

                // Check if we should treat the indexer as a property instead.
                // (See ShouldConvertIndexerToProperty for why we might do that.)
                if (aryInfo.Length == 1 &&
                    (aryInfo[0].type == null || aryInfo[0].type == typeof(string)))
                {
                    string name = (string)aryInfo[0].value;
                    if (ShouldConvertIndexerToProperty(item, ref name))
                    {
                        _parent.ReplaceIndexerByProperty(k, name);
                        goto case SourceValueType.Property;
                    }
                }

                args = new object[aryInfo.Length];

                // find the matching indexer
                MemberInfo[][] aryMembers= new MemberInfo[][]{ GetIndexers(newType, k), null };
                bool isIList = (item is IList);
                if (isIList)
                    aryMembers[1] = typeof(IList).GetDefaultMembers();

                for (int ii=0; info==null && ii<aryMembers.Length; ++ii)
                {
                    if (aryMembers[ii] == null)
                        continue;

                    MemberInfo[] defaultMembers = aryMembers[ii];

                    for (int jj=0; jj<defaultMembers.Length; ++jj)
                    {
                        PropertyInfo pi = defaultMembers[jj] as PropertyInfo;
                        if (pi != null)
                        {
                            if (MatchIndexerParameters(pi.GetIndexParameters(), aryInfo, args, isIList))
                            {
                                info = pi;
                                sourceType = newType.GetElementType();
                                if (sourceType == null)
                                    sourceType = pi.PropertyType;
                                break;
                            }
                        }
                    }
                }

                if (info == null && SystemCoreHelper.IsIDynamicMetaObjectProvider(item))
                {
                    if (MatchIndexerParameters(null, aryInfo, args, false))
                    {
                        info = SystemCoreHelper.GetIndexerAccessor(args.Length);
                        sourceType = typeof(Object);
                    }
                }

                if (isExtendedTraceEnabled)
                {
                    TraceData.Trace(TraceEventType.Warning,
                                        TraceData.GetInfo_Indexer(
                                            TraceData.Identify(_host.ParentBindingExpression),
                                            k,
                                            newType.Name,
                                            SVI[k].name,
                                            TraceData.IdentifyAccessor(info)));
                }

                break;

            case SourceValueType.Direct:
                if (!(item is ICollectionView) || _host == null || _host.IsValidValue(item))
                {
                    info = DependencyProperty.UnsetValue;
                    sourceType = newType;

                    if (Length == 1 &&
                            item is Freezable &&    // subproperty notifications only arise from Freezables
                            item != TreeContext)    // avoid self-loops
                    {
                        info = DependencyObject.DirectDependencyProperty;
                        _dependencySourcesChanged = true;
                    }
                }
                break;
            }

#if DEBUG
            if (checkCacheResult)
            {
                StringBuilder sb = new StringBuilder();

                if (!Object.Equals(info, svs.info))
                    sb.AppendLine(String.Format("  Info is wrong: expected '{0}' got '{1}'",
                                    info, svs.info));

                if (sourceType != svs.type)
                    sb.AppendLine(String.Format("  Type is wrong: expected '{0}' got '{1}'",
                                    sourceType, svs.type));

                if (item != BindingExpression.GetReference(svs.item))
                    sb.AppendLine(String.Format("  Item is wrong: expected '{0}' got '{1}'",
                                    item, BindingExpression.GetReference(svs.item)));

                int len1 = (args != null) ? args.Length : 0;
                int len2 = (svs.args != null) ? svs.args.Length : 0;
                if (len1 == len2)
                {
                    for (int i=0; i<len1; ++i)
                    {
                        if (!Object.Equals(args[i], svs.args[i]))
                        {
                            sb.AppendLine(String.Format("  args[{0}] is wrong:  expected '{1}' got '{2}'",
                                    i, args[i], svs.args[i]));
                        }
                    }
                }
                else
                    sb.AppendLine(String.Format("  Args are wrong: expected length '{0}' got length '{1}'",
                                    len1, len2));

                if (sb.Length > 0)
                {
                    Debug.Assert(false,
                        String.Format("Accessor cache returned incorrect result for ({0},{1},{2})\n{3}",
                            SVI[k].type, newType.Name, SVI[k].name, sb.ToString()));
                }

                return;
            }
#endif
            if (PropertyPath.IsStaticProperty(info))
                item = BindingExpression.StaticSource;

            svs.info = info;
            svs.args = args;
            svs.type = sourceType;
            svs.item = BindingExpression.ReplaceReference(svs.item, item);

            // cache the answer, to avoid doing all that reflection again
            // (but not if the answer is a PropertyDescriptor,
            // since then the answer potentially depends on the item itself)
            if (cacheAccessor && info != null && !(info is PropertyDescriptor))
            {
                Engine.AccessorTable[SVI[k].type, newType, SVI[k].name] =
                            new AccessorInfo(info, sourceType, args);
            }
        }
Example #4
0
        // replace the item at level k with the given item, or with an item obtained from the given parent
        private void ReplaceItem(int k, object newO, object parent)
        { 
            bool isExtendedTraceEnabled = IsExtendedTraceEnabled(TraceDataLevel.ReplaceItem);
            SourceValueState svs = new SourceValueState(); 
 
            object oldO = BindingExpression.GetReference(_arySVS[k].item);
 
            // stop listening to old item
            if (IsDynamic && SVI[k].type != SourceValueType.Direct)
            {
                INotifyPropertyChanged oldPC = oldO as INotifyPropertyChanged; 
                if (oldPC != null)
                { 
                    PropertyChangedEventManager.RemoveListener(oldPC, this, SVI[k].propertyName); 
                }
                else 
                {
                    PropertyDescriptor oldDesc = _arySVS[k].info as PropertyDescriptor;
                    if (oldDesc != null && oldO != null && oldO != BindingExpression.NullDataItem)
                    { 
                        ValueChangedEventManager.RemoveListener(oldO, this, oldDesc);
                    } 
                } 

                DependencyProperty dp = _arySVS[k].info as DependencyProperty; 
                if (dp != null)
                {
                    _dependencySourcesChanged = true;
                } 
            }
 
            // clear the IsDBNullValid cache 
            _isDBNullValidForUpdate = null;
 
            if (newO == null ||
                parent == DependencyProperty.UnsetValue ||
                parent == BindingExpression.NullDataItem ||
                parent == BindingExpressionBase.DisconnectedItem) 
            {
                _arySVS[k].item = BindingExpression.ReplaceReference(_arySVS[k].item, newO); 
 
                if (parent == DependencyProperty.UnsetValue ||
                    parent == BindingExpression.NullDataItem || 
                    parent == BindingExpressionBase.DisconnectedItem)
                {
                    _arySVS[k].collectionView = null;
                } 

                if (isExtendedTraceEnabled) 
                { 
                    TraceData.Trace(TraceEventType.Warning,
                                        TraceData.ReplaceItemShort( 
                                            TraceData.Identify(_host.ParentBindingExpression),
                                            k,
                                            TraceData.Identify(newO)));
                } 

                return; 
            } 

            // obtain the new item and its access info 
            if (newO != BindingExpression.NullDataItem)
            {
                parent = newO;              // used by error reporting
                GetInfo(k, newO, ref svs); 
                svs.collectionView = _arySVS[k].collectionView;
            } 
            else 
            {
                // Note: if we want to support binding to HasValue and/or Value 
                // properties of nullable types, we need a way to find out if
                // the rawvalue is Nullable and pass that information here.

                DrillIn drillIn = SVI[k].drillIn; 
                ICollectionView view = null;
 
                // first look for info on the parent 
                if (drillIn != DrillIn.Always)
                { 
                    GetInfo(k, parent, ref svs);
                }

                // if that fails, look for information on the view itself 
                if (svs.info == null)
                { 
                    view = CollectionViewSource.GetDefaultCollectionView(parent, TreeContext); 

                    if (view != null && drillIn != DrillIn.Always) 
                    {
                        if (view != parent)             // don't duplicate work
                            GetInfo(k, view, ref svs);
                    } 
                }
 
                // if that fails, drill in to the current item 
                if (svs.info == null && drillIn != DrillIn.Never && view != null)
                { 
                    newO = view.CurrentItem;
                    if (newO != null)
                    {
                        GetInfo(k, newO, ref svs); 
                        svs.collectionView = view;
                    } 
                    else 
                    {
                        // no current item: use previous info (if known) 
                        svs = _arySVS[k];
                        svs.collectionView = view;

                        // if there's no current item because parent is an empty 
                        // XmlDataCollection, treat it as a path error (the XPath
                        // didn't return any nodes) 
                        if (!IsEmptyXmlDataCollection(parent)) 
                        {
                            // otherwise it's not an error - currency is simply 
                            // off the collection
                            svs.item = BindingExpression.ReplaceReference(svs.item, BindingExpression.NullDataItem);
                            if (svs.info == null)
                                svs.info = DependencyProperty.UnsetValue; 
                        }
                    } 
                } 
            }
 
            // update info about new item
            if (svs.info == null)
            {
                svs.item = BindingExpression.ReplaceReference(svs.item, BindingExpression.NullDataItem); 
                _arySVS[k] = svs;
                _status = PropertyPathStatus.PathError; 
                ReportNoInfoError(k, parent); 
                return;
            } 

            _arySVS[k] = svs;
            newO = BindingExpression.GetReference(svs.item);
 
            if (isExtendedTraceEnabled)
            { 
                TraceData.Trace(TraceEventType.Warning, 
                                    TraceData.ReplaceItemLong(
                                        TraceData.Identify(_host.ParentBindingExpression), 
                                        k,
                                        TraceData.Identify(newO),
                                        TraceData.IdentifyAccessor(svs.info)));
            } 

            // start listening to new item 
            if (IsDynamic && SVI[k].type != SourceValueType.Direct) 
            {
                Engine.RegisterForCacheChanges(newO, svs.info); 

                INotifyPropertyChanged newPC = newO as INotifyPropertyChanged;
                if (newPC != null)
                { 
                    PropertyChangedEventManager.AddListener(newPC, this, SVI[k].propertyName);
                } 
                else 
                {
                    PropertyDescriptor newDesc = svs.info as PropertyDescriptor; 
                    if (newDesc != null && newO != null)
                        ValueChangedEventManager.AddListener(newO, this, newDesc);
                }
            } 

            // at the last level, set up the default transformer 
            if (_host != null && k == Length-1) 
            {
                _host.SetupDefaultValueConverter(svs.type); 

                // also, check for request to update a read-only property
                if (_host.IsReflective)
                { 
                    CheckReadOnly(newO, svs.info);
                } 
            } 
        }