问题
I'm trying to set up my styles properly. Thus I've created an external ResourceDictionary
for all common style attributes, in which I've defined a default font family like this:
<FontFamily x:Key="Default.FontFamily">Impact</FontFamily>
This way the family changes at all places when I change this single line.
using and referencing StaticResource
Now I want to use this default font family wherever nothing else is defined, which is in most places (but not all). However, I want to retain the ability of defining other font families for any place this is used. So I went with the examples I've found here and here, and defined the default font explicitly for a group box header:
<StaticResource x:Key="GroupBox.HeaderFontFamily" ResourceKey="Default.FontFamily"/>
I use this on a TextBlock
that is included in a template of my group box.
<Style x:Key="GroupBoxHeaderTextStyle" TargetType="{x:Type TextBlock}">
<Setter Property="FontFamily" Value="{StaticResource GroupBox.HeaderFontFamily}"/>
</Style>
So far, this is working. However, as soon as I add another line:
<StaticResource x:Key="GroupBox.HeaderFontFamily" ResourceKey="Default.FontFamily"/>
<StaticResource x:Key="FormLabel.FontFamily" ResourceKey="Default.FontFamily"/>
I get this exception thrown:
Exception: Cannot find resource named 'Hsetu.GroupBox.HeaderFontFamily'. Resource names are case sensitive.
So I've experienced that WPF cannot find an Element directly addressed when followed by a StaticResource
(Yes, this also counts for elements other than StaticResources. eg, if I tried to address the font family "Default.FontFamily"
directly I would get the same error, because it precedes a StaticResource
element)
using DynamicResource and referencing StaticResource
I've tried using a DynamicResource
as suggested in the 2nd example I've provided a link to above:
<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>
This throws the following error:
ArgumentException: 'System.Windows.ResourceReferenceExpression' is not a valid value for property 'FontFamily'.
using and referencing DynamicResource
Using DynamicResource
in my group box style only changed the error message:
<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'.'
adding a dummy element
So, as this problem only occurs when my StaticResource
is followed by another, I've got the idea of including a dummy element between the resources.
<StaticResource x:Key="GroupBox.HeaderFontFamily" ResourceKey="Default.FontFamily"/>
<Separator x:Key="Dummy"/>
<StaticResource x:Key="FormLabel.FontFamily" ResourceKey="Default.FontFamily"/>
Now, this works. Hooray! But wait a minute... continuing on, I tried to use the second resource "FormLabel.FontFamily"
<Style x:Key="FormLabelStyle" TargetType="{x:Type Label}">
<Setter Property="FontFamily" Value="{StaticResource FormLabel.FontFamily}"/>
</Style>
This throws another exception now:
System.InvalidCastException: 'Unable to cast object of type 'System.Windows.Controls.Separator' to type 'System.Windows.Media.FontFamily'.'
Bug?
I'm not even using the Separator
at all, so what is going on? I assume, when addressing a StaticResource, WPF actually tries to use the preceding element - which only worked in the beginning because the preceding element was a FontFamily
by chance - and not the element that is referenced with the ResourceKey
. At the same time, rendering the preceding element inaccessible directly. In order to confirm my suspicion, I've replaced the Separator
with another FontFamily
.
<StaticResource x:Key="GroupBox.HeaderFontFamily" ResourceKey="Default.FontFamily"/>
<FontFamily x:Key="Dummy">Courier New</FontFamily>
<StaticResource x:Key="FormLabel.FontFamily" ResourceKey="Default.FontFamily"/>
And indeed, it worked, but the Labels are now using the Courier New font instead of the referenced Impact font.
Btw. this does not only happen with font families but also other attributes (FontSize
, BorderThickness
, FontWeight
, etc.). So, is this actually a bug in WPF or are StaticResource
s supposed to act like this (which wouldn't make any sense to me)? How can I get to use my font family in multiple places only defining it once?
回答1:
Not sure what is going on with the odd referencing, but if you alias a resource using DynamicResource
you have to look that up using StaticResource
. Maybe there is a way to make the dynamic resource referencing another dynamic resource resolve to the original value (e.g. using a custom markup extension), but that is not what happens by default.
<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>
So the steps are:
- Declare static
- Re-declare/alias dynamic
- Look up static
To resolve the value yourself you can write a custom markup extension that uses a MultiBinding
internally to get a reference to the bound element and then resolve the resource on it.
<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();
}
}
The ugly try
/catch
exists because the ResourceReferenceExpressionConverter
has no proper implementation of CanConvertFrom
and unfortunately the ResourceReferenceExpression
is internal, so this is probably still the cleanest way of doing it. It still assumes some internals like the conversion to MarkupExtension
, though.
This extension resolves any level of aliasing, e.g. with two aliases:
<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>
回答2:
Simply inheriting form StaticResourceExtension
works for me. The designer doesn't always like it, but at run time I haven't encountered any issues.
public class StaticResourceExtension : System.Windows.StaticResourceExtension
{
public StaticResourceExtension()
{
}
public StaticResourceExtension(object resourceKey) : base(resourceKey)
{
}
}
来源:https://stackoverflow.com/questions/51968177/referencing-a-staticresource-in-another-staticresource