我可以在类似 UserControl 的样式中使用 CustomControl 的模板命名控件吗?

Can I use CustomControl's Template's named controls in a UserControl like style?

我们以前用的是UserControls,现在改成了CustomControls
UserControls 通常有命名的内部控件,可以从后面的代码访问。
现在我们在访问 CustomControls 的 ControlTemplate 的命名内部控件时遇到问题。

演示示例:

自定义控件的控件模板:

<Style TargetType="{x:Type controls:CustomControl1}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type controls:CustomControl1}">
                <Grid x:Name="PART_GridRoot" >
                    <!-- ... -->
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
自定义控件的

"Code behind":

static CustomControl1()
{
    DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl1), new FrameworkPropertyMetadata(typeof(CustomControl1)));
}

private Grid _gridRoot;

public override void OnApplyTemplate()
{
    base.OnApplyTemplate();

    _gridRoot = (Grid)this.Template.FindName("PART_GridRoot", this);;
}

public void Foo()
{
    _gridRoot.Foo(); // Null reference exception if calling in too early state
}

问题是默认 ControlTemplate 被分配(进入 CustomControl1.Template)太晚了。它不会在 ctor 中应用,也不会在调用 ApplyTemplate 或手动测量时应用。

有没有办法让默认 ControlTemplate 更早分配?
我是否应该能够在此类似 UserControl 的样式中使用 CustomControls 的模板命名控件?

OnApplyTemplate 在应用模板后调用。在此之前,无法获取您命名的内部控件,因为它们尚未创建。

这意味着像 _gridRoot 这样的字段只能在调用 OnApplyTemplate 之后才能访问。在之前和之后都可能到达的地方,如果有的话,你可以检查 null.

我可以加快 Template 的分配(实际上,默认 Style)。
这样:

public class MyControl : Control
{
    public MyControl()
    {
        ApplyDefaultStyle();
    }

    protected void ApplyDefaultStyle()
    {
        this.Style = WpfHelpers.GetDefaultStyle(this.GetType(), "OwnResourceDictionaryFullPath here");
        this.ApplyTemplate();
    }
}

public static class WpfHelpers
{
    /// <summary>
    /// Extracts the control's default Style from the resource dictionary pointed by the <see cref="resourceFullPath"/>.
    /// So the Style without x:Key, with TargetType <see cref="controlType"/>
    /// </summary>
    public static Style GetDefaultStyle(Type controlType, string resourceFullPath)
    {
        Uri resourceLocater = null;
        string assemblyName = null;
        try
        {
            assemblyName = controlType.Assembly.GetName().Name;
            resourceLocater = new Uri($"/{assemblyName};component/{resourceFullPath}", UriKind.Relative);
            var resourceDictionary = (ResourceDictionary)Application.LoadComponent(resourceLocater);
            var style = resourceDictionary[controlType] as Style;
            return style;
        }
        catch(Exception e)
        {
            //Log.Warn
            return null;
        }
    }
}

所以我手动分配了默认值 Style
我知道我已经失去了这种自动主题化的可能性,但我可以接受。
我只能希望这个解决方案没有缺点,比如性能问题。对我来说,这与 UserControlInitializeComponent() 调用非常相似。