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

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

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

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

То, что я уже пробовал безрезультатно:

  • Создайте экземпляр App.xaml, который находится в библиотеке пользовательских элементов управления, из моего приложения форм. Это не работает, потому что URI моих ресурсов ищет встроенный ресурс, а не мой локальный словарь ресурсов (тогда я мог бы просто скопировать файлы ресурсов из элемента управления в соответствующее место в моем приложении форм при сборке). Могу ли я использовать здесь DeferrableContent? Однако, насколько я мог найти в Интернете, об этом атрибуте и о том, как его следует использовать, не так много.
  • I would like to use post builds for both App and dictionaries, however, the App instantiation is a static reference to a compiled App.xaml as far as I can tell. So, App.xaml must live within the form at least
    • I did try to have a duplicated App.xaml with a post build moving the resourcedictionary.xaml. I figured that a duplicated app.xaml is ok since that is the driving force and you might not want to rely on one from the control anyway (which circles back and makes you wonder if you should then have the App.xaml in the control at all? Unless you want to allow a default that uses embedded resources....) That too failed saying it could not find the resource even though it was placed where the URI should have been pointing to. The decompiled code points to 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 не разрешены в проектах библиотеки. Я мог бы встроить это в ресурсы элемента управления, но тогда это всегда будет иметь приоритет над любыми ресурсами уровня приложения, и я потеряю настраиваемость.

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

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


person Justin Pihony    schedule 06.04.2012    source источник


Ответы (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}"
   ... />

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

person Nicolas Repiquet    schedule 26.04.2012
comment
+1 Это лучший ответ, который я когда-либо видел. Поступая так, я могу разместить свой ресурсный код где угодно и заставить его действовать так, как мне нужно. Но как это будет работать в дизайнере? Будет ли он создавать новый экземпляр MyResources и использовать значения по умолчанию? - person Justin Pihony; 26.04.2012
comment
+1 молодец! Мне даже в голову не пришло так подойти к нему! - person Dom; 18.06.2013
comment
Анализируется и компилируется ли XAML в каждой среде выполнения или при каждой сборке? - person Zev Spitz; 27.04.2014
comment
@ZevSpitz Каждый раз, когда вы сохраняете файл XAML, Visual Studio запускает для него специальный инструмент, очень похожий на шаблон T4. Так что это даже не время сборки, это время редакции! - person Nicolas Repiquet; 27.04.2014

У меня тоже была проблема с темами стилей и доступными статическими ресурсами. Итак, я создал автономную библиотеку, в которой в основном не было ничего, кроме используемых тем, которые были вложены, как ваши ОБЪЕДИНЕННЫЕ ресурсы вашего предыдущего связанного вопроса.

Затем в форме 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» очень похож на ваши вложенные словари объединенных ресурсов, и он прекрасно видит все из других библиотек.

person DRapp    schedule 20.04.2012
comment
Однако это не распространяется на мой UserControl во время разработки. И даже если бы я сослался на библиотеку словаря ресурсов в своем UserControl, она все равно была бы встроена в UserControl. Возможно, мне нужно сделать вопрос немного яснее, но мне нужно что-то, с чем можно было бы работать в дизайне (по умолчанию), которое затем можно было бы переопределить во время выполнения (контент resourcedictionary.xaml, а не встроенный) - person Justin Pihony; 20.04.2012

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

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

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

    <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

person Phil    schedule 25.04.2012
comment
Чем это отличается от того, что предлагает @DRapp? - person Justin Pihony; 26.04.2012
comment
Что ж, я исправляю проблему Content = {StaticResource SomeVariableName}, помещая ContentControl в ваш UserControl, не вмешиваясь в app.xaml. UserControl является локализуемым и делится ресурсами с WinForms с помощью файлов .ResX. Как это то же самое? - person Phil; 26.04.2012