如果我从代码中设置名称,Template.FindName将不起作用

时间:2019-02-15 07:18:16

标签: wpf xaml

如下面的代码所示,我有ScrollViewer的后代,并在ctor中设置了MyScrollView的名称。但是,当我尝试在控件模板中使用MyScrollViewer时,无法通过Template.FindName

找到一个

如果我将<local:MyScrollViewer />更改为<local:MyScrollViewer Name=""PART_ContentHost"" />,代码将按预期工作,但是我正在寻找解决方案而不更改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");
    }
}   

已更新: 即使我将控件模板放入MainWindow.xaml(并从MainWindow ctor中删除),也无法正常工作。

        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>

2 个答案:

答案 0 :(得分:2)

使用beforeEach创建MyScrollViewer时不会调用template的构造函数,因此存储了创建XamlReader的名称 在内部词典中。参见MyScrollViewer。当template.ChildNames可见时将首先调用MyScrollViewer的构造函数,但为时已晚。

从XAML创建的模板,它通过解析来注意到子名称,而不创建子实例。稍后将创建子实例,但模板保留旧名称。因此,如果您使用新名称调用MyTextBox,将不会找到它们。
尝试

Template.FindNames

答案 1 :(得分:1)

  

但是我正在寻找不更改XAML的解决方案

ScrollViewer的构造函数中设置名称将无效。您必须在模板中进行设置,才能将其注册到模板的名称范围中。

如果您不想在模板中为元素分配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;
    }
}