【问题标题】:Referencing a StaticResource in another StaticResource在另一个 StaticResource 中引用一个 StaticResource
【发布时间】:2018-08-22 13:37:18
【问题描述】:

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

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

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

使用和引用静态资源

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

<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 时(是的,这也适用于静态资源以外的元素。例如,如果我尝试直接寻址字体系列 "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>

这会引发以下错误:

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>

System.InvalidCastException:'无法将'System.Windows.ResourceReferenceExpression'类型的对象转换为'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:'无法将'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"/>

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

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

【问题讨论】:

    标签: wpf staticresource


    【解决方案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>
    

    所以步骤是:

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

    【讨论】:

    • 好吧,我已经介绍了这个例子,它抛出了一个异常。我已经扩展了代码示例,以便您更好地了解我已经这样做了。
    • @OttoAbnormalverbraucher:您在样式设置器中使用它,它可能带有自己的延迟机制(因为它只是描述要设置的内容而不是直接实际设置值),因此它不会解决资源参考。与动态引用资源一样,您可能必须手动解决它。
    • @OttoAbnormalverbraucher:我为造型案例制定了解决方案,请参阅编辑。
    • 我实际上是在不同的地方做这两件事。我会尽快尝试并完成其余的样式问题,这可能需要几天时间,但到目前为止谢谢。
    • 虽然这确实有效,但由于 try-catch 块而大大增加了加载时间。我们经常使用这个资源解析器。这是唯一的方法吗?
    【解决方案2】:

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

    public class StaticResourceExtension : System.Windows.StaticResourceExtension
    {
        public StaticResourceExtension()
        {
        }
    
        public StaticResourceExtension(object resourceKey) : base(resourceKey)
        {
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-04-28
      • 2015-09-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多