WPF, cannot bind a property because it says it'

2019-06-01 05:24发布

问题:

I have a UserControl with a Template property that I've set up as a DependencyProperty:

public partial class TemplateDetail : UserControl
{
    public static readonly DependencyProperty _templateProperty =
        DependencyProperty.Register(
            "Template",
            typeof(Template),
            typeof(TemplateDetail)
        );

    public TemplateDetail()
    {
        InitializeComponent();
        Template = new Template();
        DataContext = Template;
    }

    public Template Template
    {
        get
        {
            return (Template)GetValue(_templateProperty);
        }
        set { SetValue(_templateProperty, value); }
    }
}

I'm trying to use this UserControl in the XAML for a Window, SaveTemplateDialog, and I'm trying to set its Template property to the Template property in my SaveTemplateDialog class:

<local:TemplateDetail HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
                      MinWidth="100" Template="{Binding}"
                      Width="{Binding ElementName=Scroller, Path=ViewportWidth}"/>

The DataContext in SaveTemplateDialog is set to its Template property, which is also a dependency property. However, for the XAML above, Template="{Binding}" is underlined in blue in Visual Studio and it says:

A 'Binding' cannot be set on the 'Template' property of type 'TemplateDetail'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject.

Sure enough, when I load my app, the contents of the Template that TemplateDetail tries to display is blank, when I know there are values in it. Why does it give this message if I've registered TemplateDetail.Template as a dependency property?

Edit: now I'm quite confused. In my SaveTemplateDialog window, I'm doing the following to pass along the template to the TemplateDetail UserControl:

<local:TemplateDetail HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
                      TemplateData="{Binding Path=NewTemplate, Mode=OneWay}"
                      Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="3"/>

I know Path=NewTemplate is getting the right data because I also have this XAML in SaveTemplateDialog, which shows the value I would expect in the MyType property of NewTemplate:

<TextBlock Text="{Binding Path=NewTemplate.MyType}" />

That TextBlock shows what I expect. However, apparently the right data is not getting to TemplateDetail or I'm not binding correctly there, because I can't get that same MyType property (or the other Template properties) to show up in TemplateDetail. In TemplateDetail:

<TextBlock Text="{Binding Path=TemplateData.MyType}" Grid.Row="2" Grid.Column="2"
           HorizontalAlignment="Stretch" Width="200"/>

And here's the TemplateDetail class as it is now:

public partial class TemplateDetail : MainWindowUserControl
{
    public static readonly DependencyProperty TemplateDataProperty =
        DependencyProperty.Register(
            "TemplateData",
            typeof(Template),
            typeof(TemplateDetail),
            new PropertyMetadata(
                new Template("Default Template", null)
            )
        );

    public TemplateDetail()
    {
        InitializeComponent();
        DataContext = this;
    }

    public Template TemplateData
    {
        get
        {
            return (Template)GetValue(TemplateDataProperty);
        }
        set { SetValue(TemplateDataProperty, value); }
    }
}

回答1:

You should change the name of your DependencyProperty to the use the standard convention like PropertyNameProperty instead of naming like a field. When you set something in XAML it calls SetValue and expects the property to be named using the string name ("Template" here) followed by "Property".

UPDATE for second question:

When you set DataContext = this in your TemplateDetail constructor that is causing the source for your Path=NewTemplate Binding to be changed from the inherited DataContext to the TemplateDetail control itself. You should be seeing an error in your Output window that the NewTemplate property can't be found on the object of type TemplateDetail. Instead of resetting the DataContext of the UserControl itself, I will usually give the root layout Panel in my UserControl XAML an x:Name and set LayoutRoot.DataContext = this in the constructor instead.



回答2:

Make sure that Template isn't a reserved word that is causing the XAML processor to get confused. Try a different word.

(posted as an answer per Sarah Vessel's suggestion).



回答3:

I think something is not getting updated as it should. I found that if I did the following in my NewTemplate property in my SaveTemplateDialog window, then my TemplateDetail UserControl is properly filled out with all the data I expect:

public Template NewTemplate
{
    get
    {
        return (Template)GetValue(NewTemplateProperty);
    }
    set
    {
        SetValue(NewTemplateProperty, value);

        // The magic line, explicitly setting TemplateData property on
        // the TemplateDetails UserControl:
        uct_templateDetails.TemplateData = value;
    }
}

This leads me to believe just having the following in SaveTemplateDialog's XAML is not enough to send the new instance to TemplateDetails whenever NewTemplate is updated:

<local:TemplateDetail HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
                      TemplateData="{Binding Path=NewTemplate}" Grid.Row="2"
                      Grid.Column="1" Grid.ColumnSpan="3"
                      x:Name="uct_templateDetails"/>

I would love for someone to point out what I'm doing wrong here because it doesn't seem like I should have to explicitly say uct_templateDetails.TemplateData = value; whenever SaveTemplateDialog.NewTemplate is changed. I thought that was the point of a dependency property, that updating it would cause things that use it to get the updated value.