|
// 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: Helper operations for data bindings.
//
// See spec at Data Binding.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.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Markup;
using System.Windows.Threading;
using MS.Internal.Data;
namespace System.Windows.Data
{
/// <summary>
/// Operations to manipulate data bindings.
/// </summary>
public static class BindingOperations
{
//------------------------------------------------------
//
// Public Properties
//
//------------------------------------------------------
/// <summary>
/// A sentinel object. WPF assigns this as the DataContext of elements
/// that leave an ItemsControl because (a) the corresponding item is
/// removed from the ItemsSource collection, or (b) the element is
/// scrolled out of view and re-virtualized. Bindings that use DataContext
/// react by unhooking from property-changed events. This keeps the
/// discarded elements from interfering with the still-visible elements.
/// </summary>
public static object DisconnectedSource
{
get { return BindingExpressionBase.DisconnectedItem; }
}
//------------------------------------------------------
//
// Public Methods
//
//------------------------------------------------------
/// <summary>
/// Attach a BindingExpression to a property.
/// </summary>
/// <remarks>
/// A new BindingExpression is created from the given description, and attached to
/// the given property of the given object. This method is the way to
/// attach a Binding to an arbitrary DependencyObject that may not expose
/// its own SetBinding method.
/// </remarks>
/// <param name="target">object on which to attach the Binding</param>
/// <param name="dp">property to which to attach the Binding</param>
/// <param name="binding">description of the Binding</param>
/// <exception cref="ArgumentNullException"> target and dp and binding cannot be null </exception>
public static BindingExpressionBase SetBinding(DependencyObject target, DependencyProperty dp, BindingBase binding)
{
ArgumentNullException.ThrowIfNull(target);
ArgumentNullException.ThrowIfNull(dp);
ArgumentNullException.ThrowIfNull(binding);
// target.VerifyAccess();
BindingExpressionBase bindExpr = binding.CreateBindingExpression(target, dp);
// BUG: should prevent transfers and updates until BindingExpression is ready
//BindingExpression.SetFlag(BindingFlags.iInTransfer | BindingFlags.iInUpdate);
target.SetValue(dp, bindExpr);
return bindExpr;
}
/// <summary>
/// Retrieve a BindingBase.
/// </summary>
/// <remarks>
/// This method returns null if no Binding has been set on the given
/// property.
/// </remarks>
/// <param name="target">object from which to retrieve the binding</param>
/// <param name="dp">property from which to retrieve the binding</param>
/// <exception cref="ArgumentNullException"> target and dp cannot be null </exception>
public static BindingBase GetBindingBase(DependencyObject target, DependencyProperty dp)
{
BindingExpressionBase b = GetBindingExpressionBase(target, dp);
return (b != null) ? b.ParentBindingBase : null;
}
/// <summary>
/// Retrieve a Binding.
/// </summary>
/// <remarks>
/// This method returns null if no Binding has been set on the given
/// property.
/// </remarks>
/// <param name="target">object from which to retrieve the binding</param>
/// <param name="dp">property from which to retrieve the binding</param>
/// <exception cref="ArgumentNullException"> target and dp cannot be null </exception>
public static Binding GetBinding(DependencyObject target, DependencyProperty dp)
{
return GetBindingBase(target, dp) as Binding;
}
/// <summary>
/// Retrieve a PriorityBinding.
/// </summary>
/// <remarks>
/// This method returns null if no Binding has been set on the given
/// property.
/// </remarks>
/// <param name="target">object from which to retrieve the binding</param>
/// <param name="dp">property from which to retrieve the binding</param>
/// <exception cref="ArgumentNullException"> target and dp cannot be null </exception>
public static PriorityBinding GetPriorityBinding(DependencyObject target, DependencyProperty dp)
{
return GetBindingBase(target, dp) as PriorityBinding;
}
/// <summary>
/// Retrieve a MultiBinding.
/// </summary>
/// <remarks>
/// This method returns null if no Binding has been set on the given
/// property.
/// </remarks>
/// <param name="target">object from which to retrieve the binding</param>
/// <param name="dp">property from which to retrieve the binding</param>
/// <exception cref="ArgumentNullException"> target and dp cannot be null </exception>
public static MultiBinding GetMultiBinding(DependencyObject target, DependencyProperty dp)
{
return GetBindingBase(target, dp) as MultiBinding;
}
/// <summary>
/// Retrieve a BindingExpressionBase.
/// </summary>
/// <remarks>
/// This method returns null if no Binding has been set on the given
/// property.
/// </remarks>
/// <param name="target">object from which to retrieve the BindingExpression</param>
/// <param name="dp">property from which to retrieve the BindingExpression</param>
/// <exception cref="ArgumentNullException"> target and dp cannot be null </exception>
public static BindingExpressionBase GetBindingExpressionBase(DependencyObject target, DependencyProperty dp)
{
ArgumentNullException.ThrowIfNull(target);
ArgumentNullException.ThrowIfNull(dp);
// target.VerifyAccess();
Expression expr = StyleHelper.GetExpression(target, dp);
return expr as BindingExpressionBase;
}
/// <summary>
/// Retrieve a BindingExpression.
/// </summary>
/// <remarks>
/// This method returns null if no Binding has been set on the given
/// property.
/// </remarks>
/// <param name="target">object from which to retrieve the BindingExpression</param>
/// <param name="dp">property from which to retrieve the BindingExpression</param>
/// <exception cref="ArgumentNullException"> target and dp cannot be null </exception>
public static BindingExpression GetBindingExpression(DependencyObject target, DependencyProperty dp)
{
BindingExpressionBase expr = GetBindingExpressionBase(target, dp);
PriorityBindingExpression pb = expr as PriorityBindingExpression;
if (pb != null)
expr = pb.ActiveBindingExpression;
return expr as BindingExpression;
}
/// <summary>
/// Retrieve a MultiBindingExpression.
/// </summary>
/// <remarks>
/// This method returns null if no MultiBinding has been set on the given
/// property.
/// </remarks>
/// <param name="target">object from which to retrieve the MultiBindingExpression</param>
/// <param name="dp">property from which to retrieve the MultiBindingExpression</param>
/// <exception cref="ArgumentNullException"> target and dp cannot be null </exception>
public static MultiBindingExpression GetMultiBindingExpression(DependencyObject target, DependencyProperty dp)
{
return GetBindingExpressionBase(target, dp) as MultiBindingExpression;
}
/// <summary>
/// Retrieve a PriorityBindingExpression.
/// </summary>
/// <remarks>
/// This method returns null if no PriorityBinding has been set on the given
/// property.
/// </remarks>
/// <param name="target">object from which to retrieve the PriorityBindingExpression</param>
/// <param name="dp">property from which to retrieve the PriorityBindingExpression</param>
/// <exception cref="ArgumentNullException"> target and dp cannot be null </exception>
public static PriorityBindingExpression GetPriorityBindingExpression(DependencyObject target, DependencyProperty dp)
{
return GetBindingExpressionBase(target, dp) as PriorityBindingExpression;
}
/// <summary>
/// Remove data Binding (if any) from a property.
/// </summary>
/// <remarks>
/// If the given property is data-bound, via a Binding, PriorityBinding or MultiBinding,
/// the BindingExpression is removed, and the property's value changes to what it
/// would be as if no local value had ever been set.
/// If the given property is not data-bound, this method has no effect.
/// </remarks>
/// <param name="target">object from which to remove Binding</param>
/// <param name="dp">property from which to remove Binding</param>
/// <exception cref="ArgumentNullException"> target and dp cannot be null </exception>
public static void ClearBinding(DependencyObject target, DependencyProperty dp)
{
ArgumentNullException.ThrowIfNull(target);
ArgumentNullException.ThrowIfNull(dp);
// target.VerifyAccess();
if (IsDataBound(target, dp))
target.ClearValue(dp);
}
/// <summary>
/// Remove all data Binding (if any) from a DependencyObject.
/// </summary>
/// <param name="target">object from which to remove bindings</param>
/// <exception cref="ArgumentNullException"> DependencyObject target cannot be null </exception>
public static void ClearAllBindings(DependencyObject target)
{
ArgumentNullException.ThrowIfNull(target);
// target.VerifyAccess();
LocalValueEnumerator lve = target.GetLocalValueEnumerator();
// Batch properties that have BindingExpressions since clearing
// during a local value enumeration is illegal
ArrayList batch = new ArrayList(8);
while (lve.MoveNext())
{
LocalValueEntry entry = lve.Current;
if (IsDataBound(target, entry.Property))
{
batch.Add(entry.Property);
}
}
// Clear all properties that are storing BindingExpressions
for (int i = 0; i < batch.Count; i++)
{
target.ClearValue((DependencyProperty)batch[i]);
}
}
/// <summary>Return true if the property is currently data-bound</summary>
/// <exception cref="ArgumentNullException"> DependencyObject target cannot be null </exception>
public static bool IsDataBound(DependencyObject target, DependencyProperty dp)
{
ArgumentNullException.ThrowIfNull(target);
ArgumentNullException.ThrowIfNull(dp);
// target.VerifyAccess();
object o = StyleHelper.GetExpression(target, dp);
return (o is BindingExpressionBase);
}
/// <summary>
/// Register a callback used to synchronize access to a given collection.
/// </summary>
/// <param name="collection"> The collection that needs synchronized access. </param>
/// </param name="context"> An arbitrary object. This object is passed back into
/// the callback; it is not used otherwise. It provides a way for the
/// application to store information it knows at registration time, which it
/// can then use at collection-access time. Typically this information will
/// help the application determine the synchronization mechanism used to
/// control access to the given collection. </param>
/// <param name="synchronizationCallback"> The callback to be invoked whenever
/// access to the collection is required. </param>
public static void EnableCollectionSynchronization(
IEnumerable collection,
object context,
CollectionSynchronizationCallback synchronizationCallback)
{
ArgumentNullException.ThrowIfNull(collection);
ArgumentNullException.ThrowIfNull(synchronizationCallback);
ViewManager.Current.RegisterCollectionSynchronizationCallback(
collection, context, synchronizationCallback);
}
/// <summary>
/// Register a lock object used to synchronize access to a given collection.
/// </summary>
/// <param name="collection"> The collection that needs synchronized access. </param>
/// <param name="lockObject"> The object to lock when accessing the collection. </param>
public static void EnableCollectionSynchronization(
IEnumerable collection,
object lockObject)
{
ArgumentNullException.ThrowIfNull(collection);
ArgumentNullException.ThrowIfNull(lockObject);
ViewManager.Current.RegisterCollectionSynchronizationCallback(
collection, lockObject, null);
}
/// <summary>
/// Remove the synchronization registered for a given collection. Any subsequent
/// access to the collection will be unsynchronized.
/// </summary>
/// <param name="collection"> The collection that needs its synchronized access removed. </param>
public static void DisableCollectionSynchronization(
IEnumerable collection)
{
ArgumentNullException.ThrowIfNull(collection);
ViewManager.Current.RegisterCollectionSynchronizationCallback(
collection, null, null);
}
/// <summary>
/// A caller can use this method to access a collection using the
/// synchronization that the application has registered for that collection.
/// If no synchronization is registered, the access method is simply called
/// directly.
/// </summary>
/// <notes>
/// 1. This method is provided chiefly for collection views, which need to
/// redirect access through the application-provided synchronization pattern.
/// An application typically doesn't need to call it, as the application
/// can use its synchronization directly.
/// 2. It is usually convenient to provide a closure as the access
/// method. This closure will have access to local variables at the
/// site of the call. For example, the following code reads _collection[3]:
/// void MyMethod()
/// {
/// int index = 3;
/// int result = 0;
/// BindingOperations.AccessCollection(
/// _collection,
/// () => { result = _collection[index]; },
/// false); // read-access
/// }
/// Note that the access method refers to local variables (index, result)
/// of MyMethod, as well as to an instance variable (_collection) of the
/// 'this' object.
/// </notes>
public static void AccessCollection(
IEnumerable collection,
Action accessMethod,
bool writeAccess)
{
ViewManager vm = ViewManager.Current;
if (vm == null)
throw new InvalidOperationException(SR.Format(SR.AccessCollectionAfterShutDown, collection));
vm.AccessCollection(collection, accessMethod, writeAccess);
}
/// <summary>
/// Returns a list of all binding expressions that are:
/// a) top-level (do not belong to a parent MultiBindingExpression or BindingGroup)
/// b) source-updating (binding mode is TwoWay or OneWayToSource)
/// c) currently dirty or invalid
/// and d) attached to a descendant of the given DependencyObject (if non-null).
/// These are the bindings that may need attention before executing a command.
/// </summary>
public static ReadOnlyCollection<BindingExpressionBase> GetSourceUpdatingBindings(DependencyObject root)
{
List<BindingExpressionBase> list = DataBindEngine.CurrentDataBindEngine.CommitManager.GetBindingsInScope(root);
return new ReadOnlyCollection<BindingExpressionBase>(list);
}
/// <summary>
/// Returns a list of all BindingGroups that are:
/// a) currently dirty or invalid
/// and b) attached to a descendant of the given DependencyObject (if non-null).
/// </summary>
public static ReadOnlyCollection<BindingGroup> GetSourceUpdatingBindingGroups(DependencyObject root)
{
List<BindingGroup> list = DataBindEngine.CurrentDataBindEngine.CommitManager.GetBindingGroupsInScope(root);
return new ReadOnlyCollection<BindingGroup>(list);
}
//------------------------------------------------------
//
// Public Events
//
//------------------------------------------------------
/// <summary>
/// This event is raised when a collection is first noticed by the data-binding system.
/// It provides an opportunity to register information about the collection,
/// before the data-binding system begins to use the collection.
/// </summary>
/// <notes>
/// In an application with multiple UI threads (i.e. multiple Dispatchers),
/// this event is raised independently on each thread the first time the
/// collection is noticed on that thread.
/// </notes>
public static event EventHandler<CollectionRegisteringEventArgs> CollectionRegistering;
/// <summary>
/// This event is raised when a collection view is first noticed by the data-binding system.
/// It provides an opportunity to modify the collection view,
/// before the data-binding system begins to use it.
/// </summary>
/// <notes>
/// In an application with multiple UI threads (i.e. multiple Dispatchers),
/// each thread has its own set of collection views. This event is raised
/// on the thread that owns the given collection view.
/// </notes>
public static event EventHandler<CollectionViewRegisteringEventArgs> CollectionViewRegistering;
//------------------------------------------------------
//
// Internal Methods
//
//------------------------------------------------------
// return false if this is an invalid value for UpdateSourceTrigger
internal static bool IsValidUpdateSourceTrigger(UpdateSourceTrigger value)
{
switch (value)
{
case UpdateSourceTrigger.Default:
case UpdateSourceTrigger.PropertyChanged:
case UpdateSourceTrigger.LostFocus:
case UpdateSourceTrigger.Explicit:
return true;
default:
return false;
}
}
// The following properties and methods have no internal callers. They
// can be called by suitably privileged external callers via reflection.
// They are intended to be used by test programs and the DRT.
// Enable or disable the cleanup pass. For use by tests that measure
// perf, to avoid noise from the cleanup pass.
internal static bool IsCleanupEnabled
{
get { return DataBindEngine.CurrentDataBindEngine.CleanupEnabled; }
set { DataBindEngine.CurrentDataBindEngine.CleanupEnabled = value; }
}
// Force a cleanup pass (even if IsCleanupEnabled is true). For use
// by leak-detection tests, to avoid false leak reports about objects
// held by the DataBindEngine that can be cleaned up. Returns true
// if something was actually cleaned up.
internal static bool Cleanup()
{
return DataBindEngine.CurrentDataBindEngine.Cleanup();
}
// Print various interesting statistics
[Conditional("DEBUG")]
internal static void PrintStats()
{
DataBindEngine.CurrentDataBindEngine.AccessorTable.PrintStats();
}
// Trace the size of the accessor table after each generation
internal static bool TraceAccessorTableSize
{
get { return DataBindEngine.CurrentDataBindEngine.AccessorTable.TraceSize; }
set { DataBindEngine.CurrentDataBindEngine.AccessorTable.TraceSize = value; }
}
// Raise the CollectionRegistering event
internal static void OnCollectionRegistering(IEnumerable collection, object parent)
{
if (CollectionRegistering != null)
CollectionRegistering(null, new CollectionRegisteringEventArgs(collection, parent));
}
// Raise the CollectionViewRegistering event
internal static void OnCollectionViewRegistering(CollectionView view)
{
if (CollectionViewRegistering != null)
CollectionViewRegistering(null, new CollectionViewRegisteringEventArgs(view));
}
#if LiveShapingInstrumentation
public static void SetNodeSize(int nodeSize)
{
LiveShapingBlock.SetNodeSize(nodeSize);
}
public static void SetQuickSortThreshold(int threshold)
{
LiveShapingTree.SetQuickSortThreshold(threshold);
}
public static void SetBinarySearchThreshold(int threshold)
{
LiveShapingTree.SetBinarySearchThreshold(threshold);
}
public static void ResetComparisons(ListCollectionView lcv)
{
lcv.ResetComparisons();
}
public static void ResetCopies(ListCollectionView lcv)
{
lcv.ResetCopies();
}
public static void ResetAverageCopy(ListCollectionView lcv)
{
lcv.ResetAverageCopy();
}
public static int GetComparisons(ListCollectionView lcv)
{
return lcv.GetComparisons();
}
public static int GetCopies(ListCollectionView lcv)
{
return lcv.GetCopies();
}
public static double GetAverageCopy(ListCollectionView lcv)
{
return lcv.GetAverageCopy();
}
#endif // LiveShapingInstrumentation
#region Exception logging
// For use when testing whether an exception is thrown, even though the
// exception is caught. The catch-block logs the exception, and the
// test code can examine the log.
// Enable exception-logging.
// Test code uses the following pattern (suitably translated to reflection-based calls):
// using (ExceptionLogger logger = BindingOperations.EnableExceptionLogging())
// {
// DoSomethingThatMightThrowACaughtException();
// if (logger.Log.Count > 0)
// {
// // an exception was thrown - react appropriately
// }
// }
internal static IDisposable EnableExceptionLogging()
{
ExceptionLogger newLogger = new ExceptionLogger();
Interlocked.CompareExchange<ExceptionLogger>(ref _exceptionLogger, newLogger, null);
return _exceptionLogger;
}
// Log an exception, if logging is active (called from framework catch-blocks that opt-in)
internal static void LogException(Exception ex)
{
ExceptionLogger logger = _exceptionLogger;
if (logger != null)
{
logger.LogException(ex);
}
}
private static ExceptionLogger _exceptionLogger;
internal class ExceptionLogger : IDisposable
{
// Log an exception
internal void LogException(Exception ex)
{
_log.Add(ex);
}
// when the active logger is disposed, turn off logging
void IDisposable.Dispose()
{
Interlocked.CompareExchange<ExceptionLogger>(ref _exceptionLogger, null, this);
}
internal List<Exception> Log { get { return _log; } }
List<Exception> _log = new List<Exception>();
}
#endregion Exception logging
}
}
|