Ссылка на StaticResource в другом StaticResource

Я пытаюсь настроить мои стили правильно. Таким образом, я создал внешний ResourceDictionary для всех общих атрибутов стиля, в которых я определил семейство шрифтов по умолчанию, например:

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

Таким образом, семья меняется во всех местах, когда я меняю эту единственную строку.

использование и ссылки на StaticResource

Теперь я хочу использовать это семейство шрифтов по умолчанию везде, где ничего не определено, что есть в большинстве мест (но не во всех). Однако я хочу сохранить возможность определения других семейств шрифтов для любого места, где это используется. Поэтому я пошел с примерами, которые нашел здесь и здесь, и определил шрифт по умолчанию для заголовка группового блока:

<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 (Да, это также относится к элементам, отличным от 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' не является допустимым значением для свойства '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"/>

И действительно, это сработало, но Labels теперь использует шрифт Courier New вместо ссылочного шрифта Impact.

Btw. это происходит не только с семействами шрифтов, но и с другими атрибутами (FontSize, BorderThickness, FontWeight, так далее.). Итак, действительно ли это ошибка в 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>

Просто наследуем форму StaticResourceExtensionработает на меня. Дизайнеру это не всегда нравится, но во время выполнения я не сталкивался с какими-либо проблемами.

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

    public StaticResourceExtension(object resourceKey) : base(resourceKey)
    {
    }
}
Другие вопросы по тегам