Reputation: 350
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
Reputation: 7325
The constructor of MyScrollViewer
will not be called on template
creation with XamlReader
, so the names with which are stored in internal dictionaries. See MyScrollViewer
was createdtemplate.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
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