|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
//
// Description: Defines BindingExpression object, the run-time instance of data binding.
//
// See spec at Data Binding.mht
//
using System.Collections;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.Windows.Threading;
using System.Threading;
using System.Windows.Controls;
using System.Windows.Markup;
using MS.Internal;
using MS.Internal.Data;
namespace System.Windows.Data
{
/// <summary>
/// called whenever any exception is encountered when trying to update
/// the value to the source. The application author can provide its own
/// handler for handling exceptions here. If the delegate returns
/// null - dont throw an error or provide a ValidationError.
/// Exception - returns the exception itself, we will fire the exception using Async exception model.
/// ValidationError - it will set itself as the BindingInError and add it to the elements Validation errors.
/// </summary>
public delegate object UpdateSourceExceptionFilterCallback(object bindExpression, Exception exception);
/// <summary>
/// Describes a single run-time instance of data binding, binding a target
/// (element, DependencyProperty) to a source (object, property, XML node)
/// </summary>
public sealed class BindingExpression : BindingExpressionBase, IDataBindEngineClient, IWeakEventListener
{
//------------------------------------------------------
//
// Enums
//
//------------------------------------------------------
internal enum SourceType { Unknown, CLR, XML }
private enum AttachAttempt { First, Again, Last }
//------------------------------------------------------
//
// Constructors
//
//------------------------------------------------------
private BindingExpression(Binding binding, BindingExpressionBase owner)
: base(binding, owner)
{
UseDefaultValueConverter = (ParentBinding.Converter == null);
if (TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.ShowPath))
{
PropertyPath pp = binding.Path;
string path = (pp != null) ? pp.Path : String.Empty;
if (String.IsNullOrEmpty(binding.XPath))
{
TraceData.TraceAndNotifyWithNoParameters(TraceEventType.Warning,
TraceData.BindingPath(
TraceData.Identify(path)),
this);
}
else
{
TraceData.TraceAndNotifyWithNoParameters(TraceEventType.Warning,
TraceData.BindingXPathAndPath(
TraceData.Identify(binding.XPath),
TraceData.Identify(path)),
this);
}
}
}
//------------------------------------------------------
//
// Interfaces
//
//------------------------------------------------------
void IDataBindEngineClient.TransferValue()
{
TransferValue();
}
void IDataBindEngineClient.UpdateValue()
{
UpdateValue();
}
bool IDataBindEngineClient.AttachToContext(bool lastChance)
{
AttachToContext(lastChance ? AttachAttempt.Last : AttachAttempt.Again);
return (StatusInternal != BindingStatusInternal.Unattached);
}
void IDataBindEngineClient.VerifySourceReference(bool lastChance)
{
DependencyObject target = TargetElement;
if (target == null)
return; // binding was detached since scheduling this task
ObjectRef sourceRef = ParentBinding.SourceReference;
DependencyObject mentor = !UsingMentor ? target : Helper.FindMentor(target);
ObjectRefArgs args = new ObjectRefArgs() {
ResolveNamesInTemplate = ResolveNamesInTemplate,
};
object source = sourceRef.GetDataObject(mentor, args);
if (source != DataItem)
{
// the source reference resolves differently from before, so
// re-attach to the tree context
AttachToContext(lastChance ? AttachAttempt.Last : AttachAttempt.Again);
}
}
void IDataBindEngineClient.OnTargetUpdated()
{
OnTargetUpdated();
}
DependencyObject IDataBindEngineClient.TargetElement
{
get { return !UsingMentor ? TargetElement : Helper.FindMentor(TargetElement); }
}
//------------------------------------------------------
//
// Public Properties
//
//------------------------------------------------------
/// <summary> Binding from which this expression was created </summary>
public Binding ParentBinding { get { return (Binding)ParentBindingBase; } }
/// <summary>The data item actually used by this BindingExpression</summary>
public object DataItem { get { return GetReference(_dataItem); } }
/// <summary> The object that gets changed during UpdateSource </summary>
public object ResolvedSource { get { return SourceItem; } }
/// <summary> The name of the property that gets changed during UpdateSource </summary>
public string ResolvedSourcePropertyName { get { return SourcePropertyName; } }
/// <summary>The data source actually used by this BindingExpression</summary>
internal object DataSource
{
get
{
DependencyObject target = TargetElement;
if (target == null)
return null;
// if we're using DataContext, find the source for the DataContext
if (_ctxElement != null)
return GetDataSourceForDataContext(ContextElement);
// otherwise use the explicit source
ObjectRef or = ParentBinding.SourceReference;
return or.GetObject(target, new ObjectRefArgs());
}
}
//------------------------------------------------------
//
// Public Methods
//
//------------------------------------------------------
/// <summary> Send the current value back to the source </summary>
/// <remarks> Does nothing when binding's Mode is not TwoWay or OneWayToSource </remarks>
public override void UpdateSource()
{
if (IsDetached)
throw new InvalidOperationException(SR.BindingExpressionIsDetached);
NeedsUpdate = true; // force update
Update(); // update synchronously
}
/// <summary> Force a data transfer from source to target </summary>
public override void UpdateTarget()
{
if (IsDetached)
throw new InvalidOperationException(SR.BindingExpressionIsDetached);
if (Worker != null)
{
Worker.RefreshValue(); // calls TransferValue
}
}
#region Expression overrides
/// <summary>
/// Notification that a Dependent that this Expression established has
/// been invalidated as a result of a Source invalidation
/// </summary>
/// <param name="d">DependencyObject that was invalidated</param>
/// <param name="args">Changed event args for the property that was invalidated</param>
internal override void OnPropertyInvalidation(DependencyObject d, DependencyPropertyChangedEventArgs args)
{
ArgumentNullException.ThrowIfNull(d);
DependencyProperty dp = args.Property;
if (dp == null)
throw new InvalidOperationException(SR.Format(SR.ArgumentPropertyMustNotBeNull, "Property", "args"));
// ignore irrelevant notifications. This test must happen before any marshalling.
bool relevant = !IgnoreSourcePropertyChange;
if (dp == FrameworkElement.DataContextProperty && d == ContextElement)
{
relevant = true; // changes from context element are always relevant
}
else if (dp == CollectionViewSource.ViewProperty && d == CollectionViewSource)
{
relevant = true; // changes from the CollectionViewSource are always relevant
}
else if (dp == FrameworkElement.LanguageProperty && UsesLanguage && d == TargetElement)
{
relevant = true; // changes from target's Language are always relevant
}
else if (relevant)
{
relevant = (Worker != null) && (Worker.UsesDependencyProperty(d, dp));
}
if (!relevant)
return;
base.OnPropertyInvalidation(d, args);
}
#endregion Expression overrides
//------------------------------------------------------
//
// Protected Methods
//
//------------------------------------------------------
/// <summary>
/// Invalidate the given child expression.
/// </summary>
internal override void InvalidateChild(BindingExpressionBase bindingExpression)
{
// BindingExpression does not support child bindings
}
/// <summary>
/// Change the dependency sources for the given child expression.
/// </summary>
internal override void ChangeSourcesForChild(BindingExpressionBase bindingExpression, WeakDependencySource[] newSources)
{
// BindingExpression does not support child bindings
}
/// <summary>
/// Replace the given child expression with a new one.
/// </summary>
internal override void ReplaceChild(BindingExpressionBase bindingExpression)
{
// BindingExpression does not support child bindings
}
// register the leaf bindings with the binding group
internal override void UpdateBindingGroup(BindingGroup bg)
{
bg.UpdateTable(this);
}
//------------------------------------------------------
//
// Internal Properties
//
//------------------------------------------------------
// The ContextElement is the DependencyObject (if any) whose DataContext
// is used as the starting point for the evaluation of the BindingExpression Path.
// We should not store a strong reference to the context element,
// for the same reasons as mentioned above for TargetElement. Instead,
// we store a weak reference. Callers should be prepared for the case
// ContextElement==null, which is different from _ctxElement==null. The
// former means the BindingExpression uses a context element, but that element has
// been GC'd; the latter means that the BindingExpression does not use a context
// element.
internal DependencyObject ContextElement
{
get
{
if (_ctxElement != null)
return _ctxElement.Target as DependencyObject;
else
return null;
}
}
// The CollectionViewSource is the source object, as a CollectionViewSource.
internal CollectionViewSource CollectionViewSource
{
get
{
WeakReference wr = (WeakReference)GetValue(Feature.CollectionViewSource, null);
return (wr == null) ? null : (CollectionViewSource)wr.Target;
}
set
{
if (value == null)
ClearValue(Feature.CollectionViewSource);
else
SetValue(Feature.CollectionViewSource, new WeakReference(value));
}
}
/// <summary> True if this binding expression should ignore changes from the source </summary>
internal bool IgnoreSourcePropertyChange
{
get
{
if (IsTransferPending || IsInUpdate)
return true;
return false;
}
}
internal PropertyPath Path
{
get { return ParentBinding.Path; }
}
internal IValueConverter Converter
{
get { return (IValueConverter)GetValue(Feature.Converter, null); }
set { SetValue(Feature.Converter, value, null); }
}
// MultiBinding looks at this to find out what type its MultiValueConverter should
// convert back to, when this BindingExpression is not using a user-specified converter.
internal Type ConverterSourceType
{
get { return _sourceType; }
}
// the item whose property changes when we UpdateSource
internal object SourceItem
{
get { return (Worker != null) ? Worker.SourceItem : null; }
}
// the name of the property that changes when we UpdateSource
internal string SourcePropertyName
{
get { return (Worker != null) ? Worker.SourcePropertyName : null; }
}
// the value of the source property
internal object SourceValue
{
get { return (Worker != null) ? Worker.RawValue() : DependencyProperty.UnsetValue; }
}
internal override bool IsParentBindingUpdateTriggerDefault
{
get { return (ParentBinding.UpdateSourceTrigger == UpdateSourceTrigger.Default); }
}
internal override bool IsDisconnected
{
get { return GetReference(_dataItem) == DisconnectedItem; }
}
//------------------------------------------------------
//
// Internal Methods
//
//------------------------------------------------------
// Create a new BindingExpression from the given Bind description
internal static BindingExpression CreateBindingExpression(DependencyObject d,
DependencyProperty dp,
Binding binding,
BindingExpressionBase parent)
{
FrameworkPropertyMetadata fwMetaData = dp.GetMetadata(d.DependencyObjectType) as FrameworkPropertyMetadata;
if ((fwMetaData != null && !fwMetaData.IsDataBindingAllowed) || dp.ReadOnly)
throw new ArgumentException(SR.Format(SR.PropertyNotBindable, dp.Name), "dp");
// create the BindingExpression
BindingExpression bindExpr = new BindingExpression(binding, parent);
bindExpr.ResolvePropertyDefaultSettings(binding.Mode, binding.UpdateSourceTrigger, fwMetaData);
// Two-way Binding with an empty path makes no sense
if (bindExpr.IsReflective && binding.XPath == null &&
(binding.Path == null || String.IsNullOrEmpty(binding.Path.Path)))
throw new InvalidOperationException(SR.TwoWayBindingNeedsPath);
return bindExpr;
}
// Note: For Nullable types, DefaultValueConverter is created for the inner type of the Nullable.
// Nullable "Drill-down" service is not provided for user provided Converters.
internal void SetupDefaultValueConverter(Type type)
{
if (!UseDefaultValueConverter)
return;
if (IsInMultiBindingExpression)
{
Converter = null;
_sourceType = type;
}
else if (type != null && type != _sourceType)
{
_sourceType = type;
IValueConverter converter = Engine.GetDefaultValueConverter(type,
TargetProperty.PropertyType, IsReflective);
// null converter means failure to create one
if (converter == null && TraceData.IsEnabled)
{
TraceData.TraceAndNotifyWithNoParameters(TraceEventType.Error,
TraceData.CannotCreateDefaultValueConverter(
type,
TargetProperty.PropertyType,
(IsReflective ? "two-way" : "one-way")),
this );
}
if (converter == DefaultValueConverter.ValueConverterNotNeeded)
{
converter = null; // everyone else takes null for an answer.
}
Converter = converter;
}
}
// return true if DataContext is set locally (not inherited) on DependencyObject
internal static bool HasLocalDataContext(DependencyObject d)
{
bool hasModifiers;
BaseValueSourceInternal valueSource = d.GetValueSource(FrameworkElement.DataContextProperty, null, out hasModifiers);
return (valueSource != BaseValueSourceInternal.Inherited) &&
(valueSource != BaseValueSourceInternal.Default || hasModifiers);
}
//------------------------------------------------------
//
// Private Properties
//
//------------------------------------------------------
private bool CanActivate
{
get { return StatusInternal != BindingStatusInternal.Unattached; }
}
private BindingWorker Worker { get { return _worker; } }
private DynamicValueConverter DynamicConverter
{
get
{
if (!HasValue(Feature.DynamicConverter))
{
Invariant.Assert(Worker != null);
// pass along the static source and target types to find same DefaultValueConverter as SetupDefaultValueConverter
SetValue(Feature.DynamicConverter, new DynamicValueConverter(IsReflective, Worker.SourcePropertyType, Worker.TargetPropertyType), null);
}
return (DynamicValueConverter)GetValue(Feature.DynamicConverter, null);
}
}
private DataSourceProvider DataProvider
{
get { return (DataSourceProvider)GetValue(Feature.DataProvider, null); }
set { SetValue(Feature.DataProvider, value, null); }
}
//------------------------------------------------------
//
// Private Methods
//
//------------------------------------------------------
#region Attachment
/// <summary>
/// Attach the binding expression to the given target object and property.
/// </summary>
internal override bool AttachOverride(DependencyObject target, DependencyProperty dp)
{
if (!base.AttachOverride(target, dp))
return false;
// listen for InheritanceContext change (if target is mentored)
if (ParentBinding.SourceReference == null || ParentBinding.SourceReference.UsesMentor)
{
DependencyObject mentor = Helper.FindMentor(target);
if (mentor != target)
{
InheritanceContextChangedEventManager.AddHandler(target, OnInheritanceContextChanged);
UsingMentor = true;
if (TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.Attach))
{
TraceData.TraceAndNotifyWithNoParameters(TraceEventType.Warning,
TraceData.UseMentor(
TraceData.Identify(this),
TraceData.Identify(mentor)),
this);
}
}
}
// listen for lost focus
if (IsUpdateOnLostFocus)
{
Invariant.Assert(!IsInMultiBindingExpression, "Source BindingExpressions of a MultiBindingExpression should never be UpdateOnLostFocus.");
LostFocusEventManager.AddHandler(target, OnLostFocus);
}
// attach to things that need tree context. Do it synchronously
// if possible, otherwise post a task. This gives the parser et al.
// a chance to assemble the tree before we start walking it.
AttachToContext(AttachAttempt.First);
if (StatusInternal == BindingStatusInternal.Unattached)
{
Engine.AddTask(this, TaskOps.AttachToContext);
if (TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.AttachToContext))
{
TraceData.TraceAndNotifyWithNoParameters(TraceEventType.Warning,
TraceData.DeferAttachToContext(
TraceData.Identify(this)),
this);
}
}
GC.KeepAlive(target); // keep target alive during activation (bug 956831)
return true;
}
/// <summary>
/// Detach the binding expression from its target object and property.
/// </summary>
internal override void DetachOverride()
{
Deactivate();
DetachFromContext();
// detach from target element
DependencyObject target = TargetElement;
if (target != null && IsUpdateOnLostFocus)
{
LostFocusEventManager.RemoveHandler(target, OnLostFocus);
}
// detach from INDEI.ErrorsChanged event
if (ValidatesOnNotifyDataErrors)
{
WeakReference dataErrorWR = (WeakReference)GetValue(Feature.DataErrorValue, null);
INotifyDataErrorInfo dataErrorValue = (dataErrorWR == null) ? null : dataErrorWR.Target as INotifyDataErrorInfo;
if (dataErrorValue != null)
{
ErrorsChangedEventManager.RemoveHandler(dataErrorValue, OnErrorsChanged);
SetValue(Feature.DataErrorValue, null, null);
}
}
ChangeValue(DependencyProperty.UnsetValue, false);
base.DetachOverride();
}
// try to get information from the tree context (parent, root, etc.)
// If everything succeeds, activate the binding.
// If anything fails in a way that might succeed after further layout,
// just return (with status == Unattached). The binding engine will try
// again later. For hard failures, set an error status; no more chances.
// During the "last chance" attempt, treat all failures as "hard".
void AttachToContext(AttachAttempt attempt)
{
// if the target has been GC'd, just give up
DependencyObject target = TargetElement;
if (target == null)
return; // status will be Detached
bool isExtendedTraceEnabled = TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.AttachToContext);
bool traceObjectRef = TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.SourceLookup);
// certain features should never be tried on the first attempt, as
// they certainly require at least one layout pass
if (attempt == AttachAttempt.First)
{
// relative source with ancestor lookup
ObjectRef or = ParentBinding.SourceReference;
if (or != null && or.TreeContextIsRequired(target))
{
if (isExtendedTraceEnabled)
{
TraceData.TraceAndNotifyWithNoParameters(TraceEventType.Warning,
TraceData.SourceRequiresTreeContext(
TraceData.Identify(this),
or.Identify()),
this);
}
return;
}
}
bool lastChance = (attempt == AttachAttempt.Last);
if (isExtendedTraceEnabled)
{
TraceData.TraceAndNotifyWithNoParameters(TraceEventType.Warning,
TraceData.AttachToContext(
TraceData.Identify(this),
lastChance ? " (last chance)" : String.Empty),
this);
}
// if the path has unresolved type names, the parser needs namesapce
// information to resolve them. See XmlTypeMapper.GetTypeFromName.
// Ignore this requirement during the last chance, and just let
// GetTypeFromName fail if it wants to.
if (!lastChance && ParentBinding.TreeContextIsRequired)
{
if (target.GetValue(XmlAttributeProperties.XmlnsDictionaryProperty) == null ||
target.GetValue(XmlAttributeProperties.XmlNamespaceMapsProperty) == null)
{
if (isExtendedTraceEnabled)
{
TraceData.TraceAndNotifyWithNoParameters(TraceEventType.Warning,
TraceData.PathRequiresTreeContext(
TraceData.Identify(this),
ParentBinding.Path.Path),
this);
}
return;
}
}
// if the binding uses a mentor, check that it exists
DependencyObject mentor = !UsingMentor ? target : Helper.FindMentor(target);
if (mentor == null)
{
if (isExtendedTraceEnabled)
{
TraceData.TraceAndNotifyWithNoParameters(TraceEventType.Warning,
TraceData.NoMentorExtended(
TraceData.Identify(this)),
this);
}
if (lastChance)
{
SetStatus(BindingStatusInternal.PathError);
if (TraceData.IsEnabled)
{
TraceData.TraceAndNotify(TraceEventType.Error, TraceData.NoMentor, this);
}
}
return;
}
// determine the element whose DataContext governs this BindingExpression
DependencyObject contextElement = null; // no context element
bool contextElementFound = true;
if (ParentBinding.SourceReference == null)
{
contextElement = mentor; // usually the mentor/target element
// special cases:
// 1. if target property is DataContext, use the target's parent.
// This enables <X DataContext="{Binding...}"/> and
// 2. if the target is ContentPresenter and the target property
// is Content, use the parent. This enables
// <ContentPresenter Content="{Binding...}"/>
// 3. if target is CVS, and its inheritance context was set
// via DataContext, use the mentor's parent. This enables
// <X.DataContext> <CollectionViewSource Source={Binding ...}/>
CollectionViewSource cvs;
if (TargetProperty == FrameworkElement.DataContextProperty ||
(TargetProperty == ContentPresenter.ContentProperty &&
target is ContentPresenter) ||
(UsingMentor &&
(cvs = target as CollectionViewSource) != null &&
cvs.PropertyForInheritanceContext == FrameworkElement.DataContextProperty)
)
{
contextElement = FrameworkElement.GetFrameworkParent(contextElement);
contextElementFound = (contextElement != null);
}
}
else
{
RelativeObjectRef ror = ParentBinding.SourceReference as RelativeObjectRef;
if (ror != null && ror.ReturnsDataContext)
{
object o = ror.GetObject(mentor, new ObjectRefArgs() { IsTracing = traceObjectRef});
contextElement = o as DependencyObject; // ref to another element's DataContext
contextElementFound = (o != DependencyProperty.UnsetValue);
}
}
if (isExtendedTraceEnabled)
{
TraceData.TraceAndNotifyWithNoParameters(TraceEventType.Warning,
TraceData.ContextElement(
TraceData.Identify(this),
TraceData.Identify(contextElement),
contextElementFound ? "OK" : "error"),
this);
}
// if we need a context element, check that we found it
if (!contextElementFound)
{
if (lastChance)
{
SetStatus(BindingStatusInternal.PathError);
if (TraceData.IsEnabled)
{
TraceData.TraceAndNotify(TraceEventType.Error, TraceData.NoDataContext, this);
}
}
return;
}
// determine the source object, from which the path evaluation starts
object source;
ObjectRef sourceRef;
if (contextElement != null)
{
source = contextElement.GetValue(FrameworkElement.DataContextProperty);
// if the data context is default null, try again later; future
// layout may change the inherited value.
// Ignore this requirement during the last chance, and just let
// the binding to null DataContext proceed.
if (source == null && !lastChance && !HasLocalDataContext(contextElement))
{
if (isExtendedTraceEnabled)
{
TraceData.TraceAndNotifyWithNoParameters(TraceEventType.Warning,
TraceData.NullDataContext(
TraceData.Identify(this)),
this);
}
return;
}
}
else if ((sourceRef = ParentBinding.SourceReference) != null)
{
ObjectRefArgs args = new ObjectRefArgs() {
IsTracing = traceObjectRef,
ResolveNamesInTemplate = ResolveNamesInTemplate,
};
source = sourceRef.GetDataObject(mentor, args);
// check that the source could be found
if (source == DependencyProperty.UnsetValue)
{
if (lastChance)
{
SetStatus(BindingStatusInternal.PathError);
if (TraceData.IsEnabled)
{
TraceData.TraceAndNotify(TraceLevel, TraceData.NoSource(sourceRef), this);
}
}
return;
}
else if (!lastChance && args.NameResolvedInOuterScope)
{
// if ElementName resolved in an outer scope, it's possible
// that future work might add the name to an inner scope.
// Schedule a task to check this.
Engine.AddTask(this, TaskOps.VerifySourceReference);
}
}
else
{
// we get here only if we need ambient data context, but there
// is no context element. E.g. binding the DataContext property
// of an element with no parent. Just use null.
source = null;
}
// if we get this far, all the ingredients for a successful binding
// are present. Remember what we've found and activate the binding.
if (contextElement != null)
_ctxElement = new WeakReference(contextElement);
// attach to context element
ChangeWorkerSources(null, 0);
if (!UseDefaultValueConverter)
{
Converter = ParentBinding.Converter;
if (Converter == null)
{
throw new InvalidOperationException(SR.MissingValueConverter); // report instead of throw?
}
}
// join the right binding group (if any)
JoinBindingGroup(IsReflective, contextElement);
SetStatus(BindingStatusInternal.Inactive);
// inner BindingExpressions of PriorityBindingExpressions may not need to be activated
if (IsInPriorityBindingExpression)
ParentPriorityBindingExpression.InvalidateChild(this);
else // singular BindingExpressions and those in MultiBindingExpressions should always activate
Activate(source);
GC.KeepAlive(target); // keep target alive during activation (bug 956831)
}
// Detach from things that may require tree context
private void DetachFromContext()
{
// detach from data source
if (HasValue(Feature.DataProvider))
{
DataChangedEventManager.RemoveHandler(DataProvider, OnDataChanged);
}
if (!UseDefaultValueConverter)
{
Converter = null;
}
if (!IsInBindingExpressionCollection)
ChangeSources(null);
if (UsingMentor)
{
DependencyObject target = TargetElement;
if (target != null)
InheritanceContextChangedEventManager.RemoveHandler(target, OnInheritanceContextChanged);
}
_ctxElement = null;
}
#endregion Attachment
#region Activation
// Activate the BindingExpression, if necessary and possible
internal override void Activate()
{
if (!CanActivate)
return;
if (_ctxElement == null)
{
// only activate once if there's an explicit source
if (StatusInternal == BindingStatusInternal.Inactive)
{
DependencyObject target = TargetElement;
if (target != null)
{
if (UsingMentor)
{
target = Helper.FindMentor(target);
if (target == null)
{
// mentor is not available
SetStatus(BindingStatusInternal.PathError);
if (TraceData.IsEnabled)
{
TraceData.TraceAndNotify(TraceEventType.Error, TraceData.NoMentor, this);
}
return;
}
}
Activate(ParentBinding.SourceReference.GetDataObject(target,
new ObjectRefArgs() { ResolveNamesInTemplate = ResolveNamesInTemplate }));
}
}
}
else
{
DependencyObject contextElement = ContextElement;
if (contextElement == null)
{
// context element has been GC'd, or unavailable (e.g. no mentor)
SetStatus(BindingStatusInternal.PathError);
if (TraceData.IsEnabled)
{
TraceData.TraceAndNotify(TraceEventType.Error, TraceData.NoDataContext, this);
}
return;
}
object item = contextElement.GetValue(FrameworkElement.DataContextProperty);
// if binding inactive or the data item has changed, (re-)activate
if (StatusInternal == BindingStatusInternal.Inactive || !System.Windows.Controls.ItemsControl.EqualsEx(item, DataItem))
{
Activate(item);
}
}
}
internal void Activate(object item)
{
DependencyObject target = TargetElement;
if (target == null)
return;
if (item == DisconnectedItem)
{
// disconnect from the former item, with the least acceptable impact
Disconnect();
return;
}
bool isExtendedTraceEnabled = TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.Activate);
Deactivate();
// apply magic (for CVS, DSP, etc.), unless asked not to
if (!ParentBinding.BindsDirectlyToSource)
{
CollectionViewSource cvs = item as CollectionViewSource;
this.CollectionViewSource = cvs;
if (cvs != null)
{
item = cvs.CollectionView;
// the CVS is one of our implicit sources
ChangeWorkerSources(null, 0);
if (isExtendedTraceEnabled)
{
TraceData.TraceAndNotifyWithNoParameters(TraceEventType.Warning,
TraceData.UseCVS(
TraceData.Identify(this),
TraceData.Identify(cvs)),
this);
}
}
else
{
// when the source is DataSourceProvider, use its data instead
item = DereferenceDataProvider(item);
}
}
_dataItem = CreateReference(item);
if (isExtendedTraceEnabled)
{
TraceData.TraceAndNotifyWithNoParameters(TraceEventType.Warning,
TraceData.ActivateItem(
TraceData.Identify(this),
TraceData.Identify(item)),
this);
}
if (Worker == null)
CreateWorker();
// mark the BindingExpression active
SetStatus(BindingStatusInternal.Active);
// attach to data item (may set error status)
Worker.AttachDataItem();
// BUG: allow transfers now; see remark inside BindOperations.SetBinding and BindingExpression.TransferValue
//ClearFlag(BindingFlags.iInUpdate);
// initial transfer
bool initialTransferIsUpdate = IsOneWayToSource;
object currentValue;
if (ShouldUpdateWithCurrentValue(target, out currentValue))
{
initialTransferIsUpdate = true;
ChangeValue(currentValue, /*notify*/false);
NeedsUpdate = true;
}
if (!initialTransferIsUpdate)
{
ValidationError error;
object value = GetInitialValue(target, out error);
bool useValueFromBindingGroup = (value == NullDataItem);
if (!useValueFromBindingGroup)
{
TransferValue(value, false);
}
if (error != null)
{
UpdateValidationError(error, useValueFromBindingGroup);
}
}
else if (!IsInMultiBindingExpression) // MultiBinding initiates its own update
{
UpdateValue();
}
GC.KeepAlive(target); // keep target alive during activation (bug 956831)
}
// return the value for the initial data transfer. Specifically:
// normal case - return UnsetValue (we'll get the value from the source object)
// valid proposed value - return the proposed value
// invalid proposed value - two subcases:
// one-way binding - return default/fallback value
// two-way binding - retrun NullDataItem (meaning "don't transfer")
//
// In both subcases, adopt the validation errors discovered earlier.
// In the two-way subcase, instead of a source-to-target transfer, we set the
// the target property to the (saved) raw proposed value, as if the user
// had edited this property.
object GetInitialValue(DependencyObject target, out ValidationError error)
{
object proposedValue;
// find the binding group this binding would join if it were two-way (even if it isn't)
BindingGroup bindingGroup = RootBindingExpression.FindBindingGroup(true, ContextElement);
BindingGroup.ProposedValueEntry entry;
// get the proposed value from the binding group
if (bindingGroup == null ||
(entry = bindingGroup.GetProposedValueEntry(SourceItem, SourcePropertyName)) == null)
{
// no proposed value
error = null;
proposedValue = DependencyProperty.UnsetValue;
}
else
{
// adopt the validation error (possibly null)
error = entry.ValidationError;
if (IsReflective && TargetProperty.IsValidValue(entry.RawValue))
{
// two-way binding - set the target property directly
target.SetValue(TargetProperty, entry.RawValue);
proposedValue = NullDataItem;
bindingGroup.RemoveProposedValueEntry(entry);
}
else if (entry.ConvertedValue == DependencyProperty.UnsetValue)
{
// invalid proposed value - use fallback/default
proposedValue = UseFallbackValue();
}
else
{
// valid proposed value
proposedValue = entry.ConvertedValue;
}
// if this binding didn't take over responsibility for the proposed
// value, add it to the list of bindings using the proposed value
if (proposedValue != NullDataItem)
{
bindingGroup.AddBindingForProposedValue(this, SourceItem, SourcePropertyName);
}
}
return proposedValue;
}
internal override void Deactivate()
{
// inactive BindingExpressions don't need any more work
if (StatusInternal == BindingStatusInternal.Inactive)
return;
if (TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.Activate))
{
TraceData.TraceAndNotifyWithNoParameters(TraceEventType.Warning,
TraceData.Deactivate(
TraceData.Identify(this)),
this);
}
// stop transfers
CancelPendingTasks();
// detach from data item
if (Worker != null)
Worker.DetachDataItem();
// restore default value, in case source/converter fail to provide a good value
ChangeValue(DefaultValueObject, false);
// don't keep a handle to old data item if the BindingExpression is inactive
_dataItem = null;
SetStatus(BindingStatusInternal.Inactive);
}
// disconnect from the former item, with the least acceptable impact.
// This arises when a container is removed from an ItemsControl.
internal override void Disconnect()
{
// disconnect this binding from its former item
_dataItem = CreateReference(DisconnectedItem);
if (Worker == null)
return;
Worker.AttachDataItem();
base.Disconnect();
}
// if the root item is a DataSourceProvider, use its Data instead
// and listen for DataChanged events. (Unless overridden explicitly
// by the BindsDirectlyToSource property).
private object DereferenceDataProvider(object item)
{
DataSourceProvider newDataProvider = item as DataSourceProvider;
DataSourceProvider oldDataProvider = DataProvider;
if (newDataProvider != oldDataProvider)
{
// we have a new data provider - retarget the event handler
if (oldDataProvider != null)
{
DataChangedEventManager.RemoveHandler(oldDataProvider, OnDataChanged);
}
DataProvider = newDataProvider;
oldDataProvider = newDataProvider;
if (TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.Activate))
{
TraceData.TraceAndNotifyWithNoParameters(TraceEventType.Warning,
TraceData.UseDataProvider(
TraceData.Identify(this),
TraceData.Identify(newDataProvider)),
this);
}
if (newDataProvider != null)
{
DataChangedEventManager.AddHandler(newDataProvider, OnDataChanged);
newDataProvider.InitialLoad();
}
}
return (oldDataProvider != null) ? oldDataProvider.Data : item;
}
// Return the object from which the given value was obtained, if possible
internal override object GetSourceItem(object newValue)
{
return SourceItem;
}
#endregion Activation
#region Worker
private void CreateWorker()
{
Invariant.Assert(Worker == null, "duplicate call to CreateWorker");
_worker = new ClrBindingWorker(this, Engine);
}
// worker calls here if it changes its dependency sources
// n is the number of real entries in newWorkerSources (which may be longer)
internal void ChangeWorkerSources(WeakDependencySource[] newWorkerSources, int n)
{
int offset = 0;
int size = n;
// create the new sources array, and add the context and CollectionViewSource elements
DependencyObject contextElement = ContextElement;
CollectionViewSource cvs = CollectionViewSource;
bool usesLanguage = UsesLanguage;
if (contextElement != null)
++size;
if (cvs != null)
++size;
if (usesLanguage)
++size;
WeakDependencySource[] newSources = (size > 0) ? new WeakDependencySource[size] : null;
if (contextElement != null)
{
newSources[offset++] = new WeakDependencySource(_ctxElement, FrameworkElement.DataContextProperty);
}
if (cvs != null)
{
WeakReference wr = GetValue(Feature.CollectionViewSource, null) as WeakReference;
newSources[offset++] =
(wr != null) ? new WeakDependencySource(wr, CollectionViewSource.ViewProperty)
: new WeakDependencySource(cvs, CollectionViewSource.ViewProperty);
}
if (usesLanguage)
{
newSources[offset++] = new WeakDependencySource(TargetElementReference, FrameworkElement.LanguageProperty);
}
// add the worker's sources
if (n > 0)
Array.Copy(newWorkerSources, 0, newSources, offset, n);
// tell the property engine
ChangeSources(newSources);
}
#endregion Worker
#region Value
// transfer a value from the source to the target
void TransferValue()
{
TransferValue(DependencyProperty.UnsetValue, false);
}
// transfer a value from the source to the target
internal void TransferValue(object newValue, bool isASubPropertyChange)
{
// if the target element has been GC'd, do nothing
DependencyObject target = TargetElement;
if (target == null)
return;
// if the BindingExpression hasn't activated, do nothing
if (Worker == null)
return;
Type targetType = GetEffectiveTargetType();
IValueConverter implicitConverter = null;
bool isExtendedTraceEnabled = TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.Transfer);
// clear the Pending flag before actually doing the transfer. That way if another
// thread sets the flag, we'll schedule another transfer. This might do more
// transfers than absolutely necessary, but it guarantees that we'll eventually pick
// up the value from the last change.
IsTransferPending = false;
IsInTransfer = true;
UsingFallbackValue = false;
// get the raw value from the source
object value = (newValue == DependencyProperty.UnsetValue) ? Worker.RawValue() : newValue;
UpdateNotifyDataErrors(value);
if (isExtendedTraceEnabled)
{
TraceData.TraceAndNotifyWithNoParameters(TraceEventType.Warning,
TraceData.GetRawValue(
TraceData.Identify(this),
TraceData.Identify(value)),
this);
}
// apply any necessary conversions
if (value != DependencyProperty.UnsetValue)
{
#if !TargetNullValueBC //BreakingChange
bool doNotSetStatus = false;
#endif
if (!UseDefaultValueConverter)
{
// if there's a user-defined converter, call it without catching
// exceptions (bug 992237). It can return DependencyProperty.UnsetValue
// to indicate a failure to convert.
value = Converter.Convert(value, targetType, ParentBinding.ConverterParameter, GetCulture());
if (IsDetached)
{
// user code detached the binding. Give up.
return;
}
if (isExtendedTraceEnabled)
{
TraceData.TraceAndNotifyWithNoParameters(TraceEventType.Warning,
TraceData.UserConverter(
TraceData.Identify(this),
TraceData.Identify(value)),
this);
}
// chain in a default value converter if the returned value's type is not compatible with the targetType
if ( ((value != null) && (value != Binding.DoNothing) && (value != DependencyProperty.UnsetValue))
&& !targetType.IsAssignableFrom(value.GetType()))
{
// the dynamic converter is shared between Transfer and Update directions
// once instantiated, DefaultValueConverters are kept in a lookup table, making swapping
// default value converters in the DynamicValueConverter reasonably fast
implicitConverter = DynamicConverter;
}
}
else
{
// if there's no user-defined converter, use the default converter (if any)
// we chose earlier (in SetupDefaultValueConverter)
implicitConverter = Converter;
}
// apply an implicit conversion, if needed. This can be
// a) null conversion
// b) string formatting
// c) type conversion
if ((value != Binding.DoNothing) && (value != DependencyProperty.UnsetValue))
{
// ultimately, TargetNullValue should get assigned implicitly,
// even if the user doesn't declare it. We can't do this yet because
// of back-compat. I wrote it both ways, and #if'd out the breaking
// change.
#if TargetNullValueBC //BreakingChange
if (IsNullValue(value))
#else
if (EffectiveTargetNullValue != DependencyProperty.UnsetValue &&
IsNullValue(value))
#endif
{
value = EffectiveTargetNullValue;
if (isExtendedTraceEnabled)
{
TraceData.TraceAndNotifyWithNoParameters(TraceEventType.Warning,
TraceData.NullConverter(
TraceData.Identify(this),
TraceData.Identify(value)),
this);
}
}
#if !TargetNullValueBC //BreakingChange
// For DBNull, unless there's a user converter, we handle it here
else if ((value == DBNull.Value) && (Converter == null || UseDefaultValueConverter))
{
if (targetType.IsGenericType && targetType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
value = null;
}
else
{
value = DependencyProperty.UnsetValue;
// The 3.5 code failed to set the status to UpdateTargetError in this
// case. It's a bug, but we have to maintain the buggy behavior for
// back-compat.
doNotSetStatus = true;
}
if (isExtendedTraceEnabled)
{
TraceData.TraceAndNotifyWithNoParameters(TraceEventType.Warning,
TraceData.ConvertDBNull(
TraceData.Identify(this),
TraceData.Identify(value)),
this);
}
}
#endif
else if (implicitConverter != null || EffectiveStringFormat != null)
{
// call a DefaultValueConverter:
// NOTE:
// here we pass in the TargetElement that is expected by our default value converters;
// this does violate the general rule that value converters should be stateless
// and must not be aware of e.g. their target element.
// Our DefaultValueConverters are all internal and only use this target element
// to determine a BaseUri for their TypeConverters
// -> hence a reluctant exeption of above rule
value = ConvertHelper(implicitConverter, value, targetType, TargetElement, GetCulture());
if (isExtendedTraceEnabled)
{
TraceData.TraceAndNotifyWithNoParameters(TraceEventType.Warning,
TraceData.DefaultConverter(
TraceData.Identify(this),
TraceData.Identify(value)),
this);
}
}
}
if (
#if !TargetNullValueBC //BreakingChange
!doNotSetStatus &&
#endif
value == DependencyProperty.UnsetValue)
{
SetStatus(BindingStatusInternal.UpdateTargetError);
}
}
// the special value DoNothing means no error, but no data transfer
if (value == Binding.DoNothing)
goto Done;
// if the value isn't acceptable to the target property, don't use it
// (in MultiBinding, the value will go through the multi-converter, so
// it's too early to make this judgment)
if (!IsInMultiBindingExpression && value != IgnoreDefaultValue &&
value != DependencyProperty.UnsetValue && !TargetProperty.IsValidValue(value))
{
if (TraceData.IsEnabled && !IsInBindingExpressionCollection)
{
TraceData.TraceAndNotify(TraceLevel, TraceData.BadValueAtTransfer, this,
traceParameters: new object[] { value, this });
}
if (isExtendedTraceEnabled)
{
TraceData.TraceAndNotifyWithNoParameters(TraceEventType.Warning,
TraceData.BadValueAtTransferExtended(
TraceData.Identify(this),
TraceData.Identify(value)),
this);
}
value = DependencyProperty.UnsetValue;
if (StatusInternal == BindingStatusInternal.Active)
SetStatus(BindingStatusInternal.UpdateTargetError);
}
// If we haven't obtained a value yet,
// use the fallback value. This could happen when the currency
// has moved off the end of the collection, e.g.
if (value == DependencyProperty.UnsetValue)
{
value = UseFallbackValue();
if (isExtendedTraceEnabled)
{
TraceData.TraceAndNotifyWithNoParameters(TraceEventType.Warning,
TraceData.UseFallback(
TraceData.Identify(this),
TraceData.Identify(value)),
this);
}
}
// Ignore a default source value by setting the value to NoValue;
// this causes the property engine to obtain the value elsewhere
if (value == IgnoreDefaultValue)
{
value = Expression.NoValue;
}
if (isExtendedTraceEnabled)
{
TraceData.TraceAndNotifyWithNoParameters(TraceEventType.Warning,
TraceData.TransferValue(
TraceData.Identify(this),
TraceData.Identify(value)),
this);
}
// if this is a re-transfer after a source update and the value
// hasn't changed, don't do any more work.
bool realTransfer = !(IsInUpdate && System.Windows.Controls.ItemsControl.EqualsEx(value, Value));
if (realTransfer)
{
// update the cached value
ChangeValue(value, true);
// push the new value through the property engine
Invalidate(isASubPropertyChange);
// evaluate "forward" validation rules
ValidateOnTargetUpdated();
}
// after updating all the state (value, validation), mark the binding clean
Clean();
if (realTransfer)
{
OnTargetUpdated();
}
Done:
IsInTransfer = false;
GC.KeepAlive(target); // keep target alive during transfer (bug 956831)
}
// run the validation rules marked as ValidateOnTargetUpdated
private void ValidateOnTargetUpdated()
{
// update the validation state
ValidationError validationError = null;
Collection<ValidationRule> validationRules = ParentBinding.ValidationRulesInternal;
CultureInfo culture = null;
bool needDataErrorRule = ParentBinding.ValidatesOnDataErrors;
if (validationRules != null)
{
// these may be needed by several rules, but only compute them once
object rawValue = DependencyProperty.UnsetValue;
object itemValue = DependencyProperty.UnsetValue;
foreach (ValidationRule validationRule in validationRules)
{
if (validationRule.ValidatesOnTargetUpdated)
{
if (validationRule is DataErrorValidationRule)
{
needDataErrorRule = false;
}
object value;
switch (validationRule.ValidationStep)
{
case ValidationStep.RawProposedValue:
if (rawValue == DependencyProperty.UnsetValue)
{
rawValue = GetRawProposedValue();
}
value = rawValue;
break;
case ValidationStep.ConvertedProposedValue:
case ValidationStep.UpdatedValue:
case ValidationStep.CommittedValue:
if (itemValue == DependencyProperty.UnsetValue)
{
itemValue = Worker.RawValue();
}
value = itemValue;
break;
default:
throw new InvalidOperationException(SR.Format(SR.ValidationRule_UnknownStep, validationRule.ValidationStep, validationRule));
}
// lazy-fetch culture (avoids exception when target DP is Language)
if (culture == null)
{
culture = GetCulture();
}
validationError = RunValidationRule(validationRule, value, culture);
if (validationError != null)
break;
}
}
}
if (needDataErrorRule && validationError == null)
{
// lazy-fetch culture (avoids exception when target DP is Language)
if (culture == null)
{
culture = GetCulture();
}
validationError = RunValidationRule(DataErrorValidationRule.Instance, this, culture);
}
UpdateValidationError(validationError);
}
ValidationError RunValidationRule(ValidationRule validationRule, object value, CultureInfo culture)
{
ValidationError error;
ValidationResult validationResult = validationRule.Validate(value, culture, this);
if (validationResult.IsValid)
{
error = null;
}
else
{
if (TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.Update))
{
TraceData.TraceAndNotifyWithNoParameters(TraceEventType.Warning,
TraceData.ValidationRuleFailed(
TraceData.Identify(this),
TraceData.Identify(validationRule)),
this);
}
error = new ValidationError(validationRule, this, validationResult.ErrorContent, null);
}
return error;
}
private object ConvertHelper(IValueConverter converter, object value, Type targetType, object parameter, CultureInfo culture)
{
// use the StringFormat (if appropriate) in preference to the default converter
string stringFormat = EffectiveStringFormat;
Invariant.Assert(converter != null || stringFormat != null);
// 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 IsCritical[Application]Exception() handles these correctly.
#pragma warning disable 56500
object convertedValue = null;
try
{
if (stringFormat != null)
{
convertedValue = String.Format(culture, stringFormat, value);
}
else
{
convertedValue = converter.Convert(value, targetType, parameter, culture);
}
}
// 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)
{
// the DefaultValueConverter can end up calling BaseUriHelper.GetBaseUri()
// which can raise SecurityException if the app does not have the right FileIO privileges
if (CriticalExceptions.IsCriticalApplicationException(ex))
throw;
if (TraceData.IsEnabled)
{
string name = String.IsNullOrEmpty(stringFormat) ? converter.GetType().Name : "StringFormat";
TraceData.TraceAndNotify(TraceLevel,
TraceData.BadConverterForTransfer(
name,
AvTrace.ToStringHelper(value),
AvTrace.TypeName(value)),
this, ex);
}
convertedValue = DependencyProperty.UnsetValue;
}
catch // non CLS compliant exception
{
if (TraceData.IsEnabled)
{
TraceData.TraceAndNotify(TraceLevel,
TraceData.BadConverterForTransfer(
converter.GetType().Name,
AvTrace.ToStringHelper(value),
AvTrace.TypeName(value)),
this);
}
convertedValue = DependencyProperty.UnsetValue;
}
#pragma warning restore 56500
#pragma warning restore 1634, 1691
return convertedValue;
}
private object ConvertBackHelper(IValueConverter converter,
object value,
Type sourceType,
object parameter,
CultureInfo culture)
{
Invariant.Assert(converter != null);
// 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 IsCritical[Application]Exception() handles these correctly.
#pragma warning disable 56500
object convertedValue = null;
try
{
convertedValue = converter.ConvertBack(value, sourceType, parameter, culture);
}
// 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)
{
// the DefaultValueConverter can end up calling BaseUriHelper.GetBaseUri()
// which can raise SecurityException if the app does not have the right FileIO privileges
ex = CriticalExceptions.Unwrap(ex);
if (CriticalExceptions.IsCriticalApplicationException(ex))
throw;
if (TraceData.IsEnabled)
{
TraceData.TraceAndNotify(TraceEventType.Error,
TraceData.BadConverterForUpdate(
AvTrace.ToStringHelper(Value),
AvTrace.TypeName(value)),
this, ex);
}
ProcessException(ex, ValidatesOnExceptions);
convertedValue = DependencyProperty.UnsetValue;
}
catch // non CLS compliant exception
{
if (TraceData.IsEnabled)
{
TraceData.TraceAndNotify(TraceEventType.Error,
TraceData.BadConverterForUpdate(
AvTrace.ToStringHelper(Value),
AvTrace.TypeName(value)),
this);
}
convertedValue = DependencyProperty.UnsetValue;
}
#pragma warning restore 56500
#pragma warning restore 1634, 1691
return convertedValue;
}
internal void ScheduleTransfer(bool isASubPropertyChange)
{
if (isASubPropertyChange && Converter != null)
{
// a converter doesn't care about sub-property changes
isASubPropertyChange = false;
}
TransferValue(DependencyProperty.UnsetValue, isASubPropertyChange);
}
void OnTargetUpdated()
{
if (NotifyOnTargetUpdated)
{
DependencyObject target = TargetElement;
if (target != null)
{
if ( !IsInMultiBindingExpression // not an inner BindingExpression
&& ( !IsInPriorityBindingExpression
|| this == ParentPriorityBindingExpression.ActiveBindingExpression) // in ProrityBinding, and either active
|| (IsAttaching && (StatusInternal==BindingStatusInternal.Active || UsingFallbackValue) // or about to become active
)
)
{
// while attaching a normal (not style-defined) BindingExpression,
// we must defer raising the event until after the
// property has been invalidated, so that the event handler
// gets the right value if it asks (bug 1036862)
if (IsAttaching && RootBindingExpression == target.ReadLocalValue(TargetProperty))
{
Engine.AddTask(this, TaskOps.RaiseTargetUpdatedEvent);
}
else
{
OnTargetUpdated(target, TargetProperty);
}
}
}
}
}
void OnSourceUpdated()
{
if (NotifyOnSourceUpdated)
{
DependencyObject target = TargetElement;
if (target != null)
{
if ( !IsInMultiBindingExpression // not an inner BindingExpression
&& ( !IsInPriorityBindingExpression
|| this == ParentPriorityBindingExpression.ActiveBindingExpression)) // not an inactive BindingExpression
{
OnSourceUpdated(target, TargetProperty);
}
}
}
}
internal override bool ShouldReactToDirtyOverride()
{
// if the binding has disconnected, no need to react to Dirty()
return (DataItem != DisconnectedItem);
}
// transfer a value from target to source
internal override bool UpdateOverride()
{
// various reasons not to update:
if ( !NeedsUpdate // nothing to do
|| !IsReflective // no update desired
|| IsInTransfer // in a transfer
|| Worker == null // not activated
|| !Worker.CanUpdate // no source (currency moved off end)
)
return true;
return UpdateValue();
}
internal override object ConvertProposedValue(object value)
{
object rawValue = value;
bool isExtendedTraceEnabled = TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.Update);
if (isExtendedTraceEnabled)
{
TraceData.TraceAndNotifyWithNoParameters(TraceEventType.Warning,
TraceData.UpdateRawValue(
TraceData.Identify(this),
TraceData.Identify(value)),
this);
}
Type sourceType = Worker.SourcePropertyType;
IValueConverter implicitConverter = null;
CultureInfo culture = GetCulture();
// apply user-defined converter
if (Converter != null)
{
if (!UseDefaultValueConverter)
{
// if there's a user-defined converter, call it without catching
// exceptions (bug 992237). It can return DependencyProperty.UnsetValue
// to indicate a failure to convert.
value = Converter.ConvertBack(value, sourceType, ParentBinding.ConverterParameter, culture);
if (IsDetached)
{
// user code detached the binding. Give up.
return Binding.DoNothing;
}
if (isExtendedTraceEnabled)
{
TraceData.TraceAndNotifyWithNoParameters(TraceEventType.Warning,
TraceData.UserConvertBack(
TraceData.Identify(this),
TraceData.Identify(value)),
this);
}
// chain in a default value converter if the returned value's type is not compatible with the sourceType
if (value != Binding.DoNothing && value != DependencyProperty.UnsetValue &&
!IsValidValueForUpdate(value, sourceType))
{
// the dynamic converter is shared between Transfer and Update directions
// once instantiated, DefaultValueConverters are kept in a lookup table, making swapping
// default value converters reasonably fast
implicitConverter = DynamicConverter;
}
}
else
{
implicitConverter = Converter;
}
}
// apply an implicit conversion, if needed. This can be
// a) null conversion
// b) type conversion
if (value != Binding.DoNothing && value != DependencyProperty.UnsetValue)
{
if (IsNullValue(value))
{
if (value == null || !IsValidValueForUpdate(value, sourceType))
{
if (Worker.IsDBNullValidForUpdate)
{
value = DBNull.Value;
}
else
{
value = NullValueForType(sourceType);
}
}
}
else if (implicitConverter != null)
{
// here we pass in the TargetElement, see NOTE of caution in TransferValue() why this is ok
value = ConvertBackHelper(implicitConverter, value, sourceType, this.TargetElement, culture);
if (isExtendedTraceEnabled)
{
TraceData.TraceAndNotifyWithNoParameters(TraceEventType.Warning,
TraceData.DefaultConvertBack(
TraceData.Identify(this),
TraceData.Identify(value)),
this);
}
}
}
if (isExtendedTraceEnabled)
{
TraceData.TraceAndNotifyWithNoParameters(TraceEventType.Warning,
TraceData.Update(
TraceData.Identify(this),
TraceData.Identify(value)),
this);
}
// if the conversion failed, signal a validation error
if (value == DependencyProperty.UnsetValue)
{
if (ValidationError == null)
{
ValidationError validationError = new ValidationError(ConversionValidationRule.Instance, this, SR.Format(SR.Validation_ConversionFailed, rawValue), null);
UpdateValidationError(validationError);
}
}
return value;
}
/// <summary>
/// Get the converted proposed value and inform the binding group
/// <summary>
internal override bool ObtainConvertedProposedValue(BindingGroup bindingGroup)
{
bool result = true;
object value;
if (NeedsUpdate)
{
value = bindingGroup.GetValue(this);
if (value != DependencyProperty.UnsetValue)
{
value = ConvertProposedValue(value);
if (value == DependencyProperty.UnsetValue)
{
result = false;
}
}
}
else
{
value = BindingGroup.DeferredSourceValue;
}
bindingGroup.SetValue(this, value);
return result;
}
internal override object UpdateSource(object value)
{
// If there is a failure to convert, then Update failed.
if (value == DependencyProperty.UnsetValue)
{
SetStatus(BindingStatusInternal.UpdateSourceError);
}
if (value == Binding.DoNothing || value == DependencyProperty.UnsetValue ||
ShouldIgnoreUpdate())
{
return value;
}
// 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 IsCritical[Application]Exception() handles these correctly.
#pragma warning disable 56500
try
{
BeginSourceUpdate();
Worker.UpdateValue(value);
}
// 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)
{
ex = CriticalExceptions.Unwrap(ex);
if (CriticalExceptions.IsCriticalApplicationException(ex))
throw;
if (TraceData.IsEnabled)
TraceData.TraceAndNotify(TraceEventType.Error, TraceData.WorkerUpdateFailed, this, ex);
ProcessException(ex, (ValidatesOnExceptions || BindingGroup != null));
SetStatus(BindingStatusInternal.UpdateSourceError);
value = DependencyProperty.UnsetValue;
}
catch // non CLS compliant exception
{
if (TraceData.IsEnabled)
TraceData.TraceAndNotify(TraceEventType.Error, TraceData.WorkerUpdateFailed, this);
SetStatus(BindingStatusInternal.UpdateSourceError);
value = DependencyProperty.UnsetValue;
}
finally
{
EndSourceUpdate();
}
#pragma warning restore 56500
#pragma warning restore 1634, 1691
OnSourceUpdated();
return value;
}
/// <summary>
/// Update the source value and inform the binding group
/// <summary>
internal override bool UpdateSource(BindingGroup bindingGroup)
{
bool result = true;
if (NeedsUpdate)
{
object value = bindingGroup.GetValue(this);
value = UpdateSource(value);
bindingGroup.SetValue(this, value);
if (value == DependencyProperty.UnsetValue)
{
result = false;
}
}
return result;
}
/// <summary>
/// Store the value in the binding group
/// </summary>
internal override void StoreValueInBindingGroup(object value, BindingGroup bindingGroup)
{
bindingGroup.SetValue(this, value);
}
/// <summary>
/// Run validation rules for the given step
/// <summary>
internal override bool Validate(object value, ValidationStep validationStep)
{
// run rules attached to this binding
bool result = base.Validate(value, validationStep);
if (validationStep == ValidationStep.UpdatedValue)
{
if (result && ParentBinding.ValidatesOnDataErrors)
{
// remember the old validation error, if it came from the implicit DataError rule
ValidationError oldValidationError = GetValidationErrors(validationStep);
if (oldValidationError != null &&
oldValidationError.RuleInError != DataErrorValidationRule.Instance)
{
oldValidationError = null;
}
// run the DataError rule, even though it doesn't appear in the
// ValidationRules collection
ValidationError error = RunValidationRule(DataErrorValidationRule.Instance, this, GetCulture());
if (error != null)
{
UpdateValidationError(error);
result = false;
}
else if (oldValidationError != null)
{
// the implicit rule is now valid - clear the old error
UpdateValidationError(null);
}
}
}
else if (validationStep == ValidationStep.CommittedValue)
{
if (result)
{
NeedsValidation = false;
}
}
return result;
}
/// <summary>
/// Run validation rules for the given step, and inform the binding group
/// <summary>
internal override bool CheckValidationRules(BindingGroup bindingGroup, ValidationStep validationStep)
{
if (!NeedsValidation)
return true;
object value;
switch (validationStep)
{
case ValidationStep.RawProposedValue:
value = GetRawProposedValue();
break;
case ValidationStep.ConvertedProposedValue:
value = bindingGroup.GetValue(this);
break;
case ValidationStep.UpdatedValue:
case ValidationStep.CommittedValue:
value = this;
break;
default:
throw new InvalidOperationException(SR.Format(SR.ValidationRule_UnknownStep, validationStep, bindingGroup));
}
return Validate(value, validationStep);
}
/// <summary>
/// Get the proposed value(s) that would be written to the source(s), applying
/// conversion and checking UI-side validation rules.
/// </summary>
internal override bool ValidateAndConvertProposedValue(out Collection<ProposedValue> values)
{
Debug.Assert(NeedsValidation, "check NeedsValidation before calling this");
values = null;
// validate raw proposed value
object rawValue = GetRawProposedValue();
bool isValid = Validate(rawValue, ValidationStep.RawProposedValue);
// apply conversion and validate it
object convertedValue = isValid ? ConvertProposedValue(rawValue)
: DependencyProperty.UnsetValue;
if (convertedValue == Binding.DoNothing)
{
convertedValue = DependencyProperty.UnsetValue;
}
else if (convertedValue == DependencyProperty.UnsetValue)
{
isValid = false;
}
else
{
isValid = Validate(convertedValue, ValidationStep.ConvertedProposedValue);
}
// return the result
values = new Collection<ProposedValue>();
values.Add(new ProposedValue(this, rawValue, convertedValue));
return isValid;
}
private bool IsValidValueForUpdate(object value, Type sourceType)
{
// null is always valid, even for value types. The reflection layer
// apparently converts null to default(T).
if (value == null)
return true;
// if direct assignment is possible, the value is valid
if (sourceType.IsAssignableFrom(value.GetType()))
return true;
// if the value is DBNull, ask the worker (answer depends on several factors)
if (Convert.IsDBNull(value))
return Worker.IsDBNullValidForUpdate;
// otherwise the value is invalid
return false;
}
private void ProcessException(Exception ex, bool validate)
{
object filteredException = null;
ValidationError validationError = null;
// If there is not ExceptionFilter, then Wrap the
// exception in a ValidationError.
if (ExceptionFilterExists())
{
filteredException = CallDoFilterException(ex);
if (filteredException == null)
return;
validationError = filteredException as ValidationError;
}
// See if an ExceptionValidationRule is in effect
if (validationError == null && validate)
{
ValidationRule exceptionValidationRule = ExceptionValidationRule.Instance;
if (filteredException == null)
{
validationError = new ValidationError(exceptionValidationRule, this, ex.Message, ex);
}
else
{
validationError = new ValidationError(exceptionValidationRule, this, filteredException, ex);
}
}
if (validationError != null)
{
UpdateValidationError(validationError);
}
}
// Sometimes a source update gets requested as a result of event leapfrogging,
// where the binding has not yet been informed of a pending change to its
// path. In this case, we should simply ignore the update.
private bool ShouldIgnoreUpdate()
{
// The detection algorithm below is expensive, so only do when the target
// property is especially susceptible to the problem
if (TargetProperty.OwnerType != typeof(System.Windows.Controls.Primitives.Selector) && // SelectedItem, SelectedValue, SelectedIndex, etc.
TargetProperty != ComboBox.TextProperty // ComboBox.Text
)
{
return false;
}
// Re-evaluate the entire path. If it doesn't produce the same source
// object as our cached value, there is presumably a change notification
// somewhere along the path that hasn't been delivered yet, due to event
// leapfrogging.
// get initial (top-level) item
object item;
DependencyObject contextElement = ContextElement;
if (contextElement == null)
{
DependencyObject target = TargetElement;
if (target != null && UsingMentor)
{
target = Helper.FindMentor(target);
}
if (target == null)
{
return true;
}
item = ParentBinding.SourceReference.GetDataObject(target,
new ObjectRefArgs() { ResolveNamesInTemplate = ResolveNamesInTemplate });
}
else
{
item = contextElement.GetValue(FrameworkElement.DataContextProperty);
}
// Apply auto-magic rules for CVS, DSP, etc.
// This logic should agree with Activate(item), except for event hookup.
if (!ParentBinding.BindsDirectlyToSource)
{
CollectionViewSource cvs;
DataSourceProvider dsp;
if ((cvs = item as CollectionViewSource) != null)
{
item = cvs.CollectionView;
}
else if ((dsp = item as DataSourceProvider) != null)
{
item = dsp.Data;
}
}
// If the top-level item is different, ignore the update
if (!System.Windows.Controls.ItemsControl.EqualsEx(DataItem, item))
return true;
// check the rest of the path
return !Worker.IsPathCurrent();
}
#endregion Value
#region INotifyDataErrorInfo
// fetch the current data errors from the source object (and its value),
// and update the list of data-error ValidationResults accordingly
internal void UpdateNotifyDataErrors(INotifyDataErrorInfo indei, string propertyName, object value)
{
if (!ValidatesOnNotifyDataErrors || IsDetached)
return;
// replace the value object, if it has changed
WeakReference dataErrorWR = (WeakReference)GetValue(Feature.DataErrorValue, null);
INotifyDataErrorInfo dataErrorValue = (dataErrorWR == null) ? null : dataErrorWR.Target as INotifyDataErrorInfo;
if (value != DependencyProperty.UnsetValue && value != dataErrorValue && IsDynamic)
{
if (dataErrorValue != null)
ErrorsChangedEventManager.RemoveHandler(dataErrorValue, OnErrorsChanged);
INotifyDataErrorInfo newDataErrorValue = value as INotifyDataErrorInfo;
object newDataErrorWR = ReplaceReference(dataErrorWR, newDataErrorValue);
SetValue(Feature.DataErrorValue, newDataErrorWR, null);
dataErrorValue = newDataErrorValue;
if (newDataErrorValue != null)
ErrorsChangedEventManager.AddHandler(newDataErrorValue, OnErrorsChanged);
}
// fetch the errors from the last item and from its value
IsDataErrorsChangedPending = false;
try
{
List<object> propertyErrors = GetDataErrors(indei, propertyName);
List<object> valueErrors = GetDataErrors(dataErrorValue, String.Empty);
List<object> errors = MergeErrors(propertyErrors, valueErrors);
UpdateNotifyDataErrorValidationErrors(errors);
}
catch (Exception ex)
{ // if GetErrors or the enumerators throw, leave the old errors in place
if (CriticalExceptions.IsCriticalApplicationException(ex))
throw;
}
}
void UpdateNotifyDataErrors(object value)
{
if (!ValidatesOnNotifyDataErrors)
return;
UpdateNotifyDataErrors(SourceItem as INotifyDataErrorInfo, SourcePropertyName, value);
}
// fetch errors for the given property
internal static List<object> GetDataErrors(INotifyDataErrorInfo indei, string propertyName)
{
const int RetryCount = 3;
List<object> result = null;
if (indei != null && indei.HasErrors)
{
// if a worker thread is updating the source's errors while we're trying to
// read them, the enumerator will throw. The interface doesn't provide
// any way to work around this, so we'll just try it a few times hoping
// for success.
for (int i=RetryCount; i>=0; --i)
{
try
{
result = new List<object>();
IEnumerable ie = indei.GetErrors(propertyName);
if (ie != null)
{
foreach (object o in ie)
{
result.Add(o);
}
}
break;
}
catch (InvalidOperationException)
{
// on the last try, let the exception bubble up
if (i == 0)
throw;
}
}
}
if (result != null && result.Count == 0)
result = null;
return result;
}
List<object> MergeErrors(List<object> list1, List<object> list2)
{
if (list1 == null)
return list2;
if (list2 == null)
return list1;
foreach (object o in list2)
list1.Add(o);
return list1;
}
#endregion INotifyDataErrorInfo
#region Event handlers
private void OnDataContextChanged(DependencyObject contextElement)
{
// ADO BindingExpressions change the data context when a field changes.
// If the field is the one we're updating, ignore the DC change.
if (!IsInUpdate && CanActivate)
{
if (IsReflective && RootBindingExpression.ParentBindingBase.BindingGroupName == String.Empty)
{
RejoinBindingGroup(IsReflective, contextElement);
}
object newItem = contextElement.GetValue(FrameworkElement.DataContextProperty);
if (!System.Windows.Controls.ItemsControl.EqualsEx(DataItem, newItem))
{
Activate(newItem);
}
}
}
internal void OnCurrentChanged(object sender, EventArgs e)
{
if (TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.Events))
{
TraceData.TraceAndNotifyWithNoParameters(TraceEventType.Warning,
TraceData.GotEvent(
TraceData.Identify(this),
"CurrentChanged",
TraceData.Identify(sender)),
this);
}
Worker.OnCurrentChanged(sender as ICollectionView, e);
}
internal void OnCurrentChanging(object sender, CurrentChangingEventArgs e)
{
if (TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.Events))
{
TraceData.TraceAndNotifyWithNoParameters(TraceEventType.Warning,
TraceData.GotEvent(
TraceData.Identify(this),
"CurrentChanging",
TraceData.Identify(sender)),
this);
}
Update();
// Consider Cancel if anything goes wrong.
}
void OnDataChanged(object sender, EventArgs e)
{
if (TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.Events))
{
TraceData.TraceAndNotifyWithNoParameters(TraceEventType.Warning,
TraceData.GotEvent(
TraceData.Identify(this),
"DataChanged",
TraceData.Identify(sender)),
this);
}
Activate(sender);
}
void OnInheritanceContextChanged(object sender, EventArgs e)
{
if (TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.Events))
{
TraceData.TraceAndNotifyWithNoParameters(TraceEventType.Warning,
TraceData.GotEvent(
TraceData.Identify(this),
"InheritanceContextChanged",
TraceData.Identify(sender)),
this);
}
if (StatusInternal == BindingStatusInternal.Unattached)
{
// retry bindings immediately when InheritanceContext changes,
// so that triggers, animations, and rendering see the bound
// value when they initialize their own local cache (bug DD 139838).
Engine.CancelTask(this, TaskOps.AttachToContext); // cancel existing task
AttachToContext(AttachAttempt.Again);
if (StatusInternal == BindingStatusInternal.Unattached)
{
// if that didn't work, run the task again
// By adding a new task, the engine will add a LayoutUpdated handler
Engine.AddTask(this, TaskOps.AttachToContext);
}
}
else
{
AttachToContext(AttachAttempt.Last);
}
}
internal override void OnLostFocus(object sender, RoutedEventArgs e)
{
if (TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.Events))
{
TraceData.TraceAndNotifyWithNoParameters(TraceEventType.Warning,
TraceData.GotEvent(
TraceData.Identify(this),
"LostFocus",
TraceData.Identify(sender)),
this);
}
Update();
}
void OnErrorsChanged(object sender, DataErrorsChangedEventArgs e)
{
// if notification was on the right thread, just do the work (normal case)
if (Dispatcher.Thread == Thread.CurrentThread)
{
UpdateNotifyDataErrors(DependencyProperty.UnsetValue);
}
else if (!IsDataErrorsChangedPending)
{
// otherwise invoke an operation to do the work on the right context
IsDataErrorsChangedPending = true;
Engine.Marshal(
(arg) => { UpdateNotifyDataErrors(DependencyProperty.UnsetValue);
return null; }, null);
}
}
/// <summary>
/// Handle events from the centralized event table
/// </summary>
bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
{
return false; // this method is no longer used (but must remain, for compat)
}
#endregion Event handlers
#region Helper functions
//
// If this BindingExpression's ParentBinding has an ExceptionFilter set,
// call it, otherwise give the MultiBinding (if there is one)
// a chance.
//
private object CallDoFilterException(Exception ex)
{
if (ParentBinding.UpdateSourceExceptionFilter != null)
{
return ParentBinding.DoFilterException(this, ex);
}
else if (IsInMultiBindingExpression)
{
return ParentMultiBindingExpression.ParentMultiBinding.DoFilterException(this, ex);
}
return null;
}
private bool ExceptionFilterExists()
{
return ( (ParentBinding.UpdateSourceExceptionFilter != null) ||
(IsInMultiBindingExpression && ParentMultiBindingExpression.ParentMultiBinding.UpdateSourceExceptionFilter != null)
);
}
// surround any code that changes the value of a BindingExpression by
// using (bindExpr.ChangingValue())
// { ... }
internal IDisposable ChangingValue()
{
return new ChangingValueHelper(this);
}
// cancel any pending work
internal void CancelPendingTasks()
{
Engine.CancelTasks(this);
}
// replace this BindingExpression with a new one
void Replace()
{
DependencyObject target = TargetElement;
if (target != null)
{
if (IsInBindingExpressionCollection)
ParentBindingExpressionBase.ReplaceChild(this);
else
BindingOperations.SetBinding(target, TargetProperty, ParentBinding);
}
}
// raise the TargetUpdated event (explicit polymorphism)
internal static void OnTargetUpdated(DependencyObject d, DependencyProperty dp)
{
DataTransferEventArgs args = new DataTransferEventArgs(d, dp);
args.RoutedEvent = Binding.TargetUpdatedEvent;
FrameworkObject fo = new FrameworkObject(d);
if (!fo.IsValid && d != null)
{
fo.Reset(Helper.FindMentor(d));
}
fo.RaiseEvent(args);
}
// raise the SourceUpdatedEvent event (explicit polymorphism)
internal static void OnSourceUpdated(DependencyObject d, DependencyProperty dp)
{
DataTransferEventArgs args = new DataTransferEventArgs(d, dp);
args.RoutedEvent = Binding.SourceUpdatedEvent;
FrameworkObject fo = new FrameworkObject(d);
if (!fo.IsValid && d != null)
{
fo.Reset(Helper.FindMentor(d));
}
fo.RaiseEvent(args);
}
internal override void HandlePropertyInvalidation(DependencyObject d, DependencyPropertyChangedEventArgs args)
{
DependencyProperty dp = args.Property;
if (TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.Events))
{
TraceData.TraceAndNotifyWithNoParameters(TraceEventType.Warning,
TraceData.GotPropertyChanged(
TraceData.Identify(this),
TraceData.Identify(d),
dp.Name),
this);
}
if (dp == FrameworkElement.DataContextProperty)
{
DependencyObject contextElement = ContextElement;
if (d == contextElement)
{
IsTransferPending = false; // clear this flag, even if data context isn't changing
OnDataContextChanged(contextElement);
}
}
if (dp == CollectionViewSource.ViewProperty)
{
CollectionViewSource cvs = this.CollectionViewSource;
if (d == cvs)
{
Activate(cvs);
}
}
if (dp == FrameworkElement.LanguageProperty && UsesLanguage && d == TargetElement)
{
InvalidateCulture();
TransferValue();
}
if (Worker != null)
{
Worker.OnSourceInvalidation(d, dp, args.IsASubPropertyChange);
}
}
private class ChangingValueHelper : IDisposable
{
internal ChangingValueHelper(BindingExpression b)
{
_bindingExpression = b;
b.CancelPendingTasks();
}
public void Dispose()
{
_bindingExpression.TransferValue();
GC.SuppressFinalize(this);
}
BindingExpression _bindingExpression;
}
void SetDataItem(object newItem)
{
_dataItem = CreateReference(newItem);
}
// find the DataSource object (if any) that produced the DataContext
// for element d
object GetDataSourceForDataContext(DependencyObject d)
{
// look for ancestor that contributed the inherited value
DependencyObject ancestor;
BindingExpression b = null;
for (ancestor = d;
ancestor != null;
ancestor = FrameworkElement.GetFrameworkParent(ancestor))
{
if (HasLocalDataContext(ancestor))
{
b = BindingOperations.GetBindingExpression(ancestor, FrameworkElement.DataContextProperty) as BindingExpression;
break;
}
}
if (b != null)
return b.DataSource;
return null;
}
#endregion Helper functions
//------------------------------------------------------
//
// Private Fields
//
//------------------------------------------------------
WeakReference _ctxElement;
object _dataItem;
BindingWorker _worker;
Type _sourceType;
internal static readonly object NullDataItem = new NamedObject("NullDataItem");
internal static readonly object IgnoreDefaultValue = new NamedObject("IgnoreDefaultValue");
internal static readonly object StaticSource = new NamedObject("StaticSource");
}
}
|