WPF Binding View as Content

2019-07-20 08:09发布

问题:

I'm experimenting with some code where I need to mix programmatically created controls with controls defined in XAML.

Can someone explain why when I bind to the Elements View property, the property's 'get' is called twice, yet when binding to the Template property it gets called only once (as expected).

Example output when binding to View :

StringElement
StringElement
BoolElement
BoolElement
StringElement
StringElement

Example output when binding to Template :

StringElement
BoolElement
StringElement

--

<Window x:Class="BindView.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" WindowStartupLocation="CenterScreen">
<ItemsControl ItemsSource="{Binding Elements}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <ContentPresenter Content="{Binding View}" />
        </DataTemplate>
        <!--<DataTemplate>
            <ContentPresenter ContentTemplate="{Binding Template}" />
        </DataTemplate>-->
    </ItemsControl.ItemTemplate>
</ItemsControl>

public abstract class Element
{
    public DataTemplate Template
    {
        get
        {
            Console.WriteLine(GetType().Name);
            return OnGetTemplate();
        }
    }

    public abstract DataTemplate OnGetTemplate();

    public UIElement View 
    { 
        get 
        {
            Console.WriteLine(GetType().Name);
            return OnGetView(); 
        } 
    }

    public abstract UIElement OnGetView();

    protected DataTemplate CreateTemplate(Type viewType)
    {
        var xaml = string.Format("<DataTemplate><{0} /></DataTemplate>", viewType.Name);
        var context = new ParserContext();

        context.XamlTypeMapper = new XamlTypeMapper(new string[0]);
        context.XmlnsDictionary.Add("", "http://schemas.microsoft.com/winfx/2006/xaml/presentation");
        context.XmlnsDictionary.Add("x", "http://schemas.microsoft.com/winfx/2006/xaml");

        return (DataTemplate)XamlReader.Parse(xaml, context);
    }
}

public class StringElement : Element
{
    public override DataTemplate OnGetTemplate() { return CreateTemplate(typeof(TextBox)); }
    public override UIElement OnGetView() { return new TextBox(); }
}

public class BoolElement : Element
{
    public override DataTemplate OnGetTemplate() { return CreateTemplate(typeof(CheckBox)); }
    public override UIElement OnGetView() { return new CheckBox(); }
}

public partial class MainWindow : Window
{
    public List<Element> Elements { get; private set; }

    public MainWindow()
    {
        Elements = new List<Element>() { new StringElement(), new BoolElement(), new StringElement() };
        DataContext = this;
        InitializeComponent();
    }
}

回答1:

Read this from Microsoft: Problem binding image - property called twice for each item

Posted by Microsoft on 4/28/2010 at 10:10 AM This is not a bug. WPF (or any other code) can call your property-getter at any time for any reason; there's no rule that it will be called only once. WPF (and other callers) expects that your property follows the .Net guidelines; in particular that the property-getter is fast, and that it will return the same value from call to call unless you've raised a property-changed notification.

Since you're curious, the reason for the extra call is that WPF 4.0 does some extra work when the property value is a DependencyObject, checking to see whether it can raise "sub-property" change notifications (Freezables are the chief example). This work must be done while setting up the binding path, before doing the first transfer. We could have twisted the code in knots to avoid an extra fetch, but fetches are cheap.

Basically you can't rely the getter for a DependencyObject to be called exactly once.