Code:
/ Dotnetfx_Win7_3.5.1 / Dotnetfx_Win7_3.5.1 / 3.5.1 / DEVDIV / depot / DevDiv / releases / Orcas / NetFXw7 / wpf / src / Framework / MS / Internal / Data / PropertyPathWorker.cs / 2 / PropertyPathWorker.cs
//----------------------------------------------------------------------------
//
//
// Copyright (C) Microsoft Corporation. All rights reserved.
//
//
// Description: Defines PropertyPathWorker object, workhorse for CLR bindings
//
//---------------------------------------------------------------------------
using System;
using System.Collections;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
using System.Globalization;
using System.Text;
using System.Windows.Threading;
using System.Windows;
using System.Windows.Controls; // Validation
using System.Windows.Data;
using MS.Internal;
using MS.Internal.Hashing.PresentationFramework; // HashHelper
namespace MS.Internal.Data
{
internal sealed class PropertyPathWorker: IWeakEventListener
{
//-----------------------------------------------------
//
// Constructors
//
//-----------------------------------------------------
internal PropertyPathWorker(PropertyPath path)
: this(path, DataBindEngine.CurrentDataBindEngine)
{
}
internal PropertyPathWorker(PropertyPath path, ClrBindingWorker host, bool isDynamic, DataBindEngine engine)
: this(path, engine)
{
_host = host;
_isDynamic = isDynamic;
}
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);
}
}
//------------------------------------------------------
//
// Internal Properties
//
//-----------------------------------------------------
internal int Length { get { return _parent.Length; } }
internal PropertyPathStatus Status { get { return _status; } }
internal DependencyObject TreeContext
{
get { return BindingExpression.GetReference(_treeContext) as DependencyObject; }
set { _treeContext = BindingExpression.CreateReference(value); }
}
internal void SetTreeContext(WeakReference wr)
{
_treeContext = BindingExpression.CreateReference(wr);
}
internal bool IsDBNullValidForUpdate
{
get
{
if (!_isDBNullValidForUpdate.HasValue)
{
DetermineWhetherDBNullIsValid();
}
return _isDBNullValidForUpdate.Value;
}
}
internal object SourceItem
{
get
{
int level = Length-1;
return (level >= 0) ? GetItem(level) : null;
}
}
internal string SourcePropertyName
{
get
{
DependencyProperty dp;
PropertyInfo pi;
PropertyDescriptor pd;
int level = Length-1;
if (level < 0)
return null;
SetPropertyInfo(GetAccessor(level), out pi, out pd, out dp);
return (dp != null) ? dp.Name :
(pi != null) ? pi.Name :
(pd != null) ? pd.Name : null;
}
}
//------------------------------------------------------
//
// Internal Methods
//
//------------------------------------------------------
//------- common methods ------
internal object GetItem(int level)
{
return BindingExpression.GetReference(_arySVS[level].item);
}
internal object GetAccessor(int level)
{
return _arySVS[level].info;
}
internal object[] GetIndexerArguments(int level)
{
return _arySVS[level].args;
}
internal Type GetType(int level)
{
return _arySVS[level].type;
}
//------- target mode ------
// Set the context for the path. Use this method in "target" mode
// to connect the path to a rootItem for a short time:
// using (path.SetContext(myItem))
// {
// ... call target-mode convenience methods ...
// }
internal IDisposable SetContext(object rootItem)
{
if (_contextHelper == null)
_contextHelper = new ContextHelper(this);
_contextHelper.SetContext(rootItem);
return _contextHelper;
}
//------- source mode (should only be called by ClrBindingWorker) ------
internal void AttachToRootItem(object rootItem)
{
_rootItem = BindingExpression.CreateReference(rootItem);
UpdateSourceValueState(-1, null);
}
internal void DetachFromRootItem()
{
_rootItem = BindingExpression.NullDataItem;
UpdateSourceValueState(-1, null);
_rootItem = null;
}
internal object GetValue(object item, int level)
{
bool isExtendedTraceEnabled = IsExtendedTraceEnabled(TraceDataLevel.GetValue);
DependencyProperty dp;
PropertyInfo pi;
PropertyDescriptor pd;
object value = DependencyProperty.UnsetValue;
SetPropertyInfo(_arySVS[level].info, out pi, out pd, out dp);
switch (SVI[level].type)
{
case SourceValueType.Property:
if (pi != null)
{
value = pi.GetValue(item, null);
}
else if (pd != null)
{
bool indexerIsNext = (level+1 < SVI.Length && SVI[level+1].type == SourceValueType.Indexer);
value = Engine.GetValue(item, pd, indexerIsNext);
}
else if (dp != null)
{
DependencyObject d = (DependencyObject)item;
if (level != Length-1 || _host == null || _host.TransfersDefaultValue)
value = d.GetValue(dp);
else if (!Helper.HasDefaultValue(dp, d, d as FrameworkElement, d as FrameworkContentElement))
value = d.GetValue(dp);
else
value = BindingExpression.IgnoreDefaultValue;
}
break;
case SourceValueType.Indexer:
//
if (pi != null)
{
value = pi.GetValue(item,
BindingFlags.GetProperty, null,
_arySVS[level].args,
CultureInfo.InvariantCulture);
}
else
{
throw new NotSupportedException(SR.Get(SRID.IndexedPropDescNotImplemented));
}
break;
case SourceValueType.Direct:
value = item;
break;
}
if (isExtendedTraceEnabled)
{
object accessor = _arySVS[level].info;
if (accessor == DependencyProperty.UnsetValue)
accessor = null;
TraceData.Trace(TraceEventType.Warning,
TraceData.GetValue(
TraceData.Identify(_host.ParentBindingExpression),
level,
TraceData.Identify(item),
TraceData.IdentifyAccessor(accessor),
TraceData.Identify(value)));
}
return value;
}
internal void SetValue(object item, object value)
{
bool isExtendedTraceEnabled = IsExtendedTraceEnabled(TraceDataLevel.GetValue);
PropertyInfo pi;
PropertyDescriptor pd;
DependencyProperty dp;
int level = _arySVS.Length - 1;
SetPropertyInfo(_arySVS[level].info, out pi, out pd, out dp);
if (isExtendedTraceEnabled)
{
TraceData.Trace(TraceEventType.Warning,
TraceData.SetValue(
TraceData.Identify(_host.ParentBindingExpression),
level,
TraceData.Identify(item),
TraceData.IdentifyAccessor(_arySVS[level].info),
TraceData.Identify(value)));
}
switch (SVI[level].type)
{
case SourceValueType.Property:
if (pd != null)
{
pd.SetValue(item, value);
}
else if (pi != null)
{
pi.SetValue(item, value, null);
}
else if (dp != null)
{
((DependencyObject)item).SetValue(dp, value);
}
break;
case SourceValueType.Indexer:
//
if (pi != null)
{
pi.SetValue(item, value,
BindingFlags.SetProperty, null,
_arySVS[level].args,
CultureInfo.InvariantCulture);
}
else
{
throw new NotSupportedException(SR.Get(SRID.IndexedPropDescNotImplemented));
}
break;
}
}
internal object RawValue()
{
object rawValue = RawValue(Length-1);
if (rawValue == AsyncRequestPending)
rawValue = DependencyProperty.UnsetValue; // the real value will arrive later
return rawValue;
}
// Called by BE.UpdateTarget(). Re-fetch the value at each level.
// If there's a difference, simulate a property-change at that level.
internal void RefreshValue()
{
for (int k=1; k<_arySVS.Length; ++k)
{
object oldValue = BindingExpression.GetReference(_arySVS[k].item);
if (!Object.Equals(oldValue, RawValue(k-1)))
{
UpdateSourceValueState(k-1, null);
return;
}
}
UpdateSourceValueState(Length-1, null);
}
// return the source level where the change happened, or -1 if the
// change is irrelevant.
internal int LevelForPropertyChange(object item, string propertyName)
{
// This test must be thread-safe - it can get called on the "wrong" context.
// It's read-only (good). And if another thread changes the values it reads,
// the worst that can happen is to schedule a transfer operation needlessly -
// the operation itself won't do anything (since the test is repeated on the
// right thread).
bool isIndexer = propertyName == Binding.IndexerName;
for (int k=0; k<_arySVS.Length; ++k)
{
if (BindingExpression.GetReference(_arySVS[k].item) == item &&
(String.IsNullOrEmpty(propertyName) ||
(isIndexer && SVI[k].type == MS.Internal.Data.SourceValueType.Indexer) ||
String.Equals(SVI[k].propertyName, propertyName, StringComparison.OrdinalIgnoreCase)))
{
return k;
}
}
return -1;
}
internal void OnPropertyChangedAtLevel(int level)
{
UpdateSourceValueState(level, null);
}
internal void OnCurrentChanged(ICollectionView collectionView)
{
for (int k=0; k= 0 && SVI[level].type == SourceValueType.Property)
{
object item = GetItem(level);
IDataErrorInfo idei = item as IDataErrorInfo;
if (idei != null)
{
DependencyProperty dp;
PropertyInfo pi;
PropertyDescriptor pd;
SetPropertyInfo(GetAccessor(level), out pi, out pd, out dp);
string name = (dp != null) ? dp.Name :
(pi != null) ? pi.Name :
(pd != null) ? pd.Name : null;
string error;
if (name != null)
{
// get the data error information, if any, by calling idie[name].
// We do this in a paranoid way, even though indexers with
// string-valued arguments are not supposed to throw exceptions.
// PreSharp uses message numbers that the C# compiler doesn't know about.
// Disable the C# complaints, per the PreSharp documentation.
#pragma warning disable 1634, 1691
// PreSharp complains about catching NullReference (and other) exceptions.
// It doesn't recognize that IsCriticalException() handles these correctly.
#pragma warning disable 56500
try
{
error = idei[name];
}
catch (Exception ex)
{
if (CriticalExceptions.IsCriticalException(ex))
throw;
error = null;
if (TraceData.IsEnabled)
{
TraceData.Trace(TraceEventType.Error,
TraceData.DataErrorInfoFailed(
name,
item.GetType().FullName,
ex.GetType().FullName,
ex.Message),
bindingExpressionBase);
}
}
#pragma warning restore 56500
#pragma warning restore 1634, 1691
}
else
{
error = null;
}
if (!String.IsNullOrEmpty(error))
{
result = new ValidationError(DataErrorValidationRule.Instance,
bindingExpressionBase,
error,
null);
}
}
}
return result;
}
//-----------------------------------------------------
//
// Private Properties
//
//------------------------------------------------------
bool IsDynamic { get { return _isDynamic; } }
SourceValueInfo[] SVI { get { return _parent.SVI; } }
DataBindEngine Engine { get { return _engine; } }
//-----------------------------------------------------
//
// Private Methods
//
//-----------------------------------------------------
// fill in the SourceValueState with updated infomation, starting at level k+1.
// If view isn't null, also update the current item at level k.
private void UpdateSourceValueState(int k, ICollectionView collectionView)
{
UpdateSourceValueState(k, collectionView, BindingExpression.NullDataItem, false);
}
// fill in the SourceValueState with updated infomation, starting at level k+1.
// If view isn't null, also update the current item at level k.
private void UpdateSourceValueState(int k, ICollectionView collectionView, object newValue, bool isASubPropertyChange)
{
// give host a chance to shut down the binding if the target has
// gone away
DependencyObject target = null;
if (_host != null)
{
target = _host.CheckTarget();
if (_rootItem != BindingExpression.NullDataItem && target == null)
return;
}
int initialLevel = k;
object rawValue = null;
// optimistically assume the new value will fix previous path errors
_status = PropertyPathStatus.Active;
// prepare to collect changes to dependency sources
_dependencySourcesChanged = false;
// Update the current item at level k, if requested
if (collectionView != null)
{
Debug.Assert(0<=k && k<_arySVS.Length && _arySVS[k].collectionView == collectionView, "bad parameters to UpdateSourceValueState");
ReplaceItem(k, collectionView.CurrentItem, NoParent);
}
// update the remaining levels
for (++k; k<_arySVS.Length; ++k)
{
isASubPropertyChange = false; // sub-property changes only matter at the last level
ICollectionView oldCollectionView = _arySVS[k].collectionView;
// replace the item at level k using parent from level k-1
rawValue = (newValue == BindingExpression.NullDataItem) ? RawValue(k-1) : newValue;
newValue = BindingExpression.NullDataItem;
if (rawValue == AsyncRequestPending)
{
_status = PropertyPathStatus.AsyncRequestPending;
break; // we'll resume the loop after the request completes
}
ReplaceItem(k, BindingExpression.NullDataItem, rawValue);
// replace view, if necessary
ICollectionView newCollectionView = _arySVS[k].collectionView;
if (oldCollectionView != newCollectionView && _host != null)
{
_host.ReplaceCurrentItem(oldCollectionView, newCollectionView);
}
}
// notify binding about what happened
if (_host != null)
{
_host.NewValueAvailable(_dependencySourcesChanged, initialLevel < 0, isASubPropertyChange);
}
GC.KeepAlive(target); // keep target alive during changes (bug 956831)
}
// 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)
{
_arySVS[k].item = BindingExpression.ReplaceReference(_arySVS[k].item, newO);
if (parent == DependencyProperty.UnsetValue || parent == BindingExpression.NullDataItem)
_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)
{
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;
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);
}
}
}
void ReportNoInfoError(int k, object parent)
{
// report cannot find info. Ignore when in priority bindings.
if (TraceData.IsEnabled)
{
BindingExpression bindingExpression = (_host != null) ? _host.ParentBindingExpression : null;
if (bindingExpression == null || !bindingExpression.IsInPriorityBindingExpression)
{
SourceValueInfo svi = SVI[k];
string cs = (svi.type != SourceValueType.Indexer) ? svi.name : "[" + svi.name + "]";
string ps = TraceData.DescribeSourceObject(parent);
string os = (svi.drillIn == DrillIn.Always) ? "current item of collection" : "object";
// if the parent is null, the path error probably only means the
// data provider hasn't produced any data yet. When it does,
// the binding will try again and probably succeed. Give milder
// feedback for this special case, so as not to alarm users unduly.
if (parent == null)
{
TraceData.Trace(TraceEventType.Information, TraceData.NullItem(cs, os), bindingExpression);
}
// Similarly, if the parent is the NewItemPlaceholder.
else if (parent == CollectionView.NewItemPlaceholder)
{
TraceData.Trace(TraceEventType.Information, TraceData.PlaceholderItem(cs, os), bindingExpression);
}
else
{
TraceEventType traceType = (bindingExpression != null) ? bindingExpression.TraceLevel : TraceEventType.Error;
TraceData.Trace(traceType, TraceData.ClrReplaceItem(cs, ps, os), bindingExpression);
}
}
}
}
// 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 = (oldItem != null) ? oldItem.GetType() : null;
Type newType = (item != null) ? item.GetType() : null;
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;
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;
PropertyPath.DowncastAccessor(info, out dp, out pi1, out pd);
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;
}
if (pd != null)
{
sourceType = pd.PropertyType;
}
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, newType, name))
{
_parent.ReplaceIndexerByProperty(k, name);
goto case SourceValueType.Property;
}
}
args = new object[aryInfo.Length];
// find the matching indexer
MemberInfo[][] aryMembers= new MemberInfo[][]{ newType.GetDefaultMembers(), null };
if (item is IList)
aryMembers[1] = typeof(IList).GetDefaultMembers();
for (int ii=0; info==null && ii 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
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);
}
}
// convert the (string) argument names to types appropriate for use with
// the given property. Put the results in the args[] array. Return
// true if everything works.
private bool MatchIndexerParameters(PropertyInfo pi, IndexerParameterInfo[] aryInfo, object[] args)
{
ParameterInfo[] aryPI = pi.GetIndexParameters();
// must have the right number of parameters
if (aryPI.Length != aryInfo.Length)
return false;
// each parameter must be settable from user-specified type or from a string
for (int i=0; i= _arySVS.Length)
return DependencyProperty.UnsetValue;
object item = BindingExpression.GetReference(_arySVS[k].item);
object info = _arySVS[k].info;
// try to get the value, unless (a) binding is being detached,
// (b) no info - e.g. Nullable with no value, or (c) item expected
// but not present - e.g. currency moved off the end.
if (item != BindingExpression.NullDataItem && info != null && !(item == null && info != DependencyProperty.UnsetValue))
{
object o = DependencyProperty.UnsetValue;
DependencyProperty dp = info as DependencyProperty;
// if the binding is async, post a request to get the value
if (!(dp != null || SVI[k].type == SourceValueType.Direct))
{
if (_host != null && _host.AsyncGet(item, k))
{
_status = PropertyPathStatus.AsyncRequestPending;
return AsyncRequestPending;
}
}
// PreSharp uses message numbers that the C# compiler doesn't know about.
// Disable the C# complaints, per the PreSharp documentation.
#pragma warning disable 1634, 1691
// PreSharp complains about catching NullReference (and other) exceptions.
// It doesn't recognize that IsCriticalException() handles these correctly.
#pragma warning disable 56500
try
{
o = GetValue(item, k);
}
// Catch all exceptions. There is no app code on the stack,
// so the exception isn't actionable by the app.
// Yet we don't want to crash the app.
catch (Exception ex) // if error getting value, we will use fallback/default instead
{
if (CriticalExceptions.IsCriticalException(ex))
throw;
if (_host != null)
_host.ReportGetValueError(k, item, ex);
}
catch // non CLS compliant exception
{
if (_host != null)
_host.ReportGetValueError(k, item, new InvalidOperationException(SR.Get(SRID.NonCLSException, "GetValue")));
}
#pragma warning restore 56500
#pragma warning restore 1634, 1691
return o;
}
if (_host != null)
{
_host.ReportRawValueErrors(k, item, info);
}
return DependencyProperty.UnsetValue;
}
void SetPropertyInfo(object info, out PropertyInfo pi, out PropertyDescriptor pd, out DependencyProperty dp)
{
pi = null;
pd = null;
dp = info as DependencyProperty;
if (dp == null)
{
pi = info as PropertyInfo;
if (pi == null)
pd = info as PropertyDescriptor;
}
}
void CheckReadOnly(object item, object info)
{
PropertyInfo pi;
PropertyDescriptor pd;
DependencyProperty dp;
SetPropertyInfo(info, out pi, out pd, out dp);
if (pi != null)
{
if (pi.GetSetMethod() == null)
throw new InvalidOperationException(SR.Get(SRID.CannotWriteToReadOnly, item.GetType(), pi.Name));
}
else if (pd != null)
{
if (pd.IsReadOnly)
throw new InvalidOperationException(SR.Get(SRID.CannotWriteToReadOnly, item.GetType(), pd.Name));
}
else if (dp != null)
{
if (dp.ReadOnly)
throw new InvalidOperationException(SR.Get(SRID.CannotWriteToReadOnly, item.GetType(), dp.Name));
}
}
// see whether DBNull is a valid value for update, and cache the answer
void DetermineWhetherDBNullIsValid()
{
bool result = false;
PropertyDescriptor pd;
object item = GetItem(Length - 1);
if (item != null &&
item.GetType().Namespace.StartsWith("System.Data", StringComparison.Ordinal) &&
((pd = GetAccessor(Length-1) as PropertyDescriptor) != null))
{
result = DetermineWhetherDBNullIsValid(item, pd);
}
_isDBNullValidForUpdate = result;
}
// separate method, to avoid loading System.Data.dll until necessary
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
bool DetermineWhetherDBNullIsValid(object item, PropertyDescriptor pd)
{
System.Data.DataRowView drv = item as System.Data.DataRowView;
if (drv == null)
return false;
// this code was provided by the ADO team
System.Data.DataColumn column = drv.DataView.Table.Columns[pd.Name];
return (column != null) && column.AllowDBNull;
}
///
/// Handle events from the centralized event table
///
bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
{
if (IsExtendedTraceEnabled(TraceDataLevel.Events))
{
TraceData.Trace(TraceEventType.Warning,
TraceData.GotEvent(
TraceData.Identify(_host.ParentBindingExpression),
TraceData.IdentifyWeakEvent(managerType),
TraceData.Identify(sender)));
}
if (managerType == typeof(PropertyChangedEventManager))
{
PropertyChangedEventArgs pce = (PropertyChangedEventArgs)e;
_host.OnSourcePropertyChanged(sender, pce.PropertyName);
}
else if (managerType == typeof(ValueChangedEventManager))
{
ValueChangedEventArgs vce = (ValueChangedEventArgs)e;
_host.OnSourcePropertyChanged(sender, vce.PropertyDescriptor.Name);
}
else
{
return false; // unrecognized event
}
return true;
}
bool IsExtendedTraceEnabled(TraceDataLevel level)
{
if (_host != null)
{
return TraceData.IsExtendedTraceEnabled(_host.ParentBindingExpression, level);
}
else
{
return false;
}
}
//-----------------------------------------------------
//
// Private Classes
//
//------------------------------------------------------
// helper for setting context via the "using" pattern
class ContextHelper : IDisposable
{
PropertyPathWorker _owner;
public ContextHelper(PropertyPathWorker owner)
{
_owner = owner;
}
public void SetContext(object rootItem)
{
_owner.TreeContext = rootItem as DependencyObject;
_owner.AttachToRootItem(rootItem);
}
void IDisposable.Dispose()
{
_owner.DetachFromRootItem();
_owner.TreeContext = null;
}
}
//-----------------------------------------------------
//
// Private Enums, Structs, Constants
//
//------------------------------------------------------
struct SourceValueState
{
public ICollectionView collectionView;
public object item;
public object info; // PropertyInfo or PropertyDescriptor or DP
public Type type; // Type of the value (useful for Arrays)
public object[] args; // for indexers
}
static readonly Char[] s_comma = new Char[]{','};
static readonly Char[] s_dot = new Char[]{'.'};
static readonly object NoParent = new object();
static readonly object AsyncRequestPending = new object();
//------------------------------------------------------
//
// Private Fields
//
//-----------------------------------------------------
PropertyPath _parent;
PropertyPathStatus _status;
object _treeContext;
object _rootItem;
SourceValueState[] _arySVS;
ContextHelper _contextHelper;
ClrBindingWorker _host;
DataBindEngine _engine;
bool _dependencySourcesChanged;
bool _isDynamic;
bool? _isDBNullValidForUpdate;
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.
//----------------------------------------------------------------------------
//
//
// Copyright (C) Microsoft Corporation. All rights reserved.
//
//
// Description: Defines PropertyPathWorker object, workhorse for CLR bindings
//
//---------------------------------------------------------------------------
using System;
using System.Collections;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
using System.Globalization;
using System.Text;
using System.Windows.Threading;
using System.Windows;
using System.Windows.Controls; // Validation
using System.Windows.Data;
using MS.Internal;
using MS.Internal.Hashing.PresentationFramework; // HashHelper
namespace MS.Internal.Data
{
internal sealed class PropertyPathWorker: IWeakEventListener
{
//-----------------------------------------------------
//
// Constructors
//
//-----------------------------------------------------
internal PropertyPathWorker(PropertyPath path)
: this(path, DataBindEngine.CurrentDataBindEngine)
{
}
internal PropertyPathWorker(PropertyPath path, ClrBindingWorker host, bool isDynamic, DataBindEngine engine)
: this(path, engine)
{
_host = host;
_isDynamic = isDynamic;
}
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);
}
}
//------------------------------------------------------
//
// Internal Properties
//
//-----------------------------------------------------
internal int Length { get { return _parent.Length; } }
internal PropertyPathStatus Status { get { return _status; } }
internal DependencyObject TreeContext
{
get { return BindingExpression.GetReference(_treeContext) as DependencyObject; }
set { _treeContext = BindingExpression.CreateReference(value); }
}
internal void SetTreeContext(WeakReference wr)
{
_treeContext = BindingExpression.CreateReference(wr);
}
internal bool IsDBNullValidForUpdate
{
get
{
if (!_isDBNullValidForUpdate.HasValue)
{
DetermineWhetherDBNullIsValid();
}
return _isDBNullValidForUpdate.Value;
}
}
internal object SourceItem
{
get
{
int level = Length-1;
return (level >= 0) ? GetItem(level) : null;
}
}
internal string SourcePropertyName
{
get
{
DependencyProperty dp;
PropertyInfo pi;
PropertyDescriptor pd;
int level = Length-1;
if (level < 0)
return null;
SetPropertyInfo(GetAccessor(level), out pi, out pd, out dp);
return (dp != null) ? dp.Name :
(pi != null) ? pi.Name :
(pd != null) ? pd.Name : null;
}
}
//------------------------------------------------------
//
// Internal Methods
//
//------------------------------------------------------
//------- common methods ------
internal object GetItem(int level)
{
return BindingExpression.GetReference(_arySVS[level].item);
}
internal object GetAccessor(int level)
{
return _arySVS[level].info;
}
internal object[] GetIndexerArguments(int level)
{
return _arySVS[level].args;
}
internal Type GetType(int level)
{
return _arySVS[level].type;
}
//------- target mode ------
// Set the context for the path. Use this method in "target" mode
// to connect the path to a rootItem for a short time:
// using (path.SetContext(myItem))
// {
// ... call target-mode convenience methods ...
// }
internal IDisposable SetContext(object rootItem)
{
if (_contextHelper == null)
_contextHelper = new ContextHelper(this);
_contextHelper.SetContext(rootItem);
return _contextHelper;
}
//------- source mode (should only be called by ClrBindingWorker) ------
internal void AttachToRootItem(object rootItem)
{
_rootItem = BindingExpression.CreateReference(rootItem);
UpdateSourceValueState(-1, null);
}
internal void DetachFromRootItem()
{
_rootItem = BindingExpression.NullDataItem;
UpdateSourceValueState(-1, null);
_rootItem = null;
}
internal object GetValue(object item, int level)
{
bool isExtendedTraceEnabled = IsExtendedTraceEnabled(TraceDataLevel.GetValue);
DependencyProperty dp;
PropertyInfo pi;
PropertyDescriptor pd;
object value = DependencyProperty.UnsetValue;
SetPropertyInfo(_arySVS[level].info, out pi, out pd, out dp);
switch (SVI[level].type)
{
case SourceValueType.Property:
if (pi != null)
{
value = pi.GetValue(item, null);
}
else if (pd != null)
{
bool indexerIsNext = (level+1 < SVI.Length && SVI[level+1].type == SourceValueType.Indexer);
value = Engine.GetValue(item, pd, indexerIsNext);
}
else if (dp != null)
{
DependencyObject d = (DependencyObject)item;
if (level != Length-1 || _host == null || _host.TransfersDefaultValue)
value = d.GetValue(dp);
else if (!Helper.HasDefaultValue(dp, d, d as FrameworkElement, d as FrameworkContentElement))
value = d.GetValue(dp);
else
value = BindingExpression.IgnoreDefaultValue;
}
break;
case SourceValueType.Indexer:
//
if (pi != null)
{
value = pi.GetValue(item,
BindingFlags.GetProperty, null,
_arySVS[level].args,
CultureInfo.InvariantCulture);
}
else
{
throw new NotSupportedException(SR.Get(SRID.IndexedPropDescNotImplemented));
}
break;
case SourceValueType.Direct:
value = item;
break;
}
if (isExtendedTraceEnabled)
{
object accessor = _arySVS[level].info;
if (accessor == DependencyProperty.UnsetValue)
accessor = null;
TraceData.Trace(TraceEventType.Warning,
TraceData.GetValue(
TraceData.Identify(_host.ParentBindingExpression),
level,
TraceData.Identify(item),
TraceData.IdentifyAccessor(accessor),
TraceData.Identify(value)));
}
return value;
}
internal void SetValue(object item, object value)
{
bool isExtendedTraceEnabled = IsExtendedTraceEnabled(TraceDataLevel.GetValue);
PropertyInfo pi;
PropertyDescriptor pd;
DependencyProperty dp;
int level = _arySVS.Length - 1;
SetPropertyInfo(_arySVS[level].info, out pi, out pd, out dp);
if (isExtendedTraceEnabled)
{
TraceData.Trace(TraceEventType.Warning,
TraceData.SetValue(
TraceData.Identify(_host.ParentBindingExpression),
level,
TraceData.Identify(item),
TraceData.IdentifyAccessor(_arySVS[level].info),
TraceData.Identify(value)));
}
switch (SVI[level].type)
{
case SourceValueType.Property:
if (pd != null)
{
pd.SetValue(item, value);
}
else if (pi != null)
{
pi.SetValue(item, value, null);
}
else if (dp != null)
{
((DependencyObject)item).SetValue(dp, value);
}
break;
case SourceValueType.Indexer:
//
if (pi != null)
{
pi.SetValue(item, value,
BindingFlags.SetProperty, null,
_arySVS[level].args,
CultureInfo.InvariantCulture);
}
else
{
throw new NotSupportedException(SR.Get(SRID.IndexedPropDescNotImplemented));
}
break;
}
}
internal object RawValue()
{
object rawValue = RawValue(Length-1);
if (rawValue == AsyncRequestPending)
rawValue = DependencyProperty.UnsetValue; // the real value will arrive later
return rawValue;
}
// Called by BE.UpdateTarget(). Re-fetch the value at each level.
// If there's a difference, simulate a property-change at that level.
internal void RefreshValue()
{
for (int k=1; k<_arySVS.Length; ++k)
{
object oldValue = BindingExpression.GetReference(_arySVS[k].item);
if (!Object.Equals(oldValue, RawValue(k-1)))
{
UpdateSourceValueState(k-1, null);
return;
}
}
UpdateSourceValueState(Length-1, null);
}
// return the source level where the change happened, or -1 if the
// change is irrelevant.
internal int LevelForPropertyChange(object item, string propertyName)
{
// This test must be thread-safe - it can get called on the "wrong" context.
// It's read-only (good). And if another thread changes the values it reads,
// the worst that can happen is to schedule a transfer operation needlessly -
// the operation itself won't do anything (since the test is repeated on the
// right thread).
bool isIndexer = propertyName == Binding.IndexerName;
for (int k=0; k<_arySVS.Length; ++k)
{
if (BindingExpression.GetReference(_arySVS[k].item) == item &&
(String.IsNullOrEmpty(propertyName) ||
(isIndexer && SVI[k].type == MS.Internal.Data.SourceValueType.Indexer) ||
String.Equals(SVI[k].propertyName, propertyName, StringComparison.OrdinalIgnoreCase)))
{
return k;
}
}
return -1;
}
internal void OnPropertyChangedAtLevel(int level)
{
UpdateSourceValueState(level, null);
}
internal void OnCurrentChanged(ICollectionView collectionView)
{
for (int k=0; k= 0 && SVI[level].type == SourceValueType.Property)
{
object item = GetItem(level);
IDataErrorInfo idei = item as IDataErrorInfo;
if (idei != null)
{
DependencyProperty dp;
PropertyInfo pi;
PropertyDescriptor pd;
SetPropertyInfo(GetAccessor(level), out pi, out pd, out dp);
string name = (dp != null) ? dp.Name :
(pi != null) ? pi.Name :
(pd != null) ? pd.Name : null;
string error;
if (name != null)
{
// get the data error information, if any, by calling idie[name].
// We do this in a paranoid way, even though indexers with
// string-valued arguments are not supposed to throw exceptions.
// PreSharp uses message numbers that the C# compiler doesn't know about.
// Disable the C# complaints, per the PreSharp documentation.
#pragma warning disable 1634, 1691
// PreSharp complains about catching NullReference (and other) exceptions.
// It doesn't recognize that IsCriticalException() handles these correctly.
#pragma warning disable 56500
try
{
error = idei[name];
}
catch (Exception ex)
{
if (CriticalExceptions.IsCriticalException(ex))
throw;
error = null;
if (TraceData.IsEnabled)
{
TraceData.Trace(TraceEventType.Error,
TraceData.DataErrorInfoFailed(
name,
item.GetType().FullName,
ex.GetType().FullName,
ex.Message),
bindingExpressionBase);
}
}
#pragma warning restore 56500
#pragma warning restore 1634, 1691
}
else
{
error = null;
}
if (!String.IsNullOrEmpty(error))
{
result = new ValidationError(DataErrorValidationRule.Instance,
bindingExpressionBase,
error,
null);
}
}
}
return result;
}
//-----------------------------------------------------
//
// Private Properties
//
//------------------------------------------------------
bool IsDynamic { get { return _isDynamic; } }
SourceValueInfo[] SVI { get { return _parent.SVI; } }
DataBindEngine Engine { get { return _engine; } }
//-----------------------------------------------------
//
// Private Methods
//
//-----------------------------------------------------
// fill in the SourceValueState with updated infomation, starting at level k+1.
// If view isn't null, also update the current item at level k.
private void UpdateSourceValueState(int k, ICollectionView collectionView)
{
UpdateSourceValueState(k, collectionView, BindingExpression.NullDataItem, false);
}
// fill in the SourceValueState with updated infomation, starting at level k+1.
// If view isn't null, also update the current item at level k.
private void UpdateSourceValueState(int k, ICollectionView collectionView, object newValue, bool isASubPropertyChange)
{
// give host a chance to shut down the binding if the target has
// gone away
DependencyObject target = null;
if (_host != null)
{
target = _host.CheckTarget();
if (_rootItem != BindingExpression.NullDataItem && target == null)
return;
}
int initialLevel = k;
object rawValue = null;
// optimistically assume the new value will fix previous path errors
_status = PropertyPathStatus.Active;
// prepare to collect changes to dependency sources
_dependencySourcesChanged = false;
// Update the current item at level k, if requested
if (collectionView != null)
{
Debug.Assert(0<=k && k<_arySVS.Length && _arySVS[k].collectionView == collectionView, "bad parameters to UpdateSourceValueState");
ReplaceItem(k, collectionView.CurrentItem, NoParent);
}
// update the remaining levels
for (++k; k<_arySVS.Length; ++k)
{
isASubPropertyChange = false; // sub-property changes only matter at the last level
ICollectionView oldCollectionView = _arySVS[k].collectionView;
// replace the item at level k using parent from level k-1
rawValue = (newValue == BindingExpression.NullDataItem) ? RawValue(k-1) : newValue;
newValue = BindingExpression.NullDataItem;
if (rawValue == AsyncRequestPending)
{
_status = PropertyPathStatus.AsyncRequestPending;
break; // we'll resume the loop after the request completes
}
ReplaceItem(k, BindingExpression.NullDataItem, rawValue);
// replace view, if necessary
ICollectionView newCollectionView = _arySVS[k].collectionView;
if (oldCollectionView != newCollectionView && _host != null)
{
_host.ReplaceCurrentItem(oldCollectionView, newCollectionView);
}
}
// notify binding about what happened
if (_host != null)
{
_host.NewValueAvailable(_dependencySourcesChanged, initialLevel < 0, isASubPropertyChange);
}
GC.KeepAlive(target); // keep target alive during changes (bug 956831)
}
// 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)
{
_arySVS[k].item = BindingExpression.ReplaceReference(_arySVS[k].item, newO);
if (parent == DependencyProperty.UnsetValue || parent == BindingExpression.NullDataItem)
_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)
{
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;
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);
}
}
}
void ReportNoInfoError(int k, object parent)
{
// report cannot find info. Ignore when in priority bindings.
if (TraceData.IsEnabled)
{
BindingExpression bindingExpression = (_host != null) ? _host.ParentBindingExpression : null;
if (bindingExpression == null || !bindingExpression.IsInPriorityBindingExpression)
{
SourceValueInfo svi = SVI[k];
string cs = (svi.type != SourceValueType.Indexer) ? svi.name : "[" + svi.name + "]";
string ps = TraceData.DescribeSourceObject(parent);
string os = (svi.drillIn == DrillIn.Always) ? "current item of collection" : "object";
// if the parent is null, the path error probably only means the
// data provider hasn't produced any data yet. When it does,
// the binding will try again and probably succeed. Give milder
// feedback for this special case, so as not to alarm users unduly.
if (parent == null)
{
TraceData.Trace(TraceEventType.Information, TraceData.NullItem(cs, os), bindingExpression);
}
// Similarly, if the parent is the NewItemPlaceholder.
else if (parent == CollectionView.NewItemPlaceholder)
{
TraceData.Trace(TraceEventType.Information, TraceData.PlaceholderItem(cs, os), bindingExpression);
}
else
{
TraceEventType traceType = (bindingExpression != null) ? bindingExpression.TraceLevel : TraceEventType.Error;
TraceData.Trace(traceType, TraceData.ClrReplaceItem(cs, ps, os), bindingExpression);
}
}
}
}
// 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 = (oldItem != null) ? oldItem.GetType() : null;
Type newType = (item != null) ? item.GetType() : null;
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;
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;
PropertyPath.DowncastAccessor(info, out dp, out pi1, out pd);
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;
}
if (pd != null)
{
sourceType = pd.PropertyType;
}
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, newType, name))
{
_parent.ReplaceIndexerByProperty(k, name);
goto case SourceValueType.Property;
}
}
args = new object[aryInfo.Length];
// find the matching indexer
MemberInfo[][] aryMembers= new MemberInfo[][]{ newType.GetDefaultMembers(), null };
if (item is IList)
aryMembers[1] = typeof(IList).GetDefaultMembers();
for (int ii=0; info==null && ii 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
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);
}
}
// convert the (string) argument names to types appropriate for use with
// the given property. Put the results in the args[] array. Return
// true if everything works.
private bool MatchIndexerParameters(PropertyInfo pi, IndexerParameterInfo[] aryInfo, object[] args)
{
ParameterInfo[] aryPI = pi.GetIndexParameters();
// must have the right number of parameters
if (aryPI.Length != aryInfo.Length)
return false;
// each parameter must be settable from user-specified type or from a string
for (int i=0; i= _arySVS.Length)
return DependencyProperty.UnsetValue;
object item = BindingExpression.GetReference(_arySVS[k].item);
object info = _arySVS[k].info;
// try to get the value, unless (a) binding is being detached,
// (b) no info - e.g. Nullable with no value, or (c) item expected
// but not present - e.g. currency moved off the end.
if (item != BindingExpression.NullDataItem && info != null && !(item == null && info != DependencyProperty.UnsetValue))
{
object o = DependencyProperty.UnsetValue;
DependencyProperty dp = info as DependencyProperty;
// if the binding is async, post a request to get the value
if (!(dp != null || SVI[k].type == SourceValueType.Direct))
{
if (_host != null && _host.AsyncGet(item, k))
{
_status = PropertyPathStatus.AsyncRequestPending;
return AsyncRequestPending;
}
}
// PreSharp uses message numbers that the C# compiler doesn't know about.
// Disable the C# complaints, per the PreSharp documentation.
#pragma warning disable 1634, 1691
// PreSharp complains about catching NullReference (and other) exceptions.
// It doesn't recognize that IsCriticalException() handles these correctly.
#pragma warning disable 56500
try
{
o = GetValue(item, k);
}
// Catch all exceptions. There is no app code on the stack,
// so the exception isn't actionable by the app.
// Yet we don't want to crash the app.
catch (Exception ex) // if error getting value, we will use fallback/default instead
{
if (CriticalExceptions.IsCriticalException(ex))
throw;
if (_host != null)
_host.ReportGetValueError(k, item, ex);
}
catch // non CLS compliant exception
{
if (_host != null)
_host.ReportGetValueError(k, item, new InvalidOperationException(SR.Get(SRID.NonCLSException, "GetValue")));
}
#pragma warning restore 56500
#pragma warning restore 1634, 1691
return o;
}
if (_host != null)
{
_host.ReportRawValueErrors(k, item, info);
}
return DependencyProperty.UnsetValue;
}
void SetPropertyInfo(object info, out PropertyInfo pi, out PropertyDescriptor pd, out DependencyProperty dp)
{
pi = null;
pd = null;
dp = info as DependencyProperty;
if (dp == null)
{
pi = info as PropertyInfo;
if (pi == null)
pd = info as PropertyDescriptor;
}
}
void CheckReadOnly(object item, object info)
{
PropertyInfo pi;
PropertyDescriptor pd;
DependencyProperty dp;
SetPropertyInfo(info, out pi, out pd, out dp);
if (pi != null)
{
if (pi.GetSetMethod() == null)
throw new InvalidOperationException(SR.Get(SRID.CannotWriteToReadOnly, item.GetType(), pi.Name));
}
else if (pd != null)
{
if (pd.IsReadOnly)
throw new InvalidOperationException(SR.Get(SRID.CannotWriteToReadOnly, item.GetType(), pd.Name));
}
else if (dp != null)
{
if (dp.ReadOnly)
throw new InvalidOperationException(SR.Get(SRID.CannotWriteToReadOnly, item.GetType(), dp.Name));
}
}
// see whether DBNull is a valid value for update, and cache the answer
void DetermineWhetherDBNullIsValid()
{
bool result = false;
PropertyDescriptor pd;
object item = GetItem(Length - 1);
if (item != null &&
item.GetType().Namespace.StartsWith("System.Data", StringComparison.Ordinal) &&
((pd = GetAccessor(Length-1) as PropertyDescriptor) != null))
{
result = DetermineWhetherDBNullIsValid(item, pd);
}
_isDBNullValidForUpdate = result;
}
// separate method, to avoid loading System.Data.dll until necessary
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
bool DetermineWhetherDBNullIsValid(object item, PropertyDescriptor pd)
{
System.Data.DataRowView drv = item as System.Data.DataRowView;
if (drv == null)
return false;
// this code was provided by the ADO team
System.Data.DataColumn column = drv.DataView.Table.Columns[pd.Name];
return (column != null) && column.AllowDBNull;
}
///
/// Handle events from the centralized event table
///
bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
{
if (IsExtendedTraceEnabled(TraceDataLevel.Events))
{
TraceData.Trace(TraceEventType.Warning,
TraceData.GotEvent(
TraceData.Identify(_host.ParentBindingExpression),
TraceData.IdentifyWeakEvent(managerType),
TraceData.Identify(sender)));
}
if (managerType == typeof(PropertyChangedEventManager))
{
PropertyChangedEventArgs pce = (PropertyChangedEventArgs)e;
_host.OnSourcePropertyChanged(sender, pce.PropertyName);
}
else if (managerType == typeof(ValueChangedEventManager))
{
ValueChangedEventArgs vce = (ValueChangedEventArgs)e;
_host.OnSourcePropertyChanged(sender, vce.PropertyDescriptor.Name);
}
else
{
return false; // unrecognized event
}
return true;
}
bool IsExtendedTraceEnabled(TraceDataLevel level)
{
if (_host != null)
{
return TraceData.IsExtendedTraceEnabled(_host.ParentBindingExpression, level);
}
else
{
return false;
}
}
//-----------------------------------------------------
//
// Private Classes
//
//------------------------------------------------------
// helper for setting context via the "using" pattern
class ContextHelper : IDisposable
{
PropertyPathWorker _owner;
public ContextHelper(PropertyPathWorker owner)
{
_owner = owner;
}
public void SetContext(object rootItem)
{
_owner.TreeContext = rootItem as DependencyObject;
_owner.AttachToRootItem(rootItem);
}
void IDisposable.Dispose()
{
_owner.DetachFromRootItem();
_owner.TreeContext = null;
}
}
//-----------------------------------------------------
//
// Private Enums, Structs, Constants
//
//------------------------------------------------------
struct SourceValueState
{
public ICollectionView collectionView;
public object item;
public object info; // PropertyInfo or PropertyDescriptor or DP
public Type type; // Type of the value (useful for Arrays)
public object[] args; // for indexers
}
static readonly Char[] s_comma = new Char[]{','};
static readonly Char[] s_dot = new Char[]{'.'};
static readonly object NoParent = new object();
static readonly object AsyncRequestPending = new object();
//------------------------------------------------------
//
// Private Fields
//
//-----------------------------------------------------
PropertyPath _parent;
PropertyPathStatus _status;
object _treeContext;
object _rootItem;
SourceValueState[] _arySVS;
ContextHelper _contextHelper;
ClrBindingWorker _host;
DataBindEngine _engine;
bool _dependencySourcesChanged;
bool _isDynamic;
bool? _isDBNullValidForUpdate;
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.
Link Menu

This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- ProfileManager.cs
- WebReference.cs
- CodeCompiler.cs
- XmlSchemaSubstitutionGroup.cs
- BoundingRectTracker.cs
- XmlSchemaElement.cs
- TableAdapterManagerNameHandler.cs
- LeftCellWrapper.cs
- BinaryWriter.cs
- PreviewKeyDownEventArgs.cs
- TableLayoutStyle.cs
- SafeCancelMibChangeNotify.cs
- MethodRental.cs
- TextChange.cs
- WebEvents.cs
- Geometry3D.cs
- Misc.cs
- FormViewModeEventArgs.cs
- PropertyTab.cs
- TextRange.cs
- ADMembershipProvider.cs
- ReadOnlyAttribute.cs
- QilCloneVisitor.cs
- SimpleApplicationHost.cs
- DoubleUtil.cs
- Command.cs
- DefaultAssemblyResolver.cs
- FigureHelper.cs
- AppSettingsExpressionEditor.cs
- SynchronizedMessageSource.cs
- GlyphRunDrawing.cs
- BaseConfigurationRecord.cs
- FreezableDefaultValueFactory.cs
- PropertyPath.cs
- RequestCache.cs
- CalendarButton.cs
- CodeTypeDeclaration.cs
- XsltSettings.cs
- CommonObjectSecurity.cs
- CacheSection.cs
- DataSourceDescriptorCollection.cs
- CacheOutputQuery.cs
- TableLayoutRowStyleCollection.cs
- ADConnectionHelper.cs
- DesignerSerializerAttribute.cs
- MetadataSerializer.cs
- ObjectListCommand.cs
- Directory.cs
- TextEmbeddedObject.cs
- XPathNavigatorException.cs
- CompositeFontInfo.cs
- WebExceptionStatus.cs
- CodeArrayCreateExpression.cs
- ContextMenuStrip.cs
- ZipIOExtraFieldPaddingElement.cs
- xamlnodes.cs
- SuppressMergeCheckAttribute.cs
- URLString.cs
- ActivityDesignerLayoutSerializers.cs
- RenderDataDrawingContext.cs
- Base64Decoder.cs
- ExpressionBinding.cs
- GACIdentityPermission.cs
- SymmetricKey.cs
- DataGridViewCellContextMenuStripNeededEventArgs.cs
- SmtpClient.cs
- LoadedOrUnloadedOperation.cs
- WindowsToolbar.cs
- PartialList.cs
- NamespaceInfo.cs
- _ChunkParse.cs
- SingletonInstanceContextProvider.cs
- QuaternionRotation3D.cs
- WebContext.cs
- TypeFieldSchema.cs
- DrawingState.cs
- ZipPackage.cs
- ContentPlaceHolder.cs
- ClientConfigPaths.cs
- TextClipboardData.cs
- SchemaConstraints.cs
- DeleteHelper.cs
- Stack.cs
- EndOfStreamException.cs
- MimeParameterWriter.cs
- PathNode.cs
- DataGridViewBindingCompleteEventArgs.cs
- TableCellCollection.cs
- GeometryHitTestResult.cs
- TraceXPathNavigator.cs
- XmlChildEnumerator.cs
- ServiceObjectContainer.cs
- PropertyIDSet.cs
- Fonts.cs
- DataList.cs
- NavigationWindowAutomationPeer.cs
- SchemaMapping.cs
- SqlClientFactory.cs
- FileDataSourceCache.cs
- HttpValueCollection.cs