Почему привязка к предку становится активной позже, чем привязка к элементу по его имени или привязка к DataContext?
Я заметил это при попытке установить привязку на короткий промежуток времени в коде. На самом деле, я просто хочу получить значение, предоставляемое связыванием. Поэтому я установил привязку, получил значение целевого свойства и сразу очистил привязку. Все хорошо, пока RelativeSource с режимом FindAncestor не установлен для привязки. В этом случае целевое свойство возвращает значение по умолчанию.
После некоторой отладки я обнаружил, что свойство BindingExpression для привязки FindAncestor имеет свойство Status, установленное в Unattached. Для других типов привязок BindingExpression.Status имеет значение Active.
Я написал код, чтобы проиллюстрировать это.
Window1.xaml
<Window x:Class="Wpf_SetBindingInCode.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="Window"
Title="Window1"
Height="300" Width="300"
DataContext="DataContext content">
<StackPanel>
<Button Content="Set binding" Click="SetBindingButtonClick"/>
<TextBlock x:Name="TextBlock1"/>
<TextBlock x:Name="TextBlock2"/>
<TextBlock x:Name="TextBlock3"/>
</StackPanel>
</Window>
Window1.xaml.cs
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void SetBindingButtonClick(object sender, RoutedEventArgs e)
{
Binding bindingToRelativeSource = new Binding("DataContext")
{
RelativeSource = new RelativeSource { Mode = RelativeSourceMode.FindAncestor, AncestorType = typeof(Window1) },
};
Binding bindingToElement = new Binding("DataContext")
{
ElementName = "Window"
};
Binding bindingToDataContext = new Binding();
BindingOperations.SetBinding(TextBlock1, TextBlock.TextProperty, bindingToRelativeSource);
BindingOperations.SetBinding(TextBlock2, TextBlock.TextProperty, bindingToElement);
BindingOperations.SetBinding(TextBlock3, TextBlock.TextProperty, bindingToDataContext);
Trace.WriteLine("TextBlock1.Text = \"" + TextBlock1.Text + "\"");
Trace.WriteLine("TextBlock2.Text = \"" + TextBlock2.Text + "\"");
Trace.WriteLine("TextBlock3.Text = \"" + TextBlock3.Text + "\"");
var bindingExpressionBase1 = BindingOperations.GetBindingExpressionBase(TextBlock1, TextBlock.TextProperty);
var bindingExpressionBase2 = BindingOperations.GetBindingExpressionBase(TextBlock2, TextBlock.TextProperty);
var bindingExpressionBase3 = BindingOperations.GetBindingExpressionBase(TextBlock3, TextBlock.TextProperty);
Trace.WriteLine("bindingExpressionBase1.Status = " + bindingExpressionBase1.Status);
Trace.WriteLine("bindingExpressionBase2.Status = " + bindingExpressionBase2.Status);
Trace.WriteLine("bindingExpressionBase3.Status = " + bindingExpressionBase3.Status);
}
}
Код выше производит следующий вывод:
TextBlock1.Text = ""
TextBlock2.Text = "DataContext content"
TextBlock3.Text = "DataContext content"
bindingExpressionBase1.Status = Unattached
bindingExpressionBase2.Status = Active
bindingExpressionBase3.Status = Active
Но, несмотря на это, все три TextBlocks в форме имеют ожидаемые значения - "DataContext content".
Итак, мои вопросы:
Почему привязка RelativeSourceMode.FindAncestor не предоставляет значение сразу после вызова BindingOperations.SetBinding(...)?
Есть ли способ заставить этот вид привязки обновить целевое свойство? Я пытался вызвать bindingExpression.UpdateTarget() - он не работает, как ожидалось.
1 ответ
Это по замыслу. Чтобы понять почему, давайте посмотрим на код.
Когда Expression
устанавливается как значение DependencyProperty
Expression.OnAttach
называется ( источник). Этот метод переопределяется в BindingExpressionBase
класс ( источник):
internal sealed override void OnAttach(DependencyObject d, DependencyProperty dp)
{
if (d == null)
throw new ArgumentNullException("d");
if (dp == null)
throw new ArgumentNullException("dp");
Attach(d, dp);
}
internal void Attach(DependencyObject target, DependencyProperty dp)
{
// make sure we're on the right thread to access the target
if (target != null)
{
target.VerifyAccess();
}
IsAttaching = true;
AttachOverride(target, dp);
IsAttaching = false;
}
AttachOverride
Метод тоже виртуален и переопределяется в BindingExpression
( источник).
internal override bool AttachOverride(DependencyObject target, DependencyProperty dp)
{
if (!base.AttachOverride(target, dp))
return false;
// listen for InheritanceContext change (if target is mentored)
if (ParentBinding.SourceReference == null || ParentBinding.SourceReference.UsesMentor)
{
DependencyObject mentor = Helper.FindMentor(target);
if (mentor != target)
{
InheritanceContextChangedEventManager.AddHandler(target, OnInheritanceContextChanged);
UsingMentor = true;
if (TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.Attach))
{
TraceData.Trace(TraceEventType.Warning,
TraceData.UseMentor(
TraceData.Identify(this),
TraceData.Identify(mentor)));
}
}
}
// listen for lost focus
if (IsUpdateOnLostFocus)
{
Invariant.Assert(!IsInMultiBindingExpression, "Source BindingExpressions of a MultiBindingExpression should never be UpdateOnLostFocus.");
LostFocusEventManager.AddHandler(target, OnLostFocus);
}
// attach to things that need tree context. Do it synchronously
// if possible, otherwise post a task. This gives the parser et al.
// a chance to assemble the tree before we start walking it.
AttachToContext(AttachAttempt.First);
if (StatusInternal == BindingStatusInternal.Unattached)
{
Engine.AddTask(this, TaskOps.AttachToContext);
if (TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.AttachToContext))
{
TraceData.Trace(TraceEventType.Warning,
TraceData.DeferAttachToContext(
TraceData.Identify(this)));
}
}
GC.KeepAlive(target); // keep target alive during activation (bug 956831)
return true;
}
В приведенном коде видно, что после всех действий BindingExpression
может быть еще Unattached
, Давайте посмотрим, почему это так в нашей ситуации. Для этого нам нужно определить, где меняется статус. Это может сделать IL Spy, который показывает, что статус изменяется в AttachToContext
( источник).
// try to get information from the tree context (parent, root, etc.)
// If everything succeeds, activate the binding.
// If anything fails in a way that might succeed after further layout,
// just return (with status == Unattached). The binding engine will try
// again later. For hard failures, set an error status; no more chances.
// During the "last chance" attempt, treat all failures as "hard".
void AttachToContext(AttachAttempt attempt)
{
// if the target has been GC'd, just give up
DependencyObject target = TargetElement;
if (target == null)
return; // status will be Detached
bool isExtendedTraceEnabled = TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.AttachToContext);
bool traceObjectRef = TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.SourceLookup);
// certain features should never be tried on the first attempt, as
// they certainly require at least one layout pass
if (attempt == AttachAttempt.First)
{
// relative source with ancestor lookup
ObjectRef or = ParentBinding.SourceReference;
if (or != null && or.TreeContextIsRequired(target))
{
if (isExtendedTraceEnabled)
{
TraceData.Trace(TraceEventType.Warning,
TraceData.SourceRequiresTreeContext(
TraceData.Identify(this),
or.Identify()));
}
return;
}
}
В комментариях говорится, что для некоторых функций требуется как минимум один проход макета, и что один из них RelativeSource
с поиском предка ( источник).
internal bool TreeContextIsRequired(DependencyObject target)
{
return ProtectedTreeContextIsRequired(target);
}
/// <summary> true if the ObjectRef really needs the tree context </summary>
protected override bool ProtectedTreeContextIsRequired(DependencyObject target)
{
return ( (_relativeSource.Mode == RelativeSourceMode.FindAncestor
|| (_relativeSource.Mode == RelativeSourceMode.PreviousData)));
}
Поскольку контекст дерева требуется для RelativeSource
BindingExpression
является Unattached
, Поэтому значение свойства не обновляется сразу.
взывать UpdateLayout
на любом UIElement
заставить обновление макета и присоединение BindingExpression
"S.