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 enablesand // 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 enablesand // 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
- WindowsScroll.cs
- RegexFCD.cs
- FontStyle.cs
- DataGridSortCommandEventArgs.cs
- complextypematerializer.cs
- SQLBoolean.cs
- ItemContainerProviderWrapper.cs
- complextypematerializer.cs
- DataServiceQueryProvider.cs
- CFStream.cs
- TickBar.cs
- XmlNotation.cs
- CodeDirectiveCollection.cs
- ListViewDeletedEventArgs.cs
- VirtualPathUtility.cs
- AudienceUriMode.cs
- SizeValueSerializer.cs
- AttachmentService.cs
- PaperSource.cs
- XsdBuilder.cs
- IsolatedStorage.cs
- HashJoinQueryOperatorEnumerator.cs
- FixedPage.cs
- SerializableAttribute.cs
- Bezier.cs
- UnsafeNativeMethods.cs
- PrintPreviewControl.cs
- ApplySecurityAndSendAsyncResult.cs
- TextElementEditingBehaviorAttribute.cs
- TextEffectResolver.cs
- ListenerSessionConnection.cs
- FlowDocumentReaderAutomationPeer.cs
- sqlstateclientmanager.cs
- HtmlInputHidden.cs
- QuotaThrottle.cs
- Int32CollectionValueSerializer.cs
- RelatedCurrencyManager.cs
- Splitter.cs
- Cursor.cs
- RuntimeCompatibilityAttribute.cs
- MultiViewDesigner.cs
- TabPage.cs
- SmtpReplyReaderFactory.cs
- LocalizableAttribute.cs
- GridViewColumnHeader.cs
- Hex.cs
- ArcSegment.cs
- ParagraphVisual.cs
- DbUpdateCommandTree.cs
- SecurityTokenResolver.cs
- StyleModeStack.cs
- AccessedThroughPropertyAttribute.cs
- ClientTargetSection.cs
- PenLineJoinValidation.cs
- RuleSetDialog.cs
- ResXResourceWriter.cs
- DataGridColumn.cs
- AddingNewEventArgs.cs
- LinkLabelLinkClickedEvent.cs
- PartitionResolver.cs
- FullTextState.cs
- TextAction.cs
- SqlConnectionPoolProviderInfo.cs
- SymbolResolver.cs
- OutputCacheProfileCollection.cs
- NegationPusher.cs
- Verify.cs
- MetadataArtifactLoaderXmlReaderWrapper.cs
- BlockingCollection.cs
- ShapingEngine.cs
- XmlArrayItemAttribute.cs
- XPathDocumentNavigator.cs
- BamlBinaryWriter.cs
- StreamResourceInfo.cs
- CodeTypeParameterCollection.cs
- sortedlist.cs
- LocalizationComments.cs
- AnnotationHighlightLayer.cs
- AcceleratedTokenProvider.cs
- DbConnectionStringCommon.cs
- NetworkAddressChange.cs
- XmlSchemaCompilationSettings.cs
- Blend.cs
- MetadataItemCollectionFactory.cs
- DataGridViewImageColumn.cs
- Mapping.cs
- FunctionOverloadResolver.cs
- CommandID.cs
- ISO2022Encoding.cs
- AnimationClock.cs
- Manipulation.cs
- TextDecoration.cs
- ComplexType.cs
- MetabaseSettingsIis7.cs
- TextTreeTextElementNode.cs
- AnimatedTypeHelpers.cs
- RawAppCommandInputReport.cs
- EdmProviderManifest.cs
- pingexception.cs
- WebControlsSection.cs