// 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