wpf - 在另一个 StaticResource 中引用一个 StaticResource

标签 wpf staticresource

我正在尝试正确设置我的样式。因此,我创建了一个外部 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"/>

我抛出了这个异常:

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



所以我经历过,当 WPF 后跟 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>

这会引发以下错误:

ArgumentException: 'System.Windows.ResourceReferenceExpression' is not a valid value for property '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="{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偶然 - 而不是 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 中的错误还是 StaticResource s 应该这样做(这对我来说没有任何意义)?我怎样才能在多个地方使用我的字体系列,只定义一次?

最佳答案

不确定奇数引用发生了什么,但如果您使用 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 的自定义标记扩展。在内部获取对绑定(bind)元素的引用,然后解析其上的资源。
    <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>
    

    关于wpf - 在另一个 StaticResource 中引用一个 StaticResource,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51968177/

    相关文章:

    Silverlight 4 - 在另一个 ResourceDictionary 中使用 StaticResource

    wpf - CompositeCollection/CollectionViewSource 混淆

    wpf - 使用资源作为转换导致绑定(bind)转换器

    c# - 将项目从列表框中拖放到特定的 Canvas 中并启动方法

    wpf - 无法更新 DataGrid

    java - 无法共享静态资源

    vue.js - 有没有办法只对 webpack 中的特定资源(图像)禁用 filenameHashing?

    c# - 媒体播放器如何播放列表框中的所有文件?

    WPF 弹出式替代方案

    c# - 为什么异步等待任务中的同步代码比异步代码慢得多