我可以在类似 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
。
我知道我已经失去了这种自动主题化的可能性,但我可以接受。
我只能希望这个解决方案没有缺点,比如性能问题。对我来说,这与 UserControl
的 InitializeComponent()
调用非常相似。
我们以前用的是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
。
我知道我已经失去了这种自动主题化的可能性,但我可以接受。
我只能希望这个解决方案没有缺点,比如性能问题。对我来说,这与 UserControl
的 InitializeComponent()
调用非常相似。