在另一个 StaticResource 中引用一个 StaticResource

Referencing a StaticResource in another StaticResource

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

<FontFamily x:Key="Default.FontFamily">Impact</FontFamily>

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

使用和引用 StaticResource

现在我想在没有定义其他任何地方的地方使用这个默认字体系列,这是在大多数地方(但不是全部)。但是,我想保留为任何使用它的地方定义其他字体系列的能力。所以我使用了我找到的示例 and here,并为组框明确定义了默认字体 header:

<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"/>

我抛出了这个异常:

Exception: Cannot find resource named 'Hsetu.GroupBox.HeaderFontFamily'. Resource names are case sensitive.

所以我遇到过 WPF 无法找到直接寻址的元素后跟 StaticResource(是的,这也适用于 StaticResources 以外的元素。例如,如果我尝试寻址字体系列 "Default.FontFamily" 直接我会得到同样的错误,因为它在 StaticResource 元素之前)

使用 DynamicResource 并引用 StaticResource

我试过按照第二个例子中的建议使用 DynamicResource 我在上面提供了 link:

<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>

这将引发以下错误:

ArgumentException: 'System.Windows.ResourceReferenceExpression' is not a valid value for property '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>

System.InvalidCastException: 'Unable to cast object of type 'System.Windows.ResourceReferenceExpression' to type 'System.Windows.Media.FontFamily'.'

添加虚拟元素

因此,由于此问题仅在我的 StaticResource 后跟另一个时出现,我想在资源之间包含一个虚拟元素。

<StaticResource x:Key="GroupBox.HeaderFontFamily" ResourceKey="Default.FontFamily"/>
<Separator x:Key="Dummy"/>
<StaticResource x:Key="FormLabel.FontFamily" ResourceKey="Default.FontFamily"/>

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

<Style x:Key="FormLabelStyle" TargetType="{x:Type Label}">
    <Setter Property="FontFamily" Value="{StaticResource FormLabel.FontFamily}"/>
</Style>

现在抛出另一个异常:

System.InvalidCastException: 'Unable to cast object of type 'System.Windows.Controls.Separator' to type 'System.Windows.Media.FontFamily'.'

错误?

我什至没有使用 Separator,这是怎么回事?我假设,在寻址 StaticResource 时,WPF 实际上会尝试使用前面的元素——它只在开始时起作用,因为前面的元素偶然是一个 FontFamily——而不是用 [=30= 引用的元素].同时,使前面的元素无法直接访问。为了证实我的怀疑,我将 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"/>

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

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

不确定奇怪的引用是怎么回事,但是如果您使用 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>

所以步骤是:

  1. 声明静态
  2. Re-declare/alias动态
  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>
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>

简单地继承形式 StaticResourceExtension 对我有用。设计师并不总是喜欢它,但是运行次我没有遇到任何问题。

public class StaticResourceExtension : System.Windows.StaticResourceExtension
{
    public StaticResourceExtension()
    {
    }

    public StaticResourceExtension(object resourceKey) : base(resourceKey)
    {
    }
}