views:

367

answers:

2

While investigating an issue with application I'm working on, I've came across one behavior I don't quite understand. It seems that when you have a TextBox (for example) with bound Text property the system takes one more layout pass than when you have a static Text.

Could anyone, please, explain why this extra pass is happening? Does the engine lay the unbound control first then binds it and then lays it once again?

To test this I've built such test case:

I've declared a class inherited from TextBox (so I can override ArrangeOverride):

public class MultiBoundTextBox : TextBox
{
    protected override Size ArrangeOverride(Size arrangeBounds)
    {
        Console.WriteLine("TextBox.Arrange");
        return base.ArrangeOverride(arrangeBounds);
    }
}

Then I placed an instance of this text box in a window:

<local:MultiBoundTextBox x:Name="tb">
Some text
</local:MultiBoundTextBox>

And added some code for the window for testing:

    public Window11()
    {
        InitializeComponent();
    }

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        Console.WriteLine("Window.Loaded");
    }

    protected override Size ArrangeOverride(Size arrangeBounds)
    {
        Console.WriteLine("Window.Arrange");
        return base.ArrangeOverride(arrangeBounds);
    }

    private void Window_Initialized(object sender, EventArgs e)
    {
        Console.WriteLine("Window.Initialized");
        tb.DataContext = DateTime.Now;
    }

Now when I run this I get this output:

Window.Initialized
Window.Arrange
TextBox.Arrange
Window.Arrange
Window.Loaded

But if I change Text property to be bound like this:

    <local:MultiBoundTextBox x:Name="tb">
        <Binding Path="Day" Mode="OneWay" />
    </local:MultiBoundTextBox>

I get this in the output:

Window.Initialized
Window.Arrange
TextBox.Arrange
Window.Arrange
TextBox.Arrange
Window.Arrange
Window.Loaded

Notice the extra pair of TextBox.Arrange and Window.Arrange. Why is this extra pass necessary?

+1  A: 

Not directly an answer, but what if you add a converter to the binding that doesn't do anything except write out a message that shows you at which point the binding is evaluated?

public sealed class LoggingConverter : IValueConverter
{
    public void Convert(object value, Type targetType,
                        object parameter, CultureInfo culture)
    {
        Console.WriteLine("Binding.Convert");
        return value;
    }

    public void ConvertBack(object value, Type targetType,
                            object parameter, CultureInfo culture)
    {
        Console.WriteLine("Binding.ConvertBack");
        return value;
    }
}
Drew Noakes
+1 for suggesting a potential debugging aid.
Steffen Opel
+1  A: 

Does the engine lay the unbound control first then binds it and then lays it once again?

This might indeed be the case - WPF data binding is built heavily upon Dependency Properties, which do in fact impact the WPF layout process, see Layout Performance Considerations:

Dependency properties whose values can cause the layout system to be initialized are marked with public flags. AffectsMeasure and AffectsArrange provide useful clues as to which property value changes will force a recursive update by the layout system. In general, any property that can affect the size of an element's bounding box should set the AffectsMeasure flag to true. For more information, please see Dependency Properties Overview.

And specifically regarding your question see this citation from Optimizing Performance: Layout and Design:

The layout pass process is invoked again if any of the following actions occur:

  • [...]
  • When a change occurs to the value of a dependency property that is marked with metadata affecting the measure or arrange passes.

Consequently I could imagine that the initial layout pass hasn't been considered any different from the use case of a bound value change later on, which would explain the behavior you are experiencing. While this might still be a missed opportunity for optimizing the startup experience, the usual optimization caveats apply: no optimization without measurement - e.g. this supposed redundancy (if technically avoidable at all) may have no measurable impact because the window/control has not yet been shown etc.


Debugging:

To add on Drews suggestion of a debugging aid, there is a new dedicated debugging aid related to binding introduced in .NET Framework 3.5, see PresentationTraceSources.TraceLevel - example:

<Window ... xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase">
    <local:MultiBoundTextBox x:Name="tb">
        <Binding Path="Day" Mode="OneWay"
                 diag:PresentationTraceSources.TraceLevel="High"/>
    </local:MultiBoundTextBox>
</Window>

There are some constraints for this to work though, be sure to read section Remarks within PresentationTraceSources Class.

Steffen Opel
Thanks for pointing out `PresentationTraceSources.TraceLevel` -- that looks like it'll be useful one day.
Drew Noakes