如何在设计时在Prism模块中使用资源字典?

7
我正在使用Prism框架在一个带有多个独立XAP模块的Silverlight应用程序中。我在我的Shell项目中定义了一个资源字典。在我的模块中,我可以很好地使用这些资源,但是由于这些模块直到运行时才与Shell解耦,因此设计者无法显示或识别它们。
是否有一种方法可以使模块在设计时意识到我的资源,而不必在每个视图XAML中合并我的资源文件? 我的资源文件位于“common”项目中。
4个回答

5

我认为我有一个设计时资源的绝佳解决方案。

好处:

  1. 它适用于任何基于模块的(MEF,UNITY等)应用程序。
  2. 它适用于任何设计器(Visual Studio,Blend等)
  3. 它不会创建多个相同的ResourceDictionary实例

让我们考虑以下解决方案:

  • MyApp.Shell (.exe)
  • MyApp.Module1 (.dll) - 使用MEF在运行时加载
  • MyApp.Module2 (.dll) - 使用MEF在运行时加载
  • MyApp.Common (.dll) - 被所有项目引用

您可以在MyApp.Common中定义brushes、隐式样式、模板等。

使用我的SharedResourceDictionary将ResourceDictionary包含在所有项目中。在设计时,它将为每个设计器加载ResourceDictionary,在运行时,只有在必要时才会加载ResourceDictionary。

使用示例:

在App.xaml中包含SharedResourceDictionary

<Application.Resources>
  <ResourceDictionary>
    <ResourceDictionary.MergedDictionaries>
      <common:SharedResourceDictionary SharedSource="MyApp.Common;component/CommonResources.xaml" />
  </ResourceDictionary>
</Application.Resources>

在设计器无法找到某些共享资源的情况下,例如在MyApp.Module1 / UserControl1.xaml中,需要在各处包含SharedResourceDictionary。

<UserControl.Resources>
  <common:SharedResourceDictionary SharedSource="MyApp.Common;component/CommonResources.xaml" />
</UserControl.Resources>

来源:

/// <summary>
/// Loads singleton instance of ResourceDictionary to current scope;
/// </summary>
public class SharedResourceDictionary : ResourceDictionary
{
  /// <summary>
  /// store weak references to loaded ResourceDictionary, to ensure that ResourceDictionary won't be instanciated multiple times
  /// </summary>
  protected static Dictionary<string, WeakReference> SharedResources = new Dictionary<string, WeakReference>();

  public string SharedSource
  {
    get { return _SharedSource; }
    set
    {
      if (_SharedSource != value)
      {
        _SharedSource = value;
        sharedSourceChanged();
      }
    }
  }
  private string _SharedSource;


  private void sharedSourceChanged()
  {
    //ResourceDictionary will be instanciated only once
    ResourceDictionary sharedResourceDictionary;

    lock (SharedResources)
    {
      WeakReference weakResourceDictionary = null;
      if (SharedResources.ContainsKey(_SharedSource))
      {
        weakResourceDictionary = SharedResources[_SharedSource];
      }
      else
      {
        SharedResources.Add(_SharedSource, null);
      }

      if (weakResourceDictionary == null || !weakResourceDictionary.IsAlive) //load ResourceDictionary or get reference to exiting
      {
        sharedResourceDictionary = (ResourceDictionary)Application.LoadComponent(new Uri(_SharedSource, UriKind.Relative));
        weakResourceDictionary = new WeakReference(sharedResourceDictionary);
      }
      else
      {
        sharedResourceDictionary = (ResourceDictionary)weakResourceDictionary.Target;
      }

      SharedResources[_SharedSource] = weakResourceDictionary;
    }


    if (Application.Current != null)
    {
      //if sharedResourceDictionary is defined in application scope do not add it to again to current scope
      if (containsResourceDictionary(Application.Current.Resources, sharedResourceDictionary))
      {
        return;
      }
    }

    this.MergedDictionaries.Add(sharedResourceDictionary);
  }

  private bool containsResourceDictionary(ResourceDictionary scope, ResourceDictionary rs)
  {
    foreach (var subScope in scope.MergedDictionaries)
    {
      if (subScope == rs) return true;
      if (containsResourceDictionary(subScope, rs)) return true;
    }
    return false;
  }
}

我认为你应该避免在Application.Resources中定义资源。如果你想在多个视图之间共享模块内的资源,你不希望需要去你的App程序集中为模块定义这些资源。 - Alan
@Alan:如果您的模块分别位于不同的程序集中,并且您想共享资源字典(比如说主题)并支持设计时,那么您需要将资源字典添加到每个视图中。这在运行时非常低效,因为您会有许多相同的资源字典实例。我的解决方案很好,尽管我会使用附加属性来替代继承自ResourceDictionary。 - Liero
我认为你的解决方案非常好,不要误会。它在许多情况下都可以工作。但是,既然这是Prism,想象一下您正在从目录加载模块,而您的应用程序不知道哪些模块存在。现在,您想在特定模块内的组件之间共享一些资源。您希望它在设计时和运行时都能正常工作。我认为您需要使用主题级别的资源(Themes/generic.xaml),并使用ComponentResourceKeys引用您的资源,以便以共享方式工作,因为应用程序级别的资源不再可用(至少在设计时不可用)。 - Alan

4
我发现有几种解决方法:
1)当您创建模块项目时,请将App.xaml保留在项目中,而不是删除它,并在其中实例化资源,就像它自己是一个独立的应用程序一样(如果已经删除了它,还可以向项目添加一个新的Application类)。当您的模块加载到Shell中时,该文件将被忽略,因此在设计时间内它基本上是无效的。这在Visual Studio和Blend中都很有效,但如果您有多个模块,则内存占用可能会成为问题。
2)使用设计时资源。有关设置此项的一些信息,请参见此处:http://adamkinney.com/blog/2010/05/04/design-time-resources-in-expression-blend-4-rc/。这仅提供Blend支持,并且在Visual Studio中,您的视图将被剥离所有样式和格式。对我来说这并不理想,因为我喜欢在Visual Studio中处理UI的某些方面。似乎也没有记录手动设置设计时资源的方式。

2

将资源从Shell迁移到共享程序集并使设计师正常工作的小型个人经验指南

基于阅读类似问题和在互联网上搜索同样/类似问题的一些想法。我主要是因为问题2(下面)与此问题有关,所以才写这篇文章。

所以,我们拥有相同的设计,所有样式和资源都在Shell中。这产生了两个问题:

  1. XAML-Editor中没有上下文帮助(<-找不到资源)
  2. 设计师无法正常显示(<-找不到资源)

因此,我们将所有样式迁移到共享程序集(资源)中。

要解决第一个问题,您需要像Liero建议的那样添加每个UserControl的资源字典。我没有尝试过他的SharedDictionary,但普通的ResourceDictionary绝对可以恢复上下文帮助并删除蓝色下划线。然而,设计师仍然无法正常显示。

所以第二个问题。有一个小技巧可以在设计时将样式带到设计师中,只需参考这篇文章即可。基本上,您需要将名为DesignTimeResources.xaml的资源字典添加到您的项目中,其中包含对您的资源的引用:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="pack://application:,,,/Resources;component/Themes/Generic.xaml"/>
    </ResourceDictionary.MergedDictionaries>

</ResourceDictionary>

然后将其移动到Properties文件夹。然后手动编辑项目文件并将此文件的项目更改为:

<Page Include="Properties\DesignTimeResources.xaml" Condition="'$(DesignTime)'=='true' OR ('$(SolutionPath)'!='' AND Exists('$(SolutionPath)') AND '$(BuildingInsideVisualStudio)'!='true' AND '$(BuildingInsideExpressionBlend)'!='true')">
      <Generator>MSBuild:Compile</Generator>
      <SubType>Designer</SubType>
      <ContainsDesignTimeResources>true</ContainsDesignTimeResources>
    </Page>

基本上,这是一个文件,如果您添加设计时资源,Blend将生成它。VS不能创建它,但可以很好地读取它。编辑项目文件表示您不想在发布中使用此文件。

这里还有两个小问题,也许会帮助某些人。

  • When migrating resources from Shell to Resources, our Resources project won't build with weird errors that it cannot find UserControls referenced from style files (all problematic controls were defined in the Resources project as well). They were working just fine when referenced from Shell before. The problem was that some tools (like Resharper) automatically reference these controls in namespace like "clr-namespace:XXX;assembly=Resources". The ";assembly=Resources"-part you should delete, as it is the same assembly now.
  • We already head some local resources in our UserControls, like this:

    <UserControl.Resources>
        <PresentationHelpers:BoolToVisibilityConverter x:Key="boolToVisibilityConverter" />
    </UserControl.Resources>
    

一开始,我只是将新的ResourceDictionary添加到这个块中,它要求我提供一个x:Key。我习惯于直接将资源添加到UserControl.Resources中,所以我没有意识到为了合并另一个字典,你需要使用<ResourceDictionary>标签,而这通常是可以省略的。因此,它看起来像这样:

<UserControl.Resources>
  <ResourceDictionary>
    <ResourceDictionary.MergedDictionaries>
      <Helpers:RedbexResourceDictionary Source="pack://application:,,,/Resources;component/Themes/Generic.xaml" />
    </ResourceDictionary.MergedDictionaries>
    <PresentationHelpers:BoolToVisibilityConverter x:Key="boolToVisibilityConverter" />
  </ResourceDictionary>
</UserControl.Resources>

1
如果您想为视图提供设计时数据,我建议阅读this article。它展示了如何使用Blend在项目中创建设计时数据,这些数据不包含在应用程序的发布版本中。
希望对您有所帮助。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接