How to use resource dictionary in prism modules at

2019-03-13 17:06发布

问题:

I am using prism framework in a silverlight app with multiple modules in separate XAPs.

I have a resource dictionary defined in my in my shell project. In my modules I can use the resources fine, but since the modules are decoupled from the shell until they are loaded at runtime the designer does not show them or recognize them.

Is there a way to make the modules aware of my resources at design time without merging my resource file in every view xaml?

My resource files are in a "common" project.

回答1:

I think I have definitely solution for design-time resources.

Benefits:

  1. It works in any module based (MEF, UNITY..) application.
  2. It works in any designer (Visual Studio, Blend..)
  3. It does not create multiple instances of the same ResourceDictionary

Let's consider following solution:

  • MyApp.Shell (.exe)
  • MyApp.Module1 (.dll) - loaded at runtime using MEF
  • MyApp.Module2 (.dll) - loaded at runtime using MEF
  • MyApp.Common (.dll) - referenced by all projects

you can define brushes, implicit styles, templates etc in MyApp.Common.

use my SharedResourceDictionary to include the ResourceDictionary in all projects. At design-time it will load the ResourceDictionary for each designer, at runtime the ResourceDictionary will be loaded only when necessary.

Usage example:

include SharedResourceDictionary in App.xaml

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

include SharedResourceDictionary everywhere designer fails to find some share resource, e.g. in MyApp.Module1/UserControl1.xaml

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

Source:

/// <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;
  }
}


回答2:

I have found there are a couple of solutions to this:

1) When you create a module project, leave the App.xaml in the project instead of deleting it and instantiate your resources in there just as if it were its own application by itself (you can also add a new Application class to the project if you have already deleted it). When your module is loaded into the shell that file will be ignored so it's essentially only valid during design time. This works well in visual studio and blend although if you have many modules, memory footprint may become a problem.

2) Using design time resources. Some info about setting this up here: http://adamkinney.com/blog/2010/05/04/design-time-resources-in-expression-blend-4-rc/. This offers only blend support and your views will be stripped of all styles and formatting in visual studio. This was not ideal for me because I like working on certain aspects of the UI in visual studio. There also doesn't seem to be a documented way of manually setting up design time resources.



回答3:

Small own-experience guide for migrating resources from Shell to shared essembly and making designer work just fine

Some thoughts based on reading such questions and searching internet on the same/similar problem. I'm writing this primarily because of problem 2 (below), which is related to this issue, IMHO.

So, we had the same design, all styles and resources were in Shell. This produced 2 problems:

  1. Context help in XAML-Editor was not available (<- resources not found)
  2. Designer wouldn't show up properly (<- resources not found)

So we migrated all styles to shared assembly (Resources).

To solve the first problem you would need sth like Liero proposed, i.e. add resource dictionary to each UserControl. I didn't try his SharedDictionary, but normal ResourceDictionary definitely brings context help back and removes blue-underscore lines. Designer however still didn't show up properly.

So the second problem. There is a small trick to bring styles to designer at design time only described in this article. Basically you add a resource dictionary named DesignTimeResources.xaml to your project that contains reference to your resources:

<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>

Than move it to Properties folder. Than edit manually project file and change the item for this file to this:

<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>

Basically it's a file that Blend would generate if you add design time resources. VS cannot create it, although can read it just fine. The editing of project file says that you don't want basically this file in release.

Two minor gotchas here also, perhaps it will help somebody.

  • 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>
    

So at first I just added new ResourceDictionary into this block, which asked me to provide an x:Key. I was so used to add resources directly to UserControl.Resources, that I didn't first realise that in order to merge another dictionary you would need <ResourceDictionary> tag that normally you could skip. So it will look like this:

<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>


回答4:

If you're looking to provide design time data for your views may I suggest reading this article. It shows how to use Blend to create design time data within your project which is not included in the release builds of the application.

Hope it helps.