2015-04-14 4 views
0

Я заметил это, пытаясь установить привязку в течение короткого периода времени в коде. На самом деле, я просто хочу получить значение, предоставляемое привязкой. Поэтому я установил привязку, получаю значение свойства target и сразу очищаю привязку. Все хорошо, пока не будет установлен RelativeSource с режимом FindAncestor для привязки. В этом случае свойство target возвращает значение по умолчанию.Почему привязка к предку становится активным позже привязки к элементу по его имени или привязке к DataContext?

После некоторой отладки я обнаружил, что BindingExpression для привязки FindAncestor имеет свойство Свойство, установленное на 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».

Так что мои вопросы:

  1. Почему RelativeSourceMode.FindAncestor связывания не дает значение сразу после BindingOperations.SetBinding (...) называется?

  2. Есть ли способ принудительно связать этот вид привязки для обновления цели ? Я попытался вызвать bindingExpression.UpdateTarget() - он не работает, как ожидалось.

ответ

2

Это по дизайну. Чтобы понять, почему, давайте рассмотрим код.

Когда Expression устанавливается как значение в DependencyProperty в Expression.OnAttach называется (source). Этот метод переопределен в классе (BindingExpressionBasesource):

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 (source).

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 (source).

// 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 с предком поиска (source).

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))); 
} 

Поскольку контекст дерева требуется для RelativeSourceBindingExpression является Unattached. Поэтому значение свойства не обновляется немедленно.

Призыв UpdateLayout на любые UIElement для изменения макета и установки BindingExpression.

+1

Отличное объяснение, спасибо, спасибо! Но как насчет моего второго вопроса? Знаете ли вы способ принудительно привязать выражение привязки к контексту дерева? –

+1

Вызовите метод 'UpdateLayout' для любого' UIElement' из текущего потока. –

+0

Да, вызывается 'UpdateLayout' для данного примера. Хотя в моем реальном сценарии это не так ... Во всяком случае, я отметил ответ как принятый. Благодаря! –