我正在尝试正确设置样式。因此,我为所有常见的样式属性创建了一个外部ResourceDictionary
,在其中定义了一个默认的字体家族,如下所示:
<FontFamily x:Key="Default.FontFamily">Impact</FontFamily>
这样,当我更改这一行时,家庭在所有地方都会发生变化。
使用和引用StaticResource
现在,我想在没有其他定义的地方使用此默认字体系列,在大多数地方(但不是全部)。但是,我想保留在使用该字体的任何地方定义其他字体系列的功能。因此,我使用了发现的here和here的示例,并为组框标题明确定义了默认字体:
<StaticResource x:Key="GroupBox.HeaderFontFamily" ResourceKey="Default.FontFamily"/>
我在群组框模板中包含的TextBlock
上使用它。
<Style x:Key="GroupBoxHeaderTextStyle" TargetType="{x:Type TextBlock}">
<Setter Property="FontFamily" Value="{StaticResource GroupBox.HeaderFontFamily}"/>
</Style>
到目前为止,这是可行的。但是,一旦我添加另一行:
<StaticResource x:Key="GroupBox.HeaderFontFamily" ResourceKey="Default.FontFamily"/>
<StaticResource x:Key="FormLabel.FontFamily" ResourceKey="Default.FontFamily"/>
我抛出此异常:
异常:找不到名为“ Hsetu.GroupBox.HeaderFontFamily”的资源。资源名称区分大小写。
因此,我经历了WPF在后跟StaticResource
时找不到直接寻址的元素的情况(是的,这也适用于除StaticResources之外的其他元素。例如,如果我尝试寻址字体家族{{1 }},我将直接收到相同的错误,因为它在"Default.FontFamily"
元素之前)
使用DynamicResource并引用StaticResource
我尝试按照第二个示例中的建议使用StaticResource
,我提供了上面的链接:
DynamicResource
这将引发以下错误:
ArgumentException:'System.Windows.ResourceReferenceExpression'对于属性'FontFamily'无效。
使用和引用DynamicResource
在我的组框样式中使用<DynamicResource x:Key="GroupBox.HeaderFontFamily" ResourceKey="Default.FontFamily"/>
<DynamicResource x:Key="FormLabel.FontFamily" ResourceKey="Default.FontFamily"/>
<Style x:Key="GroupBoxHeaderTextStyle" TargetType="{x:Type TextBlock}">
<Setter Property="FontFamily" Value="{StaticResource GroupBox.HeaderFontFamily}"/>
</Style>
仅更改了错误消息:
DynamicResource
System.InvalidCastException:“无法将类型为“ System.Windows.ResourceReferenceExpression”的对象转换为类型为“ System.Windows.Media.FontFamily”。”
添加虚拟元素
因此,由于此问题仅在我的<DynamicResource x:Key="GroupBox.HeaderFontFamily" ResourceKey="Default.FontFamily"/>
<DynamicResource x:Key="FormLabel.FontFamily" ResourceKey="Default.FontFamily"/>
<Style x:Key="GroupBoxHeaderTextStyle" TargetType="{x:Type TextBlock}">
<Setter Property="FontFamily" Value="{DynamicResource GroupBox.HeaderFontFamily}"/>
</Style>
之后跟着另一个问题发生,因此我想到了在资源之间包含一个虚拟元素的想法。
StaticResource
现在,这可行。 万岁!但是,请稍等...,继续,我尝试使用第二个资源<StaticResource x:Key="GroupBox.HeaderFontFamily" ResourceKey="Default.FontFamily"/>
<Separator x:Key="Dummy"/>
<StaticResource x:Key="FormLabel.FontFamily" ResourceKey="Default.FontFamily"/>
"FormLabel.FontFamily"
这现在引发了另一个异常:
System.InvalidCastException:“无法将类型为“ System.Windows.Controls.Separator”的对象转换为类型为“ System.Windows.Media.FontFamily”。”
错误?
我根本不使用<Style x:Key="FormLabelStyle" TargetType="{x:Type Label}">
<Setter Property="FontFamily" Value="{StaticResource FormLabel.FontFamily}"/>
</Style>
,这是怎么回事?我假设,在处理StaticResource时,WPF实际上尝试使用前面的元素-仅在开始时起作用,因为前面的元素是偶然的Separator
-而不是用{{1}引用的元素}。同时,使前面的元素无法直接访问。为了证实我的怀疑,我将FontFamily
换成了另一个ResourceKey
。
Separator
确实可以,但是标签现在使用的是Courier New字体,而不是引用的Impact字体。
顺便说一句。这不仅会在字体系列中发生,还会在其他属性(FontFamily
,<StaticResource x:Key="GroupBox.HeaderFontFamily" ResourceKey="Default.FontFamily"/>
<FontFamily x:Key="Dummy">Courier New</FontFamily>
<StaticResource x:Key="FormLabel.FontFamily" ResourceKey="Default.FontFamily"/>
,FontSize
等情况下发生)。因此,这实际上是WPF中的错误,还是BorderThickness
应该这样(对我来说没有任何意义)?我如何才能在多个位置使用我的字体系列,而仅定义一次?
答案 0 :(得分:1)
不确定奇数引用是怎么回事,但是如果您使用DynamicResource
为资源加上别名,则必须使用StaticResource
对其进行查找。也许有一种方法可以使引用另一个动态资源的动态资源解析为原始值(例如,使用自定义标记扩展名),但是默认情况下不会发生这种情况。
<Grid>
<Grid.Resources>
<FontFamily x:Key="Default.FontFamily">Impact</FontFamily>
<DynamicResource x:Key="FormLabel.FontFamily" ResourceKey="Default.FontFamily"/>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" FontFamily="{StaticResource FormLabel.FontFamily}">Test</Label>
<TextBox Grid.Column="1"/>
</Grid>
步骤如下:
要自己解析该值,您可以编写一个自定义标记扩展,该扩展在内部使用MultiBinding
来获取对绑定元素的引用,然后解析该元素上的资源。
<FontFamily x:Key="Default.FontFamily">Impact</FontFamily>
<DynamicResource x:Key="FormLabel.FontFamily" ResourceKey="Default.FontFamily"/>
<Style TargetType="{x:Type Label}">
<Setter Property="FontFamily" Value="{local:CascadingDynamicResource FormLabel.FontFamily}"/>
</Style>
public class CascadingDynamicResourceExtension : MarkupExtension
{
public object ResourceKey { get; set; }
public CascadingDynamicResourceExtension() { }
public CascadingDynamicResourceExtension(object resourceKey)
{
ResourceKey = resourceKey;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
var binding = new MultiBinding { Converter = new CascadingDynamicResourceResolver() };
binding.Bindings.Add(new Binding { RelativeSource = new RelativeSource(RelativeSourceMode.Self) });
binding.Bindings.Add(new Binding { Source = ResourceKey });
return binding;
}
}
internal class CascadingDynamicResourceResolver : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var target = (FrameworkElement)values[0];
var resourceKey = values[1];
var converter = new ResourceReferenceExpressionConverter();
object value = target.FindResource(resourceKey);
while (true)
{
try
{
var dynamicResource = (DynamicResourceExtension)converter.ConvertTo(value, typeof(MarkupExtension));
value = target.FindResource(dynamicResource.ResourceKey);
}
catch (Exception)
{
return value;
}
}
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
存在丑陋的try
/ catch
是因为ResourceReferenceExpressionConverter
没有正确实现CanConvertFrom
,不幸的是ResourceReferenceExpression
是内部的,所以这可能仍然是最干净的方法。不过,它仍然假设一些内部因素,例如转换为MarkupExtension
。
此扩展程序可解决任何级别的别名,例如有两个别名:
<FontFamily x:Key="Default.FontFamily">Impact</FontFamily>
<DynamicResource x:Key="FormLabel.FontFamily" ResourceKey="Default.FontFamily"/>
<DynamicResource x:Key="My.FontFamily" ResourceKey="FormLabel.FontFamily"/>
<Style TargetType="{x:Type Label}">
<Setter Property="FontFamily" Value="{local:CascadingDynamicResource My.FontFamily}"/>
</Style>
答案 1 :(得分:0)
仅继承格式StaticResourceExtension
对我有用。设计师并不总是喜欢它,但是在运行时我没有遇到任何问题。
public class StaticResourceExtension : System.Windows.StaticResourceExtension
{
public StaticResourceExtension()
{
}
public StaticResourceExtension(object resourceKey) : base(resourceKey)
{
}
}