Code:
/ Net / Net / 3.5.50727.3053 / DEVDIV / depot / DevDiv / releases / Orcas / SP / wpf / src / Framework / System / Windows / Data / BindingGroup.cs / 2 / BindingGroup.cs
//----------------------------------------------------------------------------
//
//
// Copyright (C) Microsoft Corporation. All rights reserved.
//
//
// Description: Defines BindingGroup object, manages a collection of bindings.
//
//---------------------------------------------------------------------------
using System;
using System.Collections; // IList
using System.Collections.Generic; // IList
using System.Collections.ObjectModel; // Collection
using System.Collections.Specialized; // INotifyCollectionChanged
using System.ComponentModel; // IEditableObject
using System.Diagnostics; // Debug
using System.Globalization; // CultureInfo
using System.Windows;
using System.Windows.Controls; // ValidationRule
using MS.Internal.Controls; // ValidationRuleCollection
using MS.Internal; // InheritanceContextHelper
namespace System.Windows.Data
{
///
/// A BindingGroup manages a collection of bindings, and provides services for
/// item-level and cross-binding validation.
///
public class BindingGroup : DependencyObject
{
#region Constructors
//-----------------------------------------------------
//
// Constructors
//
//-----------------------------------------------------
///
/// Initializes a new instance of the BindingGroup class.
///
public BindingGroup()
{
_validationRules = new ValidationRuleCollection();
Initialize();
}
// clone the binding group. Called when setting a binding group on a
// container, from the ItemControl's ItemBindingGroup.
internal BindingGroup(BindingGroup master)
{
_validationRules = master._validationRules;
_name = master._name;
_notifyOnValidationError = master._notifyOnValidationError;
Initialize();
}
void Initialize()
{
_bindingExpressions = new BindingExpressionCollection();
((INotifyCollectionChanged)_bindingExpressions).CollectionChanged += new NotifyCollectionChangedEventHandler(OnBindingsChanged);
_itemsRW = new Collection();
_items = new WeakReadOnlyCollection(_itemsRW);
}
#endregion Constructors
#region Public properties
//------------------------------------------------------
//
// Public properties
//
//-----------------------------------------------------
///
/// The validation rules belonging to a BindingGroup are run during the
/// process of updating the source values of the bindings. Each rule
/// indicates where in that process it should run.
///
public Collection ValidationRules
{
get { return _validationRules; }
}
///
/// The collection of binding expressions belonging to this BindingGroup.
///
public Collection BindingExpressions
{
get { return _bindingExpressions; }
}
///
/// The name of this BindingGroup. A binding can elect to join this group
/// by declaring its BindingGroupName to match the name of the group.
///
public string Name
{
get { return _name; }
set { _name = value; }
}
///
/// When NotifyOnValidationError is set to True, the binding group will
/// raise a Validation.ValidationError event when its validation state changes.
///
public bool NotifyOnValidationError
{
get { return _notifyOnValidationError; }
set { _notifyOnValidationError = value; }
}
///
/// CanRestoreValues returns True if the binding group can restore
/// each of its sources (during ) to the state
/// they had at the time of the most recent .
/// This depends on whether the current sources provide a suitable
/// mechanism to implement the rollback, such as .
///
public bool CanRestoreValues
{
get
{
IList items = Items;
for (int i=items.Count-1; i>=0; --i)
{
if (!(items[i] is IEditableObject))
{
return false;
}
}
return true;
}
}
///
/// The collection of items used as sources in the bindings owned by
/// this BindingGroup. Each item appears only once, even if it is used
/// by several bindings.
///
///
/// The Items property returns a snapshot collection, reflecting the state
/// of the BindingGroup at the time of the call. As bindings in the group
/// change to use different source items, the changes are not immediately
/// visible in the collection. They become visible only when the property is
/// queried again.
///
public IList Items
{
get
{
// rebuild the Items collection, if necessary
if (!_isItemsValid)
{
// find the new set of items
IList newItems = _getValueTable.UniqueItems();
// modify the Items collection to match the new set
// First, remove items that no longer appear
for (int i=_itemsRW.Count-1; i >= 0; --i)
{
int index = FindIndexOf(_itemsRW[i], newItems);
if (index >= 0)
{
newItems.RemoveAt(index); // common item, don't add it later
}
else
{
_itemsRW.RemoveAt(i); // item no longer appears, remove it now
}
}
// then add items that are really new
for (int i=newItems.Count-1; i>=0; --i)
{
_itemsRW.Add(newItems[i]);
}
_isItemsValid = true;
}
return _items;
}
}
#endregion Public properties
#region Public Methods
//------------------------------------------------------
//
// Public Methods
//
//------------------------------------------------------
///
/// Begin an editing transaction. For each source that supports it,
/// the binding group asks the source to save its state, for possible
/// restoration during .
///
public void BeginEdit()
{
IList items = Items;
for (int i=items.Count-1; i>=0; --i)
{
IEditableObject ieo = items[i] as IEditableObject;
if (ieo != null)
{
ieo.BeginEdit();
}
}
}
///
/// End an editing transaction. The binding group attempts to update all
/// its sources with the proposed new values held in the target UI elements.
/// All validation rules are run, at the times requested by the rules.
///
///
/// True, if all validation rules succeed and no errors arise.
/// False, otherwise.
///
public bool CommitEdit()
{
return UpdateAndValidate(ValidationStep.CommittedValue);
}
///
/// Cancel an editing transaction. For each source that supports it,
/// the binding group asks the source to restore itself to the state saved
/// at the most recent . Then the binding group
/// updates all targets with values from their respective sources, discarding
/// any "dirty" values held in the targets.
///
public void CancelEdit()
{
// restore values
IList items = Items;
for (int i=items.Count-1; i>=0; --i)
{
IEditableObject ieo = items[i] as IEditableObject;
if (ieo != null)
{
ieo.CancelEdit();
}
}
// update targets
for (int i=_bindingExpressions.Count - 1; i>=0; --i)
{
_bindingExpressions[i].UpdateTarget();
}
}
///
/// Run the validation process up to the ConvertedProposedValue step.
/// This runs all validation rules marked as RawProposedValue or
/// ConvertedProposedValue, but does not update any sources with new values.
///
///
/// True, if all validation rules succeed and no errors arise.
/// False, otherwise.
///
public bool ValidateWithoutUpdate()
{
return UpdateAndValidate(ValidationStep.ConvertedProposedValue);
}
///
/// Run the validation process up to the UpdatedValue step.
/// This runs all validation rules marked as RawProposedValue or
/// ConvertedProposedValue, updates the sources with new values, and
/// runs rules marked as Updatedvalue.
///
///
/// True, if all validation rules succeed and no errors arise.
/// False, otherwise.
///
public bool UpdateSources()
{
return UpdateAndValidate(ValidationStep.UpdatedValue);
}
///
/// Find the binding that uses the given item and property, and return
/// the value appropriate to the current validation step.
///
///
/// the binding group does not contain a binding corresponding to the
/// given item and property.
///
///
/// the value is not available. This could be because an earlier validation
/// rule deemed the value invalid, or because the value could not be produced
/// for some reason, such as conversion failure.
///
///
/// This method is intended to be called from a validation rule, during
/// its Validate method.
///
public object GetValue(object item, string propertyName)
{
object value;
if (TryGetValueImpl(item, propertyName, out value))
{
return value;
}
if (value == Binding.DoNothing)
throw new ValueUnavailableException(SR.Get(SRID.BindingGroup_NoEntry, item, propertyName));
else
throw new ValueUnavailableException(SR.Get(SRID.BindingGroup_ValueUnavailable, item, propertyName));
}
///
/// Find the binding that uses the given item and property, and return
/// the value appropriate to the current validation step.
///
///
/// The method normally returns true and sets 'value' to the requested value.
/// If the value is not available, the method returns false and sets 'value'
/// to DependencyProperty.UnsetValue.
///
///
/// This method is intended to be called from a validation rule, during
/// its Validate method.
///
public bool TryGetValue(object item, string propertyName, out object value)
{
bool result = TryGetValueImpl(item, propertyName, out value);
// TryGetValueImpl sets value to DoNothing to signal "no entry".
// TryGetValue should treat this as just another unavailable value.
if (value == Binding.DoNothing)
{
value = DependencyProperty.UnsetValue;
}
return result;
}
bool TryGetValueImpl(object item, string propertyName, out object value)
{
GetValueTableEntry entry = _getValueTable[item, propertyName];
if (entry == null)
{
value = Binding.DoNothing; // signal "no entry"
return false;
}
switch (_validationStep)
{
case ValidationStep.RawProposedValue:
case ValidationStep.ConvertedProposedValue:
case ValidationStep.UpdatedValue:
case ValidationStep.CommittedValue:
value = entry.Value;
break;
// outside of validation process, use the raw value
default:
value = entry.BindingExpressionBase.RootBindingExpression.GetRawProposedValue();
break;
}
if (value == Binding.DoNothing)
{
// a converter has indicated that no value should be written to the source object.
// Therefore the source's value is the one to return to the validation rule.
BindingExpression bindingExpression = (BindingExpression)entry.BindingExpressionBase;
value = bindingExpression.SourceValue;
}
return (value != DependencyProperty.UnsetValue);
}
#endregion Public Methods
#region Internal properties
//-----------------------------------------------------
//
// Internal properties
//
//------------------------------------------------------
// Define the DO's inheritance context
internal override DependencyObject InheritanceContext
{
get { return _inheritanceContext; }
}
// Receive a new inheritance context (this will be a FE/FCE)
internal override void AddInheritanceContext(DependencyObject context, DependencyProperty property)
{
if (property != null && property.PropertyType != typeof(BindingGroup) &&
TraceData.IsEnabled)
{
string name = (property != null) ? property.Name : "(null)";
TraceData.Trace(TraceEventType.Warning,
TraceData.BindingGroupWrongProperty(name, context.GetType().FullName));
}
InheritanceContextHelper.AddInheritanceContext(context,
this,
ref _hasMultipleInheritanceContexts,
ref _inheritanceContext );
// sharing a BindingGroup among multiple hosts is bad - we wouldn't know which host
// to send the errors to (just for starters). But sharing an ItemBindingGroup is
// expected - this is what happens normally in a hierarchical control like TreeView.
// The following code tries to detect the bad case and warn the user that something
// is amiss.
if (_hasMultipleInheritanceContexts && property != ItemsControl.ItemBindingGroupProperty && TraceData.IsEnabled)
{
TraceData.Trace(TraceEventType.Warning,
TraceData.BindingGroupMultipleInheritance);
}
}
// Remove an inheritance context (this will be a FE/FCE)
internal override void RemoveInheritanceContext(DependencyObject context, DependencyProperty property)
{
InheritanceContextHelper.RemoveInheritanceContext(context,
this,
ref _hasMultipleInheritanceContexts,
ref _inheritanceContext);
}
// Says if the current instance has multiple InheritanceContexts
internal override bool HasMultipleInheritanceContexts
{
get { return _hasMultipleInheritanceContexts; }
}
#endregion Internal properties
#region Internal methods
//-----------------------------------------------------
//
// Internal methods
//
//-----------------------------------------------------
// called when a leaf binding changes its source item
internal void UpdateTable(BindingExpression bindingExpression)
{
_getValueTable.Update(bindingExpression);
_isItemsValid = false;
}
// add an entry to the value table for the given binding
internal void AddToValueTable(BindingExpressionBase bindingExpressionBase)
{
_getValueTable.EnsureEntry(bindingExpressionBase);
}
// get the value for the given binding
internal object GetValue(BindingExpressionBase bindingExpressionBase)
{
return _getValueTable.GetValue(bindingExpressionBase);
}
// set the value for the given binding
internal void SetValue(BindingExpressionBase bindingExpressionBase, object value)
{
_getValueTable.SetValue(bindingExpressionBase, value);
}
// set values to "source" for all bindings under the given root
internal void UseSourceValue(BindingExpressionBase bindingExpressionBase)
{
_getValueTable.UseSourceValue(bindingExpressionBase);
}
// add a validation error to the mentor's list
internal void AddValidationError(ValidationError validationError)
{
DependencyObject mentor = Helper.FindMentor(this);
if (mentor == null)
return;
Validation.AddValidationError(validationError, mentor, NotifyOnValidationError);
}
// remove a validation error from the mentor's list
internal void RemoveValidationError(ValidationError validationError)
{
DependencyObject mentor = Helper.FindMentor(this);
if (mentor == null)
return;
Validation.RemoveValidationError(validationError, mentor, NotifyOnValidationError);
}
// remove all errors raised at the given step, in preparation for running
// the rules at that step
void ClearValidationErrors(ValidationStep validationStep)
{
DependencyObject mentor = Helper.FindMentor(this);
if (mentor == null)
return;
ValidationErrorCollection validationErrors = Validation.GetErrorsInternal(mentor);
if (validationErrors == null)
return;
for (int i=validationErrors.Count-1; i>=0; --i)
{
ValidationError validationError = validationErrors[i];
if (validationError.BindingInError == this &&
validationError.RuleInError.ValidationStep == validationStep)
{
RemoveValidationError(validationError);
}
}
}
#endregion Internal methods
#region Private methods
//-----------------------------------------------------
//
// Private methods
//
//------------------------------------------------------
// run the validation process up to the indicated step
bool UpdateAndValidate(ValidationStep validationStep)
{
bool result = true;
for (_validationStep = ValidationStep.RawProposedValue;
_validationStep <= validationStep;
++ _validationStep)
{
switch (_validationStep)
{
case ValidationStep.RawProposedValue:
_getValueTable.ResetValues();
break;
case ValidationStep.ConvertedProposedValue:
ObtainConvertedProposedValues();
break;
case ValidationStep.UpdatedValue:
UpdateValues();
break;
case ValidationStep.CommittedValue:
CommitValues();
break;
}
if (!CheckValidationRules())
{
result = false;
break;
}
}
_validationStep = (ValidationStep)(-1);
_getValueTable.ResetValues();
return result;
}
// apply conversions to each binding in the group
void ObtainConvertedProposedValues()
{
for (int i=_bindingExpressions.Count-1; i>=0; --i)
{
_bindingExpressions[i].ObtainConvertedProposedValue(this);
}
}
// update the source value of each binding in the group
void UpdateValues()
{
for (int i=_bindingExpressions.Count-1; i>=0; --i)
{
_bindingExpressions[i].UpdateSource(this);
}
}
// check the validation rules for the current step
bool CheckValidationRules()
{
bool result = true;
// clear old errors arising from this step
ClearValidationErrors(_validationStep);
// check rules attached to the bindings
for (int i=_bindingExpressions.Count-1; i>=0; --i)
{
if (!_bindingExpressions[i].CheckValidationRules(this, _validationStep))
{
result = false;
}
}
// check rules attached to the binding group
CultureInfo culture = GetCulture();
for (int i=0, n=_validationRules.Count; i=0; --i)
{
IEditableObject ieo = items[i] as IEditableObject;
if (ieo != null)
{
ieo.EndEdit();
}
}
}
// find the index of an item in a list, where both the item and
// the list use WeakReferences
static int FindIndexOf(WeakReference wr, IList list)
{
object item = wr.Target;
if (item == null)
return -1;
for (int i=0, n=list.Count; i list = _getValueTable.RemoveRootBinding(root);
// tell each expression it is leaving the group
foreach (BindingExpressionBase expr in list)
{
expr.OnBindingGroupChanged(/*joining*/ false);
// also remove the expression from our collection. Normally this is
// a no-op, as we only get here after the expression has been removed,
// and implicit membership only adds root expressions to the collection.
// But an app (through confusion or malice) could explicitly add two
// or more expressions with the same root. We handle that case here.
_bindingExpressions.Remove(expr);
}
// cut the root's link to the group
root.LeaveBindingGroup();
}
// remove all binding expressions from the group
void RemoveAllBindingExpressions()
{
// we can't use the BindingExpressions collection - it has already
// been cleared. Instead, find the expressions that need work by
// looking in the GetValue table.
GetValueTableEntry entry;
while ((entry = _getValueTable.GetFirstEntry()) != null)
{
RemoveBindingExpression(entry.BindingExpressionBase);
}
}
#endregion Private methods
#region Private data
//-----------------------------------------------------
//
// Private data
//
//------------------------------------------------------
ValidationRuleCollection _validationRules;
string _name;
bool _notifyOnValidationError;
BindingExpressionCollection _bindingExpressions;
bool _isItemsValid;
ValidationStep _validationStep = (ValidationStep)(-1);
GetValueTable _getValueTable = new GetValueTable();
Collection _itemsRW;
WeakReadOnlyCollection _items;
CultureInfo _culture;
internal static readonly object DeferredTargetValue = new NamedObject("DeferredTargetValue");
internal static readonly object DeferredSourceValue = new NamedObject("DeferredSourceValue");
// Fields to implement DO's inheritance context
DependencyObject _inheritanceContext;
bool _hasMultipleInheritanceContexts;
#endregion Private data
#region Private types
//------------------------------------------------------
//
// Private types
//
//-----------------------------------------------------
// to support GetValue, we maintain an associative array of all the bindings,
// items, and property names that affect a binding group.
private class GetValueTable
{
// lookup by item and propertyName
public GetValueTableEntry this[object item, string propertyName]
{
get
{
for (int i=_table.Count-1; i >= 0; --i)
{
GetValueTableEntry entry = _table[i];
if (propertyName == entry.PropertyName &&
Object.Equals(item, entry.Item))
{
return entry;
}
}
return null;
}
}
// lookup by binding
public GetValueTableEntry this[BindingExpressionBase bindingExpressionBase]
{
get
{
for (int i=_table.Count-1; i >= 0; --i)
{
GetValueTableEntry entry = _table[i];
if (bindingExpressionBase == entry.BindingExpressionBase)
{
return entry;
}
}
return null;
}
}
// ensure an entry for the given binding
public void EnsureEntry(BindingExpressionBase bindingExpressionBase)
{
GetValueTableEntry entry = this[bindingExpressionBase];
if (entry == null)
{
_table.Add(new GetValueTableEntry(bindingExpressionBase));
}
}
// update (or add) the entry for the given leaf binding
public void Update(BindingExpression bindingExpression)
{
GetValueTableEntry entry = this[bindingExpression];
if (entry == null)
{
_table.Add(new GetValueTableEntry(bindingExpression));
}
else
{
entry.Update(bindingExpression);
}
}
// remove all the entries for the given root binding. Return the list of expressions.
public List RemoveRootBinding(BindingExpressionBase rootBindingExpression)
{
List result = new List();
for (int i=_table.Count-1; i >= 0; --i)
{
BindingExpressionBase expr = _table[i].BindingExpressionBase;
if (expr.RootBindingExpression == rootBindingExpression)
{
result.Add(expr);
_table.RemoveAt(i);
}
}
return result;
}
// return a list of the unique items (wrapped in WeakReferences)
public IList UniqueItems()
{
List list = new List();
for (int i=_table.Count-1; i >= 0; --i)
{
WeakReference itemWR = _table[i].ItemReference;
if (itemWR != null && BindingGroup.FindIndexOf(itemWR, list) < 0)
{
list.Add(itemWR);
}
}
return list;
}
// get the value for a binding expression
public object GetValue(BindingExpressionBase bindingExpressionBase)
{
GetValueTableEntry entry = this[bindingExpressionBase];
return (entry != null) ? entry.Value : DependencyProperty.UnsetValue;
}
// set the value for a binding expression
public void SetValue(BindingExpressionBase bindingExpressionBase, object value)
{
GetValueTableEntry entry = this[bindingExpressionBase];
if (entry != null)
{
entry.Value = value;
}
}
// reset values to "raw"
public void ResetValues()
{
for (int i=_table.Count-1; i>=0; --i)
{
_table[i].Value = BindingGroup.DeferredTargetValue;
}
}
// set values to "source" for all bindings under the given root
public void UseSourceValue(BindingExpressionBase rootBindingExpression)
{
for (int i=_table.Count-1; i>=0; --i)
{
if (_table[i].BindingExpressionBase.RootBindingExpression == rootBindingExpression)
{
_table[i].Value = BindingGroup.DeferredSourceValue;
}
}
}
// return the first entry in the table (or null)
public GetValueTableEntry GetFirstEntry()
{
return (_table.Count > 0) ? _table[0] : null;
}
Collection _table = new Collection();
}
// a single entry in the GetValueTable
private class GetValueTableEntry
{
public GetValueTableEntry(BindingExpressionBase bindingExpressionBase)
{
_bindingExpressionBase = bindingExpressionBase;
}
public void Update(BindingExpression bindingExpression)
{
if (_itemWR == null)
{
_itemWR = new WeakReference(bindingExpression.SourceItem); // WR to avoid leaks
}
else
{
_itemWR.Target = bindingExpression.SourceItem;
}
_propertyName = bindingExpression.SourcePropertyName;
}
public object Item
{
get { return _itemWR.Target; }
}
public WeakReference ItemReference
{
get { return _itemWR; }
}
public string PropertyName
{
get { return _propertyName; }
}
public BindingExpressionBase BindingExpressionBase
{
get { return _bindingExpressionBase; }
}
public object Value
{
get
{
if (_value == BindingGroup.DeferredTargetValue)
{
_value = _bindingExpressionBase.RootBindingExpression.GetRawProposedValue();
}
else if (_value == BindingGroup.DeferredSourceValue)
{
BindingExpression bindingExpression = _bindingExpressionBase as BindingExpression;
Debug.Assert(bindingExpression != null, "do not ask for source value from a [Multi,Priority]Binding");
_value = (bindingExpression != null) ? bindingExpression.SourceValue : DependencyProperty.UnsetValue;
}
return _value;
}
set { _value = value; }
}
BindingExpressionBase _bindingExpressionBase;
WeakReference _itemWR;
string _propertyName;
object _value = BindingGroup.DeferredTargetValue;
}
// add some error-checking to ObservableCollection
class BindingExpressionCollection : ObservableCollection
{
///
/// Called by base class Collection<T> when an item is added to list;
/// raises a CollectionChanged event to any listeners.
///
protected override void InsertItem(int index, BindingExpressionBase item)
{
if (item == null)
{
throw new ArgumentNullException("item");
}
base.InsertItem(index, item);
}
///
/// Called by base class Collection<T> when an item is set in list;
/// raises a CollectionChanged event to any listeners.
///
protected override void SetItem(int index, BindingExpressionBase item)
{
if (item == null)
{
throw new ArgumentNullException("item");
}
base.SetItem(index, item);
}
}
#endregion Private types
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.
//----------------------------------------------------------------------------
//
//
// Copyright (C) Microsoft Corporation. All rights reserved.
//
//
// Description: Defines BindingGroup object, manages a collection of bindings.
//
//---------------------------------------------------------------------------
using System;
using System.Collections; // IList
using System.Collections.Generic; // IList
using System.Collections.ObjectModel; // Collection
using System.Collections.Specialized; // INotifyCollectionChanged
using System.ComponentModel; // IEditableObject
using System.Diagnostics; // Debug
using System.Globalization; // CultureInfo
using System.Windows;
using System.Windows.Controls; // ValidationRule
using MS.Internal.Controls; // ValidationRuleCollection
using MS.Internal; // InheritanceContextHelper
namespace System.Windows.Data
{
///
/// A BindingGroup manages a collection of bindings, and provides services for
/// item-level and cross-binding validation.
///
public class BindingGroup : DependencyObject
{
#region Constructors
//-----------------------------------------------------
//
// Constructors
//
//-----------------------------------------------------
///
/// Initializes a new instance of the BindingGroup class.
///
public BindingGroup()
{
_validationRules = new ValidationRuleCollection();
Initialize();
}
// clone the binding group. Called when setting a binding group on a
// container, from the ItemControl's ItemBindingGroup.
internal BindingGroup(BindingGroup master)
{
_validationRules = master._validationRules;
_name = master._name;
_notifyOnValidationError = master._notifyOnValidationError;
Initialize();
}
void Initialize()
{
_bindingExpressions = new BindingExpressionCollection();
((INotifyCollectionChanged)_bindingExpressions).CollectionChanged += new NotifyCollectionChangedEventHandler(OnBindingsChanged);
_itemsRW = new Collection();
_items = new WeakReadOnlyCollection(_itemsRW);
}
#endregion Constructors
#region Public properties
//------------------------------------------------------
//
// Public properties
//
//-----------------------------------------------------
///
/// The validation rules belonging to a BindingGroup are run during the
/// process of updating the source values of the bindings. Each rule
/// indicates where in that process it should run.
///
public Collection ValidationRules
{
get { return _validationRules; }
}
///
/// The collection of binding expressions belonging to this BindingGroup.
///
public Collection BindingExpressions
{
get { return _bindingExpressions; }
}
///
/// The name of this BindingGroup. A binding can elect to join this group
/// by declaring its BindingGroupName to match the name of the group.
///
public string Name
{
get { return _name; }
set { _name = value; }
}
///
/// When NotifyOnValidationError is set to True, the binding group will
/// raise a Validation.ValidationError event when its validation state changes.
///
public bool NotifyOnValidationError
{
get { return _notifyOnValidationError; }
set { _notifyOnValidationError = value; }
}
///
/// CanRestoreValues returns True if the binding group can restore
/// each of its sources (during ) to the state
/// they had at the time of the most recent .
/// This depends on whether the current sources provide a suitable
/// mechanism to implement the rollback, such as .
///
public bool CanRestoreValues
{
get
{
IList items = Items;
for (int i=items.Count-1; i>=0; --i)
{
if (!(items[i] is IEditableObject))
{
return false;
}
}
return true;
}
}
///
/// The collection of items used as sources in the bindings owned by
/// this BindingGroup. Each item appears only once, even if it is used
/// by several bindings.
///
///
/// The Items property returns a snapshot collection, reflecting the state
/// of the BindingGroup at the time of the call. As bindings in the group
/// change to use different source items, the changes are not immediately
/// visible in the collection. They become visible only when the property is
/// queried again.
///
public IList Items
{
get
{
// rebuild the Items collection, if necessary
if (!_isItemsValid)
{
// find the new set of items
IList newItems = _getValueTable.UniqueItems();
// modify the Items collection to match the new set
// First, remove items that no longer appear
for (int i=_itemsRW.Count-1; i >= 0; --i)
{
int index = FindIndexOf(_itemsRW[i], newItems);
if (index >= 0)
{
newItems.RemoveAt(index); // common item, don't add it later
}
else
{
_itemsRW.RemoveAt(i); // item no longer appears, remove it now
}
}
// then add items that are really new
for (int i=newItems.Count-1; i>=0; --i)
{
_itemsRW.Add(newItems[i]);
}
_isItemsValid = true;
}
return _items;
}
}
#endregion Public properties
#region Public Methods
//------------------------------------------------------
//
// Public Methods
//
//------------------------------------------------------
///
/// Begin an editing transaction. For each source that supports it,
/// the binding group asks the source to save its state, for possible
/// restoration during .
///
public void BeginEdit()
{
IList items = Items;
for (int i=items.Count-1; i>=0; --i)
{
IEditableObject ieo = items[i] as IEditableObject;
if (ieo != null)
{
ieo.BeginEdit();
}
}
}
///
/// End an editing transaction. The binding group attempts to update all
/// its sources with the proposed new values held in the target UI elements.
/// All validation rules are run, at the times requested by the rules.
///
///
/// True, if all validation rules succeed and no errors arise.
/// False, otherwise.
///
public bool CommitEdit()
{
return UpdateAndValidate(ValidationStep.CommittedValue);
}
///
/// Cancel an editing transaction. For each source that supports it,
/// the binding group asks the source to restore itself to the state saved
/// at the most recent . Then the binding group
/// updates all targets with values from their respective sources, discarding
/// any "dirty" values held in the targets.
///
public void CancelEdit()
{
// restore values
IList items = Items;
for (int i=items.Count-1; i>=0; --i)
{
IEditableObject ieo = items[i] as IEditableObject;
if (ieo != null)
{
ieo.CancelEdit();
}
}
// update targets
for (int i=_bindingExpressions.Count - 1; i>=0; --i)
{
_bindingExpressions[i].UpdateTarget();
}
}
///
/// Run the validation process up to the ConvertedProposedValue step.
/// This runs all validation rules marked as RawProposedValue or
/// ConvertedProposedValue, but does not update any sources with new values.
///
///
/// True, if all validation rules succeed and no errors arise.
/// False, otherwise.
///
public bool ValidateWithoutUpdate()
{
return UpdateAndValidate(ValidationStep.ConvertedProposedValue);
}
///
/// Run the validation process up to the UpdatedValue step.
/// This runs all validation rules marked as RawProposedValue or
/// ConvertedProposedValue, updates the sources with new values, and
/// runs rules marked as Updatedvalue.
///
///
/// True, if all validation rules succeed and no errors arise.
/// False, otherwise.
///
public bool UpdateSources()
{
return UpdateAndValidate(ValidationStep.UpdatedValue);
}
///
/// Find the binding that uses the given item and property, and return
/// the value appropriate to the current validation step.
///
///
/// the binding group does not contain a binding corresponding to the
/// given item and property.
///
///
/// the value is not available. This could be because an earlier validation
/// rule deemed the value invalid, or because the value could not be produced
/// for some reason, such as conversion failure.
///
///
/// This method is intended to be called from a validation rule, during
/// its Validate method.
///
public object GetValue(object item, string propertyName)
{
object value;
if (TryGetValueImpl(item, propertyName, out value))
{
return value;
}
if (value == Binding.DoNothing)
throw new ValueUnavailableException(SR.Get(SRID.BindingGroup_NoEntry, item, propertyName));
else
throw new ValueUnavailableException(SR.Get(SRID.BindingGroup_ValueUnavailable, item, propertyName));
}
///
/// Find the binding that uses the given item and property, and return
/// the value appropriate to the current validation step.
///
///
/// The method normally returns true and sets 'value' to the requested value.
/// If the value is not available, the method returns false and sets 'value'
/// to DependencyProperty.UnsetValue.
///
///
/// This method is intended to be called from a validation rule, during
/// its Validate method.
///
public bool TryGetValue(object item, string propertyName, out object value)
{
bool result = TryGetValueImpl(item, propertyName, out value);
// TryGetValueImpl sets value to DoNothing to signal "no entry".
// TryGetValue should treat this as just another unavailable value.
if (value == Binding.DoNothing)
{
value = DependencyProperty.UnsetValue;
}
return result;
}
bool TryGetValueImpl(object item, string propertyName, out object value)
{
GetValueTableEntry entry = _getValueTable[item, propertyName];
if (entry == null)
{
value = Binding.DoNothing; // signal "no entry"
return false;
}
switch (_validationStep)
{
case ValidationStep.RawProposedValue:
case ValidationStep.ConvertedProposedValue:
case ValidationStep.UpdatedValue:
case ValidationStep.CommittedValue:
value = entry.Value;
break;
// outside of validation process, use the raw value
default:
value = entry.BindingExpressionBase.RootBindingExpression.GetRawProposedValue();
break;
}
if (value == Binding.DoNothing)
{
// a converter has indicated that no value should be written to the source object.
// Therefore the source's value is the one to return to the validation rule.
BindingExpression bindingExpression = (BindingExpression)entry.BindingExpressionBase;
value = bindingExpression.SourceValue;
}
return (value != DependencyProperty.UnsetValue);
}
#endregion Public Methods
#region Internal properties
//-----------------------------------------------------
//
// Internal properties
//
//------------------------------------------------------
// Define the DO's inheritance context
internal override DependencyObject InheritanceContext
{
get { return _inheritanceContext; }
}
// Receive a new inheritance context (this will be a FE/FCE)
internal override void AddInheritanceContext(DependencyObject context, DependencyProperty property)
{
if (property != null && property.PropertyType != typeof(BindingGroup) &&
TraceData.IsEnabled)
{
string name = (property != null) ? property.Name : "(null)";
TraceData.Trace(TraceEventType.Warning,
TraceData.BindingGroupWrongProperty(name, context.GetType().FullName));
}
InheritanceContextHelper.AddInheritanceContext(context,
this,
ref _hasMultipleInheritanceContexts,
ref _inheritanceContext );
// sharing a BindingGroup among multiple hosts is bad - we wouldn't know which host
// to send the errors to (just for starters). But sharing an ItemBindingGroup is
// expected - this is what happens normally in a hierarchical control like TreeView.
// The following code tries to detect the bad case and warn the user that something
// is amiss.
if (_hasMultipleInheritanceContexts && property != ItemsControl.ItemBindingGroupProperty && TraceData.IsEnabled)
{
TraceData.Trace(TraceEventType.Warning,
TraceData.BindingGroupMultipleInheritance);
}
}
// Remove an inheritance context (this will be a FE/FCE)
internal override void RemoveInheritanceContext(DependencyObject context, DependencyProperty property)
{
InheritanceContextHelper.RemoveInheritanceContext(context,
this,
ref _hasMultipleInheritanceContexts,
ref _inheritanceContext);
}
// Says if the current instance has multiple InheritanceContexts
internal override bool HasMultipleInheritanceContexts
{
get { return _hasMultipleInheritanceContexts; }
}
#endregion Internal properties
#region Internal methods
//-----------------------------------------------------
//
// Internal methods
//
//-----------------------------------------------------
// called when a leaf binding changes its source item
internal void UpdateTable(BindingExpression bindingExpression)
{
_getValueTable.Update(bindingExpression);
_isItemsValid = false;
}
// add an entry to the value table for the given binding
internal void AddToValueTable(BindingExpressionBase bindingExpressionBase)
{
_getValueTable.EnsureEntry(bindingExpressionBase);
}
// get the value for the given binding
internal object GetValue(BindingExpressionBase bindingExpressionBase)
{
return _getValueTable.GetValue(bindingExpressionBase);
}
// set the value for the given binding
internal void SetValue(BindingExpressionBase bindingExpressionBase, object value)
{
_getValueTable.SetValue(bindingExpressionBase, value);
}
// set values to "source" for all bindings under the given root
internal void UseSourceValue(BindingExpressionBase bindingExpressionBase)
{
_getValueTable.UseSourceValue(bindingExpressionBase);
}
// add a validation error to the mentor's list
internal void AddValidationError(ValidationError validationError)
{
DependencyObject mentor = Helper.FindMentor(this);
if (mentor == null)
return;
Validation.AddValidationError(validationError, mentor, NotifyOnValidationError);
}
// remove a validation error from the mentor's list
internal void RemoveValidationError(ValidationError validationError)
{
DependencyObject mentor = Helper.FindMentor(this);
if (mentor == null)
return;
Validation.RemoveValidationError(validationError, mentor, NotifyOnValidationError);
}
// remove all errors raised at the given step, in preparation for running
// the rules at that step
void ClearValidationErrors(ValidationStep validationStep)
{
DependencyObject mentor = Helper.FindMentor(this);
if (mentor == null)
return;
ValidationErrorCollection validationErrors = Validation.GetErrorsInternal(mentor);
if (validationErrors == null)
return;
for (int i=validationErrors.Count-1; i>=0; --i)
{
ValidationError validationError = validationErrors[i];
if (validationError.BindingInError == this &&
validationError.RuleInError.ValidationStep == validationStep)
{
RemoveValidationError(validationError);
}
}
}
#endregion Internal methods
#region Private methods
//-----------------------------------------------------
//
// Private methods
//
//------------------------------------------------------
// run the validation process up to the indicated step
bool UpdateAndValidate(ValidationStep validationStep)
{
bool result = true;
for (_validationStep = ValidationStep.RawProposedValue;
_validationStep <= validationStep;
++ _validationStep)
{
switch (_validationStep)
{
case ValidationStep.RawProposedValue:
_getValueTable.ResetValues();
break;
case ValidationStep.ConvertedProposedValue:
ObtainConvertedProposedValues();
break;
case ValidationStep.UpdatedValue:
UpdateValues();
break;
case ValidationStep.CommittedValue:
CommitValues();
break;
}
if (!CheckValidationRules())
{
result = false;
break;
}
}
_validationStep = (ValidationStep)(-1);
_getValueTable.ResetValues();
return result;
}
// apply conversions to each binding in the group
void ObtainConvertedProposedValues()
{
for (int i=_bindingExpressions.Count-1; i>=0; --i)
{
_bindingExpressions[i].ObtainConvertedProposedValue(this);
}
}
// update the source value of each binding in the group
void UpdateValues()
{
for (int i=_bindingExpressions.Count-1; i>=0; --i)
{
_bindingExpressions[i].UpdateSource(this);
}
}
// check the validation rules for the current step
bool CheckValidationRules()
{
bool result = true;
// clear old errors arising from this step
ClearValidationErrors(_validationStep);
// check rules attached to the bindings
for (int i=_bindingExpressions.Count-1; i>=0; --i)
{
if (!_bindingExpressions[i].CheckValidationRules(this, _validationStep))
{
result = false;
}
}
// check rules attached to the binding group
CultureInfo culture = GetCulture();
for (int i=0, n=_validationRules.Count; i=0; --i)
{
IEditableObject ieo = items[i] as IEditableObject;
if (ieo != null)
{
ieo.EndEdit();
}
}
}
// find the index of an item in a list, where both the item and
// the list use WeakReferences
static int FindIndexOf(WeakReference wr, IList list)
{
object item = wr.Target;
if (item == null)
return -1;
for (int i=0, n=list.Count; i list = _getValueTable.RemoveRootBinding(root);
// tell each expression it is leaving the group
foreach (BindingExpressionBase expr in list)
{
expr.OnBindingGroupChanged(/*joining*/ false);
// also remove the expression from our collection. Normally this is
// a no-op, as we only get here after the expression has been removed,
// and implicit membership only adds root expressions to the collection.
// But an app (through confusion or malice) could explicitly add two
// or more expressions with the same root. We handle that case here.
_bindingExpressions.Remove(expr);
}
// cut the root's link to the group
root.LeaveBindingGroup();
}
// remove all binding expressions from the group
void RemoveAllBindingExpressions()
{
// we can't use the BindingExpressions collection - it has already
// been cleared. Instead, find the expressions that need work by
// looking in the GetValue table.
GetValueTableEntry entry;
while ((entry = _getValueTable.GetFirstEntry()) != null)
{
RemoveBindingExpression(entry.BindingExpressionBase);
}
}
#endregion Private methods
#region Private data
//-----------------------------------------------------
//
// Private data
//
//------------------------------------------------------
ValidationRuleCollection _validationRules;
string _name;
bool _notifyOnValidationError;
BindingExpressionCollection _bindingExpressions;
bool _isItemsValid;
ValidationStep _validationStep = (ValidationStep)(-1);
GetValueTable _getValueTable = new GetValueTable();
Collection _itemsRW;
WeakReadOnlyCollection _items;
CultureInfo _culture;
internal static readonly object DeferredTargetValue = new NamedObject("DeferredTargetValue");
internal static readonly object DeferredSourceValue = new NamedObject("DeferredSourceValue");
// Fields to implement DO's inheritance context
DependencyObject _inheritanceContext;
bool _hasMultipleInheritanceContexts;
#endregion Private data
#region Private types
//------------------------------------------------------
//
// Private types
//
//-----------------------------------------------------
// to support GetValue, we maintain an associative array of all the bindings,
// items, and property names that affect a binding group.
private class GetValueTable
{
// lookup by item and propertyName
public GetValueTableEntry this[object item, string propertyName]
{
get
{
for (int i=_table.Count-1; i >= 0; --i)
{
GetValueTableEntry entry = _table[i];
if (propertyName == entry.PropertyName &&
Object.Equals(item, entry.Item))
{
return entry;
}
}
return null;
}
}
// lookup by binding
public GetValueTableEntry this[BindingExpressionBase bindingExpressionBase]
{
get
{
for (int i=_table.Count-1; i >= 0; --i)
{
GetValueTableEntry entry = _table[i];
if (bindingExpressionBase == entry.BindingExpressionBase)
{
return entry;
}
}
return null;
}
}
// ensure an entry for the given binding
public void EnsureEntry(BindingExpressionBase bindingExpressionBase)
{
GetValueTableEntry entry = this[bindingExpressionBase];
if (entry == null)
{
_table.Add(new GetValueTableEntry(bindingExpressionBase));
}
}
// update (or add) the entry for the given leaf binding
public void Update(BindingExpression bindingExpression)
{
GetValueTableEntry entry = this[bindingExpression];
if (entry == null)
{
_table.Add(new GetValueTableEntry(bindingExpression));
}
else
{
entry.Update(bindingExpression);
}
}
// remove all the entries for the given root binding. Return the list of expressions.
public List RemoveRootBinding(BindingExpressionBase rootBindingExpression)
{
List result = new List();
for (int i=_table.Count-1; i >= 0; --i)
{
BindingExpressionBase expr = _table[i].BindingExpressionBase;
if (expr.RootBindingExpression == rootBindingExpression)
{
result.Add(expr);
_table.RemoveAt(i);
}
}
return result;
}
// return a list of the unique items (wrapped in WeakReferences)
public IList UniqueItems()
{
List list = new List();
for (int i=_table.Count-1; i >= 0; --i)
{
WeakReference itemWR = _table[i].ItemReference;
if (itemWR != null && BindingGroup.FindIndexOf(itemWR, list) < 0)
{
list.Add(itemWR);
}
}
return list;
}
// get the value for a binding expression
public object GetValue(BindingExpressionBase bindingExpressionBase)
{
GetValueTableEntry entry = this[bindingExpressionBase];
return (entry != null) ? entry.Value : DependencyProperty.UnsetValue;
}
// set the value for a binding expression
public void SetValue(BindingExpressionBase bindingExpressionBase, object value)
{
GetValueTableEntry entry = this[bindingExpressionBase];
if (entry != null)
{
entry.Value = value;
}
}
// reset values to "raw"
public void ResetValues()
{
for (int i=_table.Count-1; i>=0; --i)
{
_table[i].Value = BindingGroup.DeferredTargetValue;
}
}
// set values to "source" for all bindings under the given root
public void UseSourceValue(BindingExpressionBase rootBindingExpression)
{
for (int i=_table.Count-1; i>=0; --i)
{
if (_table[i].BindingExpressionBase.RootBindingExpression == rootBindingExpression)
{
_table[i].Value = BindingGroup.DeferredSourceValue;
}
}
}
// return the first entry in the table (or null)
public GetValueTableEntry GetFirstEntry()
{
return (_table.Count > 0) ? _table[0] : null;
}
Collection _table = new Collection();
}
// a single entry in the GetValueTable
private class GetValueTableEntry
{
public GetValueTableEntry(BindingExpressionBase bindingExpressionBase)
{
_bindingExpressionBase = bindingExpressionBase;
}
public void Update(BindingExpression bindingExpression)
{
if (_itemWR == null)
{
_itemWR = new WeakReference(bindingExpression.SourceItem); // WR to avoid leaks
}
else
{
_itemWR.Target = bindingExpression.SourceItem;
}
_propertyName = bindingExpression.SourcePropertyName;
}
public object Item
{
get { return _itemWR.Target; }
}
public WeakReference ItemReference
{
get { return _itemWR; }
}
public string PropertyName
{
get { return _propertyName; }
}
public BindingExpressionBase BindingExpressionBase
{
get { return _bindingExpressionBase; }
}
public object Value
{
get
{
if (_value == BindingGroup.DeferredTargetValue)
{
_value = _bindingExpressionBase.RootBindingExpression.GetRawProposedValue();
}
else if (_value == BindingGroup.DeferredSourceValue)
{
BindingExpression bindingExpression = _bindingExpressionBase as BindingExpression;
Debug.Assert(bindingExpression != null, "do not ask for source value from a [Multi,Priority]Binding");
_value = (bindingExpression != null) ? bindingExpression.SourceValue : DependencyProperty.UnsetValue;
}
return _value;
}
set { _value = value; }
}
BindingExpressionBase _bindingExpressionBase;
WeakReference _itemWR;
string _propertyName;
object _value = BindingGroup.DeferredTargetValue;
}
// add some error-checking to ObservableCollection
class BindingExpressionCollection : ObservableCollection
{
///
/// Called by base class Collection<T> when an item is added to list;
/// raises a CollectionChanged event to any listeners.
///
protected override void InsertItem(int index, BindingExpressionBase item)
{
if (item == null)
{
throw new ArgumentNullException("item");
}
base.InsertItem(index, item);
}
///
/// Called by base class Collection<T> when an item is set in list;
/// raises a CollectionChanged event to any listeners.
///
protected override void SetItem(int index, BindingExpressionBase item)
{
if (item == null)
{
throw new ArgumentNullException("item");
}
base.SetItem(index, item);
}
}
#endregion Private types
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.