Есть ли способ использовать StaticResource в управляющей библиотеке WPF и иметь возможность просмотра во время разработки?

У меня есть библиотека управления WPF, которая добавляется в приложение Windows Forms. Мы хотим, чтобы элементы управления были локализуемыми, однако я не уверен, как ПОЛНОСТЬЮ выполнить это без дублирования кода. Это то, что я делаю сейчас.

По сути, в приложении Windows Forms перед запуском основного приложения я создаю экземпляр App.xaml, который находится в приложении форм (содержит мои ссылки на мои ресурсы, которые также находятся в приложении форм). Это прекрасно работает во время выполнения.

Тем не менее, все мои пользовательские элементы управления имеют Content="{StaticResource SomeVariableName}", который в конечном итоге будет пустым. Я могу исправить это, имея app.xaml и соответствующие словари ресурсов в моей управляющей библиотеке, которые соответствуют тем, которые есть в моем приложении windows form. Однако это дублированный код.

Вещи, которые я уже пытался безрезультатно:

  • Создайте экземпляр App.xaml, который находится в библиотеке управления пользователями, из моего приложения форм. Это не работает, потому что URI для моих ресурсов ищет встроенный ресурс, а не мой словарь локальных ресурсов (я мог бы просто скопировать файлы ресурсов из элемента управления в соответствующее место в моем приложении форм при сборке). Могу ли я использовать DeferrableContent здесь? Насколько я мог найти в этом атрибуте и как его использовать, в Интернете не так много.
  • Я хотел бы использовать пост сборки как для приложения, так и для словарей, однако, насколько я могу судить, создание экземпляра приложения является статической ссылкой на скомпилированный файл App.xaml. Итак, App.xaml должен жить как минимум в форме
    • Я попытался создать дублированный App.xaml с пост-сборкой, перемещающей resourcedictionary.xaml. Я подумал, что дублированный app.xaml в порядке, так как это движущая сила, и вы, возможно, не захотите полагаться на один из элементов управления в любом случае (который возвращается назад и заставляет задуматься, должен ли вы затем иметь App.xaml в элементе управления в все? Если только вы не хотите разрешить использование по умолчанию, которое использует встроенные ресурсы....) Это тоже не помогло, сказав, что он не может найти ресурс, даже если он был расположен там, где должен был указывать URI. Декомпилированный код указывает на Uri resourceLocater = new Uri("/WindowsFormsApplication3;component/app.xaml", UriKind.Relative);

Итак, есть ли способ, чтобы это работало И имел представление времени разработки компонентов по умолчанию И избегал дублирования? Или в этом случае все в порядке? Если подпункт моего второго маркера выглядит нормально (дублированный App.xaml со сборочными скопированными ресурсами), как мне сделать так, чтобы он не искал элемент уровня компонента, а вместо этого файл первого уровня?

Последний вопрос (и я могу опубликовать это отдельно, если необходимо), на который я только что обратил внимание. Мой App.xaml встраивается в код, так что в любом случае я не могу создавать новые ResourceDictionaries. Есть какой-либо способ сделать это?

Окончательный вариант... возможно, лучший?- В любом случае я планирую использовать код Андре ван Хеерварде, поэтому я должен просто проверить наличие файла и добавить его как объединенный ресурс на лету? По сути, в моем пользовательском элементе управления есть один App.xaml, который ссылается на встроенный ResourceDictionary по умолчанию. И затем код должен искать соответствующие локализованные ресурсы на лету, которые могут быть относительными путями файлов? Единственный недостаток, который я вижу здесь, это то, что значение по умолчанию не может быть изменено на лету... что я мог бы даже иметь такой вид в указанном месте (используя какое-то соглашение) и предпочел бы это по сравнению со встроенным?

Да, и причина, по которой я не хочу внедрять встроенные ресурсы, заключается в том, что конечные пользователи могут добавлять / изменять новые локализованные ресурсы после развертывания сборки.

Я могу добавить код, если он поможет вам лучше это представить, просто дайте мне знать.

ОБНОВИТЬ

Теперь я столкнулся с еще одной проблемой со стилем, а не только с локализацией.

Вот пример одной из внутренних кнопок на одном из элементов управления:

<Button Style="{StaticResource GrayButton}"

Еще несколько вещей, которые я пробовал / думал:

  • Я не могу создать app.xaml (который никогда не будет использоваться) с ResourceDictionary, настроенным, поскольку ApplicationDefinitions не разрешены в проектах библиотеки. Я мог бы встроить это в ресурсы элемента управления, но тогда это всегда будет иметь приоритет над любыми ресурсами уровня приложения, и я теряю настраиваемость.

Вот случай подключения, который на самом деле звучит как то, что я ищу, однако он не дает никакого реального решения этой проблемы.

Решение (за пределами вершины... которое не работает), о котором я могу подумать, может сработать (и еще не попробовало), также кажется большой работой для чего-то, что, на мой взгляд, должно быть простым. Но я мог бы создать некоторые свойства зависимостей в элементе управления, к которым я могу привязаться, и затем разрешить их переопределять проектом, который будет использовать этот элемент управления. Как я уже сказал, для довольно простого запроса это кажется большой работой:). Будет ли это даже работать? И что еще более важно, есть ли лучшее, более простое решение, которое мне не хватает?

3 ответа

Решение

Однажды я столкнулся с этой проблемой, и решил ее, отбросив всю тему "Ресурсы - это объекты, проиндексированные по ключу в канонических словарях".

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

Мое решение этой проблемы состояло в том, чтобы создать пользовательский инструмент, который преобразует мои файлы ресурсов xaml в статические классы со свойством для каждого ресурса:

Итак, MyResources.xaml:

<ResourceDictionary>
  <SolidColorBrush x:Key="LightBrush" ... />
  <SolidColorBrush x:Key="DarkBrush" ... />
</ResourceDictionary>

Становится MyResources.xaml.cs

public static class MyResources {

  static MyResources() {
    // load the xaml file and assign values to static properties
  }

  public static SolidColorBrush LightBrush { get; set; }
  public static SolidColorBrush DarkBrush { get; set; }

}

Для ссылки на ресурс вы можете использовать x:Static вместо StaticResource:

<Border 
   Fill="{x:Static MyResources.LightBrush}"
   BorderBrush="{x:Static MyResources.DarkBrush}"
   ... />

Теперь у вас есть надежные ссылки, автозаполнение и проверка времени компиляции ресурсов.

У меня тоже была проблема с темами оформления и доступными статическими ресурсами. Итак, я создал автономную библиотеку, в которой в основном не было ничего, кроме тем, которые будут использоваться, все вложенные как ресурсы MERGED вашего предыдущего связанного вопроса.

Затем в форме Windows (.xaml) я просто поместил ссылку на эту библиотеку, что-то вроде

<Window x:Class="MyAppNamespace.MyView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" ... />

  <Window.Resources>
    <ResourceDictionary>
      <ResourceDictionary.MergedDictionaries>
        <!-- Common base theme -->
        <ResourceDictionary   Source="pack://application:,,,/MyLibrary;component/Themes/MyMainThemeWrapper.xaml" />
        </ResourceDictionary.MergedDictionaries>
      </ResourceDictionary>
  </Window.Resources>

  <Rest of XAML for the WPF window>

</Window>

"Компонент", по-видимому, ссылается на корень данного проекта "MyLibrary". В настоящем проекте я создал подпапку "Темы", поэтому источник включает в себя...;component/Themes/...

MyMainThemeWrapper.xaml очень похож на ваши вложенные словари Merged Resource и прекрасно видит все из других библиотек.

Вот мое частичное решение вашей проблемы. Я не пытался обрабатывать свободные ресурсы, но у меня есть некоторый успех в распределении ресурсов между WinForms и WPF.

  • Создайте библиотеку классов для хранения ваших ресурсов в файлах.ResX (например, Resources.resx, Resources.fr.resx и т. Д.)
  • Создайте свои элементы управления WPF в библиотеке пользовательских элементов управления WPF
  • Создайте свой хост WinForms
  • Ссылайтесь на ресурсы в вашей библиотеке ресурсов из WPF, используя менеджер разметки Infralution.Localization.Wpf и менеджер культуры, например

    <TextBlock Text="{Resx ResxName=ResourceLib.Resources, Key=Test}"/>
    
  • Поместите содержимое пользовательских элементов управления WPF в один или несколько словарей ресурсов в качестве шаблонов элементов управления, например,

    <ControlTemplate x:Key="TestTemplate">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition/>
            </Grid.RowDefinitions>
    
            <TextBlock Text="{Resx ResxName=ResourceLib.Resources, Key=Test}"/>
        </Grid>
    </ControlTemplate>
    
  • Используйте шаблон ресурса в ваших элементах управления

    <UserControl x:Class="WpfControls.UserControl1"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" mc:Ignorable="d" 
                 d:DesignHeight="300" d:DesignWidth="300" >
        <UserControl.Resources>
            <ResourceDictionary>
                <ResourceDictionary.MergedDictionaries>
                    <ResourceDictionary Source="ResourceDictionary.xaml"/>
                </ResourceDictionary.MergedDictionaries>
            </ResourceDictionary>
        </UserControl.Resources>
        <ContentControl Template="{StaticResource TestTemplate}" />
    </UserControl>
    
  • Добавьте пару строк кода, чтобы все заработало

    public partial class UserControl1 : UserControl
    {
        // we require a reference to the resource library to ensure it's loaded into memory
        private Class1 _class1 = new Class1();
    
        public UserControl1()
        {
            // Use the CultureManager to switch to the current culture
            CultureManager.UICulture = Thread.CurrentThread.CurrentCulture;
    
            InitializeComponent();
        }
    }
    

Вот простое демонстрационное приложение под названием WindowsFormsHost.7z

Другие вопросы по тегам