Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
260 views
in Technique[技术] by (71.8m points)

.net - Why can't I style a control with the Aero theme applied in WPF 4.0?

I recently converted a project from WPF 3.5 to WPF 4.0. Functionally, everything works, but the DataGrid style I was applying on top of the Aero theme has suddenly stopped working. As you can see from the before/after pictures below, my DataGrids went from having an Aero look plus bold headings, extra padding, and alternating row formats to just looking plain "Aero". Besides removing all references to the WPF Toolkit (since the DataGrid is now native to WPF 4.0), I really didn't change anything about my code/markup.

Before (WPF Toolkit DataGrid)

Looks like Aero w/ bold headings, extra padding, and alternate row styles

After (.NET 4.0 DataGrid)

Looks like Aero w/ nothing

As I learned in an earlier question, I am able to get the custom DataGrid styling to work again if I stop referencing the Aero resource dictionary, but then everything looks "Luna" on Windows XP (which is not what I want).

So, how do I ensure that my app always uses the Aero theme, but still apply styling on top of that theme in WPF 4.0?

Here is my App.xaml code:

<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>

Here is my DataGridResourceDictionary.xaml code:

<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>

Here's a usage sample:

<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>

Edit

It just occurred to me that maybe the problem is that I'm referencing the wrong version of the Aero framework.

Here's what I have now:

<ResourceDictionary
    Source="/PresentationFramework.Aero,
        Version=3.0.0.0,
        Culture=neutral,
        PublicKeyToken=31bf3856ad364e35,
        ProcessorArchitecture=MSIL;component/themes/aero.normalcolor.xaml" />

Should this be updated to version 4.0? What is the PublicKeyToken for version 4 (or how do I figure this out)?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Long Answer

The previous short answer provides some XAML to remedy the problem as well as a brief summary of what's causing it.

Loading a theme’s resources is not the same as changing the theme at the OS level. Loading a theme’s resources may cause adverse effects. From WPF’s point of view, a large number of implicit Styles are now present in the application. These Styles may trump other Styles. The bottom line is treating a theme like an application skin may not work without refinements.

The following long answer will provide a more in-depth discussion of the problem. A few background topics will be covered first. This will answer some of the peripheral questions asked and it will also provide a better basis for understanding the issues at hand. After that, individual aspects of the problem will be dissected and addressed with an effective debugging strategy.

Theme Versus Skin

This was great question partly because hundreds of bloggers and forum threads recommend loading a theme from file as a way of “changing your theme”. Some of the authors making this recommendation work at Microsoft and many of the authors are obviously high caliber software engineers. This approach appears to work much of the time. However, as you noticed, this approach didn’t exactly work in your scenario and required a number of refinements.

Some of this problem stems from imprecise terminology. Unfortunately, the word theme has become hopelessly overloaded. A precise definition for theme that would avoid confusion is simply the system theme. A system theme defines the default appearance of Win32 visuals on the machine. My OS is Vista. My installed themes are located at C:WINDOWSResourcesThemes. In that folder are two files: aero.theme and Windows Classic.theme. If I want to change the theme I go to either [Personalize | Theme] or [Personalize | Window Color and Appearance | Color scheme]. Although it's not immediately obvious, the options I can select from all boil down to either Aero or Classic plus some additional refinements. Because a WPF window renders its client area rather than compositing a bunch of Win32 controls, the client area will not automatically respect the theme. The theme assemblies (e.g. PresentationFramework.Aero.dll) provide a basis for extending theme functionality into WPF windows.

enter image description here enter image description here

The more general definition of theme is any look and feel configuration, at any level of granularity (OS, Application, Control). When people use the general definition, there is the potential for various degrees of confusion. Note that MSDN switches between the precise definition and the general definition without warning!

Many people would say you were loading an application skin, not a theme. Either word is arguably correct, but I would recommend this mental model simply because it causes less confusion.

So, how do I ensure that my app always uses the Aero theme…? [emphasis added]

Again, it could be said you are loading Aero’s resources as a skin. Specifically, you’re loading the ResourceDictionary located inside PresentationFramework.Aero.dll. Those resources were previously given special treatment as they were default resources. However, once inside the application, they will be treated like any other arbitrary collection of resources. Of course, Aero’s ResourceDictionary is comprehensive. Since it will be loaded at the application scope, it will effectively hide every default Style provided by the theme (Luna, in your case), plus a few other Styles, which is causing the problem. Note that ultimately, the theme is still the same (Luna).

As alluded to above, the theme is involved in Style precedence, which is itself a form of Dependency Property precedence. These precedence rules greatly demystify the observed behavior in the problem.

Explicit style. The Style property is set directly. In most scenarios, the style is not defined inline, but instead is referenced as a resource, by explicit key…

Implicit style. The Style property is not set directly. However, the Style exists at some level in the resource lookup sequence (page, application) and is keyed using a resource key that matches the type the style is to be applied to…

Default style, also known as theme style. The Style property is not set directly, and in fact will read as null... In this case, the style comes from the run-time theme evaluation that is part of the WPF presentation engine.

This blog entry takes a much deeper look at style versus default style.

.NET Assembly Inspection

This was also a great question partly because there are so many moving parts. Without an effective debugging strategy it will be almost impossible to understand what’s going on. With that in mind, .NET assembly inspection is a natural place to start.

From WPF’s point of view, a theme is essentially a ResourceDictionary serialized as BAML and embedded in a regular .NET assembly (e.g. PresentationFramework.Aero.dll). Later on, it will be necessary to view the themes as plain XAML to verify behavior in the problem.

Fortunately, Microsoft provides the 4.0 themes as XAML for developers’ convenience. I’m not sure if pre-4.0 themes are downloadable in any form from Microsoft.

For general assemblies (including pre-4.0 theme assemblies), you can use the (previously free) tool Reflector with the BamlViewer plugin to decompile the BAML back into XAML. Although not as flashy, ILSpy is a free alternative with a built in BAML decompiler.

enter image description here

.NET assemblies are littered throughout your hard drive and it’s kind of confusing. Here are their paths on my machine, which I sort of have a gut feeling for and sometimes manage to remember without trial and error.

Aero 3.0

C:Program FilesReference AssembliesMicrosoftFrameworkv3.0PresentationFramework.Aero.dll

Aero 4.0

C:WINDOWSMicrosoft.NETassemblyGAC_MSILPresentationFramework.Aerov4.0_4.0.0.0__31bf3856ad364e35PresentationFramework.Aero.dll

What is the PublicKeyToken for version 4 (or how do I figure this out)?

The easiest thing to do is use Reflector. The PublicKeyToken is the same as before: 31bf3856ad364e35

enter image description here

Additionally, sn.exe (from the Windows SDK) can extract assembly information.

On my machine, the command is:

C:Program FilesMicrosoft SDKsWindowsv7.1Bin>sn.exe -Tp "C:WINDOWSMicrosoft.NETassemblyGAC_MSILPresentationFramework.Aerov4.0_4.0.0.0__31bf3856ad364e35PresentationFramework.Aero.dll"

enter image description here

Loading Themes as Skins

Should (the PresentationFramework.Aero reference) be updated to version 4.0?

Most definitely. The DataGrid did not exist in the .NET FCL prior to 4.0. There are several ways to confirm this, but the most intuitive one is that, by your own admission, you previously accessed it via the WPF Toolkit. If you choose to not load PresentationFramework.Aero 4.0 in App.xaml, Aero’s DataGrid Style won’t be in the application resources.

Now, it turns out it doesn’t even matter. I’ll use the original XAML, break in the debugger when loading, and inspect the application-scoped resources.

enter image description here

As expected, there are two ResourceDictionaries in the application’s MergedDictionaries property, and the first ResourceDictionary is allegedly the 3.0 version of PresentationFramework.Aero. However, I see there are 266 resources in the first ResourceDictionary. At this point, it just so happens I know there are 266 resources in the Aero 4.0 theme, and only 243 resources in the Aero 3.0 theme. Moreover, there’s even a DataGrid entry! This ResourceDictionary is, in fact, the Aero 4.0 ResourceDictionary.

Perhaps someone else can explain why WPF is loading the 4.0 assembly when 3.0 was explicitly specified. What I can tell you is if the projects are retargeted to .NET 3.0 (and the compile errors are fixed), the 3.0 version of Aero will be loaded instead.

enter image description here enter image description here

As you correctly deduced, Aero 4.0 should be loaded anyway. It’s just useful to know what’s going on while debugging this.

?

Problem #1: Aero’s DataGrid Style is not Being Used

The DataGrid in this application will have zero or more Styles chained together depending on how you configure Style.BasedOn properties.

It will also have a default Style, which, in your case, is embedded in the Luna theme.

I knew just by looking at the original XAML there was a Style inheritance issue. The big DataGrid Style with ~20 Setters does not set its BasedOn property.

enter image description here

You have a Style chain of length two and your default Style is from the Luna theme. The DataGrid Style in Aero’s ResourceDictionary is simply not being used.

There are two big questions here. First, how can something like this be debugged in the first place? Second, what are the implications?

?

Debugging Style Chains

I would recommend using Snoop and/or WPF Inspector to debug WPF issues like this.

Version 0.9.9 of WPF Inspector even has a Style chain viewer. (I must warn you that this feature is currently buggy and not very useful for debugging this part of the application. Also note that it chooses to depict the default Style as part of the chain.)

The power of these tools is their ability to view and edit


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

1.4m articles

1.4m replys

5 comments

56.9k users

...