|
|
1
34
回答相对简短加载主题资源与在操作系统级别更改主题不同。加载主题资源可能会造成不利影响。从WPF的角度来看,应用程序中现在存在大量的隐式样式。这些款式可能胜过其他款式。底线是将一个主题像应用程序皮肤一样处理,如果没有改进,它可能无法工作。 有一些模拟主题变化的替代方法。
这个问题展示了一些相当复杂的WPF功能,其中的一部分似乎是未记录的。然而,它似乎不是一个bug。如果它不是一个bug,也就是说,如果它都是有意的WPF行为,那么您可能会认为WPF数据报在某些方面设计得很差。 混战者的回答非常正确。但是,这个问题是可以解决的,并且可以在不影响设计或需要重复样式设置的情况下解决。也许更重要的是,问题是 可调试 。 以下XAML有效。我把旧的xaml注释掉了,只是为了使更改更可见。要更深入地了解该问题,请参阅 long answer 。 DataGridResourceDictionary.xaml:
app.xaml:应用程序示例:
主窗口.xaml:
|
|
|
2
68
冗长的回答上一个 short answer 提供一些XAML来解决此问题,并简要总结导致此问题的原因。
下面的长答案将对这个问题进行更深入的讨论。将首先介绍一些背景主题。这将回答一些外围问题,也将为理解手头的问题提供更好的基础。之后,将使用有效的调试策略对问题的各个方面进行分析和解决。 主题与皮肤这是一个很好的问题,部分原因是数以百计的博客和论坛线程建议从文件中加载一个主题,作为一种改变主题的方法。一些提出这一建议的作者在微软工作,许多作者显然是高素质的软件工程师。这种方法 出现 经常工作。但是,正如您注意到的,这种方法在您的场景中并不完全有效,需要进行一些改进。 其中一些问题源于不精确的术语。不幸的是,“主题”这个词已经严重超载。一个可以避免混淆的主题的精确定义是 系统主题 . 一 system theme 定义计算机上Win32 Visuals的默认外观。我的操作系统是Vista。我安装的主题位于c:\windows\resources\themes。该文件夹中有两个文件:aero.theme和windows classic.theme。如果要更改主题,请转到[个性化主题]或[个性化窗口颜色和外观颜色方案]。虽然不是很明显,但我可以从所有选项中选择,归结为Aero或Classic加上一些额外的改进。由于WPF窗口呈现其客户机区域而不是组合一组Win32控件,因此客户机区域不会自动尊重主题。主题程序集(例如presentationframework.aero.dll)为将主题功能扩展到WPF窗口提供了基础。
主题的更一般的定义是任何外观和感觉配置,在任何粒度级别(操作系统、应用程序、控制)。当人们使用一般的定义时,有可能产生不同程度的混淆。注意,在没有警告的情况下,msdn在精确定义和一般定义之间切换! 很多人会说你正在加载一个应用程序 皮肤 不是主题。这两个词都可以说是正确的,但我建议使用这种心理模式,因为它可以减少混淆。
再说一次,可以说你是作为一个 皮肤 . 具体来说,您要重新加载presentationframework.aero.dll中的ResourceDictionary。这些资源以前被给予特殊处理,因为它们是默认资源。但是,一旦进入应用程序,它们将被视为任何其他任意资源集合。当然,Aero的资源字典是全面的。因为它将在应用程序范围内加载,所以它将有效地隐藏由主题提供的每个默认样式(在您的例子中是luna),以及一些其他样式,这导致了问题的产生。请注意,最终,主题仍然是相同的(Luna)。 如上所述,主题涉及 Style precedence 它本身就是 Dependency Property precedence . 这些优先规则极大地解除了问题中观察到的行为的神秘性。
这个 blog entry 对样式和默认样式进行更深入的研究。 .NET程序集检查这也是一个很好的问题,部分原因是运动部件太多了。 如果没有有效的调试策略,几乎不可能理解发生了什么。 有鉴于此,.NET组装检查是一个自然的开始。 从wpf_的角度来看,主题本质上是一个序列化为BAML并嵌入到常规.NET程序集中的资源字典(例如presentationframework.aero.dll)。稍后,有必要将主题视为纯XAML来验证问题中的行为。 幸运的是,微软提供了 4.0 themes 作为开发人员方便的XAML。我不确定是否可以从Microsoft以任何形式下载4.0之前的主题。 对于常规程序集(包括4.0之前的主题程序集),可以使用(以前是免费的)工具 Reflector 与 BamlViewer plugin 将BAML反编译回XAML。虽然没有那么华丽, ILSpy 是内置BAML反编译程序的免费替代方案。
.NET程序集在您的硬盘驱动器中随处可见,并且 itâs kind of confusing . 这是他们在我的机器上的路径,我有一种直觉,有时不经尝试和错误就能记住。 航空3.0 C:\程序文件\引用程序集\microsoft\framework\v3.0\presentationframework.aero.dll 航空4.0 C:\windows\microsoft.net\assembly\gac_u msil\presentationframework.aero\v4.0_4.0.0.0_uu31bf3856ad364e35\presentationframework.aero.dll
最简单的方法是使用反射镜。PublicKeyToken与以前相同:31BF3856AD364E35
此外, sn.exe (从Windows SDK)可以提取程序集信息。 在我的机器上,命令是: C:\Program Files\Microsoft SDK\Windows\V7.1\bin>sn.exe-tp“C:\Windows\Microsoft.NET\assembly\GAC\MSIL\presentationframework.aero\v4.0.0.0\uuu 31bf3856ad364e35\presentationframework.aero.dll”
将主题作为外观加载
非常肯定。4.0之前的.NET FCL中不存在DataGrid。有几种方法可以证实这一点,但最直观的方法是,通过您自己的承认,您以前通过WPF工具包访问过它。如果选择不在app.xaml中加载presentationframework.aero 4.0,则应用程序资源中将不包含Aero DataGrid样式。 现在,事实证明这根本不重要。我将使用原始XAML,加载时中断调试器,并检查应用程序范围的资源。
正如所料,应用程序的MergedDictionaries属性中有两个ResourceDictionary,第一个ResourceDictionary据称是PresentationFramework.Aero的3.0版本。不过,我知道 二百六十六 第一个资源字典中的资源。在这一点上,我知道Aero4.0主题中有266个资源,而Aero3.0主题中只有243个资源。此外,还有一个数据报条目!实际上,这个ResourceDictionary是Aero 4.0 ResourceDictionary。 也许其他人可以解释当显式指定了3.0时,WPF为什么要加载4.0程序集。我可以告诉你的是,如果项目被重定为.NET 3.0(编译错误是固定的),那么将改为加载3.0版本的Aero。
正如您正确推断的,Aero 4.0无论如何都应该加载。在调试时知道发生了什么是很有用的。 小精灵 问题1:未使用Aeros数据报样式此应用程序中的DataGrid将有零个或多个样式链接在一起,具体取决于如何配置Style.Basedon属性。 它还将有一个默认样式,在您的情况下,它嵌入到Luna主题中。 我只通过查看原始的XAML就知道存在样式继承问题。具有~20个setter的大数据报样式没有设置其basedon属性。
您有一个长度为2的样式链,默认样式来自luna主题。Aero的ResourceDictionary中的DataGrid样式只是没有被使用。 这里有两个大问题。首先,如何在第一时间调试类似的东西?第二,这意味着什么? 小精灵 调试样式链我建议使用 Snoop 和/或 WPF Inspector 调试这样的WPF问题。 WPF Inspector的0.9.9版本甚至有一个样式链查看器。(我必须警告您,此功能当前有问题,对于调试应用程序的这一部分不太有用。还要注意,它选择将默认样式描述为链的一部分。) 这些工具的功能是查看和编辑 深嵌套 元素位于 运行时间 . 您可以简单地将鼠标悬停在一个元素上,它的信息将立即显示在工具中。 或者,如果您只想查看像DataGrid这样的顶级元素,请在XAML中命名该元素(例如x:name=“dg”),然后在加载时中断调试器,并将元素名称放在监视窗口中。在那里,您可以通过basedon属性检查样式链。 下面,我在使用解决方案XAML时在调试器中进行了中断。数据报在样式链中有三种样式,分别有4个、17个和9个setter。我可以钻得更深一点,推断出第一种样式是DataGrid _FixedStyle。正如所料,第二个是大的, implicit 来自同一文件的数据报样式。最后,第三种风格似乎来自于Aero的ResourceDictionary。请注意,默认样式不在此链中表示。
此时应该注意的是,每个主题的DataGrid样式实际上没有变化。您可以通过从各自的 4.0主题 ,将它们复制到单独的文本文件中,然后与diff工具进行比较。 事实上,中等数量的款式 完全相同的 从主题到主题。这是值得注意的。要验证这一点,只需对包含在两个不同主题中的整个XAML运行diff。
请注意,有许多不同的元素嵌套在DataGrid中(例如,DataGridRow),每个元素都有自己的样式。尽管DataGrid样式当前在主题和主题之间是相同的,但是这些嵌套元素的样式可能会有所不同。根据在问题中观察到的行为,很明显有些是这样的。 小精灵 原始XAML不包含Aero_s DataGrid样式的含义由于在4.0主题中,数据报样式是相同的,在这种情况下,在样式链的末尾添加AerosDataGrid样式是, 基本上 多余的。Aero的DataGrid样式将与默认的DataGrid样式相同(在您的情况下,来自Luna)。当然,未来的主题在数据报样式方面总是有变化的。 不管是否有任何影响,因为您打算采用Aero的样式,这样做显然更正确,直到有特定的原因不这样做(稍后将讨论)。 最重要的是,知道发生了什么很有用。 style.basedon仅在其使用的上下文中有意义在解决方案xaml中,dataGridResourceDictionary.xaml的工作方式与您希望的工作方式完全相同。理解原因是很重要的,理解以这种方式使用它会妨碍以其他方式使用它也是很重要的。 让s说DataGridResourceDictionary中的最终样式。xaml s样式链将其basedon属性设置为类型键(例如basedon=“staticresource x:type datagrid”)。如果这样做,那么它们将继承与此键匹配的隐式样式。但是,它们继承自的样式取决于加载DataGridResourceDictionary.xaml的位置。例如,如果在加载Aero_窋的资源后,将dataGridResourceDictionary.xaml加载到合并字典中,则其样式将从相应的Aero样式继承。现在,例如,如果DataGridResourceDictionary.xaml是整个应用程序中加载的唯一一个ResourceDictionary,那么它的样式实际上将从当前主题中的相关样式继承(在您的例子中是Luna)。请注意,主题样式当然也是默认样式!
现在让我们说一下DataGridResourceDictionary.xamls样式链中的最终样式 不 设置它们的basedon属性。如果这样做,那么它们将成为各自样式链中的最终样式,并且只评估其他样式为默认样式(始终位于主题中)。请注意,这将扼杀您将Aero作为皮肤加载的预期设计,并有选择地对其部分进行细化。 请注意,在前面的示例中,如果最后一个键是字符串(例如x:key=“myStringkey”)而不是类型,则会发生相同的情况,但主题或Aero外观中不会有任何匹配的样式。加载时将引发异常。也就是说,如果始终存在一个匹配样式的上下文,悬空字符串键理论上可以工作。 在解决方案xaml中,DataGridResourceDictionary.xaml已被修改。每个样式链末尾的样式现在继承自一个附加的隐式样式。当加载到app.xaml中时,这些将解析为Aero样式。 问题2:DataGrid.ColumnHeaderStyle和DataGrid.CellStyle这是一个棘手的问题,它导致了你所看到的一些奇怪的行为。DataGrid.ColumnHeaderStyle和DataGrid.CellStyle通过隐式DataGridColumnHeader和DataGridCell样式获得优势。也就是说,它们与航空皮肤不相容。因此,它们只是从解决方案XAML中移除。 本小节的其余部分是对该问题的彻底调查。 与所有框架元素一样,DataGridColumnHeader和DataGridCell具有Style属性。此外,在DataGrid上还有两个非常相似的属性:ColumnHeaderStyle和CellStyle。您可以调用这两个属性助手属性。它们至少在概念上映射到DataGridColumnHeader.Style和DataGridCell.Style。然而,它们是如何被利用的还没有正式记录,所以我们必须深入挖掘。 属性DataGridColumnHeader.Style和DataGridCell.Style使用 value coercion . 这意味着,当查询任何一种样式时,都会使用特殊的回调来确定实际返回给调用者的样式(大部分是内部WPF代码)。这些回调可以返回 任何 他们想要的价值。最后,datagrid.columnHeaderStyle和datagrid.cellstyle 候选人 在各自的回调中返回值。 使用反射镜,我可以很容易地确定所有这些。(如有必要,也可以 step through .NET source code 从DataGridColumnHeader的静态构造函数开始,我找到Style属性,并看到它被分配了额外的元数据。具体来说,指定了强制回调。从回调开始,我单击一系列方法调用,然后快速查看发生了什么。(请注意,DataGridCell执行相同的操作,因此我不会覆盖它。)
最后一个方法,DataGridHelper.getCoercedTransferPropertyValue,实质上比较了DataGridColumnHeader.Style和DataGrid.ColumnHeaderStyle的源。哪个震源具有更高的优先级就获胜。此方法中的优先规则基于 Dependency Property predecence . 此时,将在原始XAML和解决方案XAML中检查datagrid.columnHeaderStyle。将收集一个小的信息矩阵。最后,这将解释在每个应用程序中观察到的行为。 在最初的XAML中,我闯入调试器,看到datagrid.columnHeaderStyle有一个样式源。这是有意义的,因为它是在一个样式中设置的。
在解决方案xaml中,我打开调试器,看到datagrid.columnHeaderStyle有一个默认源。这是有意义的,因为该值不是在样式(或其他任何地方)中设置的。
要检查的另一个值是DataGridColumnHeader.Style。DataGridColumnHeader是一个嵌套很深的元素,在VisualStudio中调试时不方便访问。实际上,像snoop或wpf-inspect or这样的工具将用于检查财产。 对于原始XAML,DataGridColumnHeader.Style有一个隐式的StyleReference源。这是有道理的。DataGridColumnHeaders在内部WPF代码中被实例化。它们的Style属性为空,因此它们将查找隐式样式。树从dataGridColumnHeader元素遍历到根元素。如预期,未找到样式。然后检查应用程序资源。在单独的DataGridColumnHeader样式上设置了一个字符串键(“DataGrid_ColumnHeaderStyle”)。这有效地将其隐藏在这个查找中,因此它不被使用。然后,对航空蒙皮进行搜索,找到一种典型的隐式风格。这是使用的样式。
如果此步骤与解决方案XAML一起重复,则结果相同:“implicitStyleReference”。然而,这次隐式样式是DataGridResourceDictionary.xaml中唯一的DataGridColumnHeader样式,现在已隐式设置了键。
最后,如果使用原始XAML再次重复此步骤, 空气动力蒙皮没有加载 ,结果现在是__default_!这是因为整个应用程序中没有隐式DataGridColumnHeader样式。 因此,如果未加载Aero外观,将使用DataGrid.ColumnHeaderStyle,但如果加载Aero外观,则不会使用DataGrid.ColumnHeaderStyle!如广告所示, 加载主题的资源可能会造成不利影响 . 保持笔直是很重要的,而且名字听起来都一样。下图概述了所有操作。记住,具有更高优先级的属性将获胜。
它可能不是您想要的,但这是从WPF 4.0开始DataGrid的工作方式。考虑到这一点,理论上可以将datagrid.columnHeaderStyle和datagrid.cellstyle设置在非常广泛的范围内,并且仍然能够使用隐式样式在更窄的范围内覆盖datagridColumnHeader和datagridCell样式。 同样,DataGrid.ColumnHeaderStyle和DataGrid.CellStyle通过隐式DataGridColumnHeader和DataGridCell样式获得优势。也就是说,它们与航空皮肤不相容。因此,它们只是从解决方案XAML中移除。 问题3:DataGridRow.Background如果在这一点上已经实现了建议的更改,那么屏幕上应该会出现类似以下内容的内容。(请记住,为了调试这个问题,我将主题设置为Classic。)
数据报具有Aero外观,但AlternatingRowBackground不受尊重。每隔一行都应该有灰色背景。
使用目前讨论的调试技术,我们会发现 这与问题2完全相同。 . 正在加载Aero外观中的隐式DataGridRow样式。DataGridRow.Background使用属性强制。DataGrid.AlternatingRowBackground是 候选人 可能在强制回调中返回的值。DataGridRow.background是另一个 候选人 。 这些值的来源将影响强制回调选择的值 。 现在应该很清楚了,但如果不清楚,就必须重申。 加载主题的资源可能会造成不利影响。 此子问题的简短答案是DataGridRow。只能在主题中设置背景。具体来说,它不能由样式设置器在应用程序的任何地方设置。不幸的是,这正是空气动力学皮肤发生的事情。解决这个问题至少有两种方法。 可以在Aero外观之后添加空白隐式样式。这隐藏了Aero中的冒犯风格。在空白样式中没有值,所以默认样式的值最终被使用。最后,这只起作用,因为每个4.0主题中的DataGridRow样式都是相同的。 或者,可以复制Aero的DataGridRow样式,删除背景设置器,并在Aero外观之后添加样式的其余部分。解决方案XAML使用了这种技术。通过扩展样式,应用程序在未来的场景中更可能继续寻找Aero。通过在app.xaml中隔离此扩展,可以在其他上下文中更自由地使用DataGridResourceDictionary.xaml。但是,请注意,将其添加到DataGridResourceDictionary.xaml可能更有意义,这取决于将来如何使用该文件。对于这个应用程序,任何一种方法都有效。 问题4:DataGridColumnHeader布局最后的改变是相当肤浅的。如果在进行建议的更改之后运行应用程序,则DataGridColumnHeaders的内容将左对齐,而不是居中。使用Snoop或WPF检查员可以很容易地钻取这个问题。问题的根源似乎是DataGridColumnHeaders HorizontalContentAlignment 设置为“左”。
将其设置为__Stretch_,它将按预期工作。 两者之间有一些相互作用 Layout properties 和 TextBlock 格式化属性。snoop和wpf检查器允许进行实验,并使其易于确定在任何给定情况下工作的内容。 最后的想法总而言之,加载主题资源与在操作系统级别更改主题不同。加载主题资源可能会造成不利影响。从wpf_的角度来看,应用程序中现在存在大量隐式样式。这些款式可能胜过其他款式。底线是将一个主题像应用程序皮肤一样处理,如果没有改进,它可能无法工作。 也就是说,对于通过具有优先规则的强制回调使用的“助手属性”(例如,datagrid.columnheaderstyle),我在当前WPF实现上没有完全出售。我想知道,如果目标还没有显式分配值,为什么它们可以在初始化时只在本地分配给它们的目标(例如dataGridColumnHeader.Style)。我还没有充分考虑到这一点,不知道各种各样的问题可能是什么,但是如果可能的话,它可能会使“助手属性”模型更直观,更符合其他属性,更简单。 最后,尽管它不是这个答案的焦点,但需要注意的是,加载主题资源来模拟更改主题是非常糟糕的,因为 可观的可维护性成本 . 应用程序中的现有样式不会自动基于主题资源字典中的样式。应用程序中的每个样式都必须将其based on属性设置为类型键(或者直接或间接基于另一种样式)。这是非常繁重和容易出错的。此外,对于主题感知的自定义控件,还存在可维护性成本。这个 theme resources 对于这些自定义控件,还必须加载以实现此模拟。当然,这样做之后,您可能会面临与此处所面临的样式优先问题类似的样式优先问题! 不管怎样,有不止一种方式可以让WPF应用程序(没有双关语!).我希望这个答案能对你的问题有更多的了解,并帮助你和其他人解决类似的问题。 |
|
|
3
0
我认为问题不在于PresentationFramework.Aero本身,而是在于通过包含它来获得隐式的DataGrid样式。这也可以通过在app.xaml中添加这个来看到。
如果没有显式设置,这将导致所有DataGridColumnHeader的样式丢失。 这就行了
但是,这不会
我不确定有什么好办法解决这个问题。我唯一能想到的是在数据报本身上显式地设置所有样式,但这可能不方便,特别是在许多地方使用这种样式时。
|