views:

71

answers:

3

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); }
    }
}
+2  A: 

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.

John Bowen
Hm. I did this, referencing MSDN's page on dependency properties, but my app didn't show the data in the property after I had set the property in XAML. My app always showed the initial, constructor-set value of the dependency property. I tried switching from dependency properties to having my class implement `INotifyPropertyChanged` but now it's back to saying "A 'Binding' cannot be set...".
Sarah Vessels
That's a separate problem. You shouldn't be setting default values for DPs in the constructor of your control. Instead pass a PropertyMetadata into Register with your default value (for value types) or apply it through a default Style.
John Bowen
@John: I changed it so that the default/initial value of my dependency property is set in the `Register` call. I updated my question with my current problem. Thanks!
Sarah Vessels
@John: thanks for the update. I didn't realize the `DataContext` in the thing containing the `UserControl` would change when I changed the `UserControl`'s `DataContext`. I did what you suggested, though I'm 1) not seeing expected values still and 2) not seeing any errors in Output.
Sarah Vessels
@John: I *did* find some `System.Windows.Data Error`s in the Debug output, so I'm scouring those.
Sarah Vessels
@John: one last time--got it! I had set an `x:Name` on the `<UserControl` in XAML, not on the main content container (`<Grid>` in my case). Seeing errors in Debug output helps a great deal, too.
Sarah Vessels
+2  A: 

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

Robaticus
A: 

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.

Sarah Vessels
You should never put any code other than the boilerplate SetValue call in your DP wrapper property: use a property changed callback instead. Any calls from XAML skip the wrapper and call SetValue directly, not to mention that anyone can call GetValue/SetValue from code.
John Bowen