為什麼我不能使用 WPF 4.0 中應用的 Aero 主題設置控制項的樣式?
我最近將一個項目從 WPF 3.5 轉換為 WPF 4.0。從功能上講,一切正常,但我在 Aero 主題之上應用的 DataGrid 樣式突然停止工作。從下面的前後圖片中可以看出,我的 DataGrids 從具有 Aero 外觀加上粗體標題、額外填充和交替行格式變成了簡單的“Aero”。除了刪除對 WPF Toolkit 的所有引用(因為 DataGrid 現在是 WPF 4.0 的本機版本),我真的沒有更改任何關於我的程式碼/標記的內容。
之前(WPF 工具包數據網格)
之後(.NET 4.0 DataGrid)
正如我在之前的問題中了解到的那樣,如果我停止引用 Aero 資源字典,我可以讓自定義 DataGrid 樣式再次工作,但是在 Windows XP 上一切看起來都是“Luna”(這不是我想要的)。
那麼,如何確保我的應用程序始終使用 Aero 主題,但仍然在 WPF 4.0 中在該主題之上應用樣式?
這是我的 App.xaml 程式碼:
<Application x:Class="TempProj.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Application.Resources> <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="/PresentationFramework.Aero, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, ProcessorArchitecture=MSIL;component/themes/aero.normalcolor.xaml" /> <ResourceDictionary Source="/CommonLibraryWpf;component/ResourceDictionaries/DataGridResourceDictionary.xaml" /> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> </Application.Resources> </Application>這是我的 DataGridResourceDictionary.xaml 程式碼:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Style x:Key="DataGrid_ColumnHeaderStyle" TargetType="DataGridColumnHeader"> <Setter Property="FontWeight" Value="Bold" /> <Setter Property="TextBlock.TextAlignment" Value="Center" /> <Setter Property="TextBlock.TextWrapping" Value="WrapWithOverflow" /> </Style> <Style x:Key="DataGrid_CellStyle" TargetType="DataGridCell"> <Setter Property="Padding" Value="5,5,5,5" /> <Setter Property="TextBlock.TextAlignment" Value="Center" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="DataGridCell"> <Border Padding="{TemplateBinding Padding}" Background="{TemplateBinding Background}"> <ContentPresenter /> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style> <Style TargetType="DataGrid"> <Setter Property="ColumnHeaderStyle" Value="{StaticResource DataGrid_ColumnHeaderStyle}" /> <Setter Property="CellStyle" Value="{StaticResource DataGrid_CellStyle}" /> <Setter Property="Background" Value="White" /> <Setter Property="AlternatingRowBackground" Value="#F0F0F0" /> <Setter Property="VerticalGridLinesBrush" Value="LightGray" /> <Setter Property="HeadersVisibility" Value="Column" /> <Setter Property="SelectionMode" Value="Single" /> <Setter Property="SelectionUnit" Value="FullRow" /> <Setter Property="GridLinesVisibility" Value="Vertical" /> <Setter Property="AutoGenerateColumns" Value="False" /> <Setter Property="CanUserAddRows" Value="False" /> <Setter Property="CanUserDeleteRows" Value="False" /> <Setter Property="CanUserReorderColumns" Value="True" /> <Setter Property="CanUserResizeColumns" Value="True" /> <Setter Property="CanUserResizeRows" Value="False" /> <Setter Property="CanUserSortColumns" Value="True" /> <Setter Property="IsReadOnly" Value="True" /> <Setter Property="BorderBrush" Value="#DDDDDD" /> <Setter Property="HorizontalGridLinesBrush" Value="#DDDDDD" /> <Setter Property="VerticalGridLinesBrush" Value="#DDDDDD" /> </Style> <Style x:Key="DataGrid_FixedStyle" TargetType="DataGrid" BasedOn="{StaticResource {x:Type DataGrid}}"> <Setter Property="CanUserReorderColumns" Value="False" /> <Setter Property="CanUserResizeColumns" Value="False" /> <Setter Property="CanUserResizeRows" Value="False" /> <Setter Property="CanUserSortColumns" Value="False" /> </Style> </ResourceDictionary>這是一個使用範例:
<DataGrid Grid.Row="0" Grid.Column="0" Style="{StaticResource DataGrid_FixedStyle}" ItemsSource="{Binding Coordinates}"> <DataGrid.Columns> <DataGridTextColumn Binding="{Binding X}" Header="X" /> <DataGridTextColumn Binding="{Binding Y}" Header="Y" /> <DataGridTextColumn Binding="{Binding Z}" Header="Z" /> </DataGrid.Columns> </DataGrid>編輯
我突然想到,問題可能是我引用了錯誤版本的 Aero 框架。
這是我現在擁有的:
<ResourceDictionary Source="/PresentationFramework.Aero, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, ProcessorArchitecture=MSIL;component/themes/aero.normalcolor.xaml" />這應該更新到 4.0 版嗎?第 4 版的版本是什麼
PublicKeyToken(或者我該如何解決)?
相對簡短的答案
載入主題的資源與在作業系統級別更改主題不同。載入主題的資源可能會造成不利影響。從 WPF 的角度來看,應用程序中現在存在大量隱式樣式。這些風格可能勝過其他風格。底線是將主題視為應用程序皮膚可能無法在沒有改進的情況下工作。
有一些模擬主題變化的替代方法。
- This answer to a similar question列出了一些想法。
- 這篇知識庫文章的最後一點使用了小劑量的反射,並且必須在載入應用程序之前使用。
- 這個codeplex項目使用了大量的反射,可以隨時使用。
這個問題展示了一些相當複雜的 WPF 功能,其中一部分似乎沒有記錄。但是,它似乎不是一個錯誤。如果它不是一個錯誤——也就是說,如果所有這些都是有意的 WPF 行為——你可能會爭辯說 WPF DataGrid 在某些方面設計得不好。
Meleak 的回答非常正確。但是,問題是可以解決的,並且可以在不影響您的設計或需要重複樣式設置的情況下解決。也許更重要的是,問題是可調試的。
以下 XAML 有效。我將舊的 XAML 註釋掉只是為了使更改更加明顯。如需更深入地了解該問題,請參閱長答案。
DataGridResourceDictionary.xaml:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <!-- <Style x:Key="DataGrid_ColumnHeaderStyle" TargetType="DataGridColumnHeader"> --> <Style TargetType="DataGridColumnHeader" BasedOn="{StaticResource {x:Type DataGridColumnHeader}}"> <!--New--> <Setter Property="HorizontalContentAlignment" Value="Stretch"/> <!----> <Setter Property="FontWeight" Value="Bold" /> <Setter Property="TextBlock.TextAlignment" Value="Center" /> <Setter Property="TextBlock.TextWrapping" Value="WrapWithOverflow" /> </Style> <!-- <Style x:Key="DataGrid_CellStyle" TargetType="DataGridCell"> --> <Style TargetType="DataGridCell" BasedOn="{StaticResource {x:Type DataGridCell}}"> <Setter Property="Padding" Value="5,5,5,5" /> <Setter Property="TextBlock.TextAlignment" Value="Center" /> <Setter Property="Template"> <Setter.Value> <!-- <ControlTemplate TargetType="DataGridCell"> <Border Padding="{TemplateBinding Padding}" Background="{TemplateBinding Background}"> <ContentPresenter /> </Border> </ControlTemplate> --> <ControlTemplate TargetType="{x:Type DataGridCell}"> <Border Padding="{TemplateBinding Padding}" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" SnapsToDevicePixels="True"> <ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/> </Border> </ControlTemplate> </Setter.Value> </Setter> <!--Additional Feature--> <!-- Remove keyboard focus cues on cells and tabbing on cells when only rows are selectable and the DataGrid is readonly. Note that having some kind of keyboard focus cue is typically desirable. For example, the lack of any keyboard focus cues could be confusing if an application has multiple controls and each control is showing something selected, yet there is no keyboard focus cue. It's not necessarily obvious what would happen if Control+C or Tab is pressed. So, when only rows are selectable and the DataGrid is readonly, is would be ideal to make cells not focusable at all, make the entire row focusable, and make sure the row has a focus cue. It would take much more investigation to implement this. --> <Style.Triggers> <MultiDataTrigger> <MultiDataTrigger.Conditions> <Condition Binding="{Binding RelativeSource={RelativeSource AncestorType=DataGrid}, Path=SelectionUnit}" Value="FullRow"/> <Condition Binding="{Binding RelativeSource={RelativeSource AncestorType=DataGrid}, Path=IsReadOnly}" Value="True"/> </MultiDataTrigger.Conditions> <Setter Property="BorderBrush" Value="{Binding RelativeSource={RelativeSource Mode=Self}, Path=Background}" /> <Setter Property="FocusVisualStyle" Value="{x:Null}" /> <Setter Property="IsTabStop" Value="False" /> </MultiDataTrigger> </Style.Triggers> <!----> </Style> <!-- <Style TargetType="DataGrid"> --> <Style TargetType="DataGrid" BasedOn="{StaticResource {x:Type DataGrid}}"> <!--Unworkable Design--> <!-- <Setter Property="ColumnHeaderStyle" Value="{StaticResource DataGrid_ColumnHeaderStyle}" /> <Setter Property="CellStyle" Value="{StaticResource DataGrid_CellStyle}" /> --> <Setter Property="Background" Value="White" /> <Setter Property="AlternatingRowBackground" Value="#F0F0F0" /> <!--This was a duplicate of the final PropertySetter.--> <!-- <Setter Property="VerticalGridLinesBrush" Value="LightGray" /> --> <Setter Property="HeadersVisibility" Value="Column" /> <Setter Property="SelectionMode" Value="Single" /> <Setter Property="SelectionUnit" Value="FullRow" /> <Setter Property="GridLinesVisibility" Value="Vertical" /> <Setter Property="AutoGenerateColumns" Value="False" /> <Setter Property="CanUserAddRows" Value="False" /> <Setter Property="CanUserDeleteRows" Value="False" /> <Setter Property="CanUserReorderColumns" Value="True" /> <Setter Property="CanUserResizeColumns" Value="True" /> <Setter Property="CanUserResizeRows" Value="False" /> <Setter Property="CanUserSortColumns" Value="True" /> <Setter Property="IsReadOnly" Value="True" /> <Setter Property="BorderBrush" Value="#DDDDDD" /> <Setter Property="HorizontalGridLinesBrush" Value="#DDDDDD" /> <Setter Property="VerticalGridLinesBrush" Value="#DDDDDD" /> </Style> <Style x:Key="DataGrid_FixedStyle" TargetType="DataGrid" BasedOn="{StaticResource {x:Type DataGrid}}"> <Setter Property="CanUserReorderColumns" Value="False" /> <Setter Property="CanUserResizeColumns" Value="False" /> <Setter Property="CanUserResizeRows" Value="False" /> <Setter Property="CanUserSortColumns" Value="False" /> </Style> </ResourceDictionary>應用程序.xaml:
<Application x:Class="TempProj.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" StartupUri="MainWindow.xaml"> <Application.Resources> <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <!-- <ResourceDictionary Source="/PresentationFramework.Aero, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, ProcessorArchitecture=MSIL;component/themes/aero.normalcolor.xaml" /> --> <ResourceDictionary Source="/PresentationFramework.Aero, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, ProcessorArchitecture=MSIL;component/themes/aero.normalcolor.xaml" /> <!--New--> <!-- This is a modified replica of the DataGridRow Style in the Aero skin that's evaluated next. We are hiding that Style and replacing it with this. --> <ResourceDictionary> <Style x:Key="{x:Type DataGridRow}" TargetType="{x:Type DataGridRow}"> <!-- DataGridRow.Background must not be set in this application. DataGridRow.Background must only be set in the theme. If it is set in the application, DataGrid.AlternatingRowBackground will not function properly. See: https://stackoverflow.com/questions/4239714/why-cant-i-style-a-control-with-the-aero-theme-applied-in-wpf-4-0 The removal of this Setter is the only modification we have made. --> <!-- <Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}" /> --> <Setter Property="SnapsToDevicePixels" Value="true"/> <Setter Property="Validation.ErrorTemplate" Value="{x:Null}" /> <Setter Property="ValidationErrorTemplate"> <Setter.Value> <ControlTemplate> <TextBlock Margin="2,0,0,0" VerticalAlignment="Center" Foreground="Red" Text="!" /> </ControlTemplate> </Setter.Value> </Setter> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type DataGridRow}"> <Border x:Name="DGR_Border" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" SnapsToDevicePixels="True"> <SelectiveScrollingGrid> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="*"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <DataGridCellsPresenter Grid.Column="1" ItemsPanel="{TemplateBinding ItemsPanel}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/> <DataGridDetailsPresenter SelectiveScrollingGrid.SelectiveScrollingOrientation="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path=AreRowDetailsFrozen, Converter={x:Static DataGrid.RowDetailsScrollingConverter}, ConverterParameter={x:Static SelectiveScrollingOrientation.Vertical}}" Grid.Column="1" Grid.Row="1" Visibility="{TemplateBinding DetailsVisibility}" /> <DataGridRowHeader SelectiveScrollingGrid.SelectiveScrollingOrientation="Vertical" Grid.RowSpan="2" Visibility="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path=HeadersVisibility, Converter={x:Static DataGrid.HeadersVisibilityConverter}, ConverterParameter={x:Static DataGridHeadersVisibility.Row}}"/> </SelectiveScrollingGrid> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style> </ResourceDictionary> <!----> <ResourceDictionary Source="/CommonLibraryWpf;component/ResourceDictionaries/DataGridResourceDictionary.xaml" /> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> </Application.Resources> </Application>MainWindow.xaml:
<Window x:Class="TempProj.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Window.Resources> <Vector3DCollection x:Key="Coordinates"> <Vector3D X="1" Y="0" Z="0"/> <Vector3D X="0" Y="22" Z="0"/> <Vector3D X="0" Y="0" Z="333"/> <Vector3D X="0" Y="4444" Z="0"/> <Vector3D X="55555" Y="0" Z="0"/> </Vector3DCollection> </Window.Resources> <Grid> <DataGrid Grid.Row="0" Grid.Column="0" Style="{StaticResource DataGrid_FixedStyle}" ItemsSource="{StaticResource Coordinates}"> <DataGrid.Columns> <DataGridTextColumn Binding="{Binding X}" Header="X" /> <DataGridTextColumn Binding="{Binding Y}" Header="Y" /> <DataGridTextColumn Binding="{Binding Z}" Header="Z" /> </DataGrid.Columns> </DataGrid> </Grid> </Window>
長答案
前面的簡短答案提供了一些 XAML 來解決問題,並簡要總結了導致問題的原因。
載入主題的資源與在作業系統級別更改主題不同。載入主題的資源可能會造成不利影響。從 WPF 的角度來看,應用程序中現在存在大量隱式樣式。這些風格可能勝過其他風格。底線是將主題視為應用程序皮膚可能無法在沒有改進的情況下工作。
以下長答案將對該問題進行更深入的討論。首先將介紹一些背景主題。這將回答所提出的一些外圍問題,也將為理解手頭的問題提供更好的基礎。之後,將通過有效的調試策略剖析和解決問題的各個方面。
主題與皮膚
這是一個很好的問題,部分原因是數百名部落客和論壇文章建議從文件中載入主題作為“更改主題”的一種方式。提出此建議的一些作者在 Microsoft 工作,其中許多作者顯然是高素質的軟體工程師。這種方法似乎在很多時候都有效。但是,正如您所注意到的,這種方法在您的場景中並不完全適用,需要進行一些改進。
其中一些問題源於不精確的術語。不幸的是,主題這個詞已經無可救藥地超載了。可以避免混淆的主題的精確定義就是系統主題。系統主題定義機器上 Win32 視覺效果的預設外觀。我的作業系統是 Vista。我安裝的主題位於 C:\WINDOWS\Resources\Themes。該文件夾中有兩個文件:aero.theme 和 Windows Classic.theme。如果我想改變主題,我會去
$$ Personalize | Theme $$要麼$$ Personalize | Window Color and Appearance | Color scheme $$. 雖然不是很明顯,但我可以選擇的選項歸結為 Aero 或 Classic 以及一些額外的改進。因為 WPF 視窗呈現其客戶區而不是合成一堆 Win32 控制項,所以客戶區不會自動尊重主題。主題程序集(例如 PresentationFramework.Aero.dll)為將主題功能擴展到 WPF 視窗提供了基礎。
![]()
主題的更一般定義是任何外觀和感覺配置,在任何粒度級別(作業系統、應用程序、控制)。當人們使用一般定義時,可能會出現不同程度的混淆。請注意,MSDN 在精確定義和一般定義之間切換而不會發出警告!
很多人會說您正在載入應用程序皮膚,而不是主題。任何一個詞都可以說是正確的,但我會推薦這種心理模型,因為它不會引起混淆。
那麼,如何確保我的應用始終使用 Aero主題……?
$$ emphasis added $$
同樣,可以說您正在將 Aero 的資源作為皮膚載入。具體來說,您正在載入位於 PresentationFramework.Aero.dll 中的 ResourceDictionary。這些資源以前被給予特殊處理,因為它們是預設資源。但是,一旦進入應用程序,它們將被視為任何其他任意資源集合。當然,Aero 的 ResourceDictionary 是全面的。由於它將在應用程序範圍內載入,因此它將有效地隱藏主題(在您的情況下為 Luna)提供的每個預設樣式,以及導致問題的其他一些樣式。請注意,最終,主題仍然是相同的(Luna)。
正如上面提到的,主題涉及樣式優先級,它本身就是 依賴屬性優先級的一種形式。這些優先規則極大地揭開了問題中觀察到的行為的神秘面紗。
顯式風格。Style 屬性是直接設置的。在大多數情況下,樣式不是內聯定義的,而是作為資源引用,通過顯式鍵……
隱式風格。Style 屬性不是直接設置的。但是,樣式存在於資源查找序列(頁面、應用程序)中的某個級別,並且使用與要應用樣式的類型匹配的資源鍵進行鍵控……
預設樣式,也稱為主題樣式。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。了解調試時發生的事情很有用。
問題 #1:沒有使用 Aero 的 DataGrid 樣式
此應用程序中的 DataGrid 將有零個或多個樣式連結在一起,具體取決於您如何配置 Style.BasedOn 屬性。
它還將有一個預設樣式,在您的情況下,它嵌入在 Luna 主題中。
我只通過查看原始 XAML 就知道存在樣式繼承問題。具有約 20 個 Setter 的大 DataGrid 樣式未設置其 BasedOn 屬性。
您有一個長度為 2 的樣式鏈,您的預設樣式來自 Luna 主題。Aero 的 ResourceDictionary 中的 DataGrid 樣式根本沒有被使用。
這裡有兩個大問題。首先,如何首先調試這樣的東西?第二,有什麼影響?
調試樣式鏈
我建議使用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 樣式相同,但這些嵌套元素的樣式可能會有所不同。根據觀察到的問題行為,很明顯有些人會這樣做。
原始 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 應用程序換膚的方法不止一種(不是雙關語!)。我希望這個答案能讓您對您的問題有更多的了解,並幫助您和其他人解決類似的問題。




















