// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. // // Description: Defines Binding object, which describes an instance of data Binding. // // See spec at Data Binding.mht // using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; using System.Diagnostics; using System.ComponentModel; using System.Xml; using System.Windows; using System.Windows.Controls; // Validation using System.Windows.Markup; using MS.Utility; using MS.Internal; // Invariant.Assert using MS.Internal.Controls; // Validation using MS.Internal.Data; using MS.Internal.KnownBoxes; namespace System.Windows.Data { /// /// Status of the Binding /// public enum BindingStatus { /// Binding has not yet been attached to its target Unattached = 0, /// Binding has not yet been activated Inactive, /// Binding has been successfully activated Active, /// Binding has been detached from its target Detached, /// Binding is waiting for an async operation to complete AsyncRequestPending, /// error - source path could not be resolved PathError, /// error - a legal value could not be obtained from the source UpdateTargetError, /// error - the value could not be sent to the source UpdateSourceError, } /// /// Status of the Binding /// internal enum BindingStatusInternal : byte { /// Binding has not yet been attached to its target Unattached = 0, /// Binding has not yet been activated Inactive, /// Binding has been successfully activated Active, /// Binding has been detached from its target Detached, /// Binding is waiting for an async operation to complete AsyncRequestPending, /// error - source path could not be resolved PathError, /// error - a legal value could not be obtained from the source UpdateTargetError, /// error - the value could not be sent to the source UpdateSourceError, } /// /// Describes an instance of a Binding, binding a target /// (DependencyObject, DependencyProperty) to a source (object, property) /// public class Binding : BindingBase { //------------------------------------------------------ // // Enums // //------------------------------------------------------ // Which source property is in use enum SourceProperties : byte { None, RelativeSource, ElementName, Source, StaticSource, InternalSource } //------------------------------------------------------ // // Dynamic properties and events // //------------------------------------------------------ /// /// The SourceUpdated event is raised whenever a value is transferred from the target to the source, /// but only for Bindings that have requested the event by setting BindFlags.NotifyOnSourceUpdated. /// public static readonly RoutedEvent SourceUpdatedEvent = EventManager.RegisterRoutedEvent("SourceUpdated", RoutingStrategy.Bubble, typeof(EventHandler), typeof(Binding)); /// /// Adds a handler for the SourceUpdated attached event /// /// UIElement or ContentElement that listens to this event /// Event Handler to be added public static void AddSourceUpdatedHandler(DependencyObject element, EventHandler handler) { FrameworkElement.AddHandler(element, SourceUpdatedEvent, handler); } /// /// Removes a handler for the SourceUpdated attached event /// /// UIElement or ContentElement that listens to this event /// Event Handler to be removed public static void RemoveSourceUpdatedHandler(DependencyObject element, EventHandler handler) { FrameworkElement.RemoveHandler(element, SourceUpdatedEvent, handler); } /// /// The TargetUpdated event is raised whenever a value is transferred from the source to the target, /// but only for Bindings that have requested the event by setting BindFlags.NotifyOnTargetUpdated. /// public static readonly RoutedEvent TargetUpdatedEvent = EventManager.RegisterRoutedEvent("TargetUpdated", RoutingStrategy.Bubble, typeof(EventHandler), typeof(Binding)); /// /// Adds a handler for the TargetUpdated attached event /// /// UIElement or ContentElement that listens to this event /// Event Handler to be added public static void AddTargetUpdatedHandler(DependencyObject element, EventHandler handler) { FrameworkElement.AddHandler(element, TargetUpdatedEvent, handler); } /// /// Removes a handler for the TargetUpdated attached event /// /// UIElement or ContentElement that listens to this event /// Event Handler to be removed public static void RemoveTargetUpdatedHandler(DependencyObject element, EventHandler handler) { FrameworkElement.RemoveHandler(element, TargetUpdatedEvent, handler); } // 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 checks that the type of the DP agrees with the type of the static // accessors. But setting the type of the DP to XmlNamespaceManager would // load System.Xml during the static cctor, which is considered a perf bug. // So instead we set the type of the DP to 'object' and use the // ValidateValueCallback to ensure that only values of the right type are allowed. // Meanwhile, disable the PreSharp warning #pragma warning disable 7008 /// /// The XmlNamespaceManager to use to perform Namespace aware XPath queries in XmlData bindings /// public static readonly DependencyProperty XmlNamespaceManagerProperty= DependencyProperty.RegisterAttached("XmlNamespaceManager", typeof(object), typeof(Binding), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits), new ValidateValueCallback(IsValidXmlNamespaceManager)); /// Static accessor for XmlNamespaceManager property /// DependencyObject target cannot be null public static XmlNamespaceManager GetXmlNamespaceManager(DependencyObject target) { if (target == null) throw new ArgumentNullException("target"); return (XmlNamespaceManager)target.GetValue(XmlNamespaceManagerProperty); } /// Static modifier for XmlNamespaceManager property /// DependencyObject target cannot be null public static void SetXmlNamespaceManager(DependencyObject target, XmlNamespaceManager value) { if (target == null) throw new ArgumentNullException("target"); target.SetValue(XmlNamespaceManagerProperty, value); } private static bool IsValidXmlNamespaceManager(object value) { return (value == null) || SystemXmlHelper.IsXmlNamespaceManager(value); } #pragma warning restore 7008 #pragma warning restore 1634, 1691 //------------------------------------------------------ // // Constructors // //------------------------------------------------------ /// /// Default constructor. /// public Binding() {} /// /// Convenience constructor. Sets most fields to default values. /// /// source path public Binding(string path) { if (path != null) { if (System.Windows.Threading.Dispatcher.CurrentDispatcher == null) throw new InvalidOperationException(); // This is actually never called since CurrentDispatcher will throw if null. Path = new PropertyPath(path, (object[])null); } } //------------------------------------------------------ // // Public Properties // //------------------------------------------------------ /// /// Collection<ValidationRule> is a collection of ValidationRule /// implementations on either a Binding or a MultiBinding. Each of the rules /// is run by the binding engine when validation on update to source /// public Collection ValidationRules { get { if (!HasValue(Feature.ValidationRules)) SetValue(Feature.ValidationRules, new ValidationRuleCollection()); return (ValidationRuleCollection)GetValue(Feature.ValidationRules, null); } } /// /// This method is used by TypeDescriptor to determine if this property should /// be serialized. /// [EditorBrowsable(EditorBrowsableState.Never)] public bool ShouldSerializeValidationRules() { return (HasValue(Feature.ValidationRules) && ValidationRules.Count > 0); } /// True if an exception during source updates should be considered a validation error. [DefaultValue(false)] public bool ValidatesOnExceptions { get { return TestFlag(BindingFlags.ValidatesOnExceptions); } set { bool currentValue = TestFlag(BindingFlags.ValidatesOnExceptions); if (currentValue != value) { CheckSealed(); ChangeFlag(BindingFlags.ValidatesOnExceptions, value); } } } /// True if a data error in the source item should be considered a validation error. [DefaultValue(false)] public bool ValidatesOnDataErrors { get { return TestFlag(BindingFlags.ValidatesOnDataErrors); } set { bool currentValue = TestFlag(BindingFlags.ValidatesOnDataErrors); if (currentValue != value) { CheckSealed(); ChangeFlag(BindingFlags.ValidatesOnDataErrors, value); } } } /// True if a data error from INotifyDataErrorInfo source item should be considered a validation error. [DefaultValue(true)] public bool ValidatesOnNotifyDataErrors { get { return TestFlag(BindingFlags.ValidatesOnNotifyDataErrors); } set { bool currentValue = TestFlag(BindingFlags.ValidatesOnNotifyDataErrors); if (currentValue != value) { CheckSealed(); ChangeFlag(BindingFlags.ValidatesOnNotifyDataErrors, value); } } } /// The source path (for CLR bindings). public PropertyPath Path { get { return _ppath; } set { CheckSealed(); _ppath = value; _attachedPropertiesInPath = -1; ClearFlag(BindingFlags.PathGeneratedInternally); if (_ppath != null && _ppath.StartsWithStaticProperty) { if (_sourceInUse == SourceProperties.None || _sourceInUse == SourceProperties.StaticSource || FrameworkCompatibilityPreferences.TargetsDesktop_V4_0) { // net 4.5 breaks static bindings - this is for compat SourceReference = StaticSourceRef; } else throw new InvalidOperationException(SR.Get(SRID.BindingConflict, SourceProperties.StaticSource, _sourceInUse)); } } } /// /// This method is used by TypeDescriptor to determine if this property should /// be serialized. /// [EditorBrowsable(EditorBrowsableState.Never)] public bool ShouldSerializePath() { return _ppath != null && !TestFlag(BindingFlags.PathGeneratedInternally); } /// The XPath path (for XML bindings). [DefaultValue(null)] public string XPath { get { return (string)GetValue(Feature.XPath, null); } set { CheckSealed(); SetValue(Feature.XPath, value, null); } } /// Binding mode [DefaultValue(BindingMode.Default)] public BindingMode Mode { get { switch (GetFlagsWithinMask(BindingFlags.PropagationMask)) { case BindingFlags.OneWay: return BindingMode.OneWay; case BindingFlags.TwoWay: return BindingMode.TwoWay; case BindingFlags.OneWayToSource: return BindingMode.OneWayToSource; case BindingFlags.OneTime: return BindingMode.OneTime; case BindingFlags.PropDefault: return BindingMode.Default; } Invariant.Assert(false, "Unexpected BindingMode value"); return 0; } set { CheckSealed(); BindingFlags flags = FlagsFrom(value); if (flags == BindingFlags.IllegalInput) throw new InvalidEnumArgumentException("value", (int) value, typeof(BindingMode)); ChangeFlagsWithinMask(BindingFlags.PropagationMask, flags); } } /// Update type [DefaultValue(UpdateSourceTrigger.Default)] public UpdateSourceTrigger UpdateSourceTrigger { get { switch (GetFlagsWithinMask(BindingFlags.UpdateMask)) { case BindingFlags.UpdateOnPropertyChanged: return UpdateSourceTrigger.PropertyChanged; case BindingFlags.UpdateOnLostFocus: return UpdateSourceTrigger.LostFocus; case BindingFlags.UpdateExplicitly: return UpdateSourceTrigger.Explicit; case BindingFlags.UpdateDefault: return UpdateSourceTrigger.Default; } Invariant.Assert(false, "Unexpected UpdateSourceTrigger value"); return 0; } set { CheckSealed(); BindingFlags flags = FlagsFrom(value); if (flags == BindingFlags.IllegalInput) throw new InvalidEnumArgumentException("value", (int) value, typeof(UpdateSourceTrigger)); ChangeFlagsWithinMask(BindingFlags.UpdateMask, flags); } } /// Raise SourceUpdated event whenever a value flows from target to source [DefaultValue(false)] public bool NotifyOnSourceUpdated { get { return TestFlag(BindingFlags.NotifyOnSourceUpdated); } set { bool currentValue = TestFlag(BindingFlags.NotifyOnSourceUpdated); if (currentValue != value) { CheckSealed(); ChangeFlag(BindingFlags.NotifyOnSourceUpdated, value); } } } /// Raise TargetUpdated event whenever a value flows from source to target [DefaultValue(false)] public bool NotifyOnTargetUpdated { get { return TestFlag(BindingFlags.NotifyOnTargetUpdated); } set { bool currentValue = TestFlag(BindingFlags.NotifyOnTargetUpdated); if (currentValue != value) { CheckSealed(); ChangeFlag(BindingFlags.NotifyOnTargetUpdated, value); } } } /// Raise ValidationError event whenever there is a ValidationError on Update [DefaultValue(false)] public bool NotifyOnValidationError { get { return TestFlag(BindingFlags.NotifyOnValidationError); } set { bool currentValue = TestFlag(BindingFlags.NotifyOnValidationError); if (currentValue != value) { CheckSealed(); ChangeFlag(BindingFlags.NotifyOnValidationError, value); } } } /// The Converter to apply [DefaultValue(null)] public IValueConverter Converter { get { return (IValueConverter)GetValue(Feature.Converter, null); } set { CheckSealed(); SetValue(Feature.Converter, value, null); } } /// /// The parameter to pass to converter. /// /// [DefaultValue(null)] public object ConverterParameter { get { return GetValue(Feature.ConverterParameter, null); } set { CheckSealed(); SetValue(Feature.ConverterParameter, value, null); } } /// Culture in which to evaluate the converter [DefaultValue(null)] [TypeConverter(typeof(System.Windows.CultureInfoIetfLanguageTagConverter))] public CultureInfo ConverterCulture { get { return (CultureInfo)GetValue(Feature.Culture, null); } set { CheckSealed(); SetValue(Feature.Culture, value, null); } } /// object to use as the source /// To clear this property, set it to DependencyProperty.UnsetValue. public object Source { get { WeakReference wr = (WeakReference)GetValue(Feature.ObjectSource, null); if (wr == null) return null; else { object target; return wr.TryGetTarget(out target) ? target : null; } } set { CheckSealed(); if (_sourceInUse == SourceProperties.None || _sourceInUse == SourceProperties.Source) { if (value != DependencyProperty.UnsetValue) { SetValue(Feature.ObjectSource, new WeakReference(value)); SourceReference = new ExplicitObjectRef(value); } else { ClearValue(Feature.ObjectSource); SourceReference = null; } } else throw new InvalidOperationException(SR.Get(SRID.BindingConflict, SourceProperties.Source, _sourceInUse)); } } /// /// This method is used by TypeDescriptor to determine if this property should /// be serialized. /// [EditorBrowsable(EditorBrowsableState.Never)] public bool ShouldSerializeSource() { //return _objectSource.IsAlive && _objectSource.Target != DependencyProperty.UnsetValue; // M8.2: always false return false; } /// /// Description of the object to use as the source, relative to the target element. /// [DefaultValue(null)] public RelativeSource RelativeSource { get { return (RelativeSource)GetValue(Feature.RelativeSource, null); } set { CheckSealed(); if (_sourceInUse == SourceProperties.None || _sourceInUse == SourceProperties.RelativeSource) { SetValue(Feature.RelativeSource, value, null); SourceReference = (value != null) ? new RelativeObjectRef(value) : null; } else throw new InvalidOperationException(SR.Get(SRID.BindingConflict, SourceProperties.RelativeSource, _sourceInUse)); } } /// Name of the element to use as the source [DefaultValue(null)] public string ElementName { get { return (string)GetValue(Feature.ElementSource, null); } set { CheckSealed(); if (_sourceInUse == SourceProperties.None || _sourceInUse == SourceProperties.ElementName) { SetValue(Feature.ElementSource, value, null); SourceReference = (value != null) ? new ElementObjectRef(value) : null; } else throw new InvalidOperationException(SR.Get(SRID.BindingConflict, SourceProperties.ElementName, _sourceInUse)); } } /// True if Binding should get/set values asynchronously [DefaultValue(false)] public bool IsAsync { get { return _isAsync; } set { CheckSealed(); _isAsync = value; } } /// Opaque data passed to the asynchronous data dispatcher [DefaultValue(null)] public object AsyncState { get { return GetValue(Feature.AsyncState, null); } set { CheckSealed(); SetValue(Feature.AsyncState, value, null); } } /// True if Binding should interpret its path relative to /// the data item itself. /// /// /// The normal behavior (when this property is false) /// includes special treatment for a data item that implements IDataSource. /// In this case, the path is treated relative to the object obtained /// from the IDataSource.Data property. In addition, the binding listens /// for the IDataSource.DataChanged event and reacts accordingly. /// Setting this property to true overrides this behavior and gives /// the binding access to properties on the data source object itself. /// [DefaultValue(false)] public bool BindsDirectlyToSource { get { return _bindsDirectlyToSource; } set { CheckSealed(); _bindsDirectlyToSource = value; } } /// /// 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 - don't 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 element's Validation errors. /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public UpdateSourceExceptionFilterCallback UpdateSourceExceptionFilter { get { return (UpdateSourceExceptionFilterCallback)GetValue(Feature.ExceptionFilterCallback, null); } set { SetValue(Feature.ExceptionFilterCallback, value, null); } } //------------------------------------------------------ // // Public Fields // //------------------------------------------------------ /// /// A source property or a converter can return Binding.DoNothing /// to instruct the binding engine to do nothing (i.e. do not transfer /// a value to the target, do not move to the next Binding in a /// PriorityBinding, do not use the fallback or default value). /// public static readonly object DoNothing = new NamedObject("Binding.DoNothing"); /// /// This string is used as the PropertyName of the /// PropertyChangedEventArgs to indicate that an indexer property /// has been changed. /// public const string IndexerName = "Item[]"; //------------------------------------------------------ // // Protected Methods // //------------------------------------------------------ /// /// Create an appropriate expression for this Binding, to be attached /// to the given DependencyProperty on the given DependencyObject. /// internal override BindingExpressionBase CreateBindingExpressionOverride(DependencyObject target, DependencyProperty dp, BindingExpressionBase owner) { return BindingExpression.CreateBindingExpression(target, dp, this, owner); } internal override ValidationRule LookupValidationRule(Type type) { return LookupValidationRule(type, ValidationRulesInternal); } //------------------------------------------------------ // // Internal Methods // //------------------------------------------------------ internal object DoFilterException(object bindExpr, Exception exception) { UpdateSourceExceptionFilterCallback callback = (UpdateSourceExceptionFilterCallback)GetValue(Feature.ExceptionFilterCallback, null); if (callback != null) return callback(bindExpr, exception); return exception; } // called by BindingExpression when the Binding doesn't specify a path. // (Can't use Path setter, since that replaces the BindingExpression.) internal void UsePath(PropertyPath path) { _ppath = path; SetFlag(BindingFlags.PathGeneratedInternally); } internal override BindingBase CreateClone() { return new Binding(); } internal override void InitializeClone(BindingBase baseClone, BindingMode mode) { Binding clone = (Binding)baseClone; clone._ppath = _ppath; CopyValue(Feature.XPath, clone); clone._source = _source; CopyValue(Feature.Culture, clone); clone._isAsync = _isAsync; CopyValue(Feature.AsyncState, clone); clone._bindsDirectlyToSource = _bindsDirectlyToSource; clone._doesNotTransferDefaultValue = _doesNotTransferDefaultValue; CopyValue(Feature.ObjectSource, clone); CopyValue(Feature.RelativeSource, clone); CopyValue(Feature.Converter, clone); CopyValue(Feature.ConverterParameter, clone); clone._attachedPropertiesInPath = _attachedPropertiesInPath; CopyValue(Feature.ValidationRules, clone); base.InitializeClone(baseClone, mode); } //------------------------------------------------------ // // Internal Properties // //------------------------------------------------------ internal override CultureInfo ConverterCultureInternal { get { return ConverterCulture; } } internal ObjectRef SourceReference { get { return (_source == UnsetSource) ? null : _source; } set { CheckSealed(); _source = value; DetermineSource(); } } internal bool TreeContextIsRequired { get { bool treeContextIsRequired; // attached properties in the property path (like "(DockPanel.Dock)") // need inherited value of XmlAttributeProperties properties for namespaces, // unless the properties are pre-resolved by the parser if (_attachedPropertiesInPath < 0) { if (_ppath != null) { _attachedPropertiesInPath = _ppath.ComputeUnresolvedAttachedPropertiesInPath(); } else { _attachedPropertiesInPath = 0; } } treeContextIsRequired = (_attachedPropertiesInPath > 0); // namespace prefixes in the XPath need an XmlNamespaceManager if (!treeContextIsRequired && HasValue(Feature.XPath) && XPath.IndexOf(':') >= 0) { treeContextIsRequired = true; } return treeContextIsRequired; } } // same as the public ValidationRules property, but // doesn't try to create an instance if there isn't one there internal override Collection ValidationRulesInternal { get { return (ValidationRuleCollection)GetValue(Feature.ValidationRules, null); } } // when the source property has its default value, this flag controls // whether the binding transfers the value anyway, or simply "hides" // so that the property engine obtains the target value some other way. internal bool TransfersDefaultValue { get { return !_doesNotTransferDefaultValue; } set { CheckSealed(); _doesNotTransferDefaultValue = !value; } } internal override bool ValidatesOnNotifyDataErrorsInternal { get { return ValidatesOnNotifyDataErrors; } } //------------------------------------------------------ // // Private Methods // //------------------------------------------------------ // determine the source property currently in use void DetermineSource() { _sourceInUse = (_source == UnsetSource) ? SourceProperties.None : (HasValue(Feature.RelativeSource)) ? SourceProperties.RelativeSource : (HasValue(Feature.ElementSource)) ? SourceProperties.ElementName : (HasValue(Feature.ObjectSource)) ? SourceProperties.Source : (_source == StaticSourceRef) ? SourceProperties.StaticSource : SourceProperties.InternalSource; } //------------------------------------------------------ // // Private Fields // //------------------------------------------------------ SourceProperties _sourceInUse; PropertyPath _ppath; ObjectRef _source = UnsetSource; bool _isAsync; bool _bindsDirectlyToSource; bool _doesNotTransferDefaultValue; // initially = false int _attachedPropertiesInPath; static readonly ObjectRef UnsetSource = new ExplicitObjectRef(null); static readonly ObjectRef StaticSourceRef = new ExplicitObjectRef(BindingExpression.StaticSource); } }