长答案
前面的简短答案提供了一些 XAML 来解决问题,并简要总结了导致问题的原因。
加载主题的资源与在操作系统级别更改主题不同。加载主题的资源可能会造成不利影响。从 WPF 的角度来看,应用程序中现在存在大量隐式样式。这些风格可能胜过其他风格。底线是将主题视为应用程序皮肤可能无法在没有改进的情况下工作。
以下长答案将对该问题进行更深入的讨论。首先将介绍一些背景主题。这将回答所提出的一些外围问题,也将为理解手头的问题提供更好的基础。之后,将通过有效的调试策略剖析和解决问题的各个方面。
主题与皮肤
这是一个很好的问题,部分原因是数百名博主和论坛帖子建议从文件中加载主题作为“更改主题”的一种方式。提出此建议的一些作者在 Microsoft 工作,其中许多作者显然是高素质的软件工程师。这种方法似乎在很多时候都有效。但是,正如您所注意到的,这种方法在您的场景中并不完全适用,需要进行一些改进。
其中一些问题源于不精确的术语。不幸的是,主题这个词已经无可救药地超载了。可以避免混淆的主题的精确定义就是系统主题。系统主题定义机器上 Win32 视觉效果的默认外观。我的操作系统是 Vista。我安装的主题位于 C:\WINDOWS\Resources\Themes。该文件夹中有两个文件:aero.theme 和 Windows Classic.theme。如果我想更改主题,我可以选择 [个性化 | 主题] 或 [个性化 | 窗口颜色和外观 | 配色方案]。虽然不是很明显,但我可以选择的选项归结为 Aero 或 Classic 以及一些额外的改进。因为 WPF 窗口呈现其客户区而不是合成一堆 Win32 控件,所以客户区不会自动尊重主题。主题程序集(例如 PresentationFramework.Aero.dll)为将主题功能扩展到 WPF 窗口提供了基础。

主题的更一般定义是任何外观和感觉配置,在任何粒度级别(操作系统、应用程序、控制)。当人们使用一般定义时,可能会出现不同程度的混淆。请注意,MSDN 在精确定义和一般定义之间切换而不会发出警告!
很多人会说您正在加载应用程序皮肤,而不是主题。任何一个词都可以说是正确的,但我会推荐这种心理模型,因为它不会引起混淆。
那么,如何确保我的应用始终使用 Aero主题……?[重点补充]
同样,可以说您正在将 Aero 的资源作为皮肤加载。具体来说,您正在加载位于 PresentationFramework.Aero.dll 中的 ResourceDictionary。这些资源以前被给予特殊处理,因为它们是默认资源。但是,一旦进入应用程序,它们将被视为任何其他任意资源集合。当然,Aero 的 ResourceDictionary 是全面的。由于它将在应用程序范围内加载,因此它将有效地隐藏主题(在您的情况下为 Luna)提供的每个默认样式,以及导致问题的其他一些样式。请注意,最终,主题仍然是相同的(Luna)。
正如上面提到的,主题涉及样式优先级,它本身就是 依赖属性优先级的一种形式。这些优先规则极大地揭开了问题中观察到的行为的神秘面纱。
显式风格。Style 属性是直接设置的。在大多数情况下,样式不是内联定义的,而是作为资源引用,通过显式键......</p>
隐式风格。Style 属性不是直接设置的。但是,样式存在于资源查找序列(页面、应用程序)中的某个级别,并且使用与要应用样式的类型匹配的资源键进行键控……</p>
默认样式,也称为主题样式。Style 属性不是直接设置的,实际上将读取为 null... 在这种情况下,样式来自运行时主题评估,它是 WPF 表示引擎的一部分。
此博客条目对样式与默认样式进行了更深入的研究。
.NET 装配检查
这也是一个很好的问题,部分原因是有很多活动部件。 如果没有有效的调试策略,几乎不可能理解发生了什么。 考虑到这一点,.NET 程序集检查是一个自然的起点。
从 WPF 的角度来看,主题本质上是作为 BAML 序列化并嵌入在常规 .NET 程序集(例如 PresentationFramework.Aero.dll)中的 ResourceDictionary。稍后,有必要将主题视为纯 XAML 以验证问题中的行为。
幸运的是,为了方便开发人员,Microsoft 将4.0 主题作为 XAML 提供。我不确定是否可以从 Microsoft 以任何形式下载 4.0 之前的主题。
对于常规程序集(包括 4.0 之前的主题程序集),您可以使用(以前免费的)工具Reflector和BamlViewer 插件将 BAML 反编译回 XAML。虽然不那么华丽,但 ILSpy是一个内置 BAML 反编译器的免费替代品。

.NET 程序集散布在您的整个硬盘驱动器中,这有点令人困惑。这是他们在我的机器上的路径,我有一种直觉,有时不用反复试验就能记住。
航空 3.0
C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\PresentationFramework.Aero.dll
航空 4.0
C:\WINDOWS\Microsoft.NET\assembly\GAC_MSIL\PresentationFramework.Aero\v4.0_4.0.0.0__31bf3856ad364e35\PresentationFramework.Aero.dll
第 4 版的 PublicKeyToken 是什么(或者我该如何解决)?
最简单的方法是使用反射器。PublicKeyToken和之前一样:31bf3856ad364e35

此外,sn.exe(来自 Windows SDK)可以提取程序集信息。
在我的机器上,命令是:
C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin>sn.exe -Tp "C:\WINDOWS\Microsoft.NET\assembly\GAC_MSIL\PresentationFramework.Aero\v4.0_4.0.0.0__31bf3856ad364e35\PresentationFramework. Aero.dll"

将主题加载为皮肤
(PresentationFramework.Aero 参考)是否应该更新到 4.0 版?
明确地。DataGrid 在 4.0 之前的 .NET FCL 中不存在。有几种方法可以确认这一点,但最直观的一种是,您自己承认,您之前通过 WPF 工具包访问过它。如果您选择不在 App.xaml 中加载 PresentationFramework.Aero 4.0,Aero 的 DataGrid 样式将不会出现在应用程序资源中。
现在,事实证明这甚至都无关紧要。我将使用原始 XAML,在加载时中断调试器,并检查应用程序范围的资源。

正如预期的那样,应用程序的 MergedDictionaries 属性中有两个 ResourceDictionaries,第一个 ResourceDictionary 据称是 3.0 版本的 PresentationFramework.Aero。但是,我看到第一个 ResourceDictionary 中有266个资源。此时,恰好我知道 Aero 4.0 主题中有 266 个资源,而 Aero 3.0 主题中只有 243 个资源。此外,甚至还有一个 DataGrid 条目!这个 ResourceDictionary 实际上就是 Aero 4.0 ResourceDictionary。
也许其他人可以解释为什么 WPF 在显式指定 3.0 时加载 4.0 程序集。我可以告诉您的是,如果项目重新定位到 .NET 3.0(并且编译错误已修复),则将加载 Aero 的 3.0 版本。

正如您正确推断的那样,无论如何都应该加载 Aero 4.0。了解调试时发生的事情很有用。
</p>
问题 #1:没有使用 Aero 的 DataGrid 样式
此应用程序中的 DataGrid 将有零个或多个样式链接在一起,具体取决于您如何配置 Style.BasedOn 属性。
它还将有一个默认样式,在您的情况下,它嵌入在 Luna 主题中。
我只通过查看原始 XAML 就知道存在样式继承问题。具有约 20 个 Setter 的大 DataGrid 样式未设置其 BasedOn 属性。

您有一个长度为 2 的样式链,您的默认样式来自 Luna 主题。Aero 的 ResourceDictionary 中的 DataGrid 样式根本没有被使用。
这里有两个大问题。首先,如何首先调试这样的东西?第二,有什么影响?
</p>
调试样式链
我建议使用Snoop和/或WPF Inspector来调试这样的 WPF 问题。
WPF Inspector 0.9.9 版甚至有一个样式链查看器。(我必须警告您,此功能目前存在错误,并且对于调试这部分应用程序不是很有用。还要注意,它选择将默认样式描述为链的一部分。)
这些工具的强大之处在于它们能够在运行时查看和编辑深度嵌套元素的值。您只需将鼠标悬停在一个元素上,其信息就会立即出现在工具中。
或者,如果您只是想查看像 DataGrid 这样的顶级元素,请在 XAML 中命名元素(例如 x:Name="dg"),然后在加载时中断调试器并将元素的名称放入 Watch窗户。在那里,您可以通过 BasedOn 属性检查样式链。
下面,我在使用解决方案 XAML 时破坏了调试器。DataGrid 在 Style 链中有 3 个 Style,分别有 4、17 和 9 个 Setter。我可以再深入一点,推断出第一个样式是“DataGrid_FixedStyle”。正如预期的那样,第二个是来自同一文件的大型隐式DataGrid 样式。最后,第三种样式似乎来自 Aero 的 ResourceDictionary。请注意,默认样式未在此链中表示。

此时应该注意的是,每个主题的 DataGrid Style 之间实际上没有变化。您可以通过从各自的4.0 主题中获取 DataGrid 样式来验证这一点,将它们复制到单独的文本文件中,然后将它们与差异工具进行比较。
事实上,适度数量的风格在不同主题之间是完全相同的。了解这一点很好。要验证这一点,只需在两个不同主题中保存的整个 XAML 上运行一个差异。

请注意,DataGrid 中嵌套了许多不同的元素(例如 DataGridRow),每个元素都有自己的样式。尽管当前不同主题的 DataGrid 样式相同,但这些嵌套元素的样式可能会有所不同。根据观察到的问题行为,很明显有些人会这样做。</p>
原始 XAML 未包含 Aero 的 DataGrid 样式的含义
由于 DataGrid 样式在 4.0 主题中是相同的,因此在这种情况下,将 Aero 的 DataGrid 样式添加到样式链的末尾基本上是多余的。Aero 的 DataGrid 样式将与默认的 DataGrid 样式(在您的情况下来自 Luna)相同。当然,未来的主题总是会在 DataGrid 样式方面有所不同。
不管是否有任何影响,既然您打算合并 Aero 的样式,那么这样做显然更正确,除非有特定的理由不这样做(稍后将讨论)。
最重要的是,了解正在发生的事情很有用。
Style.BasedOn 仅在使用它的上下文中才有意义
在解决方案 XAML 中,DataGridResourceDictionary.xaml 完全按照您希望的方式工作。重要的是要理解为什么,重要的是要理解以这种方式使用它会排除以其他方式使用它。
假设 DataGridResourceDictionary.xaml 的样式链中的最终样式将其 BasedOn 属性设置为 Type 键(例如 BasedOn="{StaticResource {x:Type DataGrid}}")。如果他们这样做,那么他们将从与该键匹配的隐式样式继承。但是,它们继承的样式取决于加载 DataGridResourceDictionary.xaml 的位置。例如,如果在加载 Aero 的资源后立即将 DataGridResourceDictionary.xaml 加载到合并字典中,则其样式将从适当的 Aero 样式继承。现在,例如,如果 DataGridResourceDictionary.xaml 是整个应用程序中加载的唯一 ResourceDictionary,它的样式实际上将继承自当前主题(Luna,在您的情况下)中的相关样式。请注意,主题的样式当然也将是默认样式!

现在假设 DataGridResourceDictionary.xaml 的样式链中的最终样式没有设置它们的 BasedOn 属性。如果他们这样做,那么它们将成为各自样式链中的最终样式,并且唯一评估的其他样式将是默认样式(始终位于主题中)。请注意,这会破坏您将 Aero 作为皮肤加载并有选择地改进其部分的预期设计。
请注意,在前面的示例中,如果最终键是字符串(例如 x:Key="MyStringKey")而不是 Type,则会发生相同类型的事情,但不会有任何匹配的样式在主题或航空皮肤。加载时会抛出异常。也就是说,如果始终存在找到匹配样式的上下文,则悬空字符串键理论上可以工作。
在解决方案 XAML 中,DataGridResourceDictionary.xaml 已被修改。每个样式链末尾的样式现在都继承自一个附加的隐式样式。在 App.xaml 中加载时,这些将解析为 Aero 样式。
问题 #2:DataGrid.ColumnHeaderStyle 和 DataGrid.CellStyle
这是一个令人讨厌的问题,它是造成您看到的一些奇怪行为的原因。DataGrid.ColumnHeaderStyle 和 DataGrid.CellStyle 被隐式 DataGridColumnHeader 和 DataGridCell 样式击败。也就是说,它们与 Aero 皮肤不兼容。因此,它们只是从解决方案 XAML 中删除。
本小节的其余部分是对该问题的彻底调查。DataGridColumnHeader 和 DataGridCell 与所有 FrameworkElement 一样,具有 Style 属性。此外,DataGrid 上有几个非常相似的属性:ColumnHeaderStyle 和 CellStyle。您可以将这两个属性称为“辅助属性”。它们至少在概念上映射到 DataGridColumnHeader.Style 和 DataGridCell.Style。虽然它们实际上是如何被使用的,但没有记录,所以我们必须更深入地挖掘。
属性 DataGridColumnHeader.Style 和 DataGridCell.Style 使用值强制。这意味着当查询任一 Style 时,将使用特殊回调来确定实际返回给调用者的 Style(大部分是内部 WPF 代码)。这些回调可以返回他们想要的任何值。最终,DataGrid.ColumnHeaderStyle 和 DataGrid.CellStyle 是各自回调中的候选返回值。
使用 Reflector,我可以轻松确定所有这些。(如有必要,也可以单步执行 .NET 源代码。)从 DataGridColumnHeader 的静态构造函数开始,我找到 Style 属性并看到它被分配了额外的元数据。具体来说,指定了强制回调。从该回调开始,我单击一系列方法调用并快速查看发生了什么。(请注意,DataGridCell 做同样的事情,所以我不会介绍它。)

最后一个方法DataGridHelper.GetCoercedTransferPropertyValue,本质上是比较DataGridColumnHeader.Style和DataGrid.ColumnHeaderStyle的来源。具有更高优先级的来源获胜。此方法中的优先规则基于Dependency Property predecence。
此时,将在原始 XAML 和解决方案 XAML 中检查 DataGrid.ColumnHeaderStyle。将收集一小部分信息。最终,这将解释每个应用程序中观察到的行为。
在原始 XAML 中,我中断调试器并看到 DataGrid.ColumnHeaderStyle 有一个“样式”源。这是有道理的,因为它是在 Style 中设置的。

在解决方案 XAML 中,我中断调试器并看到 DataGrid.ColumnHeaderStyle 有一个“默认”源。这是有道理的,因为该值未在样式(或其他任何地方)中设置。

要检查的另一个值是 DataGridColumnHeader.Style。DataGridColumnHeader 是一个深度嵌套的元素,在 VisualStudio 中调试时无法方便地访问。实际上,像 Snoop 或 WPF Inspector 这样的工具将用于检查属性。
对于原始 XAML,DataGridColumnHeader.Style 有一个“ImplicitStyleReference”源。这是有道理的。DataGridColumnHeaders 在内部 WPF 代码中深入实例化。他们的 Style 属性为 null,因此他们将寻找隐式 Style。树从 DataGridColumnHeader 元素遍历到根元素。正如预期的那样,没有找到任何样式。然后检查应用程序资源。您在单独的 DataGridColumnHeader 样式上设置了一个字符串键 ("DataGrid_ColumnHeaderStyle")。这有效地将其隐藏在此查找中,因此未使用。然后,搜索 Aero 皮肤,找到一个典型的隐式 Style。这是使用的样式。

如果对解决方案 XAML 重复此步骤,则结果相同:“ImplicitStyleReference”。但是,这一次,隐式样式是 DataGridResourceDictionary.xaml 中唯一的 DataGridColumnHeader 样式,现在是隐式键控的。

最后,如果使用原始 XAML 再次重复此步骤,并且 Aero 皮肤未加载,则结果现在为“默认”!这是因为整个应用程序中根本没有隐式的 DataGridColumnHeader 样式。
因此,如果未加载 Aero 皮肤,将使用 DataGrid.ColumnHeaderStyle,但如果加载了 Aero 皮肤,则不会使用!正如宣传的那样,加载主题的资源可能会造成不利影响。
要保持直截了当,名字听起来都一样。下图概括了所有操作。请记住,具有较高优先级的属性获胜。

它可能不是您想要的,但这就是 DataGrid 从 WPF 4.0 开始的工作方式。考虑到这一点,理论上您可以在非常广泛的范围内设置 DataGrid.ColumnHeaderStyle 和 DataGrid.CellStyle,并且仍然能够使用隐式样式在更窄的范围内覆盖 DataGridColumnHeader 和 DataGridCell 样式。
再次,DataGrid.ColumnHeaderStyle 和 DataGrid.CellStyle 被隐式 DataGridColumnHeader 和 DataGridCell 样式胜过。也就是说,它们与 Aero 皮肤不兼容。因此,它们只是从解决方案 XAML 中删除。
问题 #3:DataGridRow.Background
如果到目前为止建议的更改已经实施,您的屏幕上应该会出现类似于以下内容的内容。(请记住,我将主题设置为 Classic 以调试此问题。)

DataGrid 具有 Aero 外观,但没有遵守 AlternatingRowBackground。每隔一行应该有一个灰色的背景。

使用到目前为止讨论的调试技术,会发现这与问题 #2 完全相同。现在正在加载 Aero 外观内的隐式 DataGridRow 样式。DataGridRow.Background 使用属性强制。DataGrid.AlternatingRowBackground 是可能在强制回调中返回的候选值。DataGridRow.Background 是另一个候选者。 这些值的来源将影响强制回调选择的值。
现在应该很清楚,但如果没有,则必须重申。 加载主题的资源可能会造成不利影响。
这个子问题的简短回答是 DataGridRow.Background 只能在主题中设置。具体来说,它不能由应用程序中的任何地方的样式设置器设置。不幸的是,这正是 Aero 皮肤中正在发生的事情。至少有两种方法可以解决这个问题。
在 Aero 皮肤之后可以添加一个空白的隐式样式。这隐藏了 Aero 中令人反感的 Style。空白样式中没有值,因此最终会使用默认样式中的值。最后,这只是因为 DataGridRow 样式在每个 4.0 主题中都是相同的。
或者,可以复制 Aero 的 DataGridRow 样式,可以删除背景设置器,并且可以在 Aero 皮肤之后添加样式的其余部分。XAML 解决方案采用了这种技术。通过扩展 Style,应用程序更有可能在未来的场景中继续使用 Aero。通过在 App.xaml 中隔离此扩展,可以在其他上下文中更自由地使用 DataGridResourceDictionary.xaml。但是,请注意,将它添加到 DataGridResourceDictionary.xaml 可能更有意义,具体取决于将来如何使用该文件。就这个应用程序而言,任何一种方式都有效。
问题 #4:DataGridColumnHeader 布局
最后的变化是相当肤浅的。如果应用程序在到目前为止进行建议的更改后运行,则 DataGridColumnHeaders 将具有左对齐而不是居中的内容。可以使用 Snoop 或 WPF Inspector 轻松钻取此问题。问题的根源似乎是 DataGridColumnHeaders 将 Horizo ntalContentAlignment设置为“Left”。

将其设置为“拉伸”,它按预期工作。
Layout 属性和TextBlock格式属性之间存在一些相互作用。Snoop 和 WPF Inspector 允许进行实验,并可以轻松确定在任何给定情况下的工作原理。
最后的想法
总而言之,加载主题的资源与在操作系统级别更改主题不同。加载主题的资源可能会造成不利影响。从 WPF 的角度来看,应用程序中现在存在大量隐式样式。这些风格可能胜过其他风格。底线是将主题视为应用程序皮肤可能无法在没有改进的情况下工作。
也就是说,对于通过具有优先规则的强制回调使用的“辅助属性”(例如 DataGrid.ColumnHeaderStyle),我并没有完全接受当前的 WPF 实现。如果目标还没有明确分配的值,我不得不想知道为什么不能在初始化时将它们本地分配给他们的预期目标(例如 DataGridColumnHeader.Style)。我对此考虑得还不够多,无法知道各种问题可能是什么,但如果可能的话,它可能会使“辅助属性”模型更直观,与其他属性更一致,并且更加万无一失。
最后,虽然这不是这个答案的重点,但非常重要的是要注意加载主题资源以模拟更改主题特别糟糕,因为存在大量可维护性成本。应用程序中的现有样式不会自动基于主题 ResourceDictionary 中的样式。应用程序中的每个 Style 都必须将其 BasedOn 属性设置为 Type 键(或者直接或间接地基于另一个 Style)。这是非常繁重且容易出错的。此外,主题感知自定义控件存在可维护性成本。主题资源因为这些自定义控件也必须加载才能实现此模拟。当然,在这样做之后,您可能会遇到与您在此处遇到的类似的样式优先级问题!
无论如何,给 WPF 应用程序换肤的方法不止一种(不是双关语!)。我希望这个答案能让您对您的问题有更多的了解,并帮助您和其他人解决类似的问题。