Code:
/ 4.0 / 4.0 / untmp / DEVDIV_TFS / Dev10 / Releases / RTMRel / wpf / src / Framework / System / Windows / Data / BindingExpression.cs / 1305600 / 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.VerifySourceReference(bool lastChance) { DependencyObject target = TargetElement; ObjectRef sourceRef = ParentBinding.SourceReference; DependencyObject mentor = !UsingMentor ? target : Helper.FindMentor(target); ObjectRefArgs args = new ObjectRefArgs() { ResolveNamesInTemplate = ResolveNamesInTemplate, }; object source = sourceRef.GetDataObject(mentor, args); if (source != DataItem) { // the source reference resolves differently from before, so // re-attach to the tree context AttachToContext(lastChance ? AttachAttempt.Last : AttachAttempt.Again); } } void IDataBindEngineClient.OnTargetUpdated() { OnTargetUpdated(); } DependencyObject IDataBindEngineClient.TargetElement { get { return 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, new ObjectRefArgs()); } } //----------------------------------------------------- // // 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"); if (args == null) throw new ArgumentNullException("args"); DependencyProperty dp = args.Property; if (dp == null) throw new InvalidOperationException(SR.Get(SRID.ArgumentPropertyMustNotBeNull, "Property", "args")); // ignore irrelevant notifications. This test must happen before any marshalling. bool relevant = !IgnoreSourcePropertyChange; if (dp == FrameworkElement.DataContextProperty && d == ContextElement) { relevant = true; // changes from context element are always relevant } else if (dp == CollectionViewSource.ViewProperty && d == CollectionViewSource) { relevant = true; // changes from the CollectionViewSource are always relevant } else if (dp == FrameworkElement.LanguageProperty && UsesLanguage && d == TargetElement) { relevant = true; // changes from target's Language are always relevant } else if (relevant) { relevant = (Worker != null) && (Worker.UsesDependencyProperty(d, dp)); } if (!relevant) return; base.OnPropertyInvalidation(d, args); } #endregion Expression overrides //----------------------------------------------------- // // Protected Methods // //------------------------------------------------------ ////// 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 || IsInUpdate) return true; 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 override bool IsDisconnected { get { return GetReference(_dataItem) == DisconnectedItem; } } //------------------------------------------------------ // // Internal Methods // //----------------------------------------------------- // Create a new BindingExpression from the given Bind description internal static BindingExpression CreateBindingExpression(DependencyObject d, DependencyProperty dp, Binding binding, BindingExpressionBase parent) { FrameworkPropertyMetadata fwMetaData = dp.GetMetadata(d.DependencyObjectType) as FrameworkPropertyMetadata; if ((fwMetaData != null && !fwMetaData.IsDataBindingAllowed) || dp.ReadOnly) throw new ArgumentException(SR.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) { 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) { bool hasModifiers; BaseValueSourceInternal valueSource = d.GetValueSource(FrameworkElement.DataContextProperty, null, out hasModifiers); return (valueSource != BaseValueSourceInternal.Inherited) && (valueSource != BaseValueSourceInternal.Default || hasModifiers); } //------------------------------------------------------ // // Private Properties // //----------------------------------------------------- private bool CanActivate { get { return 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 bool AttachOverride(DependencyObject target, DependencyProperty dp) { if (!base.AttachOverride(target, dp)) return false; // listen for InheritanceContext change (if target is mentored) if (ParentBinding.SourceReference == null || ParentBinding.SourceReference.UsesMentor) { DependencyObject mentor = Helper.FindMentor(target); if (mentor != target) { InheritanceContextChangedEventManager.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) return true; } ////// 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 DependencyObject target = TargetElement; if (target != null && IsUpdateOnLostFocus) { LostFocusEventManager.RemoveListener(target, 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, new ObjectRefArgs() { IsTracing = 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) { ObjectRefArgs args = new ObjectRefArgs() { IsTracing = traceObjectRef, ResolveNamesInTemplate = ResolveNamesInTemplate, }; source = sourceRef.GetDataObject(mentor, args); // check that the source could be found if (source == DependencyProperty.UnsetValue) { if (lastChance) { SetStatus(BindingStatus.PathError); if (TraceData.IsEnabled) { TraceData.Trace(TraceLevel, TraceData.NoSource(sourceRef), this); } } return; } else if (!lastChance && args.NameResolvedInOuterScope) { // if ElementName resolved in an outer scope, it's possible // that future work might add the name to an inner scope. // Schedule a task to check this. Engine.AddTask(this, TaskOps.VerifySourceReference); } } else { // we get here only if we need ambient data context, but there // is no context element. E.g. binding the DataContext property // of an element with no parent. Just use null. source = null; } // if we get this far, all the ingredients for a successful binding // are present. Remember what we've found and activate the binding. if (contextElement != null) _ctxElement = new WeakReference(contextElement); // attach to context element ChangeWorkerSources(null, 0); if (!UseDefaultValueConverter) { _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() { // 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, new ObjectRefArgs() { ResolveNamesInTemplate = ResolveNamesInTemplate })); } } } 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; if (item == DisconnectedItem) { // disconnect from the former item, with the least acceptable impact Disconnect(); 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 bool initialTransferIsUpdate = IsOneWayToSource; object currentValue; if (ShouldUpdateWithCurrentValue(target, out currentValue)) { initialTransferIsUpdate = true; ChangeValue(currentValue, /*notify*/false); NeedsUpdate = true; } if (!initialTransferIsUpdate) { ValidationError error; object value = GetInitialValue(target, out error); if (value != NullDataItem) { TransferValue(value, false); } if (error != null) { UpdateValidationError(error); } } else if (!IsInMultiBindingExpression) // MultiBinding initiates its own update { UpdateValue(); } GC.KeepAlive(target); // keep target alive during activation (bug 956831) } // return the value for the initial data transfer. Specifically: // normal case - return UnsetValue (we'll get the value from the source object) // valid proposed value - return the proposed value // invalid proposed value - two subcases: // one-way binding - return default/fallback value // two-way binding - retrun NullDataItem (meaning "don't transfer") // // In both subcases, adopt the validation errors discovered earlier. // In the two-way subcase, instead of a source-to-target transfer, we set the // the target property to the (saved) raw proposed value, as if the user // had edited this property. object GetInitialValue(DependencyObject target, out ValidationError error) { object proposedValue; // find the binding group this binding would join if it were two-way (even if it isn't) BindingGroup bindingGroup = RootBindingExpression.FindBindingGroup(true, ContextElement); BindingGroup.ProposedValueEntry entry; // get the proposed value from the binding group if (bindingGroup == null || (entry = bindingGroup.GetProposedValueEntry(SourceItem, SourcePropertyName)) == null) { // no proposed value error = null; proposedValue = DependencyProperty.UnsetValue; } else { // adopt the validation error (possibly null) error = entry.ValidationError; if (IsReflective && TargetProperty.IsValidValue(entry.RawValue)) { // two-way binding - set the target property directly target.SetValue(TargetProperty, entry.RawValue); proposedValue = NullDataItem; bindingGroup.RemoveProposedValueEntry(entry); } else if (entry.ConvertedValue == DependencyProperty.UnsetValue) { // invalid proposed value - use fallback/default proposedValue = UseFallbackValue(); } else { // valid proposed value proposedValue = entry.ConvertedValue; } // if this binding didn't take over responsibility for the proposed // value, add it to the list of bindings using the proposed value if (proposedValue != NullDataItem) { bindingGroup.AddBindingForProposedValue(this, SourceItem, SourcePropertyName); } } return proposedValue; } internal override void Deactivate() { 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); } // disconnect from the former item, with the least acceptable impact. // This arises when a container is removed from an ItemsControl. internal override void Disconnect() { // disconnect this binding from its former item _dataItem = CreateReference(DisconnectedItem); if (Worker == null) return; Worker.AttachDataItem(); base.Disconnect(); } // if the root item is a DataSourceProvider, use its Data instead // and listen for DataChanged events. (Unless overridden explicitly // by the BindsDirectlyToSource property). private object DereferenceDataProvider(object item) { DataSourceProvider 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; bool usesLanguage = UsesLanguage; if (contextElement != null) ++size; if (cvs != null) ++size; if (usesLanguage) ++size; WeakDependencySource[] newSources = (size > 0) ? new WeakDependencySource[size] : null; if (contextElement != null) { newSources[offset++] = new WeakDependencySource(_ctxElement, FrameworkElement.DataContextProperty); } if (cvs != null) { WeakReference wr = _collectionViewSource as WeakReference; newSources[offset++] = (wr != null) ? new WeakDependencySource(wr, CollectionViewSource.ViewProperty) : new WeakDependencySource(cvs, CollectionViewSource.ViewProperty); } if (usesLanguage) { newSources[offset++] = new WeakDependencySource(TargetElementReference, FrameworkElement.LanguageProperty); } // add the worker's sources if (n > 0) Array.Copy(newWorkerSources, 0, newSources, offset, n); // tell the property engine ChangeSources(newSources); } #endregion Worker #region Value // transfer a value from the source to the target void TransferValue() { TransferValue(DependencyProperty.UnsetValue, false); } // transfer a value from the source to the target internal void TransferValue(object newValue, bool isASubPropertyChange) { // if the target element has been GC'd, do nothing DependencyObject target = TargetElement; if (target == null) return; // if the BindingExpression hasn't activated, do nothing if (Worker == null) return; Type targetType = GetEffectiveTargetType(); IValueConverter implicitConverter = null; bool isExtendedTraceEnabled = TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.Transfer); // clear the Pending flag before actually doing the transfer. That way if another // thread sets the flag, we'll schedule another transfer. This might do more // transfers than absolutely necessary, but it guarantees that we'll eventually pick // up the value from the last change. IsTransferPending = false; IsInTransfer = true; UsingFallbackValue = false; // get the raw value from the source object value = (newValue == DependencyProperty.UnsetValue) ? Worker.RawValue() : newValue; 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))); } // if this is a re-transfer after a source update and the value // hasn't changed, don't do any more work. if (IsInUpdate && Object.Equals(value, Value)) { goto Done; } // 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 IsCritical[Application]Exception() handles these correctly. #pragma warning disable 56500 object convertedValue = null; try { if (stringFormat != null) { convertedValue = String.Format(culture, stringFormat, value); } else { convertedValue = converter.Convert(value, targetType, parameter, culture); } } // Catch all exceptions. There is no app code on the stack, // so the exception isn't actionable by the app. // Yet we don't want to crash the app. catch (Exception ex) { // the DefaultValueConverter can end up calling BaseUriHelper.GetBaseUri() // which can raise SecurityException if the app does not have the right FileIO privileges if (CriticalExceptions.IsCriticalApplicationException(ex)) throw; if (TraceData.IsEnabled) { string name = String.IsNullOrEmpty(stringFormat) ? converter.GetType().Name : "StringFormat"; TraceData.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 IsCritical[Application]Exception() handles these correctly. #pragma warning disable 56500 object convertedValue = null; try { convertedValue = converter.ConvertBack(value, sourceType, parameter, culture); } // Catch all exceptions. There is no app code on the stack, // so the exception isn't actionable by the app. // Yet we don't want to crash the app. catch (Exception ex) { // the DefaultValueConverter can end up calling BaseUriHelper.GetBaseUri() // which can raise SecurityException if the app does not have the right FileIO privileges ex = CriticalExceptions.Unwrap(ex); if (CriticalExceptions.IsCriticalApplicationException(ex)) throw; if (TraceData.IsEnabled) { TraceData.Trace(TraceEventType.Error, TraceData.BadConverterForUpdate( AvTrace.ToStringHelper(Value), AvTrace.TypeName(value)), this, ex); } ProcessException(ex, ValidatesOnExceptions); convertedValue = DependencyProperty.UnsetValue; } catch // non CLS compliant exception { if (TraceData.IsEnabled) { TraceData.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(DependencyProperty.UnsetValue, isASubPropertyChange); } void OnTargetUpdated() { if (NotifyOnTargetUpdated) { DependencyObject target = TargetElement; if (target != null) { if ( !IsInMultiBindingExpression // not an inner BindingExpression && ( !IsInPriorityBindingExpression || this == ParentPriorityBindingExpression.ActiveBindingExpression) // in ProrityBinding, and either active || (IsAttaching && (Status==BindingStatus.Active || UsingFallbackValue) // or about to become active ) ) { // while attaching a normal (not style-defined) BindingExpression, // we must defer raising the event until after the // property has been invalidated, so that the event handler // gets the right value if it asks (bug 1036862) if (IsAttaching && RootBindingExpression == target.ReadLocalValue(TargetProperty)) { Engine.AddTask(this, TaskOps.RaiseTargetUpdatedEvent); } else { OnTargetUpdated(target, TargetProperty); } } } } } void OnSourceUpdated() { if (NotifyOnSourceUpdated) { DependencyObject target = TargetElement; if (target != null) { if ( !IsInMultiBindingExpression // not an inner BindingExpression && ( !IsInPriorityBindingExpression || this == ParentPriorityBindingExpression.ActiveBindingExpression)) // not an inactive BindingExpression { OnSourceUpdated(target, TargetProperty); } } } } // 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) { object rawValue = 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))); } // if the conversion failed, signal a validation error if (value == DependencyProperty.UnsetValue) { if (ValidationError == null) { ValidationError validationError = new ValidationError(ConversionValidationRule.Instance, this, SR.Get(SRID.Validation_ConversionFailed, rawValue), null); UpdateValidationError(validationError); } } return value; } /// /// Get the converted proposed value and inform the binding group /// internal override bool ObtainConvertedProposedValue(BindingGroup bindingGroup) { bool result = true; object value; if (NeedsUpdate) { value = bindingGroup.GetValue(this); if (value != DependencyProperty.UnsetValue) { value = ConvertProposedValue(value); if (value == DependencyProperty.UnsetValue) { result = false; } } } else { value = BindingGroup.DeferredSourceValue; } bindingGroup.SetValue(this, value); return result; } internal override object UpdateSource(object value) { // If there is a failure to convert, then Update failed. if (value == DependencyProperty.UnsetValue) { SetStatus(BindingStatus.UpdateSourceError); } if (value == Binding.DoNothing || value == DependencyProperty.UnsetValue || ShouldIgnoreUpdate()) { return value; } // PreSharp uses message numbers that the C# compiler doesn't know about. // Disable the C# complaints, per the PreSharp documentation. #pragma warning disable 1634, 1691 // PreSharp complains about catching NullReference (and other) exceptions. // It doesn't recognize that IsCritical[Application]Exception() handles these correctly. #pragma warning disable 56500 try { BeginSourceUpdate(); Worker.UpdateValue(value); } // Catch all exceptions. There is no app code on the stack, // so the exception isn't actionable by the app. // Yet we don't want to crash the app. catch (Exception ex) { ex = CriticalExceptions.Unwrap(ex); if (CriticalExceptions.IsCriticalApplicationException(ex)) throw; if (TraceData.IsEnabled) TraceData.Trace(TraceEventType.Error, TraceData.WorkerUpdateFailed, this, ex); ProcessException(ex, (ValidatesOnExceptions || BindingGroup != null)); SetStatus(BindingStatus.UpdateSourceError); value = DependencyProperty.UnsetValue; } catch // non CLS compliant exception { if (TraceData.IsEnabled) TraceData.Trace(TraceEventType.Error, TraceData.WorkerUpdateFailed, this); SetStatus(BindingStatus.UpdateSourceError); value = DependencyProperty.UnsetValue; } 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 bool UpdateSource(BindingGroup bindingGroup) { bool result = true; if (NeedsUpdate) { object value = bindingGroup.GetValue(this); value = UpdateSource(value); bindingGroup.SetValue(this, value); if (value == DependencyProperty.UnsetValue) { result = false; } } return result; } /// /// 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 (validationStep == ValidationStep.UpdatedValue) { if (result && ParentBinding.ValidatesOnDataErrors) { // remember the old validation error, if it came from the implicit DataError rule ValidationError oldValidationError = GetValidationErrors(validationStep); if (oldValidationError != null && oldValidationError.RuleInError != DataErrorValidationRule.Instance) { oldValidationError = null; } // run the DataError rule, even though it doesn't appear in the // ValidationRules collection ValidationError error = RunValidationRule(DataErrorValidationRule.Instance, this, GetCulture()); if (error != null) { UpdateValidationError(error); result = false; } else if (oldValidationError != null) { // the implicit rule is now valid - clear the old error UpdateValidationError(null); } } } else if (validationStep == ValidationStep.CommittedValue) { if (result) { NeedsValidation = false; } } return result; } /// /// 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); } /// /// Get the proposed value(s) that would be written to the source(s), applying /// conversion and checking UI-side validation rules. /// internal override bool ValidateAndConvertProposedValue(out Collectionvalues) { Debug.Assert(NeedsValidation, "check NeedsValidation before calling this"); values = null; // validate raw proposed value object rawValue = GetRawProposedValue(); bool isValid = Validate(rawValue, ValidationStep.RawProposedValue); // apply conversion and validate it object convertedValue = isValid ? ConvertProposedValue(rawValue) : DependencyProperty.UnsetValue; if (convertedValue == Binding.DoNothing) { convertedValue = DependencyProperty.UnsetValue; } else if (convertedValue == DependencyProperty.UnsetValue) { isValid = false; } else { isValid = Validate(convertedValue, ValidationStep.ConvertedProposedValue); } // return the result values = new Collection (); values.Add(new ProposedValue(this, rawValue, convertedValue)); return isValid; } private bool IsValidValueForUpdate(object value, Type sourceType) { // null is always valid, even for value types. The reflection layer // apparently converts null to default(T). if (value == null) return true; // if direct assignment is possible, the value is valid if (sourceType.IsAssignableFrom(value.GetType())) return true; // if the value is DBNull, ask the worker (answer depends on several factors) if (Convert.IsDBNull(value)) return Worker.IsDBNullValidForUpdate; // otherwise the value is invalid return false; } private void ProcessException(Exception ex, bool validate) { object filteredException = null; ValidationError validationError = null; // If there is not ExceptionFilter, then Wrap the // exception in a ValidationError. if (ExceptionFilterExists()) { filteredException = CallDoFilterException(ex); if (filteredException == null) return; validationError = filteredException as ValidationError; } // See if an ExceptionValidationRule is in effect if (validationError == null && validate) { ValidationRule exceptionValidationRule = ExceptionValidationRule.Instance; if (filteredException == null) { validationError = new ValidationError(exceptionValidationRule, this, ex.Message, ex); } else { validationError = new ValidationError(exceptionValidationRule, this, filteredException, ex); } } if (validationError != null) { UpdateValidationError(validationError); } } // Sometimes a source update gets requested as a result of event leapfrogging, // where the binding has not yet been informed of a pending change to its // path (e.g. Dev10 445406). In this case, we should simply ignore the update. private bool ShouldIgnoreUpdate() { // The detection algorithm below is expensive, so only do when the target // property is especially susceptible to the problem if (TargetProperty.OwnerType != typeof(System.Windows.Controls.Primitives.Selector) && // SelectedItem, SelectedValue, SelectedIndex, etc. TargetProperty != ComboBox.TextProperty // ComboBox.Text ) { return false; } // Re-evaluate the entire path. If it doesn't produce the same source // object as our cached value, there is presumably a change notification // somewhere along the path that hasn't been delivered yet, due to event // leapfrogging. // get initial (top-level) item object item; DependencyObject contextElement = ContextElement; if (contextElement == null) { DependencyObject target = TargetElement; if (target != null && UsingMentor) { target = Helper.FindMentor(target); } if (target == null) { return true; } item = ParentBinding.SourceReference.GetDataObject(target, new ObjectRefArgs() { ResolveNamesInTemplate = ResolveNamesInTemplate }); } else { item = contextElement.GetValue(FrameworkElement.DataContextProperty); } // Apply auto-magic rules for CVS, DSP, etc. // This logic should agree with Activate(item), except for event hookup. if (!ParentBinding.BindsDirectlyToSource) { CollectionViewSource cvs; DataSourceProvider dsp; if ((cvs = item as CollectionViewSource) != null) { item = cvs.CollectionView; } else if ((dsp = item as DataSourceProvider) != null) { item = dsp.Data; } } // If the top-level item is different, ignore the update if (!Object.Equals(DataItem, item)) return true; // check the rest of the path return !Worker.IsPathCurrent(); } #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) { if (IsReflective && RootBindingExpression.ParentBindingBase.BindingGroupName == String.Empty) { RejoinBindingGroup(IsReflective, contextElement); } 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); } // replace this BindingExpression with a new one void Replace() { DependencyObject target = TargetElement; if (target != null) { if (IsInBindingExpressionCollection) ParentBindingExpressionBase.ReplaceChild(this); else BindingOperations.SetBinding(target, TargetProperty, ParentBinding); } } // raise the TargetUpdated event (explicit polymorphism) internal static void OnTargetUpdated(DependencyObject d, DependencyProperty dp) { DataTransferEventArgs args = new DataTransferEventArgs(d, dp); args.RoutedEvent = Binding.TargetUpdatedEvent; FrameworkObject fo = new FrameworkObject(d); if (!fo.IsValid && d != null) { fo.Reset(Helper.FindMentor(d)); } fo.RaiseEvent(args); } // raise the SourceUpdatedEvent event (explicit polymorphism) internal static void OnSourceUpdated(DependencyObject d, DependencyProperty dp) { DataTransferEventArgs args = new DataTransferEventArgs(d, dp); args.RoutedEvent = Binding.SourceUpdatedEvent; FrameworkObject fo = new FrameworkObject(d); if (!fo.IsValid && d != null) { fo.Reset(Helper.FindMentor(d)); } fo.RaiseEvent(args); } internal override void HandlePropertyInvalidation(DependencyObject d, DependencyPropertyChangedEventArgs args) { DependencyProperty dp = args.Property; if (TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.Events)) { TraceData.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 (dp == FrameworkElement.LanguageProperty && UsesLanguage && d == TargetElement) { InvalidateCulture(); } if (Worker != null) { Worker.OnSourceInvalidation(d, dp, args.IsASubPropertyChange); } } private class ChangingValueHelper : IDisposable { internal ChangingValueHelper(BindingExpression b) { _bindingExpression = b; b.CancelPendingTasks(); } public void Dispose() { _bindingExpression.TransferValue(); GC.SuppressFinalize(this); } BindingExpression _bindingExpression; } void SetDataItem(object newItem) { _dataItem = CreateReference(newItem); } // find the DataSource object (if any) that produced the DataContext // for element d object GetDataSourceForDataContext(DependencyObject d) { // look for ancestor that contributed the inherited value DependencyObject ancestor; BindingExpression b = null; for (ancestor = d; ancestor != null; ancestor = FrameworkElement.GetFrameworkParent(ancestor)) { if (HasLocalDataContext(ancestor)) { b = BindingOperations.GetBindingExpression(ancestor, FrameworkElement.DataContextProperty) as BindingExpression; break; } } if (b != null) return b.DataSource; return null; } #endregion Helper functions //------------------------------------------------------ // // Private Fields // //----------------------------------------------------- WeakReference _ctxElement; object _dataItem; BindingWorker _worker; 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
- SchemaSetCompiler.cs
- TaskFormBase.cs
- DSACryptoServiceProvider.cs
- BordersPage.cs
- CompositeActivityMarkupSerializer.cs
- ObjectDataSource.cs
- DataGridViewCellStyle.cs
- CodeTypeReference.cs
- TraceContext.cs
- CatalogPartCollection.cs
- ExceptionHelpers.cs
- NativeMethods.cs
- Rules.cs
- MonthCalendarDesigner.cs
- CodeAttributeDeclarationCollection.cs
- RightsManagementEncryptionTransform.cs
- TagMapCollection.cs
- HttpSocketManager.cs
- PageCatalogPart.cs
- DataTable.cs
- ReflectionServiceProvider.cs
- SAPICategories.cs
- XmlWriterDelegator.cs
- graph.cs
- CodeSnippetCompileUnit.cs
- EmbeddedObject.cs
- LogicalCallContext.cs
- TabletDeviceInfo.cs
- FormViewDeletedEventArgs.cs
- ProxyWebPart.cs
- HasRunnableWorkflowEvent.cs
- SQLByte.cs
- DataListCommandEventArgs.cs
- SQLInt16Storage.cs
- DataTableReader.cs
- DataGridTextBox.cs
- Serializer.cs
- XmlQueryOutput.cs
- TextPattern.cs
- EntityTypeEmitter.cs
- ExpressionConverter.cs
- XmlElement.cs
- UnmanagedMarshal.cs
- TreeViewTemplateSelector.cs
- FormClosedEvent.cs
- WebPartDisplayMode.cs
- SystemException.cs
- MultipartContentParser.cs
- MarshalByRefObject.cs
- CollectionView.cs
- DataGridViewButtonColumn.cs
- ColorAnimation.cs
- ScrollPattern.cs
- XmlSchemaExporter.cs
- Overlapped.cs
- BasicCellRelation.cs
- CmsUtils.cs
- CurrentChangedEventManager.cs
- RevocationPoint.cs
- MasterPage.cs
- GeneratedCodeAttribute.cs
- ObjectTag.cs
- TimeIntervalCollection.cs
- EnumerableRowCollectionExtensions.cs
- GlobalProxySelection.cs
- NavigationHelper.cs
- ConstraintManager.cs
- PointCollectionConverter.cs
- CodeActivity.cs
- RawStylusInputReport.cs
- OptionalColumn.cs
- HostedBindingBehavior.cs
- ReflectionTypeLoadException.cs
- ZipFileInfo.cs
- Triangle.cs
- X509ChainPolicy.cs
- List.cs
- AspNetSynchronizationContext.cs
- CharAnimationUsingKeyFrames.cs
- DefaultPropertyAttribute.cs
- PeerNameRegistration.cs
- DataGridViewRowErrorTextNeededEventArgs.cs
- StandardMenuStripVerb.cs
- JobPageOrder.cs
- XPathNodeIterator.cs
- ImmComposition.cs
- Accessors.cs
- WSDualHttpBindingElement.cs
- ClientReliableChannelBinder.cs
- LicenseContext.cs
- BuilderElements.cs
- KeyGesture.cs
- documentsequencetextview.cs
- MemoryFailPoint.cs
- DefaultMemberAttribute.cs
- DrawingContextWalker.cs
- SlotInfo.cs
- HttpWebRequestElement.cs
- X509Certificate2.cs
- WorkflowOwnershipException.cs