在另一个 StaticResource 中引用一个 StaticResource

Ott*_*her 5 wpf staticresource

我正在尝试正确设置我的风格。因此,我为所有常见样式属性创建了一个外部ResourceDictionary,在其中定义了一个默认字体系列,如下所示:

<FontFamily x:Key="Default.FontFamily">Impact</FontFamily>
Run Code Online (Sandbox Code Playgroud)

这样,当我更改这一行时,家庭在所有地方都会发生变化。

使用和引用 StaticResource

现在我想在没有定义任何其他内容的地方使用这个默认字体系列,这是在大多数地方(但不是全部)。但是,我想保留为任何使用此字体的地方定义其他字体系列的能力。因此,我使用了此处此处找到的示例,并为组框标题显式定义了默认字体:

<StaticResource x:Key="GroupBox.HeaderFontFamily" ResourceKey="Default.FontFamily"/>
Run Code Online (Sandbox Code Playgroud)

我在TextBlock我的组框模板中使用它。

<Style x:Key="GroupBoxHeaderTextStyle" TargetType="{x:Type TextBlock}">
    <Setter Property="FontFamily" Value="{StaticResource GroupBox.HeaderFontFamily}"/>
</Style>
Run Code Online (Sandbox Code Playgroud)

到目前为止,这是有效的。但是,一旦我添加另一行:

<StaticResource x:Key="GroupBox.HeaderFontFamily" ResourceKey="Default.FontFamily"/>
<StaticResource x:Key="FormLabel.FontFamily" ResourceKey="Default.FontFamily"/>
Run Code Online (Sandbox Code Playgroud)

我抛出这个异常:

异常:找不到名为“Hsetu.GroupBox.HeaderFontFamily”的资源。资源名称区分大小写。

所以我经历过,WPF 在后面跟着 a 时无法找到直接寻址的元素StaticResource(是的,这也适用于 StaticResources 以外的元素。例如,如果我尝试直接寻址字体系列,"Default.FontFamily"我会得到相同的错误,因为它先于一个StaticResource元素)

使用 DynamicResource 并引用 StaticResource

我尝试使用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>
Run Code Online (Sandbox Code Playgroud)

这会引发以下错误:

ArgumentException:“System.Windows.ResourceReferenceExpression”不是属性“FontFamily”的有效值。

使用和引用 DynamicResource

在我的组框样式中使用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="{DynamicResource GroupBox.HeaderFontFamily}"/>
</Style>
Run Code Online (Sandbox Code Playgroud)

System.InvalidCastException:“无法将类型为“System.Windows.ResourceReferenceExpression”的对象转换为类型为“System.Windows.Media.FontFamily”。”

添加虚拟元素

因此,由于此问题仅在 myStaticResource后面跟着另一个问题时才会出现,因此我想到在资源之间包含一个虚拟元素。

<StaticResource x:Key="GroupBox.HeaderFontFamily" ResourceKey="Default.FontFamily"/>
<Separator x:Key="Dummy"/>
<StaticResource x:Key="FormLabel.FontFamily" ResourceKey="Default.FontFamily"/>
Run Code Online (Sandbox Code Playgroud)

现在,这有效了。万岁!但是等一下...继续,我尝试使用第二个资源"FormLabel.FontFamily"

<Style x:Key="FormLabelStyle" TargetType="{x:Type Label}">
    <Setter Property="FontFamily" Value="{StaticResource FormLabel.FontFamily}"/>
</Style>
Run Code Online (Sandbox Code Playgroud)

现在这会引发另一个异常:

System.InvalidCastException:“无法将类型为‘System.Windows.Controls.Separator’的对象转换为类型为‘System.Windows.Media.FontFamily’。”

漏洞?

我什至根本没有使用过Separator,所以这是怎么回事?我假设,在寻址 StaticResource 时,WPF 实际上尝试使用前面的元素(该元素仅在开始时有效,因为前面的元素是偶然的FontFamily),而不是使用ResourceKey. 同时,使前面的元素无法直接访问。为了证实我的怀疑,我用Separator另一个FontFamily.

<StaticResource x:Key="GroupBox.HeaderFontFamily" ResourceKey="Default.FontFamily"/>
<FontFamily x:Key="Dummy">Courier New</FontFamily>
<StaticResource x:Key="FormLabel.FontFamily" ResourceKey="Default.FontFamily"/>
Run Code Online (Sandbox Code Playgroud)

事实上,它确实有效,但标签现在使用 Courier New 字体,而不是引用的 Impact 字体。

顺便提一句。这不仅会发生在字体系列上,还会发生在其他属性(FontSizeBorderThicknessFontWeight等)上。那么,这实际上是 WPF 中的一个错误还是应该StaticResource像这样运行(这对我来说没有任何意义)?如何在多个地方使用我的字体系列,只需定义一次?

H.B*_*.B. 3

不确定奇怪的引用发生了什么,但如果您使用别名资源,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>
Run Code Online (Sandbox Code Playgroud)

所以步骤是:

  1. 声明静态
  2. 重新声明/别名动态
  3. 查找静态

要自行解析该值,您可以编写一个自定义标记扩展,该扩展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>
Run Code Online (Sandbox Code Playgroud)
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();
    }
}
Run Code Online (Sandbox Code Playgroud)

丑陋的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>
Run Code Online (Sandbox Code Playgroud)