Ссылка на 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>
Итак, шаги:
- Объявить статический
- Повторно объявить / псевдоним динамический
- Посмотри на статический
Чтобы разрешить значение самостоятельно, вы можете написать собственное расширение разметки, которое использует 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)
{
}
}