模块化 WPF 中的资源(使用 Caliburn.Micro 和 MEF)
Resources in modularized WPF (with Caliburn.Micro and MEF)
我整天都在寻找这个问题的答案,但没有提出任何直接适用于我的案例的解决方案,或者任何有效的解决方案(在一个案例中我发现它是适用的)。
我设置了一个 Caliburn.Micro 框架来使用 MEF,并且我可以很好地加载我的模块化元素。缺少的一件事是让 WPF 识别我在我的一个模块中使用的资源。
如何在我的应用引导程序中加载模块
[ImportMany]
private IEnumerable<IMyModule> _myModules;
protected override void Configure()
{
// Because Configure() is also called from SelectAssemblies(), we cannot instantiate MEF again because it will create conflicts.
if (_configured)
{
return;
}
AggregateCatalog aggregateCatalog = new AggregateCatalog(AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType<ComposablePartCatalog>());
aggregateCatalog.Catalogs.Add(new DirectoryCatalog(ConfigurationManager.AppSettings["MyModuleFolderLocation"]));
aggregateCatalog.Catalogs.Add(new AssemblyCatalog(GetType().Assembly));
_container = new CompositionContainer(aggregateCatalog);
CompositionBatch batch = new CompositionBatch();
batch.AddExportedValue<IWindowManager>(new WindowManager());
batch.AddExportedValue<IEventAggregator>(new EventAggregator());
batch.AddExportedValue(_container);
_container.Compose(batch);
_container.SatisfyImportsOnce(this);
_configured = true;
}
protected override IEnumerable<Assembly> SelectAssemblies()
{
// SelectAssemblies() is called before Configure(), so manually force Configure() to run first so that MEF is instantiated properly
Configure();
if (!_configured)
{
throw new Exception("Unable to configure assemblies");
}
List<Assembly> assemblies = new List<Assembly>();
assemblies.Add(Assembly.GetExecutingAssembly());
// Need to add all module assemblies so that Caliburn will be able to find the View for a ViewModel
foreach(IMyModule myModule in _myModules)
{
Assembly assembly = myModule.GetType().Assembly;
assemblies.Add(assembly);
}
return assemblies.Distinct();
}
这可以很好地使模块正确显示。
但是当一个模块已经使用了一个图片时,这个图片是永远不会显示的,因为这种加载显然没有考虑资源。
我在模块项目中创建了一个 Resources.resx 文件并向其中添加了一个图像。 Visual Studio 中显示的图像文件有一个生成操作,上面写着 "Resource" 和 "Do not copy (to output directory)"。这应该意味着图像嵌入到生成的 DLL 文件中。
图片放在模块项目中名为"Resources"的文件夹中,XAML使用方式如下:
<Image Source="/Resources/myImage.png" />
图像在Visual Studio中的预览中显示,但在应用程序运行时不显示。
我试过但没有用
- 以另一种方式引用图像:
<Image Source="pack://application:,,,/Resources/myImage.png" />
- 获取 BAML 形式的资源并将它们重新插入到正在执行的程序集中,就像在这个问题中一样:Instantiate ResourceDictionary xaml from other Assembly(这会导致此行出现 OutOfMemoryException
var reader = new Baml2006Reader(stream);
)
- 很多其他答案都引用了 ResourceDictionary,但我有一个 Resource.resx 文件(它只生成一个不是 ResourceDictionary 的内部 class)
问题依旧
如何让 WPF/Caliburn.Micro 识别来自 MEF 加载的 DLL 的资源?
首先,您应该阅读带有Style
的程序集。然后,需要使用 Baml2006Reader
从外部库读取 BAML 文件。让我举个例子:
private GetResourceDictionary()
{
string address = @"WpfCustomControlLibrary1.dll";
Assembly skinAssembly = Assembly.LoadFrom(address);
string[] resourceDictionaries = skinAssembly.GetManifestResourceNames();
Stream bamlStream = null;
string name = "themes/AllStylesDictionary.baml";//themes/AllStylesDictionary.baml
foreach (string resourceName in resourceDictionaries)
{
ManifestResourceInfo info = skinAssembly.GetManifestResourceInfo(resourceName);
if (info.ResourceLocation != ResourceLocation.ContainedInAnotherAssembly)
{
Stream resourceStream = skinAssembly.GetManifestResourceStream(resourceName);
using (ResourceReader reader = new ResourceReader(resourceStream))
{
foreach (DictionaryEntry entry in reader)
{
if (entry.Key.ToString().Equals(name.ToLower()))
{
bamlStream = entry.Value as Stream;
}
}
}
}
}
ResourceDictionary rd = LoadBaml<ResourceDictionary>(bamlStream);
Application.Current.Resources.MergedDictionaries.Add(rd);
Style style = Application.Current.Resources.MergedDictionaries[0]["myStyle"] as Style;
button.Style = style;
}
和:
public static T LoadBaml<T>(Stream stream)
{
var reader = new Baml2006Reader(stream);
var writer = new XamlObjectWriter(reader.SchemaContext);
while (reader.Read())
writer.WriteNode(reader);
return (T)writer.Result;
}
更新:
如果你想从另一个库加载图像,你应该使用下面的代码:
yourImage.Source = new Bitmap(System.Reflection.Assembly.GetEntryAssembly().
GetManifestResourceStream("MyProject.Resources.myimage.png"));
更新1:
从外部加载图像dll
。
foreach (DictionaryEntry entry in reader)
{
if (entry.Key.ToString().Equals(name.ToLower()))
{
bamlStream = entry.Value as Stream;
BitmapImage bmp = LoadImage(bamlStream);
img.Source = bmp;
}
}
public static BitmapImage LoadImage(Stream stream)
{
BitmapImage bmi;
using (MemoryStream ms1 = new MemoryStream())
{
stream.CopyTo(ms1);
bmi = new BitmapImage();
bmi.BeginInit();
bmi.StreamSource = new MemoryStream(ms1.ToArray());
bmi.EndInit();
}
return bmi;
}
回答
将此语法用于 Source
属性 用于具有 Build Action: Resource
的图像
<Image Source="/AssemblyName;component/Resources/MyImage.png" />
其中 AssemblyName
是程序集的名称(在项目属性中定义),/Resource/MyImage.png
是图像的路径(在项目中定义)。 component
必须始终存在。
旁注
在@StepUp 的大量帮助之后,我最初决定使用从这个问题中学到的知识来提出一个新问题,并改写所有内容以更具体地针对我的问题。
在写这个新问题时,我最终在谷歌上搜索了可能有助于重新措辞的短语和命令,然后我偶然发现了这个页面:http://www.geekchamp.com/tips/wp7-working-with-images-content-vs-resource-build-action
显然,WPF Image 控件有很多方法来定义 Source 属性。我已经尝试了很多不同的源输入,并认为我已经尝试了所有这些,但是上面链接的页面证明我错了。
据我测试,上述语法似乎适用于标有 Build Action: Resource
的图像。因此,我不再需要图像的 RESX 文件,并且在引导 MEF 时不需要任何特殊处理。
我整天都在寻找这个问题的答案,但没有提出任何直接适用于我的案例的解决方案,或者任何有效的解决方案(在一个案例中我发现它是适用的)。
我设置了一个 Caliburn.Micro 框架来使用 MEF,并且我可以很好地加载我的模块化元素。缺少的一件事是让 WPF 识别我在我的一个模块中使用的资源。
如何在我的应用引导程序中加载模块
[ImportMany]
private IEnumerable<IMyModule> _myModules;
protected override void Configure()
{
// Because Configure() is also called from SelectAssemblies(), we cannot instantiate MEF again because it will create conflicts.
if (_configured)
{
return;
}
AggregateCatalog aggregateCatalog = new AggregateCatalog(AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType<ComposablePartCatalog>());
aggregateCatalog.Catalogs.Add(new DirectoryCatalog(ConfigurationManager.AppSettings["MyModuleFolderLocation"]));
aggregateCatalog.Catalogs.Add(new AssemblyCatalog(GetType().Assembly));
_container = new CompositionContainer(aggregateCatalog);
CompositionBatch batch = new CompositionBatch();
batch.AddExportedValue<IWindowManager>(new WindowManager());
batch.AddExportedValue<IEventAggregator>(new EventAggregator());
batch.AddExportedValue(_container);
_container.Compose(batch);
_container.SatisfyImportsOnce(this);
_configured = true;
}
protected override IEnumerable<Assembly> SelectAssemblies()
{
// SelectAssemblies() is called before Configure(), so manually force Configure() to run first so that MEF is instantiated properly
Configure();
if (!_configured)
{
throw new Exception("Unable to configure assemblies");
}
List<Assembly> assemblies = new List<Assembly>();
assemblies.Add(Assembly.GetExecutingAssembly());
// Need to add all module assemblies so that Caliburn will be able to find the View for a ViewModel
foreach(IMyModule myModule in _myModules)
{
Assembly assembly = myModule.GetType().Assembly;
assemblies.Add(assembly);
}
return assemblies.Distinct();
}
这可以很好地使模块正确显示。
但是当一个模块已经使用了一个图片时,这个图片是永远不会显示的,因为这种加载显然没有考虑资源。 我在模块项目中创建了一个 Resources.resx 文件并向其中添加了一个图像。 Visual Studio 中显示的图像文件有一个生成操作,上面写着 "Resource" 和 "Do not copy (to output directory)"。这应该意味着图像嵌入到生成的 DLL 文件中。
图片放在模块项目中名为"Resources"的文件夹中,XAML使用方式如下:
<Image Source="/Resources/myImage.png" />
图像在Visual Studio中的预览中显示,但在应用程序运行时不显示。
我试过但没有用
- 以另一种方式引用图像:
<Image Source="pack://application:,,,/Resources/myImage.png" />
- 获取 BAML 形式的资源并将它们重新插入到正在执行的程序集中,就像在这个问题中一样:Instantiate ResourceDictionary xaml from other Assembly(这会导致此行出现 OutOfMemoryException
var reader = new Baml2006Reader(stream);
) - 很多其他答案都引用了 ResourceDictionary,但我有一个 Resource.resx 文件(它只生成一个不是 ResourceDictionary 的内部 class)
问题依旧
如何让 WPF/Caliburn.Micro 识别来自 MEF 加载的 DLL 的资源?
首先,您应该阅读带有Style
的程序集。然后,需要使用 Baml2006Reader
从外部库读取 BAML 文件。让我举个例子:
private GetResourceDictionary()
{
string address = @"WpfCustomControlLibrary1.dll";
Assembly skinAssembly = Assembly.LoadFrom(address);
string[] resourceDictionaries = skinAssembly.GetManifestResourceNames();
Stream bamlStream = null;
string name = "themes/AllStylesDictionary.baml";//themes/AllStylesDictionary.baml
foreach (string resourceName in resourceDictionaries)
{
ManifestResourceInfo info = skinAssembly.GetManifestResourceInfo(resourceName);
if (info.ResourceLocation != ResourceLocation.ContainedInAnotherAssembly)
{
Stream resourceStream = skinAssembly.GetManifestResourceStream(resourceName);
using (ResourceReader reader = new ResourceReader(resourceStream))
{
foreach (DictionaryEntry entry in reader)
{
if (entry.Key.ToString().Equals(name.ToLower()))
{
bamlStream = entry.Value as Stream;
}
}
}
}
}
ResourceDictionary rd = LoadBaml<ResourceDictionary>(bamlStream);
Application.Current.Resources.MergedDictionaries.Add(rd);
Style style = Application.Current.Resources.MergedDictionaries[0]["myStyle"] as Style;
button.Style = style;
}
和:
public static T LoadBaml<T>(Stream stream)
{
var reader = new Baml2006Reader(stream);
var writer = new XamlObjectWriter(reader.SchemaContext);
while (reader.Read())
writer.WriteNode(reader);
return (T)writer.Result;
}
更新:
如果你想从另一个库加载图像,你应该使用下面的代码:
yourImage.Source = new Bitmap(System.Reflection.Assembly.GetEntryAssembly().
GetManifestResourceStream("MyProject.Resources.myimage.png"));
更新1:
从外部加载图像dll
。
foreach (DictionaryEntry entry in reader)
{
if (entry.Key.ToString().Equals(name.ToLower()))
{
bamlStream = entry.Value as Stream;
BitmapImage bmp = LoadImage(bamlStream);
img.Source = bmp;
}
}
public static BitmapImage LoadImage(Stream stream)
{
BitmapImage bmi;
using (MemoryStream ms1 = new MemoryStream())
{
stream.CopyTo(ms1);
bmi = new BitmapImage();
bmi.BeginInit();
bmi.StreamSource = new MemoryStream(ms1.ToArray());
bmi.EndInit();
}
return bmi;
}
回答
将此语法用于 Source
属性 用于具有 Build Action: Resource
<Image Source="/AssemblyName;component/Resources/MyImage.png" />
其中 AssemblyName
是程序集的名称(在项目属性中定义),/Resource/MyImage.png
是图像的路径(在项目中定义)。 component
必须始终存在。
旁注
在@StepUp 的大量帮助之后,我最初决定使用从这个问题中学到的知识来提出一个新问题,并改写所有内容以更具体地针对我的问题。
在写这个新问题时,我最终在谷歌上搜索了可能有助于重新措辞的短语和命令,然后我偶然发现了这个页面:http://www.geekchamp.com/tips/wp7-working-with-images-content-vs-resource-build-action
显然,WPF Image 控件有很多方法来定义 Source 属性。我已经尝试了很多不同的源输入,并认为我已经尝试了所有这些,但是上面链接的页面证明我错了。
据我测试,上述语法似乎适用于标有 Build Action: Resource
的图像。因此,我不再需要图像的 RESX 文件,并且在引导 MEF 时不需要任何特殊处理。