Code:
/ Net / Net / 3.5.50727.3053 / DEVDIV / depot / DevDiv / releases / Orcas / SP / wpf / src / Framework / System / Windows / Data / BindingExpression.cs / 4 / BindingExpression.cs
//----------------------------------------------------------------------------
//
//
// Copyright (C) Microsoft Corporation. All rights reserved.
//
//
// Description: Defines BindingExpression object, the run-time instance of data binding.
//
// See spec at http://avalon/connecteddata/Specs/Data%20Binding.mht
//
//---------------------------------------------------------------------------
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.ComponentModel;
using System.Globalization;
using System.Windows.Threading;
using System.Threading;
using System.Xml;
using System.Windows;
using System.Windows.Input;
using System.Windows.Controls;
using System.Windows.Markup;
using MS.Utility;
using MS.Internal;
using MS.Internal.Controls; // Validation
using MS.Internal.Data;
using MS.Internal.KnownBoxes;
using MS.Internal.Utility; // TraceLog
namespace System.Windows.Data
{
///
/// 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.
///
public delegate object UpdateSourceExceptionFilterCallback(object bindExpression, Exception exception);
///
/// Describes a single run-time instance of data binding, binding a target
/// (element, DependencyProperty) to a source (object, property, XML node)
///
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.Trace(TraceEventType.Warning,
TraceData.BindingPath(
TraceData.Identify(path)));
}
else
{
TraceData.Trace(TraceEventType.Warning,
TraceData.BindingXPathAndPath(
TraceData.Identify(binding.XPath),
TraceData.Identify(path)));
}
}
}
//------------------------------------------------------
//
// Interfaces
//
//------------------------------------------------------
void IDataBindEngineClient.TransferValue()
{
TransferValue();
}
void IDataBindEngineClient.UpdateValue()
{
UpdateValue();
}
bool IDataBindEngineClient.AttachToContext(bool lastChance)
{
AttachToContext(lastChance ? AttachAttempt.Last : AttachAttempt.Again);
return (Status != BindingStatus.Unattached);
}
void IDataBindEngineClient.OnTargetUpdated()
{
OnTargetUpdated();
}
DependencyObject IDataBindEngineClient.TargetElement
{
get { return TargetElement; }
}
//-----------------------------------------------------
//
// Public Properties
//
//------------------------------------------------------
/// Binding from which this expression was created
public Binding ParentBinding { get { return (Binding)ParentBindingBase; } }
/// The data item actually used by this BindingExpression
public object DataItem { get { return GetReference(_dataItem); } }
/// The data source actually used by this BindingExpression
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, false);
}
}
//-----------------------------------------------------
//
// Public Methods
//
//-----------------------------------------------------
/// Send the current value back to the source
/// Does nothing when binding's Mode is not TwoWay or OneWayToSource
public override void UpdateSource()
{
if (Status == BindingStatus.Detached)
throw new InvalidOperationException(SR.Get(SRID.BindingExpressionIsDetached));
NeedsUpdate = true; // force update
Update(true); // update synchronously
}
/// Force a data transfer from source to target
public override void UpdateTarget()
{
if (Status == BindingStatus.Detached)
throw new InvalidOperationException(SR.Get(SRID.BindingExpressionIsDetached));
if (Worker != null)
{
Worker.RefreshValue(); // calls TransferValue
}
}
#region Expression overrides
///
/// Called to evaluate the Expression value
///
/// DependencyObject being queried
/// Property being queried
/// Computed value. Default (of the target) if unavailable.
internal override object GetValue(DependencyObject d, DependencyProperty dp)
{
return Value;
}
///
/// Allows Expression to store set values
///
/// DependencyObject being set
/// Property being set
/// Value being set
/// true if Expression handled storing of the value
internal override bool SetValue(DependencyObject d, DependencyProperty dp, object value)
{
if (IsReflective)
{
Value = value;
return true;
}
else
{
// if the binding doesn't push values back to the source, allow
// SetValue to overwrite the binding with a local value
return false;
}
}
///
/// Notification that a Dependent that this Expression established has
/// been invalidated as a result of a Source invalidation
///
/// DependencyObject that was invalidated
/// Changed event args for the property that was invalidated
internal override void OnPropertyInvalidation(DependencyObject d, DependencyPropertyChangedEventArgs args)
{
if (d == null)
throw new ArgumentNullException("d");
DependencyProperty dp = args.Property;
if (dp == null)
throw new ArgumentNullException("dp");
// 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 (relevant)
{
relevant = (Worker != null) && (Worker.UsesDependencyProperty(d, dp));
}
if (!relevant)
return;
// if the notification arrived on the right Dispatcher, handle it now.
if (Dispatcher.Thread == Thread.CurrentThread)
{
HandlePropertyInvalidation(d, args);
}
else // Otherwise, marshal it to the right Dispatcher.
{
IsTransferPending = true;
Dispatcher.BeginInvoke(
DispatcherPriority.DataBind,
new DispatcherOperationCallback(HandlePropertyInvalidationOperation),
new object[]{d, args});
}
}
#endregion Expression overrides
//-----------------------------------------------------
//
// Protected Methods
//
//------------------------------------------------------
///
/// Invalidate the given child expression.
///
internal override void InvalidateChild(BindingExpressionBase bindingExpression)
{
// BindingExpression does not support child bindings
}
///
/// Change the dependency sources for the given child expression.
///
internal override void ChangeSourcesForChild(BindingExpressionBase bindingExpression, WeakDependencySource[] newSources)
{
// BindingExpression does not support child bindings
}
///
/// Replace the given child expression with a new one.
///
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 { return (CollectionViewSource)GetReference(_collectionViewSource); }
set { _collectionViewSource = CreateReference(value); }
}
/// True if this binding expression should ignore changes from the source
internal bool IgnoreSourcePropertyChange
{
get
{
if (IsTransferPending)
return true;
if (IsInUpdate)
{
// allow changes from an update to flow back to
// the target if there's a converter or string format, and
// the binding isn't in "immediate" mode (bug 1207214)
return ((Converter == null && EffectiveStringFormat == null) || IsUpdateOnPropertyChanged);
}
return false;
}
}
internal PropertyPath Path
{
get { return ParentBinding.Path; }
}
internal IValueConverter Converter
{
get { return _valueConverter; }
set { _valueConverter = value; }
}
// 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 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.Get(SRID.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.Get(SRID.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)
{
// this method is called when the last item in the path is replaced.
// BindingGroup also wants to know about this.
BindingGroup bindingGroup = BindingGroup;
if (bindingGroup != null)
{
bindingGroup.UpdateTable(this);
}
if (!UseDefaultValueConverter)
return;
if (IsInMultiBindingExpression)
{
Converter = null;
_sourceType = type;
}
else if (type != null && type != _sourceType)
{
_sourceType = type;
Converter = Engine.GetDefaultValueConverter(_sourceType,
TargetProperty.PropertyType, IsReflective);
// null converter means failure to create one
if (Converter == null && TraceData.IsEnabled)
{
TraceData.Trace(TraceEventType.Error,
TraceData.CannotCreateDefaultValueConverter(
_sourceType,
TargetProperty.PropertyType,
(IsReflective ? "two-way" : "one-way")),
this );
}
if (Converter == DefaultValueConverter.ValueConverterNotNeeded)
{
Converter = null; // everyone else takes null for an answer.
}
}
}
// return true if DataContext is set locally (not inherited) on DependencyObject
internal static bool HasLocalDataContext(DependencyObject d)
{
return (d.ReadLocalValue(FrameworkElement.DataContextProperty) != DependencyProperty.UnsetValue);
}
//------------------------------------------------------
//
// Private Properties
//
//-----------------------------------------------------
private bool CanActivate
{
get { return Status != BindingStatus.Unattached; }
}
private BindingWorker Worker { get { return _worker; } }
private DynamicValueConverter DynamicConverter
{
get
{
if (_dynamicConverter == null)
{
Invariant.Assert(Worker != null);
// pass along the static source and target types to find same DefaultValueConverter as SetupDefaultValueConverter
_dynamicConverter = new DynamicValueConverter(IsReflective, Worker.SourcePropertyType, Worker.TargetPropertyType);
}
return _dynamicConverter;
}
}
//-----------------------------------------------------
//
// Private Methods
//
//-----------------------------------------------------
#region Attachment
///
/// Attach the binding expression to the given target object and property.
///
internal override void AttachOverride(DependencyObject target, DependencyProperty dp)
{
base.AttachOverride(target, dp);
// listen for InheritanceContext change (if target is mentored)
if (ParentBinding.SourceReference == null || ParentBinding.SourceReference.UsesMentor)
{
DependencyObject mentor = Helper.FindMentor(target);
if (mentor != target)
{
InheritanceContextChangedEventManager.AddListener(target, this);
UsingMentor = true;
if (TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.Attach))
{
TraceData.Trace(TraceEventType.Warning,
TraceData.UseMentor(
TraceData.Identify(this),
TraceData.Identify(mentor)));
}
}
}
// listen for lost focus
if (IsUpdateOnLostFocus)
{
Invariant.Assert(!IsInMultiBindingExpression, "Source BindingExpressions of a MultiBindingExpression should never be UpdateOnLostFocus.");
LostFocusEventManager.AddListener(target, this);
}
// 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 (Status == BindingStatus.Unattached)
{
if (TraceLog != null)
{
string path = (ParentBinding.Path != null) ? ParentBinding.Path.Path : null;
TraceLog.Add("Defer attach to {0} of {1}, path = {2} xpath = {3}",
dp.Name, TraceLog.IdFor(target), path, ParentBinding.XPath);
}
Engine.AddTask(this, TaskOps.AttachToContext);
if (TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.AttachToContext))
{
TraceData.Trace(TraceEventType.Warning,
TraceData.DeferAttachToContext(
TraceData.Identify(this)));
}
}
GC.KeepAlive(target); // keep target alive during activation (bug 956831)
}
///
/// Detach the binding expression from its target object and property.
///
internal override void DetachOverride()
{
if (TraceLog != null)
{
TraceLog.Add("Detaching. status = {0} {1}", Status, new StackTrace());
}
Deactivate();
DetachFromContext();
// detach from target element
if (IsUpdateOnLostFocus)
{
LostFocusEventManager.RemoveListener(TargetElement, this);
}
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
if (TraceLog != null)
{
TraceLog.Add("AttachToContext target = {0}, attempt = {1}", TraceLog.IdFor(target), attempt);
}
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.Trace(TraceEventType.Warning,
TraceData.SourceRequiresTreeContext(
TraceData.Identify(this),
or.Identify()));
}
return;
}
}
bool lastChance = (attempt == AttachAttempt.Last);
if (isExtendedTraceEnabled)
{
TraceData.Trace(TraceEventType.Warning,
TraceData.AttachToContext(
TraceData.Identify(this),
lastChance ? " (last chance)" : String.Empty));
}
// 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.Trace(TraceEventType.Warning,
TraceData.PathRequiresTreeContext(
TraceData.Identify(this),
ParentBinding.Path.Path));
}
return;
}
}
// if the binding uses a mentor, check that it exists
DependencyObject mentor = !UsingMentor ? target : Helper.FindMentor(target);
if (mentor == null)
{
if (isExtendedTraceEnabled)
{
TraceData.Trace(TraceEventType.Warning,
TraceData.NoMentorExtended(
TraceData.Identify(this)));
}
if (lastChance)
{
SetStatus(BindingStatus.PathError);
if (TraceData.IsEnabled)
{
TraceData.Trace(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 and
// 2. if the target is ContentPresenter and the target property
// is Content, use the parent. This enables
//
// 3. if target is CVS, and its inheritance context was set
// via DataContext, use the mentor's parent. This enables
//
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, traceObjectRef);
contextElement = o as DependencyObject; // ref to another element's DataContext
contextElementFound = (o != DependencyProperty.UnsetValue);
}
}
if (isExtendedTraceEnabled)
{
TraceData.Trace(TraceEventType.Warning,
TraceData.ContextElement(
TraceData.Identify(this),
TraceData.Identify(contextElement),
contextElementFound ? "OK" : "error"));
}
// if we need a context element, check that we found it
if (!contextElementFound)
{
if (lastChance)
{
SetStatus(BindingStatus.PathError);
if (TraceData.IsEnabled)
{
TraceData.Trace(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.Trace(TraceEventType.Warning,
TraceData.NullDataContext(
TraceData.Identify(this)));
}
return;
}
}
else if ((sourceRef = ParentBinding.SourceReference) != null)
{
source = sourceRef.GetDataObject(mentor, traceObjectRef, ResolveNamesInTemplate);
// check that the source could be found
if (source == DependencyProperty.UnsetValue)
{
if (lastChance)
{
SetStatus(BindingStatus.PathError);
if (TraceData.IsEnabled)
{
TraceData.Trace(TraceLevel, TraceData.NoSource(sourceRef), this);
}
}
return;
}
}
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)
{
_valueConverter = ParentBinding.Converter;
if (_valueConverter == null)
{
throw new InvalidOperationException(SR.Get(SRID.MissingValueConverter)); // report instead of throw?
}
}
// join the right binding group (if any)
JoinBindingGroup(IsReflective, contextElement);
SetStatus(BindingStatus.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()
{
LeaveBindingGroup();
// detach from data source
if (_dataProvider != null)
{
if (TraceLog != null)
{
TraceLog.Add("-OnDataChanged to {0}", TraceLog.IdFor(_dataProvider));
}
DataChangedEventManager.RemoveListener(_dataProvider, this);
}
if (!UseDefaultValueConverter)
{
_valueConverter = null;
}
if (!IsInBindingExpressionCollection)
ChangeSources(null);
if (UsingMentor)
{
DependencyObject target = TargetElement;
if (target != null)
InheritanceContextChangedEventManager.RemoveListener(target, this);
}
_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 (Status == BindingStatus.Inactive)
{
DependencyObject target = TargetElement;
if (target != null)
{
if (UsingMentor)
{
target = Helper.FindMentor(target);
if (target == null)
{
// mentor is not available
SetStatus(BindingStatus.PathError);
if (TraceData.IsEnabled)
{
TraceData.Trace(TraceEventType.Error, TraceData.NoMentor, this);
}
return;
}
}
Activate(ParentBinding.SourceReference.GetDataObject(target, ResolveNamesInTemplate, false));
}
}
}
else
{
DependencyObject contextElement = ContextElement;
if (contextElement == null)
{
// context element has been GC'd, or unavailable (e.g. no mentor)
SetStatus(BindingStatus.PathError);
if (TraceData.IsEnabled)
{
TraceData.Trace(TraceEventType.Error, TraceData.NoDataContext, this);
}
return;
}
object item = contextElement.GetValue(FrameworkElement.DataContextProperty);
// if binding inactive or the data item has changed, (re-)activate
if (Status == BindingStatus.Inactive || !Object.Equals(item, DataItem))
{
Activate(item);
}
}
}
internal void Activate(object item)
{
DependencyObject target = TargetElement;
if (target == null)
return;
bool isExtendedTraceEnabled = TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.Activate);
Deactivate();
if (TraceLog != null)
TraceLog.Add("Activate item = {0}", TraceLog.IdFor(item));
// 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.Trace(TraceEventType.Warning,
TraceData.UseCVS(
TraceData.Identify(this),
TraceData.Identify(cvs)));
}
}
else
{
// when the source is DataSourceProvider, use its data instead
item = DereferenceDataProvider(item);
}
}
_dataItem = CreateReference(item);
if (isExtendedTraceEnabled)
{
TraceData.Trace(TraceEventType.Warning,
TraceData.ActivateItem(
TraceData.Identify(this),
TraceData.Identify(item)));
}
if (Worker == null)
CreateWorker();
// mark the BindingExpression active
SetStatus(BindingStatus.Active);
// attach to data item (may set error status)
Worker.AttachDataItem();
//
// initial transfer
if (!IsOneWayToSource)
{
TransferValue();
}
else
{
UpdateValue();
}
GC.KeepAlive(target); // keep target alive during activation (bug 956831)
}
internal override void Deactivate()
{
if (TraceLog != null)
TraceLog.Add("Deactivate");
// inactive BindingExpressions don't need any more work
if (Status == BindingStatus.Inactive)
return;
if (TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.Activate))
{
TraceData.Trace(TraceEventType.Warning,
TraceData.Deactivate(
TraceData.Identify(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(BindingStatus.Inactive);
}
// 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 dataProvider = item as DataSourceProvider;
if (dataProvider != _dataProvider)
{
// we have a new data provider - retarget the event handler
if (_dataProvider != null)
{
if (TraceLog != null)
{
TraceLog.Add("-OnDataChanged to {0}", TraceLog.IdFor(_dataProvider));
}
DataChangedEventManager.RemoveListener(_dataProvider, this);
}
_dataProvider = dataProvider;
if (TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.Activate))
{
TraceData.Trace(TraceEventType.Warning,
TraceData.UseDataProvider(
TraceData.Identify(this),
TraceData.Identify(_dataProvider)));
}
if (_dataProvider != null)
{
if (TraceLog != null)
{
TraceLog.Add("+OnDataChanged to {0}", TraceLog.IdFor(_dataProvider));
}
DataChangedEventManager.AddListener(_dataProvider, this);
_dataProvider.InitialLoad();
}
}
return (_dataProvider != null) ? _dataProvider.Data : item;
}
#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;
if (contextElement != null)
++size;
if (cvs != null)
++size;
WeakDependencySource[] newSources = (size > 0) ? new WeakDependencySource[size] : null;
if (contextElement != null)
{
newSources[offset++] = new WeakDependencySource(_ctxElement, FrameworkElement.DataContextProperty);
}
if (cvs != null)
{
WeakReference wr = _collectionViewSource as WeakReference;
newSources[offset++] =
(wr != null) ? new WeakDependencySource(wr, CollectionViewSource.ViewProperty)
: new WeakDependencySource(cvs, CollectionViewSource.ViewProperty);
}
// 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(NullDataItem, 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 = TargetProperty.PropertyType;
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 == NullDataItem) ? Worker.RawValue() : newValue;
if (isExtendedTraceEnabled)
{
TraceData.Trace(TraceEventType.Warning,
TraceData.GetRawValue(
TraceData.Identify(this),
TraceData.Identify(value)));
}
// 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 (isExtendedTraceEnabled)
{
TraceData.Trace(TraceEventType.Warning,
TraceData.UserConverter(
TraceData.Identify(this),
TraceData.Identify(value)));
}
// 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.Trace(TraceEventType.Warning,
TraceData.NullConverter(
TraceData.Identify(this),
TraceData.Identify(value)));
}
}
#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.Trace(TraceEventType.Warning,
TraceData.ConvertDBNull(
TraceData.Identify(this),
TraceData.Identify(value)));
}
}
#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.Trace(TraceEventType.Warning,
TraceData.DefaultConverter(
TraceData.Identify(this),
TraceData.Identify(value)));
}
}
}
if (
#if !TargetNullValueBC //BreakingChange
!doNotSetStatus &&
#endif
value == DependencyProperty.UnsetValue)
{
SetStatus(BindingStatus.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.Trace(TraceLevel, TraceData.BadValueAtTransfer, value, this);
}
if (isExtendedTraceEnabled)
{
TraceData.Trace(TraceEventType.Warning,
TraceData.BadValueAtTransferExtended(
TraceData.Identify(this),
TraceData.Identify(value)));
}
value = DependencyProperty.UnsetValue;
if (Status == BindingStatus.Active)
SetStatus(BindingStatus.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.Trace(TraceEventType.Warning,
TraceData.UseFallback(
TraceData.Identify(this),
TraceData.Identify(value)));
}
}
// 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.Trace(TraceEventType.Warning,
TraceData.TransferValue(
TraceData.Identify(this),
TraceData.Identify(value)));
}
// update the cached value
ChangeValue(value, true);
Invalidate(isASubPropertyChange);
OnTargetUpdated();
ValidateOnTargetUpdated();
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 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:
if (itemValue == DependencyProperty.UnsetValue)
{
itemValue = Worker.RawValue();
}
value = itemValue;
break;
case ValidationStep.UpdatedValue:
case ValidationStep.CommittedValue:
value = this;
break;
default:
throw new InvalidOperationException(SR.Get(SRID.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);
if (validationResult.IsValid)
{
error = null;
}
else
{
if (TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.Update))
{
TraceData.Trace(TraceEventType.Warning,
TraceData.ValidationRuleFailed(
TraceData.Identify(this),
TraceData.Identify(validationRule)));
}
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 IsCriticalException() 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.IsCriticalException(ex))
throw;
if (TraceData.IsEnabled)
{
string name = String.IsNullOrEmpty(stringFormat) ? converter.GetType().Name : "StringFormat";
TraceData.Trace(TraceLevel,
TraceData.BadConverterForTransfer(
name,
AvTrace.ToStringHelper(value),
AvTrace.TypeName(value)),
this, ex);
}
convertedValue = DependencyProperty.UnsetValue;
}
catch // non CLS compliant exception
{
if (TraceData.IsEnabled)
{
TraceData.Trace(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 IsCriticalException() 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
if (CriticalExceptions.IsCriticalException(ex))
throw;
if (TraceData.IsEnabled)
{
TraceData.Trace(TraceEventType.Error,
TraceData.BadConverterForUpdate(
AvTrace.ToStringHelper(Value),
AvTrace.TypeName(value)),
this, ex);
}
ProcessException(ex);
convertedValue = DependencyProperty.UnsetValue;
}
catch // non CLS compliant exception
{
if (TraceData.IsEnabled)
{
TraceData.Trace(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(NullDataItem, isASubPropertyChange);
}
void OnTargetUpdated()
{
if (NotifyOnTargetUpdated)
{
DependencyObject target = TargetElement;
if (target != null)
{
if ( !IsInMultiBindingExpression // not an inner BindingExpression
&& ( !IsInPriorityBindingExpression
|| this == ParentPriorityBindingExpression.ActiveBindingExpression)) // not an inactive BindingExpression
{
// 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 && this == 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);
}
}
}
}
// transfer a value from target to source
internal override void Update(bool synchronous)
{
// 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;
if (synchronous)
{
UpdateValue();
}
else
{
Engine.AddTask(this, TaskOps.UpdateValue);
}
}
internal override object ConvertProposedValue(object value)
{
bool isExtendedTraceEnabled = TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.Update);
if (isExtendedTraceEnabled)
{
TraceData.Trace(TraceEventType.Warning,
TraceData.UpdateRawValue(
TraceData.Identify(this),
TraceData.Identify(value)));
}
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 (isExtendedTraceEnabled)
{
TraceData.Trace(TraceEventType.Warning,
TraceData.UserConvertBack(
TraceData.Identify(this),
TraceData.Identify(value)));
}
// 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 (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.Trace(TraceEventType.Warning,
TraceData.DefaultConvertBack(
TraceData.Identify(this),
TraceData.Identify(value)));
}
}
}
if (isExtendedTraceEnabled)
{
TraceData.Trace(TraceEventType.Warning,
TraceData.Update(
TraceData.Identify(this),
TraceData.Identify(value)));
}
return value;
}
///
/// Get the converted proposed value and inform the binding group
///
internal override void ObtainConvertedProposedValue(BindingGroup bindingGroup)
{
object value;
if (NeedsUpdate)
{
value = bindingGroup.GetValue(this);
if (value != DependencyProperty.UnsetValue)
{
value = ConvertProposedValue(value);
}
}
else
{
value = BindingGroup.DeferredSourceValue;
}
bindingGroup.SetValue(this, value);
}
internal override object UpdateSource(object value)
{
// If there is a failure to convert, then Update failed.
if (value == DependencyProperty.UnsetValue)
{
SetStatus(BindingStatus.UpdateSourceError);
}
if (value == Binding.DoNothing || value == DependencyProperty.UnsetValue)
{
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 IsCriticalException() 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)
{
if (CriticalExceptions.IsCriticalException(ex))
throw;
if (TraceData.IsEnabled)
TraceData.Trace(TraceEventType.Error, TraceData.WorkerUpdateFailed, this, ex);
ProcessException(ex);
SetStatus(BindingStatus.UpdateSourceError);
}
catch // non CLS compliant exception
{
if (TraceData.IsEnabled)
TraceData.Trace(TraceEventType.Error, TraceData.WorkerUpdateFailed, this);
SetStatus(BindingStatus.UpdateSourceError);
}
finally
{
EndSourceUpdate();
}
#pragma warning restore 56500
#pragma warning restore 1634, 1691
OnSourceUpdated();
return value;
}
///
/// Update the source value and inform the binding group
///
internal override void UpdateSource(BindingGroup bindingGroup)
{
if (NeedsUpdate)
{
object value = bindingGroup.GetValue(this);
value = UpdateSource(value);
bindingGroup.SetValue(this, value);
}
}
///
/// Store the value in the binding group
///
internal override void StoreValueInBindingGroup(object value, BindingGroup bindingGroup)
{
bindingGroup.SetValue(this, value);
}
///
/// Run validation rules for the given step
///
internal override bool Validate(object value, ValidationStep validationStep)
{
// run rules attached to this binding
bool result = base.Validate(value, validationStep);
if (result && validationStep == ValidationStep.CommittedValue)
{
if (ParentBinding.ValidatesOnDataErrors)
{
// 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;
}
}
if (result)
{
NeedsValidation = false;
}
}
return result;
}
///
/// Run validation rules for the given step, and inform the binding group
///
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.Get(SRID.ValidationRule_UnknownStep, validationStep, bindingGroup));
}
return Validate(value, validationStep);
}
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)
{
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 && ValidatesOnExceptions)
{
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)
{
// use MarkInvalid rather than UpdateValidationErrors because
// this is essentially an explicit invalidation as this BindingExpression has
// already passed all of the declared ValidationRules
Validation.MarkInvalid(this, validationError);
}
}
#endregion Value
#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)
{
object newItem = contextElement.GetValue(FrameworkElement.DataContextProperty);
if (!Object.Equals(DataItem, newItem))
Activate(newItem);
}
}
///
/// Handle events from the centralized event table
///
bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
{
if (TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.Events))
{
TraceData.Trace(TraceEventType.Warning,
TraceData.GotEvent(
TraceData.Identify(this),
TraceData.IdentifyWeakEvent(managerType),
TraceData.Identify(sender)));
}
if (managerType == typeof(CurrentChangingEventManager))
{
Update(true);
//
}
else if (managerType == typeof(CurrentChangedEventManager))
{
Worker.OnCurrentChanged(sender as ICollectionView, e);
}
else if (managerType == typeof(DataChangedEventManager))
{
if (TraceLog != null)
{
TraceLog.Add(" OnDataChanged from {0} at {1}", TraceLog.IdFor(_dataProvider), TraceLog.IdFor(this));
}
Activate(sender);
}
else if (managerType == typeof(LostFocusEventManager))
{
Update(true);
}
else if (managerType == typeof(InheritanceContextChangedEventManager))
{
if (Status == BindingStatus.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).
AttachToContext(AttachAttempt.Again);
if (Status != BindingStatus.Unattached)
{
// if that worked, we don't need to run the task again
Engine.CancelTask(this, TaskOps.AttachToContext);
}
}
else
{
AttachToContext(AttachAttempt.Last);
}
}
else
{
return false; // unrecognized event
}
return true;
}
#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);
}
// invalidate the target property
void Invalidate(bool isASubPropertyChange)
{
// don't invalidate during Attach. The property engine does it
// already, and it would interfere with the on-demand activation
// of style-defined BindingExpressions.
if (IsAttaching)
return;
DependencyObject target = TargetElement;
if (target != null)
{
if (IsInBindingExpressionCollection)
ParentBindingExpressionBase.InvalidateChild(this);
else
{
if (TargetProperty != NoTargetProperty)
{
// recompute expression
if (!isASubPropertyChange)
{
target.InvalidateProperty(TargetProperty);
}
else
{
target.NotifySubPropertyChange(TargetProperty);
}
}
}
}
}
// 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);
}
private object HandlePropertyInvalidationOperation(object o)
{
// This is the case where the source of the Binding belonged to a different Dispatcher
// than the target. For this scenario the source marshals off the invalidation information
// onto the target's Dispatcher queue. This is where we unpack the marshalled information
// to fire the invalidation on the target object.
object[] args = (object[])o;
HandlePropertyInvalidation((DependencyObject)args[0], (DependencyPropertyChangedEventArgs)args[1]);
return null;
}
private void HandlePropertyInvalidation(DependencyObject d, DependencyPropertyChangedEventArgs args)
{
DependencyProperty dp = args.Property;
if (TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.Events))
{
TraceData.Trace(TraceEventType.Warning,
TraceData.GotPropertyChanged(
TraceData.Identify(this),
TraceData.Identify(d),
dp.Name));
}
if (dp == FrameworkElement.DataContextProperty)
{
DependencyObject contextElement = ContextElement;
if (d == contextElement)
{
OnDataContextChanged(contextElement);
}
}
if (dp == CollectionViewSource.ViewProperty)
{
CollectionViewSource cvs = this.CollectionViewSource;
if (d == cvs)
{
Activate(cvs);
}
}
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();
}
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;
IValueConverter _valueConverter;
Type _sourceType;
DataSourceProvider _dataProvider;
object _collectionViewSource;
DynamicValueConverter _dynamicConverter;
internal static readonly object NullDataItem = new NamedObject("NullDataItem");
internal static readonly object IgnoreDefaultValue = new NamedObject("IgnoreDefaultValue");
}
}
// 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 BindingExpression object, the run-time instance of data binding.
//
// See spec at http://avalon/connecteddata/Specs/Data%20Binding.mht
//
//---------------------------------------------------------------------------
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.ComponentModel;
using System.Globalization;
using System.Windows.Threading;
using System.Threading;
using System.Xml;
using System.Windows;
using System.Windows.Input;
using System.Windows.Controls;
using System.Windows.Markup;
using MS.Utility;
using MS.Internal;
using MS.Internal.Controls; // Validation
using MS.Internal.Data;
using MS.Internal.KnownBoxes;
using MS.Internal.Utility; // TraceLog
namespace System.Windows.Data
{
///
/// 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.
///
public delegate object UpdateSourceExceptionFilterCallback(object bindExpression, Exception exception);
///
/// Describes a single run-time instance of data binding, binding a target
/// (element, DependencyProperty) to a source (object, property, XML node)
///
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.Trace(TraceEventType.Warning,
TraceData.BindingPath(
TraceData.Identify(path)));
}
else
{
TraceData.Trace(TraceEventType.Warning,
TraceData.BindingXPathAndPath(
TraceData.Identify(binding.XPath),
TraceData.Identify(path)));
}
}
}
//------------------------------------------------------
//
// Interfaces
//
//------------------------------------------------------
void IDataBindEngineClient.TransferValue()
{
TransferValue();
}
void IDataBindEngineClient.UpdateValue()
{
UpdateValue();
}
bool IDataBindEngineClient.AttachToContext(bool lastChance)
{
AttachToContext(lastChance ? AttachAttempt.Last : AttachAttempt.Again);
return (Status != BindingStatus.Unattached);
}
void IDataBindEngineClient.OnTargetUpdated()
{
OnTargetUpdated();
}
DependencyObject IDataBindEngineClient.TargetElement
{
get { return TargetElement; }
}
//-----------------------------------------------------
//
// Public Properties
//
//------------------------------------------------------
/// Binding from which this expression was created
public Binding ParentBinding { get { return (Binding)ParentBindingBase; } }
/// The data item actually used by this BindingExpression
public object DataItem { get { return GetReference(_dataItem); } }
/// The data source actually used by this BindingExpression
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, false);
}
}
//-----------------------------------------------------
//
// Public Methods
//
//-----------------------------------------------------
/// Send the current value back to the source
/// Does nothing when binding's Mode is not TwoWay or OneWayToSource
public override void UpdateSource()
{
if (Status == BindingStatus.Detached)
throw new InvalidOperationException(SR.Get(SRID.BindingExpressionIsDetached));
NeedsUpdate = true; // force update
Update(true); // update synchronously
}
/// Force a data transfer from source to target
public override void UpdateTarget()
{
if (Status == BindingStatus.Detached)
throw new InvalidOperationException(SR.Get(SRID.BindingExpressionIsDetached));
if (Worker != null)
{
Worker.RefreshValue(); // calls TransferValue
}
}
#region Expression overrides
///
/// Called to evaluate the Expression value
///
/// DependencyObject being queried
/// Property being queried
/// Computed value. Default (of the target) if unavailable.
internal override object GetValue(DependencyObject d, DependencyProperty dp)
{
return Value;
}
///
/// Allows Expression to store set values
///
/// DependencyObject being set
/// Property being set
/// Value being set
/// true if Expression handled storing of the value
internal override bool SetValue(DependencyObject d, DependencyProperty dp, object value)
{
if (IsReflective)
{
Value = value;
return true;
}
else
{
// if the binding doesn't push values back to the source, allow
// SetValue to overwrite the binding with a local value
return false;
}
}
///
/// Notification that a Dependent that this Expression established has
/// been invalidated as a result of a Source invalidation
///
/// DependencyObject that was invalidated
/// Changed event args for the property that was invalidated
internal override void OnPropertyInvalidation(DependencyObject d, DependencyPropertyChangedEventArgs args)
{
if (d == null)
throw new ArgumentNullException("d");
DependencyProperty dp = args.Property;
if (dp == null)
throw new ArgumentNullException("dp");
// 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 (relevant)
{
relevant = (Worker != null) && (Worker.UsesDependencyProperty(d, dp));
}
if (!relevant)
return;
// if the notification arrived on the right Dispatcher, handle it now.
if (Dispatcher.Thread == Thread.CurrentThread)
{
HandlePropertyInvalidation(d, args);
}
else // Otherwise, marshal it to the right Dispatcher.
{
IsTransferPending = true;
Dispatcher.BeginInvoke(
DispatcherPriority.DataBind,
new DispatcherOperationCallback(HandlePropertyInvalidationOperation),
new object[]{d, args});
}
}
#endregion Expression overrides
//-----------------------------------------------------
//
// Protected Methods
//
//------------------------------------------------------
///
/// Invalidate the given child expression.
///
internal override void InvalidateChild(BindingExpressionBase bindingExpression)
{
// BindingExpression does not support child bindings
}
///
/// Change the dependency sources for the given child expression.
///
internal override void ChangeSourcesForChild(BindingExpressionBase bindingExpression, WeakDependencySource[] newSources)
{
// BindingExpression does not support child bindings
}
///
/// Replace the given child expression with a new one.
///
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 { return (CollectionViewSource)GetReference(_collectionViewSource); }
set { _collectionViewSource = CreateReference(value); }
}
/// True if this binding expression should ignore changes from the source
internal bool IgnoreSourcePropertyChange
{
get
{
if (IsTransferPending)
return true;
if (IsInUpdate)
{
// allow changes from an update to flow back to
// the target if there's a converter or string format, and
// the binding isn't in "immediate" mode (bug 1207214)
return ((Converter == null && EffectiveStringFormat == null) || IsUpdateOnPropertyChanged);
}
return false;
}
}
internal PropertyPath Path
{
get { return ParentBinding.Path; }
}
internal IValueConverter Converter
{
get { return _valueConverter; }
set { _valueConverter = value; }
}
// 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 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.Get(SRID.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.Get(SRID.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)
{
// this method is called when the last item in the path is replaced.
// BindingGroup also wants to know about this.
BindingGroup bindingGroup = BindingGroup;
if (bindingGroup != null)
{
bindingGroup.UpdateTable(this);
}
if (!UseDefaultValueConverter)
return;
if (IsInMultiBindingExpression)
{
Converter = null;
_sourceType = type;
}
else if (type != null && type != _sourceType)
{
_sourceType = type;
Converter = Engine.GetDefaultValueConverter(_sourceType,
TargetProperty.PropertyType, IsReflective);
// null converter means failure to create one
if (Converter == null && TraceData.IsEnabled)
{
TraceData.Trace(TraceEventType.Error,
TraceData.CannotCreateDefaultValueConverter(
_sourceType,
TargetProperty.PropertyType,
(IsReflective ? "two-way" : "one-way")),
this );
}
if (Converter == DefaultValueConverter.ValueConverterNotNeeded)
{
Converter = null; // everyone else takes null for an answer.
}
}
}
// return true if DataContext is set locally (not inherited) on DependencyObject
internal static bool HasLocalDataContext(DependencyObject d)
{
return (d.ReadLocalValue(FrameworkElement.DataContextProperty) != DependencyProperty.UnsetValue);
}
//------------------------------------------------------
//
// Private Properties
//
//-----------------------------------------------------
private bool CanActivate
{
get { return Status != BindingStatus.Unattached; }
}
private BindingWorker Worker { get { return _worker; } }
private DynamicValueConverter DynamicConverter
{
get
{
if (_dynamicConverter == null)
{
Invariant.Assert(Worker != null);
// pass along the static source and target types to find same DefaultValueConverter as SetupDefaultValueConverter
_dynamicConverter = new DynamicValueConverter(IsReflective, Worker.SourcePropertyType, Worker.TargetPropertyType);
}
return _dynamicConverter;
}
}
//-----------------------------------------------------
//
// Private Methods
//
//-----------------------------------------------------
#region Attachment
///
/// Attach the binding expression to the given target object and property.
///
internal override void AttachOverride(DependencyObject target, DependencyProperty dp)
{
base.AttachOverride(target, dp);
// listen for InheritanceContext change (if target is mentored)
if (ParentBinding.SourceReference == null || ParentBinding.SourceReference.UsesMentor)
{
DependencyObject mentor = Helper.FindMentor(target);
if (mentor != target)
{
InheritanceContextChangedEventManager.AddListener(target, this);
UsingMentor = true;
if (TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.Attach))
{
TraceData.Trace(TraceEventType.Warning,
TraceData.UseMentor(
TraceData.Identify(this),
TraceData.Identify(mentor)));
}
}
}
// listen for lost focus
if (IsUpdateOnLostFocus)
{
Invariant.Assert(!IsInMultiBindingExpression, "Source BindingExpressions of a MultiBindingExpression should never be UpdateOnLostFocus.");
LostFocusEventManager.AddListener(target, this);
}
// 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 (Status == BindingStatus.Unattached)
{
if (TraceLog != null)
{
string path = (ParentBinding.Path != null) ? ParentBinding.Path.Path : null;
TraceLog.Add("Defer attach to {0} of {1}, path = {2} xpath = {3}",
dp.Name, TraceLog.IdFor(target), path, ParentBinding.XPath);
}
Engine.AddTask(this, TaskOps.AttachToContext);
if (TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.AttachToContext))
{
TraceData.Trace(TraceEventType.Warning,
TraceData.DeferAttachToContext(
TraceData.Identify(this)));
}
}
GC.KeepAlive(target); // keep target alive during activation (bug 956831)
}
///
/// Detach the binding expression from its target object and property.
///
internal override void DetachOverride()
{
if (TraceLog != null)
{
TraceLog.Add("Detaching. status = {0} {1}", Status, new StackTrace());
}
Deactivate();
DetachFromContext();
// detach from target element
if (IsUpdateOnLostFocus)
{
LostFocusEventManager.RemoveListener(TargetElement, this);
}
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
if (TraceLog != null)
{
TraceLog.Add("AttachToContext target = {0}, attempt = {1}", TraceLog.IdFor(target), attempt);
}
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.Trace(TraceEventType.Warning,
TraceData.SourceRequiresTreeContext(
TraceData.Identify(this),
or.Identify()));
}
return;
}
}
bool lastChance = (attempt == AttachAttempt.Last);
if (isExtendedTraceEnabled)
{
TraceData.Trace(TraceEventType.Warning,
TraceData.AttachToContext(
TraceData.Identify(this),
lastChance ? " (last chance)" : String.Empty));
}
// 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.Trace(TraceEventType.Warning,
TraceData.PathRequiresTreeContext(
TraceData.Identify(this),
ParentBinding.Path.Path));
}
return;
}
}
// if the binding uses a mentor, check that it exists
DependencyObject mentor = !UsingMentor ? target : Helper.FindMentor(target);
if (mentor == null)
{
if (isExtendedTraceEnabled)
{
TraceData.Trace(TraceEventType.Warning,
TraceData.NoMentorExtended(
TraceData.Identify(this)));
}
if (lastChance)
{
SetStatus(BindingStatus.PathError);
if (TraceData.IsEnabled)
{
TraceData.Trace(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 and
// 2. if the target is ContentPresenter and the target property
// is Content, use the parent. This enables
//
// 3. if target is CVS, and its inheritance context was set
// via DataContext, use the mentor's parent. This enables
//
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, traceObjectRef);
contextElement = o as DependencyObject; // ref to another element's DataContext
contextElementFound = (o != DependencyProperty.UnsetValue);
}
}
if (isExtendedTraceEnabled)
{
TraceData.Trace(TraceEventType.Warning,
TraceData.ContextElement(
TraceData.Identify(this),
TraceData.Identify(contextElement),
contextElementFound ? "OK" : "error"));
}
// if we need a context element, check that we found it
if (!contextElementFound)
{
if (lastChance)
{
SetStatus(BindingStatus.PathError);
if (TraceData.IsEnabled)
{
TraceData.Trace(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.Trace(TraceEventType.Warning,
TraceData.NullDataContext(
TraceData.Identify(this)));
}
return;
}
}
else if ((sourceRef = ParentBinding.SourceReference) != null)
{
source = sourceRef.GetDataObject(mentor, traceObjectRef, ResolveNamesInTemplate);
// check that the source could be found
if (source == DependencyProperty.UnsetValue)
{
if (lastChance)
{
SetStatus(BindingStatus.PathError);
if (TraceData.IsEnabled)
{
TraceData.Trace(TraceLevel, TraceData.NoSource(sourceRef), this);
}
}
return;
}
}
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)
{
_valueConverter = ParentBinding.Converter;
if (_valueConverter == null)
{
throw new InvalidOperationException(SR.Get(SRID.MissingValueConverter)); // report instead of throw?
}
}
// join the right binding group (if any)
JoinBindingGroup(IsReflective, contextElement);
SetStatus(BindingStatus.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()
{
LeaveBindingGroup();
// detach from data source
if (_dataProvider != null)
{
if (TraceLog != null)
{
TraceLog.Add("-OnDataChanged to {0}", TraceLog.IdFor(_dataProvider));
}
DataChangedEventManager.RemoveListener(_dataProvider, this);
}
if (!UseDefaultValueConverter)
{
_valueConverter = null;
}
if (!IsInBindingExpressionCollection)
ChangeSources(null);
if (UsingMentor)
{
DependencyObject target = TargetElement;
if (target != null)
InheritanceContextChangedEventManager.RemoveListener(target, this);
}
_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 (Status == BindingStatus.Inactive)
{
DependencyObject target = TargetElement;
if (target != null)
{
if (UsingMentor)
{
target = Helper.FindMentor(target);
if (target == null)
{
// mentor is not available
SetStatus(BindingStatus.PathError);
if (TraceData.IsEnabled)
{
TraceData.Trace(TraceEventType.Error, TraceData.NoMentor, this);
}
return;
}
}
Activate(ParentBinding.SourceReference.GetDataObject(target, ResolveNamesInTemplate, false));
}
}
}
else
{
DependencyObject contextElement = ContextElement;
if (contextElement == null)
{
// context element has been GC'd, or unavailable (e.g. no mentor)
SetStatus(BindingStatus.PathError);
if (TraceData.IsEnabled)
{
TraceData.Trace(TraceEventType.Error, TraceData.NoDataContext, this);
}
return;
}
object item = contextElement.GetValue(FrameworkElement.DataContextProperty);
// if binding inactive or the data item has changed, (re-)activate
if (Status == BindingStatus.Inactive || !Object.Equals(item, DataItem))
{
Activate(item);
}
}
}
internal void Activate(object item)
{
DependencyObject target = TargetElement;
if (target == null)
return;
bool isExtendedTraceEnabled = TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.Activate);
Deactivate();
if (TraceLog != null)
TraceLog.Add("Activate item = {0}", TraceLog.IdFor(item));
// 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.Trace(TraceEventType.Warning,
TraceData.UseCVS(
TraceData.Identify(this),
TraceData.Identify(cvs)));
}
}
else
{
// when the source is DataSourceProvider, use its data instead
item = DereferenceDataProvider(item);
}
}
_dataItem = CreateReference(item);
if (isExtendedTraceEnabled)
{
TraceData.Trace(TraceEventType.Warning,
TraceData.ActivateItem(
TraceData.Identify(this),
TraceData.Identify(item)));
}
if (Worker == null)
CreateWorker();
// mark the BindingExpression active
SetStatus(BindingStatus.Active);
// attach to data item (may set error status)
Worker.AttachDataItem();
//
// initial transfer
if (!IsOneWayToSource)
{
TransferValue();
}
else
{
UpdateValue();
}
GC.KeepAlive(target); // keep target alive during activation (bug 956831)
}
internal override void Deactivate()
{
if (TraceLog != null)
TraceLog.Add("Deactivate");
// inactive BindingExpressions don't need any more work
if (Status == BindingStatus.Inactive)
return;
if (TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.Activate))
{
TraceData.Trace(TraceEventType.Warning,
TraceData.Deactivate(
TraceData.Identify(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(BindingStatus.Inactive);
}
// 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 dataProvider = item as DataSourceProvider;
if (dataProvider != _dataProvider)
{
// we have a new data provider - retarget the event handler
if (_dataProvider != null)
{
if (TraceLog != null)
{
TraceLog.Add("-OnDataChanged to {0}", TraceLog.IdFor(_dataProvider));
}
DataChangedEventManager.RemoveListener(_dataProvider, this);
}
_dataProvider = dataProvider;
if (TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.Activate))
{
TraceData.Trace(TraceEventType.Warning,
TraceData.UseDataProvider(
TraceData.Identify(this),
TraceData.Identify(_dataProvider)));
}
if (_dataProvider != null)
{
if (TraceLog != null)
{
TraceLog.Add("+OnDataChanged to {0}", TraceLog.IdFor(_dataProvider));
}
DataChangedEventManager.AddListener(_dataProvider, this);
_dataProvider.InitialLoad();
}
}
return (_dataProvider != null) ? _dataProvider.Data : item;
}
#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;
if (contextElement != null)
++size;
if (cvs != null)
++size;
WeakDependencySource[] newSources = (size > 0) ? new WeakDependencySource[size] : null;
if (contextElement != null)
{
newSources[offset++] = new WeakDependencySource(_ctxElement, FrameworkElement.DataContextProperty);
}
if (cvs != null)
{
WeakReference wr = _collectionViewSource as WeakReference;
newSources[offset++] =
(wr != null) ? new WeakDependencySource(wr, CollectionViewSource.ViewProperty)
: new WeakDependencySource(cvs, CollectionViewSource.ViewProperty);
}
// 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(NullDataItem, 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 = TargetProperty.PropertyType;
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 == NullDataItem) ? Worker.RawValue() : newValue;
if (isExtendedTraceEnabled)
{
TraceData.Trace(TraceEventType.Warning,
TraceData.GetRawValue(
TraceData.Identify(this),
TraceData.Identify(value)));
}
// 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 (isExtendedTraceEnabled)
{
TraceData.Trace(TraceEventType.Warning,
TraceData.UserConverter(
TraceData.Identify(this),
TraceData.Identify(value)));
}
// 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.Trace(TraceEventType.Warning,
TraceData.NullConverter(
TraceData.Identify(this),
TraceData.Identify(value)));
}
}
#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.Trace(TraceEventType.Warning,
TraceData.ConvertDBNull(
TraceData.Identify(this),
TraceData.Identify(value)));
}
}
#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.Trace(TraceEventType.Warning,
TraceData.DefaultConverter(
TraceData.Identify(this),
TraceData.Identify(value)));
}
}
}
if (
#if !TargetNullValueBC //BreakingChange
!doNotSetStatus &&
#endif
value == DependencyProperty.UnsetValue)
{
SetStatus(BindingStatus.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.Trace(TraceLevel, TraceData.BadValueAtTransfer, value, this);
}
if (isExtendedTraceEnabled)
{
TraceData.Trace(TraceEventType.Warning,
TraceData.BadValueAtTransferExtended(
TraceData.Identify(this),
TraceData.Identify(value)));
}
value = DependencyProperty.UnsetValue;
if (Status == BindingStatus.Active)
SetStatus(BindingStatus.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.Trace(TraceEventType.Warning,
TraceData.UseFallback(
TraceData.Identify(this),
TraceData.Identify(value)));
}
}
// 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.Trace(TraceEventType.Warning,
TraceData.TransferValue(
TraceData.Identify(this),
TraceData.Identify(value)));
}
// update the cached value
ChangeValue(value, true);
Invalidate(isASubPropertyChange);
OnTargetUpdated();
ValidateOnTargetUpdated();
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 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:
if (itemValue == DependencyProperty.UnsetValue)
{
itemValue = Worker.RawValue();
}
value = itemValue;
break;
case ValidationStep.UpdatedValue:
case ValidationStep.CommittedValue:
value = this;
break;
default:
throw new InvalidOperationException(SR.Get(SRID.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);
if (validationResult.IsValid)
{
error = null;
}
else
{
if (TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.Update))
{
TraceData.Trace(TraceEventType.Warning,
TraceData.ValidationRuleFailed(
TraceData.Identify(this),
TraceData.Identify(validationRule)));
}
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 IsCriticalException() 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.IsCriticalException(ex))
throw;
if (TraceData.IsEnabled)
{
string name = String.IsNullOrEmpty(stringFormat) ? converter.GetType().Name : "StringFormat";
TraceData.Trace(TraceLevel,
TraceData.BadConverterForTransfer(
name,
AvTrace.ToStringHelper(value),
AvTrace.TypeName(value)),
this, ex);
}
convertedValue = DependencyProperty.UnsetValue;
}
catch // non CLS compliant exception
{
if (TraceData.IsEnabled)
{
TraceData.Trace(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 IsCriticalException() 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
if (CriticalExceptions.IsCriticalException(ex))
throw;
if (TraceData.IsEnabled)
{
TraceData.Trace(TraceEventType.Error,
TraceData.BadConverterForUpdate(
AvTrace.ToStringHelper(Value),
AvTrace.TypeName(value)),
this, ex);
}
ProcessException(ex);
convertedValue = DependencyProperty.UnsetValue;
}
catch // non CLS compliant exception
{
if (TraceData.IsEnabled)
{
TraceData.Trace(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(NullDataItem, isASubPropertyChange);
}
void OnTargetUpdated()
{
if (NotifyOnTargetUpdated)
{
DependencyObject target = TargetElement;
if (target != null)
{
if ( !IsInMultiBindingExpression // not an inner BindingExpression
&& ( !IsInPriorityBindingExpression
|| this == ParentPriorityBindingExpression.ActiveBindingExpression)) // not an inactive BindingExpression
{
// 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 && this == 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);
}
}
}
}
// transfer a value from target to source
internal override void Update(bool synchronous)
{
// 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;
if (synchronous)
{
UpdateValue();
}
else
{
Engine.AddTask(this, TaskOps.UpdateValue);
}
}
internal override object ConvertProposedValue(object value)
{
bool isExtendedTraceEnabled = TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.Update);
if (isExtendedTraceEnabled)
{
TraceData.Trace(TraceEventType.Warning,
TraceData.UpdateRawValue(
TraceData.Identify(this),
TraceData.Identify(value)));
}
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 (isExtendedTraceEnabled)
{
TraceData.Trace(TraceEventType.Warning,
TraceData.UserConvertBack(
TraceData.Identify(this),
TraceData.Identify(value)));
}
// 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 (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.Trace(TraceEventType.Warning,
TraceData.DefaultConvertBack(
TraceData.Identify(this),
TraceData.Identify(value)));
}
}
}
if (isExtendedTraceEnabled)
{
TraceData.Trace(TraceEventType.Warning,
TraceData.Update(
TraceData.Identify(this),
TraceData.Identify(value)));
}
return value;
}
///
/// Get the converted proposed value and inform the binding group
///
internal override void ObtainConvertedProposedValue(BindingGroup bindingGroup)
{
object value;
if (NeedsUpdate)
{
value = bindingGroup.GetValue(this);
if (value != DependencyProperty.UnsetValue)
{
value = ConvertProposedValue(value);
}
}
else
{
value = BindingGroup.DeferredSourceValue;
}
bindingGroup.SetValue(this, value);
}
internal override object UpdateSource(object value)
{
// If there is a failure to convert, then Update failed.
if (value == DependencyProperty.UnsetValue)
{
SetStatus(BindingStatus.UpdateSourceError);
}
if (value == Binding.DoNothing || value == DependencyProperty.UnsetValue)
{
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 IsCriticalException() 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)
{
if (CriticalExceptions.IsCriticalException(ex))
throw;
if (TraceData.IsEnabled)
TraceData.Trace(TraceEventType.Error, TraceData.WorkerUpdateFailed, this, ex);
ProcessException(ex);
SetStatus(BindingStatus.UpdateSourceError);
}
catch // non CLS compliant exception
{
if (TraceData.IsEnabled)
TraceData.Trace(TraceEventType.Error, TraceData.WorkerUpdateFailed, this);
SetStatus(BindingStatus.UpdateSourceError);
}
finally
{
EndSourceUpdate();
}
#pragma warning restore 56500
#pragma warning restore 1634, 1691
OnSourceUpdated();
return value;
}
///
/// Update the source value and inform the binding group
///
internal override void UpdateSource(BindingGroup bindingGroup)
{
if (NeedsUpdate)
{
object value = bindingGroup.GetValue(this);
value = UpdateSource(value);
bindingGroup.SetValue(this, value);
}
}
///
/// Store the value in the binding group
///
internal override void StoreValueInBindingGroup(object value, BindingGroup bindingGroup)
{
bindingGroup.SetValue(this, value);
}
///
/// Run validation rules for the given step
///
internal override bool Validate(object value, ValidationStep validationStep)
{
// run rules attached to this binding
bool result = base.Validate(value, validationStep);
if (result && validationStep == ValidationStep.CommittedValue)
{
if (ParentBinding.ValidatesOnDataErrors)
{
// 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;
}
}
if (result)
{
NeedsValidation = false;
}
}
return result;
}
///
/// Run validation rules for the given step, and inform the binding group
///
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.Get(SRID.ValidationRule_UnknownStep, validationStep, bindingGroup));
}
return Validate(value, validationStep);
}
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)
{
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 && ValidatesOnExceptions)
{
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)
{
// use MarkInvalid rather than UpdateValidationErrors because
// this is essentially an explicit invalidation as this BindingExpression has
// already passed all of the declared ValidationRules
Validation.MarkInvalid(this, validationError);
}
}
#endregion Value
#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)
{
object newItem = contextElement.GetValue(FrameworkElement.DataContextProperty);
if (!Object.Equals(DataItem, newItem))
Activate(newItem);
}
}
///
/// Handle events from the centralized event table
///
bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
{
if (TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.Events))
{
TraceData.Trace(TraceEventType.Warning,
TraceData.GotEvent(
TraceData.Identify(this),
TraceData.IdentifyWeakEvent(managerType),
TraceData.Identify(sender)));
}
if (managerType == typeof(CurrentChangingEventManager))
{
Update(true);
//
}
else if (managerType == typeof(CurrentChangedEventManager))
{
Worker.OnCurrentChanged(sender as ICollectionView, e);
}
else if (managerType == typeof(DataChangedEventManager))
{
if (TraceLog != null)
{
TraceLog.Add(" OnDataChanged from {0} at {1}", TraceLog.IdFor(_dataProvider), TraceLog.IdFor(this));
}
Activate(sender);
}
else if (managerType == typeof(LostFocusEventManager))
{
Update(true);
}
else if (managerType == typeof(InheritanceContextChangedEventManager))
{
if (Status == BindingStatus.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).
AttachToContext(AttachAttempt.Again);
if (Status != BindingStatus.Unattached)
{
// if that worked, we don't need to run the task again
Engine.CancelTask(this, TaskOps.AttachToContext);
}
}
else
{
AttachToContext(AttachAttempt.Last);
}
}
else
{
return false; // unrecognized event
}
return true;
}
#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);
}
// invalidate the target property
void Invalidate(bool isASubPropertyChange)
{
// don't invalidate during Attach. The property engine does it
// already, and it would interfere with the on-demand activation
// of style-defined BindingExpressions.
if (IsAttaching)
return;
DependencyObject target = TargetElement;
if (target != null)
{
if (IsInBindingExpressionCollection)
ParentBindingExpressionBase.InvalidateChild(this);
else
{
if (TargetProperty != NoTargetProperty)
{
// recompute expression
if (!isASubPropertyChange)
{
target.InvalidateProperty(TargetProperty);
}
else
{
target.NotifySubPropertyChange(TargetProperty);
}
}
}
}
}
// 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);
}
private object HandlePropertyInvalidationOperation(object o)
{
// This is the case where the source of the Binding belonged to a different Dispatcher
// than the target. For this scenario the source marshals off the invalidation information
// onto the target's Dispatcher queue. This is where we unpack the marshalled information
// to fire the invalidation on the target object.
object[] args = (object[])o;
HandlePropertyInvalidation((DependencyObject)args[0], (DependencyPropertyChangedEventArgs)args[1]);
return null;
}
private void HandlePropertyInvalidation(DependencyObject d, DependencyPropertyChangedEventArgs args)
{
DependencyProperty dp = args.Property;
if (TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.Events))
{
TraceData.Trace(TraceEventType.Warning,
TraceData.GotPropertyChanged(
TraceData.Identify(this),
TraceData.Identify(d),
dp.Name));
}
if (dp == FrameworkElement.DataContextProperty)
{
DependencyObject contextElement = ContextElement;
if (d == contextElement)
{
OnDataContextChanged(contextElement);
}
}
if (dp == CollectionViewSource.ViewProperty)
{
CollectionViewSource cvs = this.CollectionViewSource;
if (d == cvs)
{
Activate(cvs);
}
}
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();
}
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;
IValueConverter _valueConverter;
Type _sourceType;
DataSourceProvider _dataProvider;
object _collectionViewSource;
DynamicValueConverter _dynamicConverter;
internal static readonly object NullDataItem = new NamedObject("NullDataItem");
internal static readonly object IgnoreDefaultValue = new NamedObject("IgnoreDefaultValue");
}
}
// 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
- CultureTable.cs
- columnmapfactory.cs
- DataBoundControlAdapter.cs
- AuthenticationService.cs
- TextBounds.cs
- BitStack.cs
- IndexedEnumerable.cs
- Membership.cs
- DesigntimeLicenseContext.cs
- DigitShape.cs
- ClonableStack.cs
- CodeDirectoryCompiler.cs
- InfoCardArgumentException.cs
- VirtualPathData.cs
- OperationBehaviorAttribute.cs
- ManagementException.cs
- AnonymousIdentificationModule.cs
- FormViewInsertedEventArgs.cs
- ADConnectionHelper.cs
- WeakReferenceEnumerator.cs
- HttpEncoderUtility.cs
- SamlAssertion.cs
- SuppressMessageAttribute.cs
- ListViewPagedDataSource.cs
- StringWriter.cs
- XPathNavigator.cs
- ContentPlaceHolder.cs
- AggregateNode.cs
- InternalResources.cs
- RecognizedAudio.cs
- ParsedRoute.cs
- ShaderEffect.cs
- DataViewSetting.cs
- TextBoxDesigner.cs
- DataGridViewColumn.cs
- documentsequencetextcontainer.cs
- CompilerGlobalScopeAttribute.cs
- QuaternionRotation3D.cs
- ErrorLog.cs
- RoutedEventValueSerializer.cs
- SqlConnectionStringBuilder.cs
- TypeConverterHelper.cs
- _Rfc2616CacheValidators.cs
- RemoteArgument.cs
- SQLGuid.cs
- DataGridCaption.cs
- XPathDescendantIterator.cs
- FilePrompt.cs
- SerializationHelper.cs
- ListBoxItemWrapperAutomationPeer.cs
- XmlSchemaElement.cs
- ToolStripPanelRenderEventArgs.cs
- ObjectTag.cs
- ControlValuePropertyAttribute.cs
- FormatPage.cs
- ToolStripContentPanel.cs
- DateTimeConverter.cs
- GlyphingCache.cs
- LowerCaseStringConverter.cs
- TreeNode.cs
- XmlSchemaValidationException.cs
- __Filters.cs
- LicFileLicenseProvider.cs
- NotCondition.cs
- FilePresentation.cs
- EventDescriptor.cs
- ParameterCollection.cs
- AppDomainManager.cs
- CharacterMetricsDictionary.cs
- LOSFormatter.cs
- DocumentPageView.cs
- LayoutManager.cs
- ServiceNameCollection.cs
- BindUriHelper.cs
- ChtmlSelectionListAdapter.cs
- AuthenticationServiceManager.cs
- MetafileHeaderEmf.cs
- DocumentXmlWriter.cs
- HwndProxyElementProvider.cs
- SettingsPropertyCollection.cs
- Help.cs
- ClientProtocol.cs
- UpdateCompiler.cs
- SecurityElement.cs
- DesignerTransaction.cs
- PersonalizationStateInfo.cs
- DifferencingCollection.cs
- LocalizeDesigner.cs
- ReturnType.cs
- LayoutTableCell.cs
- DeriveBytes.cs
- XmlFormatReaderGenerator.cs
- HtmlGenericControl.cs
- COSERVERINFO.cs
- Errors.cs
- DbParameterHelper.cs
- SettingsProperty.cs
- SmtpFailedRecipientException.cs
- GridViewRow.cs
- RequestContextBase.cs