Template.FindName does not work if I set Name from code

As you can see in code below, I have descendant of ScrollViewer, and set Name of MyScrollView in ctor. But when I try using MyScrollViewer in control template I cannot find one via Template.FindName

If I change <local:MyScrollViewer /> to <local:MyScrollViewer Name=""PART_ContentHost"" /> code works as expected, but I am looking for solution without changing XAML.

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        var parserContext = new ParserContext
        {
            XamlTypeMapper = new XamlTypeMapper(new string[0]),
            XmlnsDictionary =
            {
                { "", "http://schemas.microsoft.com/winfx/2006/xaml/presentation" },
                { "x", "http://schemas.microsoft.com/winfx/2006/xaml" },
                { "local", "clr-namespace:" + typeof(MyScrollViewer).Namespace + ";assembly=" + typeof(MyScrollViewer).Assembly.FullName}
            }
        };
        var template = (ControlTemplate)XamlReader.Parse(@"
    <ControlTemplate TargetType=""TextBox"">
        <Border>
             <local:MyScrollViewer />
             <!--<local:MyScrollViewer Name=""PART_ContentHost""/> -->
        </Border>
    </ControlTemplate>
", parserContext);
        // Name=""PART_ContentHost""
        Content = new MyTextBox { Template = template };
    }
}

public class MyScrollViewer: ScrollViewer
{
    public MyScrollViewer() => Name = "PART_ContentHost";
}
public class MyTextBox: TextBox
{
    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
        var contentHost = Template.FindName("PART_ContentHost", this);
        if (contentHost == null)
            throw new Exception("Can not find PART_ContentHost");
    }
}   

updated: Even I put my control template into MainWindow.xaml (and remove from MainWindow ctor), it doesnot work.

        public MainWindow()
        {
            InitializeComponent();
        }


<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"        
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Grid.Resources>
            <ControlTemplate TargetType="local:MyTextBox" x:Key="TextBoxTemplate">
                <local:MyScrollViewer />
            </ControlTemplate>
        </Grid.Resources>
        <local:MyTextBox Template="{StaticResource TextBoxTemplate}" />
    </Grid>
</Window>

Upvotes: 1

Views: 427

Answers (2)

Rekshino
Rekshino

Reputation: 7325

The constructor of MyScrollViewer will not be called on template creation with XamlReader, so the names with which MyScrollViewer was created are stored in internal dictionaries. See template.ChildNames. The constructor of MyScrollViewer will first being called, when MyTextBox becomes visible, but it's already too late.

Template being created from XAML and it notices the children names by parsing, without creation of children instances. Later the children instances will be created, but the template hold old names. So if you call Template.FindNames with new names, they will not be found.
Try

var contentHost = Template.FindName("2_T", this);

Upvotes: 2

mm8
mm8

Reputation: 169360

but I am looking for solution without changing XAML

Setting the name in the constructor of the ScrollViewer won't work. You have to set it in the template for it to be registered in the namescope of the template.

If you don't want to assign the element a Name in the template, you could wait for it to get created and then find it in the visual tree without using a name:

public class MyTextBox : TextBox
{
    public MyTextBox()
    {
        Loaded += MyTextBox_Loaded;
    }

    private void MyTextBox_Loaded(object sender, RoutedEventArgs e)
    {
        MyScrollViewer sv = FindVisualChild<MyScrollViewer>(this);
        //...
    }

    private static T FindVisualChild<T>(Visual visual) where T : Visual
    {
        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(visual); i++)
        {
            Visual child = (Visual)VisualTreeHelper.GetChild(visual, i);
            if (child != null)
            {
                T correctlyTyped = child as T;
                if (correctlyTyped != null)
                {
                    return correctlyTyped;
                }

                T descendent = FindVisualChild<T>(child);
                if (descendent != null)
                {
                    return descendent;
                }
            }
        }
        return null;
    }
}

Upvotes: 1

Related Questions