通过反射访问 Themes/Generic.xaml

Posted

技术标签:

【中文标题】通过反射访问 Themes/Generic.xaml【英文标题】:Access Themes/Generic.xaml via reflection 【发布时间】:2022-01-11 13:38:23 【问题描述】:

有什么方法可以使用反射访问 Themes/Generic ResourceDictionary

我尝试挖掘 Application.Resources 但这似乎是错误的地方。 然后我尝试分析 Assembly 的 ResourceStream,但也没有成功。

那么有谁知道我如何从程序集中获取提到的ResourceDictionary 的实例?

在有人问“为什么”之前 => 因为我想搞砸它。

这是我得到的最接近的,但它会引发无法加载某些部分的错误,所以我假设它是“副本”而不是使用的那个?

        System.Uri resourceLocater = new System.Uri("/ASMNAME;component/themes/generic.xaml", System.UriKind.Relative);

        ResourceDictionary dictionary =(ResourceDictionary) Application.LoadComponent(resourceLocater);

【问题讨论】:

伙计,我的回答返回了实际的 ResourceDictionary - 与 FindResource 一样!它最初是编译到 DLL 中的!这是二进制数据!您必须阅读 ite 并将其转换为 XAML!然后你可以解析这个 XAML 并创建一个 ResourceDictionary 的实例!这就是它的工作原理。这与 WPF 框架所做的和 FindResource 所做的相同! 它使用 relection 返回 generic.xaml 的 ResourceDitionary。请参阅这行代码:var genericXamlResources = System.Windows.Markup.XamlReader.Load(themesBamlReader) as ResourceDictionary; - 在我提取样式之前作为示例。 @BionicCode 不完全。 FrameWork 使用它的缓存实例,这是我在答案中发布的方式。添加新样式不适用于您的独奏,因为它基本上是一个副本,而不是使用的那个。看看这些方法做了什么,他们在幕后使用字典。我删除了我的反对票,因为你删除了你所有的个人 cmets 【参考方案1】:

@CSharpie 的provided solution 很慢,因为它使用反射来调用内部 库代码。但更重要的是,它的实现非常不安全且非常不稳定,因为它依赖于随时可能更改的非公共库代码。在生产代码中使用这样的实现是不负责任和不专业的。 在引用答案的情况下,不要使用反射来调用FrameworkElement.FindResource 的内部,而是直接调用此方法!

更好地使用 public API:

Generic.xaml

<ResourceDictionary>
  <Style TargetType="x:Type MyCustomControl">
    ...
  </Style>
</ResourceDictionary>

以下代码假定 Generic.xaml 文件位于名为“Net.Wpf”的程序集中。您可以拨打Assembly.GetManifestResourceNames 获取现有资源名称的列表。 在示例中,所需名称为“Net.Wpf.g.resources”。 然后像这样查找资源:


// Get the assembly that contains the Themes/Generic.xaml file
Assembly themesAssembly = Assembly.GetExecutingAssembly();
await using Stream themesResourceStream = themesAssembly.GetManifestResourceStream("Net.Wpf.g.resources");
using var resourceReader = new System.Resources.ResourceReader(themesResourceStream);
await using var themesBamlStream = resourceReader
  .OfType<DictionaryEntry>()
  .First(entry => entry.Key.Equals("themes/generic.baml"))
  .Value as Stream;
using var themesBamlReader = new System.Windows.Baml2006.Baml2006Reader(themesBamlStream);
var genericXamlResources = System.Windows.Markup.XamlReader.Load(themesBamlReader) as ResourceDictionary;
Style customControlStyle = genericXamlResources[typeof(MyCustomControl)] as Style;

// *******************************
// Same can be achieved by calling
Style customControlStyle = FindResource(typeof(ShortcutButton)) as Style;

请注意,XAML 资源字典(构建操作设置为“页面”)通常在嵌入到程序集中之前被编译为 BAML(一种二进制格式)。因此,您必须先从 BAML 转换回 XAML,然后才能访问资源。

【讨论】:

我特别要求一种通过 relfection 来实现的方法,因为您提到的方法在更改字典内容时对运行时没有影响。在这种情况下,反射的性能也可以忽略不计。这几微秒的开销将完全被忽视。 @CSharpie “这几微秒的开销将完全被忽视”,是的,您的伪解决方案确实存在更严重的问题。我也解决了它们,不仅是反射成本。 这里我的solution的伪部分是什么,你知道我的要求吗?你的回答也没有回答我的问题。我需要使用的 ResourceDicitonary 实例,而不是特定样式。【参考方案2】:

我现在开始工作了

我在这里找到了方法: https://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/SystemResources.cs,376

Assembly asm = .....
var systemResourcesType = typeof(Application).Assembly.GetType("System.Windows.SystemResources");
var ensuremethod = systemResourcesType.GetMethod("EnsureDictionarySlot", BindingFlags.NonPublic | BindingFlags.Static);
var loadMethod = ensuremethod.ReturnType.GetMethod("LoadGenericDictionary", BindingFlags.NonPublic | BindingFlags.Instance);

var resourceDictionaries = ensuremethod.Invoke(null, new[]  asm );

var resourceDictionary = (ResourceDictionary)loadMethod.Invoke(resourceDictionaries, new object[]false);

【讨论】:

不使用反射来调用FrameworkElement.FindResource方法的内部,直接调用这个方法!依赖内部即非公开代码是一种危险的游戏。有一个公共 API 可以实现这一点。

以上是关于通过反射访问 Themes/Generic.xaml的主要内容,如果未能解决你的问题,请参考以下文章

Java中是不是可以通过反射访问私有字段[重复]

如果可以通过反射绕过访问修饰符,它们的目的是啥?

通过反射实现多数据访问

面试官:private 可以通过反射访问,那么 private 的意义是什么?

可以通过反射类访问的公共,私有,受保护类有啥用? [复制]

访问修饰符也会影响反射吗?