Question Pourquoi ne puis-je pas styliser un contrôle avec le thème Aero appliqué dans WPF 4.0?


J'ai récemment converti un projet de WPF 3.5 à WPF 4.0. Fonctionnellement, tout fonctionne, mais le style DataGrid que je appliquais au thème Aero a soudainement cessé de fonctionner. Comme vous pouvez le voir sur les photos avant / après ci-dessous, mes données DataGrids sont passées d'un aspect Aero à des titres en gras, un remplissage supplémentaire et des formats de lignes alternés pour ressembler à "Aero". En plus de supprimer toutes les références à WPF Toolkit (puisque DataGrid est maintenant natif à WPF 4.0), je n'ai vraiment rien changé à propos de mon code / marquage.

Avant (WPF Toolkit DataGrid)

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

Après (.NET 4.0 DataGrid)

Looks like Aero w/ nothing

Comme j'ai appris en une question antérieure, Je suis capable de faire fonctionner à nouveau le style personnalisé DataGrid si j'arrête de référencer le dictionnaire de ressources Aero, mais alors tout semble "Luna" sur Windows XP (ce qui n'est pas ce que je veux).

Alors, comment puis-je m'assurer que mon application utilise toujours le thème Aero, tout en appliquant le style à ce thème? dans WPF 4.0?

Voici mon code 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>

Voici mon code 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>

Voici un exemple d'utilisation:

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

modifier

Je me suis simplement rendu compte que le problème est que je fais référence à la mauvaise version du framework Aero.

Voici ce que j'ai maintenant:

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

Est-ce que cela devrait être mis à jour à la version 4.0? Quel est le PublicKeyToken pour la version 4 (ou comment puis-je le découvrir)?


13
2017-11-21 19:08


origine


Réponses:


Réponse relativement courte

Le chargement des ressources d'un thème n'est pas la même chose que la modification du thème au niveau du système d'exploitation. Le chargement des ressources d'un thème peut entraîner des effets indésirables. Du point de vue de WPF, un grand nombre de styles implicites sont désormais présents dans l'application. Ces styles peuvent l'emporter sur les autres styles. En bout de ligne, traiter un thème comme un skin d'application peut ne pas fonctionner sans améliorations.

Il existe d'autres moyens de simuler un changement de thème.

Ce problème présente certaines fonctionnalités assez complexes de WPF et une partie de celles-ci semble ne pas être documentée. Cependant, cela ne semble pas être un bug. S'il ne s'agit pas d'un bogue, c'est-à-dire si tout cela est intentionnel, vous pourriez bien argumenter que le composant DataGrid de WPF est mal conçu dans quelques domaines.

La réponse de Meleak était vraiment sur la bonne voie. Cependant, le problème est résoluble et peut être résolu sans compromettre votre conception ou nécessitant un réglage répétitif du style. Et peut-être plus important encore, le problème est débogueur.

Le XAML suivant fonctionne. J'ai laissé le vieux XAML commenté juste pour rendre les changements plus visibles. Pour un examen plus approfondi du problème, veuillez consulter le longue réponse.

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>

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

34
2018-03-29 10:52



Longue réponse

La précédente réponse courte fournit quelques XAML pour remédier au problème ainsi qu'un bref résumé de ce qui le provoque.

Charger les ressources d'un thème n'est pas la même chose que changer le thème au   Niveau OS. Chargement des ressources d'un thème   peut causer des effets indésirables. De WPF   point de vue, un grand nombre de   Les styles implicites sont maintenant présents dans le   application. Ces styles peuvent l'emporter   autres styles. La ligne de fond est   traiter un thème comme une application   la peau peut ne pas fonctionner sans   raffinements.

La réponse longue qui suit fournira une discussion plus approfondie du problème. Quelques sujets de fond seront abordés en premier. Cela répondra à certaines des questions périphériques posées et fournira également une meilleure base pour comprendre les problèmes qui se posent. Après cela, les aspects individuels du problème seront disséqués et traités avec une stratégie de débogage efficace.

Thème Versus Peau

C'était une bonne question en partie parce que des centaines de blogueurs et de forums de discussion recommandent de charger un thème depuis un fichier afin de "changer votre thème". Certains des auteurs de cette recommandation travaillent chez Microsoft et de nombreux auteurs sont évidemment des ingénieurs logiciels de haut calibre. Cette approche apparaît travailler la plupart du temps. Cependant, comme vous l’avez remarqué, cette approche ne fonctionnait pas exactement dans votre scénario et nécessitait un certain nombre d’améliorations.

Une partie de ce problème provient d'une terminologie imprécise. Malheureusement, le thème des mots est devenu désespérément surchargé. Une définition précise du thème qui éviterait la confusion est simplement le thème du système. UNE thème du système définit l'apparence par défaut des visuels Win32 sur la machine. Mon système d'exploitation est Vista. Mes thèmes installés sont situés dans C: \ WINDOWS \ Resources \ Themes. Dans ce dossier se trouvent deux fichiers: aero.theme et Windows Classic.theme. Si je veux changer le thème, je vais soit à [Personnaliser | Thème] ou [Personnaliser | Couleur et apparence de la fenêtre | Schéma de couleur]. Bien que cela ne soit pas immédiatement évident, les options que je peux choisir se résument à Aero ou Classic et à quelques améliorations supplémentaires. Comme une fenêtre WPF restitue sa zone client plutôt que de composer un ensemble de contrôles Win32, la zone client ne respectera pas automatiquement le thème. Les assemblys de thème (par exemple, PresentationFramework.Aero.dll) fournissent une base pour étendre les fonctionnalités de thème dans les fenêtres WPF.

enter image description here enter image description here

La définition plus générale du thème est toute configuration d’apparence, quel que soit le niveau de granularité (système d’exploitation, application, contrôle). Lorsque les gens utilisent la définition générale, il existe un risque de divers degrés de confusion. Notez que MSDN bascule entre la définition précise et la définition générale sans avertissement!

Beaucoup de gens diraient que vous chargiez une application peau, pas un thème. Les deux mots sont sans doute corrects, mais je recommanderais ce modèle mental simplement parce qu'il cause moins de confusion.

Alors, comment puis-je m'assurer que mon application est toujours   utilise l'Aero thème...? [accentuation   ajoutée]

Encore une fois, on pourrait dire que vous chargez les ressources d’Aero peau. Plus précisément, vous chargez le ResourceDictionary situé dans PresentationFramework.Aero.dll. Ces ressources recevaient auparavant un traitement spécial, car il s'agissait de ressources par défaut. Cependant, une fois à l'intérieur de l'application, ils seront traités comme n'importe quelle autre collection arbitraire de ressources. Bien sûr, le ResourceDictionary d’Aero est complet. Comme il sera chargé au niveau de l'application, il masquera efficacement tous les styles par défaut fournis par le thème (Luna, dans votre cas), ainsi que quelques autres styles, ce qui pose problème. Notez que finalement, le thème est toujours le même (Luna).

Comme mentionné ci-dessus, le thème est impliqué dans Style précédent, qui est lui-même une forme de Priorité de propriété de dépendance. Ces règles de priorité démystifient grandement le comportement observé dans le problème.

Style explicite. La propriété Style est définie directement. Dans la plupart des scénarios,   le style n'est pas défini en ligne, mais   à la place est référencé comme une ressource,   par clé explicite ...

Style implicite. La propriété Style n'est pas définie directement. Cependant, le   Le style existe à un certain niveau dans le   séquence de recherche de ressources (page,   application) et est indexé à l'aide d'un   clé de ressource qui correspond au type le   le style doit être appliqué à ...

Style par défaut, aussi connu sous le nom style de thème. La propriété Style n'est pas définie directement et sera en fait   lu comme null ... Dans ce cas, le   le style vient du thème de l'exécution   évaluation qui fait partie du WPF   moteur de présentation.

Ce entrée de blog prend un regard beaucoup plus profond sur le style par rapport au style par défaut.

Inspection de l'assemblage .NET

C'était aussi une excellente question en partie parce qu'il y a tellement de pièces mobiles. Sans stratégie efficace de débogage, il sera presque impossible de comprendre ce qui se passe.  Dans cette optique, l’inspection de l’ensemble .NET est un point de départ naturel.

Du point de vue de WPF, un thème est essentiellement un ResourceDictionary sérialisé en tant que BAML et incorporé dans un assembly .NET standard (par exemple PresentationFramework.Aero.dll). Plus tard, il sera nécessaire d'afficher les thèmes en tant que simple XAML pour vérifier le comportement dans le problème.

Heureusement, Microsoft fournit le 4.0 thèmes comme XAML pour la commodité des développeurs. Je ne sais pas si les thèmes pré-4.0 sont téléchargeables sous quelque forme que ce soit de Microsoft.

Pour les assemblys généraux (y compris les assemblys de thèmes antérieurs à la version 4.0), vous pouvez utiliser l'outil (précédemment gratuit) Réflecteur avec le Plugin BamlViewer décompiler le BAML dans XAML. Bien que pas aussi flashy, ILSpy est une alternative gratuite avec un décompilateur BAML intégré.

enter image description here

Les assemblages .NET sont dispersés sur votre disque dur et c'est un peu déroutant. Voici leurs chemins sur ma machine, ce que je pense en quelque sorte et que je parviens parfois à retenir sans tâtonnements.

Aero 3.0

C: \ Program Files \ Reference Assemblies \ Microsoft \ Framework \ v3.0 \ PresentationFramework.Aero.dll

Aero 4.0

C: \ WINDOWS \ Microsoft.NET \ assembly \ GAC_MSIL \ PresentationFramework.Aero \ v4.0_4.0.0.0__31bf3856ad364e35 \ PresentationFramework.Aero.dll

Quel est le PublicKeyToken pour la version   4 (ou comment puis-je le découvrir)?

La chose la plus simple à faire est d'utiliser Reflector. Le PublicKeyToken est le même qu'auparavant: 31bf3856ad364e35

enter image description here

Aditionellement, sn.exe (à partir du kit de développement Windows) peut extraire des informations d'assemblage.

Sur ma machine, la commande est la suivante:

C: \ Program Files \ Kit de développement Microsoft \ 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 "

enter image description here

Chargement de thèmes en tant que Skins

Devrait (le PresentationFramework.Aero   référence) être mis à jour vers la version 4.0?

Très certainement. Le DataGrid n’existait pas dans le .NET FCL avant la version 4.0. Il y a plusieurs façons de le confirmer, mais la plus intuitive est que, de votre propre aveu, vous y avez déjà accédé via le Toolkit WPF. Si vous choisissez de ne pas charger PresentationFramework.Aero 4.0 dans App.xaml, le style DataGrid d'Aero ne figurera pas dans les ressources de l'application.

Maintenant, il s'avère que cela n'a même pas d'importance. Je vais utiliser le XAML d'origine, insérer le débogueur lors du chargement et inspecter les ressources de l'application.

enter image description here

Comme prévu, il existe deux ResourceDictionaries dans la propriété MergedDictionaries de l'application et le premier ResourceDictionary est la version 3.0 de PresentationFramework.Aero. Cependant, je vois qu'il y a 266 ressources dans le premier ResourceDictionary. À ce stade, il se trouve que je sais qu'il existe 266 ressources dans le thème Aero 4.0 et seulement 243 ressources dans le thème Aero 3.0. De plus, il y a même une entrée DataGrid! Ce ResourceDictionary est, en fait, l'Aero 4.0 ResourceDictionary.

Peut-être que quelqu'un d'autre peut expliquer pourquoi WPF charge l'assembly 4.0 lorsque 3.0 a été explicitement spécifié. Ce que je peux vous dire, c'est que si les projets sont redirigés vers .NET 3.0 (et que les erreurs de compilation sont corrigées), la version 3.0 d'Aero sera chargée à la place.

enter image description here enter image description here

Comme vous l'avez correctement déduit, Aero 4.0 devrait être chargé quand même. Il est utile de savoir ce qui se passe pendant le débogage.

Problème n ° 1: le style DataGrid d'Aero n'est pas utilisé

Le DataGrid dans cette application aura zéro ou plusieurs styles enchaînés en fonction de la façon dont vous configurez les propriétés Style.BasedOn.

Il aura également un style par défaut, qui, dans votre cas, est intégré au thème Luna.

Je savais juste en regardant le XAML original qu'il y avait un problème d'héritage de style. Le grand style DataGrid avec ~ 20 Setters ne définit pas sa propriété BasedOn.

enter image description here

Vous avez une chaîne de style de longueur deux et votre style par défaut provient du thème Luna. Le style DataGrid dans le ResourceDictionary d'Aero n'est tout simplement pas utilisé.

Il y a deux grandes questions ici. Tout d'abord, comment un tel système peut-il être débogué en premier lieu? Deuxièmement, quelles sont les implications?

Débogage des chaînes de style

Je recommande d'utiliser Espionner et / ou Inspecteur WPF pour déboguer les problèmes de WPF comme celui-ci.

La version 0.9.9 de WPF Inspector a même un visualiseur de chaîne de style. (Je dois vous avertir que cette fonctionnalité est actuellement buggée et pas très utile pour déboguer cette partie de l'application. Notez également qu'elle choisit de représenter le style par défaut dans la chaîne.)

La puissance de ces outils réside dans leur capacité à visualiser et à modifier les valeurs de profondément imbriqué éléments à temps d'exécution. Vous pouvez simplement passer la souris sur un élément et ses informations apparaîtront immédiatement dans l’outil.

Alternativement, si vous voulez simplement regarder un élément de niveau supérieur tel que le DataGrid, nommez l'élément dans le XAML (par exemple, x: Name = "dg"), puis introduisez le débogueur lors du chargement et insérez le nom de l'élément dans une montre. fenêtre. Vous pouvez y inspecter la chaîne Style via la propriété BasedOn.

Ci-dessous, j'ai cassé dans le débogueur tout en utilisant la solution XAML. Le DataGrid a trois styles dans la chaîne Style avec respectivement 4, 17 et 9 Setters. Je peux creuser un peu plus et en déduire que le premier style est "DataGrid_FixedStyle". Comme prévu, le second est le grand, implicite Style DataGrid du même fichier. Enfin, le troisième style semble provenir du ResourceDictionary d’Aero. Notez que le style par défaut n'est pas représenté dans cette chaîne.

enter image description here

À ce stade, il convient de noter qu’il n’ya pas de variation entre le style DataGrid de chaque thème. Vous pouvez le vérifier en prenant les styles DataGrid de leurs 4.0 thèmes, en les copiant dans des fichiers texte séparés, puis en les comparant à un outil de comparaison.

En fait, un nombre modéré de styles sont simplement identique de thème en thème. C'est bon d'être au courant. Pour le vérifier, exécutez simplement un diff sur l'ensemble du XAML contenu dans deux thèmes différents.

enter image description here

Notez qu'il existe plusieurs éléments différents imbriqués dans le DataGrid (par exemple, DataGridRow) et que chacun possède son propre style. Même si les styles DataGrid sont actuellement identiques d'un thème à l'autre, les styles pour ces éléments imbriqués peuvent varier. Basé sur le comportement observé dans le problème, il est clair que certains le font.

Implications de XAML d'origine n'incorporant pas le style DataGrid d'Aero

Étant donné que les styles DataGrid sont identiques entre les thèmes 4.0, l'ajout du style DataGrid d'Aero à la fin de la chaîne de style est, dans ce cas, fondamentalement superflu. Le style DataGrid d'Aero sera identique au style de DataGrid par défaut (de Luna, dans votre cas). Bien entendu, les thèmes futurs pourraient toujours avoir des variations par rapport au style DataGrid.

Qu'il y ait ou non des implications, puisque vous avez eu l'intention d'incorporer les styles d'Aero, il est clairement plus correct de le faire jusqu'à ce qu'il y ait une raison spécifique de ne pas le faire (ce qui sera discuté plus tard).

Plus important encore, il est utile de savoir ce qui se passe.

Style.BasedOn seulement a un sens dans le contexte dans lequel il est utilisé

Dans la solution XAML, DataGridResourceDictionary.xaml fonctionne exactement comme vous le souhaitez. Il est important de comprendre pourquoi, et il est important de comprendre que son utilisation de cette manière empêche de l'utiliser autrement.

Disons les styles finaux dans DataGridResourceDictionary.xaml Les chaînes de styles définissent leurs propriétés BasedOn sur une clé de type (par exemple BasedOn = "{StaticResource {x: Type DataGrid}}"). S'ils le font, ils hériteront alors d'un style implicite correspondant à cette clé. Toutefois, le style dont ils héritent dépend de l'endroit où DataGridResourceDictionary.xaml est chargé. Si, par exemple, DataGridResourceDictionary.xaml est chargé dans un dictionnaire fusionné juste après le chargement des ressources d'Aero, ses styles hériteront des styles Aero appropriés. Maintenant, si, par exemple, DataGridResourceDictionary.xaml est le seul ResourceDictionary chargé dans l'application entière, ses styles hériteront réellement des styles pertinents du thème actuel (Luna, dans votre cas). Notez que les styles du thème seront bien sûr les styles par défaut!

enter image description here

Maintenant, disons les styles finaux dans les chaînes de style de DataGridResourceDictionary.xaml ne pas définir leurs propriétés BasedOn. S'ils le font, alors ils seront les styles finaux dans leurs chaînes de styles respectives, et les seuls autres styles évalués seront les styles par défaut (toujours situés dans le thème). Notez que cela tuerait votre conception de charger Aero en tant que peau et en affiner certaines parties de manière sélective.

Notez que dans les exemples précédents, si la clé finale était une chaîne (par exemple, x: Key = "MyStringKey") au lieu d'un type, les mêmes choses se produiraient, mais il n'y aurait pas de styles correspondants dans les thèmes ou dans la peau Aero. Une exception serait lancée au moment du chargement. Cela dit, les clés de chaîne pendantes pourraient théoriquement fonctionner si un contexte existait toujours dans lequel un style correspondant était trouvé.

Dans la solution XAML, DataGridResourceDictionary.xaml a été modifié. Les styles à la fin de chaque chaîne de styles héritent désormais d'un style implicite supplémentaire. Lorsqu'ils sont chargés dans App.xaml, ils seront résolus en Aero Styles.

Problème n ° 2: DataGrid.ColumnHeaderStyle et DataGrid.CellStyle

C'est un problème désagréable et c'est responsable de certains des comportements étranges que vous avez vus. DataGrid.ColumnHeaderStyle et DataGrid.CellStyle sont mis en avant par les styles implicites DataGridColumnHeader et DataGridCell. C'est-à-dire qu'ils sont incompatibles avec la peau Aero. Ainsi, ils sont simplement retirés de la solution XAML. 

Le reste de cette sous-section est une enquête approfondie sur le problème. DataGridColumnHeader et DataGridCell, comme tous les FrameworkElements, ont une propriété Style. En outre, il existe deux propriétés très similaires sur DataGrid: ColumnHeaderStyle et CellStyle. Vous pouvez appeler ces deux propriétés "propriétés auxiliaires". Ils mappent, au moins conceptuellement, vers DataGridColumnHeader.Style et DataGridCell.Style. La façon dont ils sont réellement utilisés est sans-papiers, nous devons donc creuser plus profondément.

Les propriétés DataGridColumnHeader.Style et DataGridCell.Style utilisent coercition de valeur. Cela signifie que lorsque l'un des styles est interrogé, des rappels spéciaux sont utilisés pour déterminer quel style est réellement renvoyé à l'appelant (code WPF interne, pour la plupart). Ces rappels peuvent retourner tout valeur qu'ils veulent. En fin de compte, DataGrid.ColumnHeaderStyle et DataGrid.CellStyle sont candidat renvoyer des valeurs dans les rappels respectifs.

Avec Reflector, je peux facilement déterminer tout cela. (Si nécessaire, il est également possible de passer à travers le code source .NET.) À partir du constructeur statique de DataGridColumnHeader, je localise la propriété Style et voit qu’elle se voit attribuer des métadonnées supplémentaires. Plus précisément, un rappel de contrainte est spécifié. En commençant par ce rappel, je clique sur une séquence d'appels de méthodes et je vois rapidement ce qui se passe. (Notez que DataGridCell fait la même chose donc je ne le couvrirai pas.)

enter image description here

La méthode finale, DataGridHelper.GetCoercedTransferPropertyValue, compare essentiellement la source de DataGridColumnHeader.Style et DataGrid.ColumnHeaderStyle. Quelle que soit la source, la priorité la plus élevée l'emporte. Les règles de priorité de cette méthode sont basées sur Dépendance de la propriété de dépendance.

À ce stade, DataGrid.ColumnHeaderStyle sera inspecté à la fois dans XAML d'origine et dans la solution XAML. Une petite matrice d'informations sera collectée. En fin de compte, cela explique le comportement observé dans chaque application.

Dans le fichier XAML d'origine, j'insère le débogueur et constate que DataGrid.ColumnHeaderStyle possède une source de style. Cela a du sens car il a été défini dans un style.

enter image description here enter image description here

Dans la solution XAML, je rentre le débogueur et constate que DataGrid.ColumnHeaderStyle a une source 'Default'. Cela a du sens car cette valeur n'a pas été définie dans un style (ou ailleurs).

enter image description here enter image description here

L'autre valeur à inspecter est DataGridColumnHeader.Style. DataGridColumnHeader est un élément profondément imbriqué qui n'est pas facilement accessible lors du débogage dans VisualStudio. En réalité, un outil tel que Snoop ou WPF Inspector serait utilisé pour inspecter la propriété.

Avec le XAML d'origine, DataGridColumnHeader.Style a une source 'ImplicitStyleReference'. C'est logique. DataGridColumnHeaders sont instanciés en profondeur dans le code WPF interne. Leur propriété Style est null et ils chercheront donc un style implicite. L'arbre est traversé de l'élément DataGridColumnHeader vers l'élément racine. Comme prévu, aucun style n'est trouvé. Ensuite, les ressources de l'application sont vérifiées. Vous avez une clé de chaîne ("DataGrid_ColumnHeaderStyle") définie sur le seul style DataGridColumnHeader. Cela le cache efficacement dans cette recherche et il n'est donc pas utilisé. Ensuite, le skin Aero est recherché et un style implicite typique est trouvé. C'est le style qui est utilisé.

enter image description here

Si cette étape est répétée avec la solution XAML, le résultat est le même: 'ImplicitStyleReference'. Cette fois, cependant, le style implicite est le seul style DataGridColumnHeader dans DataGridResourceDictionary.xaml, désormais implicitement indexé.

enter image description here

Enfin, si cette étape est répétée une fois de plus avec le XAML original, et le skin Aero n'est pas chargé, le résultat est maintenant 'Default'! En effet, il n'y a tout simplement pas de styles implicites de DataGridColumnHeader dans l'application entière.

Par conséquent, DataGrid.ColumnHeaderStyle sera utilisé si l'enveloppe Aero n'est pas chargée, mais ne sera pas utilisée si l'enveloppe Aero est chargée! Comme annoncé, le chargement des ressources d'un thème peut entraîner des effets négatifs.

C'est beaucoup de rester droit et les noms ont tous la même sonorité. Le diagramme suivant récapitule toutes les actions. Rappelez-vous que la propriété avec la priorité la plus élevée gagne.

enter image description here

Ce n'est peut-être pas ce que vous voulez, mais c'est comme ça que le DataGrid fonctionne avec WPF 4.0. Compte tenu de ce qui précède, vous pouvez théoriquement définir DataGrid.ColumnHeaderStyle et DataGrid.CellStyle sur une très large portée, tout en ayant la possibilité de remplacer les styles DataGridColumnHeader et DataGridCell à un niveau plus restreint en utilisant des styles implicites.

Là encore, DataGrid.ColumnHeaderStyle et DataGrid.CellStyle sont mis en avant par les styles implicites DataGridColumnHeader et DataGridCell. C'est-à-dire qu'ils sont incompatibles avec la peau Aero. Ainsi, ils sont simplement retirés de la solution XAML. 

Problème n ° 3: DataGridRow.Background

Si les modifications recommandées jusqu'à ce point ont été implémentées, quelque chose qui ressemble à ce qui suit devrait apparaître sur votre écran. (Gardez à l'esprit que j'ai défini mon thème sur Classic afin de résoudre ce problème.)

enter image description here

Le DataGrid a un aspect Aero, mais AlternatingRowBackground n'est pas respecté. Chaque rangée devrait avoir un fond gris.

enter image description here

En utilisant les techniques de débogage décrites jusqu'à présent, on trouvera que c'est exactement le même type de problème que le problème n ° 2. Un style implicite de DataGridRow dans le skin Aero est en cours de chargement. DataGridRow.Background utilise la coercition de propriété. DataGrid.AlternatingRowBackground est un candidat valeur qui peut être renvoyée dans le rappel de contrainte. DataGridRow.Background est un autre candidat. D'où proviennent ces valeurs influera sur la valeur choisie par le rappel de contrainte.

Il devrait maintenant être clair, mais sinon, il faut le répéter. Le chargement des ressources d'un thème peut entraîner des effets indésirables.

La réponse courte à ce sous-problème est DataGridRow.Background doit uniquement être défini dans le thème. Plus précisément, il ne doit pas être défini par un Setter de style dans l’application. Malheureusement, c'est maintenant exactement ce qui se passe dans la peau de l'Aero. Il existe au moins deux façons de résoudre ce problème.

Un style implicite vierge peut être ajouté après le skin Aero. Cela masque le style incriminé dans Aero. Il n'y a pas de valeur dans le style vide pour que les valeurs du style par défaut finissent par être utilisées. En fin de compte, cela ne fonctionne que parce que les styles DataGridRow sont identiques dans tous les thèmes 4.0.

Il est également possible de copier le style DataGridRow d'Aero, de supprimer le Setter Background et d'ajouter le reste du style après le skin Aero. La solution XAML utilise cette technique. En étendant le style, l'application est plus susceptible de continuer à chercher Aero dans les scénarios futurs. En isolant cette extension dans App.xaml, DataGridResourceDictionary.xaml peut être utilisé plus librement dans d'autres contextes. Toutefois, notez qu'il peut être plus judicieux de l'ajouter à DataGridResourceDictionary.xaml, en fonction de l'utilisation future de ce fichier. En termes de cette application, de toute façon fonctionne.

Problème n ° 4: Disposition DataGridColumnHeader

Le changement final est assez superficiel. Si l'application est exécutée après avoir apporté les modifications recommandées jusqu'à présent, le contenu de DataGridColumnHeaders sera aligné à gauche plutôt que centré. Ce problème peut facilement être résolu avec Snoop ou WPF Inspector. La racine du problème semble être le DataGridColumnHeaders avoir HorizontalContentAlignment réglé sur 'Left'.

enter image description here

Réglez-le sur 'Stretch' et cela fonctionne comme prévu.

Il y a une certaine interaction entre le Propriétés de la mise en page et TextBlock propriétés de mise en forme. Snoop et WPF Inspector permettent des expérimentations et facilitent la détermination de ce qui fonctionne dans une situation donnée.

Dernières pensées

En résumé, le chargement des ressources d'un thème n'est pas la même chose que la modification du thème au niveau du système d'exploitation. Le chargement des ressources d'un thème peut entraîner des effets indésirables. Du point de vue de WPF, un grand nombre de styles implicites sont désormais présents dans l'application. Ces styles peuvent l'emporter sur les autres styles. En bout de ligne, traiter un thème comme un skin d'application peut ne pas fonctionner sans améliorations.

Cela dit, je ne suis pas complètement vendu sur l'implémentation actuelle de WPF en ce qui concerne les "propriétés d'assistance" (par exemple, DataGrid.ColumnHeaderStyle) utilisées via un rappel de coercition avec des règles de priorité. Je me demande pourquoi ils ne peuvent pas simplement être assignés localement à leurs cibles (par exemple, DataGridColumnHeader.Style) au moment de l'initialisation, si les cibles n'ont pas déjà une valeur explicitement affectée. Je n’ai pas suffisamment réfléchi à cela pour connaître les différents problèmes, mais si cela est possible, cela pourrait rendre le modèle «propriété d’assistant» plus intuitif, plus cohérent avec les autres propriétés et plus infaillible.

Enfin, bien que ce ne soit pas l'objet de cette réponse, il est très important de noter que le chargement des ressources d'un thème pour simuler la modification du thème est particulièrement mauvais car il y a un coût de maintenabilité substantiel. Les styles existants dans l'application ne seront pas automatiquement basés sur les styles contenus dans le ResourceDictionary du thème. Chaque style de l'application devrait définir sa propriété BasedOn sur une clé de type (ou être basée, directement ou indirectement, sur un autre style). Ceci est extrêmement lourd et sujet aux erreurs. En outre, il existe un coût de maintenabilité en ce qui concerne les contrôles personnalisés adaptés au thème. le ressources thématiques car ces contrôles personnalisés devront également être chargés pour effectuer cette simulation. Et bien sûr, après cela, vous pourriez rencontrer des problèmes de style similaires à ceux que vous avez rencontrés ici!

Quoi qu’il en soit, il existe plusieurs façons d’appliquer une application WPF (sans jeu de mots!). J'espère que cette réponse vous donnera un aperçu supplémentaire de votre problème et vous aidera, vous et les autres, à résoudre des problèmes similaires.


68
2018-04-09 14:34



Je pense que le problème n'est pas PresentationFramework.Aero en soi, mais plutôt que vous obtenez des Styles DataGrid implicites en l'incluant. Cela peut également être vu en ajoutant simplement cela dans App.xaml

<Application.Resources>
    <Style TargetType="{x:Type DataGridColumnHeader}"/>
</Application.Resources>

Cela entraînera la perte de style de toutes vos données DataGridColumnHeader si elles ne sont pas définies explicitement.

Cela fonctionnera

<DataGrid ColumnHeaderStyle="{StaticResource DataGrid_ColumnHeaderStyle}" ../>

Cependant, ce ne sera pas

<DataGrid Style="{StaticResource DataGrid_FixedStyle}" ../>

<Style x:Key="DataGrid_FixedStyle" TargetType="DataGrid">
    <Setter Property="ColumnHeaderStyle"
            Value="{StaticResource DataGrid_ColumnHeaderStyle}" />
</Style>

Je ne suis pas sûr de bien contourner cela. La seule chose à laquelle je peux penser est de définir tous les styles explicitement sur le DataGrid lui-même, mais cela peut être gênant, surtout si vous utilisez ce style dans de nombreux endroits.

<DataGrid Style="{StaticResource DataGrid_FixedStyle}"
          ColumnHeaderStyle="{StaticResource DataGrid_ColumnHeaderStyle}"
          CellStyle="{StaticResource DataGrid_CellStyle}"
          ... >

0
2017-11-22 07:22