28

This is the xaml:

<Page.Resources>
    <ControlTemplate x:Key="WeddingButtonBigTemplate" TargetType="Button">
        <Grid>
            <Image x:Name="imgNormal" Source="../Images/Married_button2.png"/>
            <TextBlock x:Name="textBlock2" Style="{StaticResource RegularBlueSpecialBoldText}" LineHeight="28" LineStackingStrategy="BlockLineHeight" HorizontalAlignment="Center" Margin="10,30,10,70" TextWrapping="Wrap" TextAlignment="Center" VerticalAlignment="Stretch" >
                <Run FontSize="20" Text="The event of"></Run>
                <Run FontSize="28" Text="{DynamicResource strBride}"></Run>
            </TextBlock>
        </Grid>
    </ControlTemplate>
</Page.Resources>

<Grid HorizontalAlignment="Center" VerticalAlignment="Top" Width="1000">
    <Button x:Name="btnWedding" HorizontalAlignment="Left" Margin="10,20,0,-49" VerticalAlignment="Top" Template="{StaticResource WeddingButtonBigTemplate}" Foreground="#FF2B4072" Width="380" Click="btnClick" />
</Grid>

I'm tring to get access to the TextBlock named textBlock2.
I've tried to override OnApplyTemplate but got null.

I've tried:

Grid gridInTemplate = (Grid)btnWedding.Template.FindName("grid", btnWedding);
var ct0 = btnWedding.Template.FindName("textBlock2", btnWedding);
var ct1 = btnWedding.FindName("textBlock2");
var ct2 = btnWedding.FindResource("textBlock2");

The gridInTemplate is null (sample taken from MSDN).
The ct# are all null, of course.

What am I missing here?

Vadim Ovchinnikov
  • 13,327
  • 5
  • 62
  • 90
toy4fun
  • 839
  • 2
  • 14
  • 33
  • 5
    I suspect your button haven't `loaded(rendered on UI)` yet. Your code will return null only in case template is applied on button. – Rohit Vats Oct 19 '13 at 14:26
  • Also `gridInTemplate` is null since you haven't specified `x:Name` to your Grid in xaml declaration. – Rohit Vats Oct 19 '13 at 14:27
  • 2
    I ran into a similar issue, had to call `UpdateLayout`, to resolve the issue @RohitVats mentioned. – Chris Aug 06 '16 at 17:57
  • if you're using Expander can take a look at http://stackoverflow.com/questions/26422811/accessing-the-children-of-an-expander-control – yu yang Jian Feb 22 '17 at 03:02

7 Answers7

23

If you have overriden OnApplyTemplate then do not use FindResource() or Template.FindName() or any hacks with VisualTreeHelper. Just use this.GetTemplateChild("textBlock2");

Templates in WPF have a self-contained namescope. This is because templates are re-used, and any name defined in a template cannot remain unique when multiple instances of a control each instantiate its template. Call the GetTemplateChild method to return references to objects that come from the template after it is instantiated. You cannot use the FrameworkElement.FindName method to find items from templates because FrameworkElement.FindName acts in a more general scope, and there is no connection between the ControlTemplate class itself and the instantiated template once it is applied.

Check this link out:

http://msdn.microsoft.com/en-us/library/system.windows.frameworkelement.gettemplatechild.aspx

If your example is microsoft example then I suggest you to read it again. You might have skipped something.

http://msdn.microsoft.com/en-us/library/bb613586.aspx

To sum up - Use GetTemplateChild() when authoring custom control e.g. OnApplyTemplate, and use Template.FindName in other situations.

dev hedgehog
  • 8,698
  • 3
  • 28
  • 55
  • 1
    This helped me. By overriding `OnApplyTemplate` (instead of `OnTemplateChange`) made it working. – Crypt32 May 29 '17 at 15:31
10

Try the following code. This will return the templated element.

this.GetTemplateChild("ControlName");
Ramesh
  • 376
  • 2
  • 6
  • 11
8

Your code is correct, but probably not in the right place... FindName will only work after the template has been applied. Typically, you use it when you override OnApplyTemplate in a custom control. Since you're not creating a custom control, you can do it in the Loaded event of the button.

Thomas Levesque
  • 286,951
  • 70
  • 623
  • 758
2

you can use VisualTreeHelper to iterate the visual tree of button to get any child. You can use this basic generic function to get it

private static DependencyObject RecursiveVisualChildFinder<T>(DependencyObject rootObject)  
{  
    var child = VisualTreeHelper.GetChild(rootObject, 0);  
    if (child == null) return null;  

    return child.GetType() == typeof (T) ? child : RecursiveVisualChildFinder<T>(child);  
}

you can use it like

TextBlock textblock = RecursiveVisualChildFinder<TextBlock>(btnWedding);
if(textblock.Name == "textBlock2")
{// Do your stuff here
}
Nitin
  • 18,344
  • 2
  • 36
  • 53
  • I'm getting the following exception: `Specified index is out of range or child at index is null. Do not call this method if VisualChildrenCount returns zero, indicating that the Visual has no children.` – toy4fun Oct 02 '13 at 07:05
  • Same problem here, added a bounty – DaveO Oct 18 '13 at 23:46
  • `var ct0 = btnWedding.Template.FindName("textBlock2", btnWedding);` works fine for me... can you share where exactly in your code are you trying to get the textblock? – Nitin Oct 19 '13 at 03:28
  • If it is on the Loaded event , its working fine as Thomas Levesque said.May be he is trying to access the code from the OnApplyTemplate, at that moment control is not available yet. – Kishore Kumar Oct 19 '13 at 15:18
1

If you can get grid control, then try using below code

TextBlock textBlock2 = (TextBlock)gridInTemplate.Children[1];
RonakThakkar
  • 903
  • 5
  • 7
  • My mistake, the grid is null, although I took the code from MSDN sample. Question updated accordingly. – toy4fun Oct 01 '13 at 13:31
1

The method "FrameworkElement.FindName(string name)" uses the namescope of the layout where the button/control is used to resolve the names. In short you can use this to find children in a grid or stack panel in your application layout. But you cannot use the same to find the children of a control which you have used in your application layout(because the templated chidren names are in a different scope)

One way you can get the children in your situation is to inherit the button. Since you will not be modifying any other property or behavior of the button the new button would work as normal one. Practically I have never used such a method of accessing the templated children as I never had the need to use them outside the scope of the control's class.

public class WeddingButton : Button
{
    public override void OnApplyTemplate()
    {
        TextBlock textBlock = this.GetTemplateChild("textBlock2") as TextBlock;
        base.OnApplyTemplate();
    }
}

<Page.Resources>
    <ControlTemplate x:Key="WeddingButtonBigTemplate" TargetType="Button">
        <Grid>
            <Image x:Name="imgNormal" Source="../Images/Married_button2.png"/>
            <TextBlock x:Name="textBlock2" Style="{StaticResource RegularBlueSpecialBoldText}" LineHeight="28" LineStackingStrategy="BlockLineHeight" HorizontalAlignment="Center" Margin="10,30,10,70" TextWrapping="Wrap" TextAlignment="Center" VerticalAlignment="Stretch" >
                <Run FontSize="20" Text="The event of"></Run>
                <Run FontSize="28" Text="{DynamicResource strBride}"></Run>
            </TextBlock>
        </Grid>
    </ControlTemplate>
</Page.Resources>

<Grid HorizontalAlignment="Center" VerticalAlignment="Top" Width="1000">
    <WeddingButton x:Name="btnWedding" HorizontalAlignment="Left" Margin="10,20,0,-49" VerticalAlignment="Top" Template="{StaticResource WeddingButtonBigTemplate}" Foreground="#FF2B4072" Width="380" Click="btnClick" />
</Grid>
Karthik
  • 990
  • 10
  • 19
1

For others who may still stumble here.

I had control on the screen that had Visibility="Collapsed" by default. Even though I toggled the visibility in the window constructor it hadn't initialized the template. Had to call ApplyTemplate() on the control before FindName() to get result.

stationElement.ApplyTemplate();
var PART_DATA = stationElement.Template.FindName("PART_DATA", stationElement);