views:

63

answers:

2

Hello all,

Let's say I've created a UserControl with the following ContentTemplate defined in XAML:

<UserControl.ContentTemplate>
    <DataTemplate> 
        <Ellipse Name="myEllipse" Stroke="White"/>
        <ContentPresenter Content="{TemplateBinding Content}"/>
    </DataTemplate>
</UserControl.ContentTemplate>

How would I access the "myEllipse" element within my code so that, for example, I could find its height with "myEllipse.Height"? I cannot access it by name directly. I attempted to create a reference to it with:

Ellipse ellipse = ContentTemplate.FindName("myEllipse",this) as Ellipse;  

It crashes when I run the program, saying it can't create an instance of my class. Perhaps I'm not using FindName correctly. If anyone can help me out it would be much appreciated.

Thanks,

Dalal

+2  A: 

In order to use FindName on a DataTemplate, you will need a reference to the ContentPresenter. See Josh Smith's article How to use FindName with a ContentControl.

What you may actually want to do is to use a ControlTemplate rather than a DataTemplate. This should be easier to use and will let users of your control apply their own content templates or use implicit templates. If you do something like this:

<UserControl.Template>
    <ControlTemplate TargetType="UserControl">
        <Grid>
            <ContentPresenter/>
            <Ellipse Name="myEllipse" Stroke="White"/>
        </Grid>
    </ControlTemplate>
</UserControl.Template>

Then in code (perhaps in an OnApplyTemplate override) you will be able to do this:

var ellipse = Template.FindName("myEllipse", this) as Ellipse;

You should also decorate your class with a TemplatePartAttribute like this:

[TemplatePart(Name="myEllipse", Type = typeof(Ellipse))]

So that if anyone re-templates your control they know to provide an Ellipse element with that name. (This is less important if the class is only used internally.)

Finally, if all you want to do is change the color of the Ellipse, then you may just want to use data binding. You could create an EllipseColor dependency property on your control and just set Stroke="{TemplateBinding EllipseColor}".

Quartermeister
Thanks for the very informative answer. I tried using a ControlTemplate in place of my DataTemplate, but my UserControl did not look the same, and some of the functionality was affected, so I would rather try to get it working with a DataTemplate, now that I've gotten this far in my control development. After reading Josh Smith's article, I tried putting the following line in the constructor for my control: ContentPresenter contentPresenter = VisualTreeHelper.GetChild(this,0) as ContentPresenter;Unfortunately, it fails at that line. I get a XAML parse exception. Any ideas?
Dalal
@Dalal: The visual tree is not populated yet in the constructor, so you're getting an out of range exception in the call to GetChild. This is being wrapped in a XAML parse exception because it failed to instantiate your object. You will need to move the code to a Load event handler or an OnApplyTemplate override. Also, the default control template for UserControl has a ContentPresenter in a Border, so `VisualTreeHelper.GetChild(this, 0)` will return a Border. You'll need to do something like `VisualTreeHelper.GetChild(VisualTreeHelper.GetChild(this, 0), 0)`.
Quartermeister
In my OnApplyTemplate override I was able to get the ContentPresenter successfully using the line `VisualTreeHelper.GetChild(VisualTreeHelper.GetChild(this, 0), 0) as ContentPresenter;` Now I had to get a reference to one of my objects named 'headerLabel', so I put `headerLabel = ContentTemplate.FindName("headerLabel", contentPresenter) as Label;` I get an InvalidOperationException: This operation is valid only on elements that have this template applied. Well, the tutorial said to pass in the ContentPresenter. I did. I also tried putting this code in the MouseDown event to no avail.
Dalal
@Dalal: I'm surprised MouseDown didn't work, but maybe OnApplyTemplate is still too early. Can you confirm that `contentPresenter.ContentTemplate == ContentTemplate`, that the ContentPresenter is using the ContentTemplate you're providing? Try calling `contentPresenter.ApplyTemplate()` before `FindName` to force the ContentPresenter to instantiate its template.
Quartermeister
You're right, I was doing it wrong, but your suggestions fixed it. I used `contentPresenter.ContentTemplate` and also added `contentPresenter.ApplyTemplate()`, and it worked! Yes!! Thank you so much. I was very frustrated. It doesn't work without ApplyTemplate(), so thanks for mentioning that, or I would have given up and used a workaround, which was to get references to my objects through their load event handlers.
Dalal
A: 

Try

<Ellipse Name="myEllipse" Stroke="{TemplateBinding Background}"/>

instead of programmatically changing it.

There's a similar example here, with a blue filled ellipse. http://msdn.microsoft.com/en-us/library/system.windows.controls.contentpresenter.aspx

Nikki9696
My problem is getting the reference to the object, not changing its stroke. I was using the stroke just as an example, but I'll edit that now since it was a bit unclear. Thanks.
Dalal