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); } }
// 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); } } }