Comment appliquer plusieurs styles dans WPF

Dans WPF, comment appliquer plusieurs styles à un FrameworkElement? Par exemple, j'ai un contrôle qui a déjà un style. J'ai aussi un style distinct que je voudrais ajouter sans souffler le premier. Les styles ont différents types de cibles, donc je ne peux pas simplement étendre l'un avec l'autre.

132
demandé sur MojoFilter 2008-08-19 16:47:44

10 réponses

Je pense que la réponse simple est que vous ne pouvez pas faire (au moins dans cette version de WPF) ce que vous essayez de faire.

C'est, pour tout élément particulier un seul Style peut être appliqué.

Cependant, comme d'autres l'ont indiqué ci-dessus, peut-être que vous pouvez utiliser BasedOn pour vous aider. Découvrez le morceau suivant de XAML lâche. Dans celui ci vous verrez que j'ai un style de base qui définit une propriété qui existe sur la classe de base de l'élément que je veux appliquer deux styles de. Et, dans le deuxième style qui est basé sur le style de base, j'ai défini une autre propriété.

Donc, l'idée ici ... si vous pouvez en quelque sorte séparer les propriétés que vous souhaitez définir ... selon la hiérarchie d'héritage de l'élément que vous souhaitez définir plusieurs styles sur ... vous pourriez avoir une solution de contournement.

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Page.Resources>
        <Style x:Key="baseStyle" TargetType="FrameworkElement">
            <Setter Property="HorizontalAlignment" Value="Left"/>
        </Style>
        <Style TargetType="Button" BasedOn="{StaticResource baseStyle}">
            <Setter Property="Content" Value="Hello World"/>
        </Style>
    </Page.Resources>
    <Grid>
        <Button Width="200" Height="50"/>
    </Grid>
</Page>


Espérons que cette aide.

Remarque:

Une chose en particulier à noter. Si vous modifiez le TargetType dans le second style (dans le premier jeu de xaml ci-dessus) à ButtonBase, les deux Styles ne sont pas appliqués. Cependant, consultez le xaml suivant ci-dessous pour contourner cette restriction. Fondamentalement, cela signifie que vous devez donner une clé au Style et le référencer avec cette clé.

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Page.Resources>
        <Style x:Key="baseStyle" TargetType="FrameworkElement">
            <Setter Property="HorizontalAlignment" Value="Left"/>
        </Style>
        <Style x:Key="derivedStyle" TargetType="ButtonBase" BasedOn="{StaticResource baseStyle}">
            <Setter Property="Content" Value="Hello World"/>
        </Style>
    </Page.Resources>
    <Grid>
        <Button Width="200" Height="50" Style="{StaticResource derivedStyle}"/>
    </Grid>
</Page>
134
répondu cplotts 2012-12-14 10:04:22

Bea Stollnitz avait un bon article de blog sur l'utilisation d'une extension de balisage pour cela, sous la rubrique " Comment puis-je définir plusieurs styles dans WPF?"

Ce blog est mort Maintenant, donc je reproduis le post ici


WPF et Silverlight offrent tous deux la possibilité de dériver un Style d'un autre Style via la propriété "BasedOn". Cette fonctionnalité permet aux développeurs d'organiser leurs styles en utilisant une hiérarchie similaire à l'héritage de classe. Considérez les points suivants styles:

<Style TargetType="Button" x:Key="BaseButtonStyle">
    <Setter Property="Margin" Value="10" />
</Style>
<Style TargetType="Button" x:Key="RedButtonStyle" BasedOn="{StaticResource BaseButtonStyle}">
    <Setter Property="Foreground" Value="Red" />
</Style>

Avec cette syntaxe, un bouton qui utilise RedButtonStyle aura sa propriété de premier plan définie sur Rouge et sa propriété de marge définie sur 10.

Cette fonctionnalité existe depuis longtemps dans WPF, et elle est nouvelle dans Silverlight 3.

Que faire si vous voulez définir plus d'un style sur un élément? Ni WPF ni Silverlight ne fournissent une solution à ce problème. Heureusement, il existe des moyens d'implémenter ce comportement dans WPF, dont je discuterai dans ce billet de blog.

WPF et Silverlight utilisent des extensions de balisage pour fournir des propriétés avec des valeurs qui nécessitent une certaine logique pour obtenir. Les extensions de balisage sont facilement reconnaissables par la présence de crochets bouclés les entourant en XAML. Par exemple, l'extension de balisage {Binding} contient une logique pour récupérer une valeur à partir d'une source de données et la mettre à jour lorsque des changements se produisent; l'extension de balisage {StaticResource} contient une logique pour récupérer une valeur à partir d'un dictionnaire de ressources basé sur une clé. Heureusement pour nous, WPF permet aux utilisateurs d'écrire leurs propres extensions de balisage. Cette fonctionnalité n'est pas encore présente dans Silverlight, donc la solution dans ce blog est uniquement applicable à WPF.

D'autres {[15] } ont écrit d'excellentes solutions pour fusionner deux styles en utilisant des extensions de balisage. Cependant, je voulais une solution qui permettait de fusionner un nombre illimité de styles, ce qui est un peu plus délicat.

Écrire une extension de balisage est simple. La première étape consiste à créer une classe cela dérive de MarkupExtension et utilisez L'attribut MarkupExtensionReturnType pour indiquer que vous avez l'intention que la valeur renvoyée par votre extension de balisage soit de type Style.

[MarkupExtensionReturnType(typeof(Style))]
public class MultiStyleExtension : MarkupExtension
{
}

Spécification des entrées de l'extension de balisage

Nous aimerions donner aux utilisateurs de notre extension de balisage un moyen simple de spécifier les styles à fusionner. Il y a essentiellement deux façons dont l'utilisateur peut spécifier des entrées à une extension de balisage. L'utilisateur peut définir des propriétés ou passer des paramètres au constructeur. Puisque dans ce scénario l'Utilisateur a besoin de la possibilité de spécifier un nombre illimité de styles, ma première approche a été de créer un constructeur qui prend n'importe quel nombre de chaînes en utilisant le mot-clé" params":

public MultiStyleExtension(params string[] inputResourceKeys)
{
}

Mon but était de pouvoir écrire les entrées comme suit:

<Button Style="{local:MultiStyle BigButtonStyle, GreenButtonStyle}" … />

Notez la virgule séparant les différentes touches de style. Malheureusement, les extensions de balisage personnalisées ne prennent pas en charge un nombre illimité de paramètres du constructeur, donc cette approche entraîne un erreur de compilation. Si je savais à l'avance combien de styles je voulais fusionner, j'aurais pu utiliser la même syntaxe XAML avec un constructeur prenant le nombre désiré de chaînes:

public MultiStyleExtension(string inputResourceKey1, string inputResourceKey2)
{
}

Comme solution de contournement, j'ai décidé que le paramètre constructeur prenne une seule chaîne qui spécifie les noms de style séparés par des espaces. La syntaxe n'est pas trop mauvaise:

private string[] resourceKeys;

public MultiStyleExtension(string inputResourceKeys)
{
    if (inputResourceKeys == null)
    {
        throw new ArgumentNullException("inputResourceKeys");
    }

    this.resourceKeys = inputResourceKeys.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);

    if (this.resourceKeys.Length == 0)
    {
        throw new ArgumentException("No input resource keys specified.");
    }
}

Calcul de la sortie de l'extension de balisage

Pour calculer la sortie d'une extension de balisage, nous devons remplacer une méthode de MarkupExtension appelée "ProvideValue". La valeur retournée par cette méthode sera définie dans la cible de l'extension de balisage.

J'ai commencé par créer une méthode d'extension pour le Style qui sait comment fusionner deux styles. Le code de cette méthode est assez simple:

public static void Merge(this Style style1, Style style2)
{
    if (style1 == null)
    {
        throw new ArgumentNullException("style1");
    }
    if (style2 == null)
    {
        throw new ArgumentNullException("style2");
    }

    if (style1.TargetType.IsAssignableFrom(style2.TargetType))
    {
        style1.TargetType = style2.TargetType;
    }

    if (style2.BasedOn != null)
    {
        Merge(style1, style2.BasedOn);
    }

    foreach (SetterBase currentSetter in style2.Setters)
    {
        style1.Setters.Add(currentSetter);
    }

    foreach (TriggerBase currentTrigger in style2.Triggers)
    {
        style1.Triggers.Add(currentTrigger);
    }

    // This code is only needed when using DynamicResources.
    foreach (object key in style2.Resources.Keys)
    {
        style1.Resources[key] = style2.Resources[key];
    }
}

Avec la logique ci-dessus, le premier style est modifié pour inclure toutes les informations du second. S'il y a des conflits (par exemple, les deux styles ont un setter pour la même propriété), le second style gagne. Notez qu'en plus de copier des styles et des déclencheurs, j'ai également pris en compte les valeurs TargetType et BasedOn ainsi que toutes les ressources que le second style peut avoir. Pour le TargetType du style fusionné, j'ai utilisé le type le plus dérivé. Si le second style a un style BasedOn, je fusionne récursivement sa hiérarchie de styles. S'il a des ressources, je les copie dans le premier style. Si ces ressources sont référencées à l'aide de {StaticResource}, elles sont résolues statiquement avant cela le code de fusion s'exécute, et il n'est donc pas nécessaire de les déplacer. J'ai ajouté ce code au cas où nous utiliserions DynamicResources.

La méthode d'extension ci-dessus permet la syntaxe suivante:

style1.Merge(style2);

Cette syntaxe est utile à condition d'avoir des instances des deux styles dans ProvideValue. Tout ce que je reçois du constructeur est une liste de clés de chaîne pour ces styles. S'il y avait un support pour les paramètres dans les paramètres du constructeur, j'aurais pu utiliser ce qui suit syntaxe pour obtenir les instances de style réelles:

<Button Style="{local:MultiStyle {StaticResource BigButtonStyle}, {StaticResource GreenButtonStyle}}" … />
public MultiStyleExtension(params Style[] styles)
{
}

, Mais qui ne fonctionne pas. Et même si la limitation params n'existait pas, nous frapperions probablement une autre limitation des extensions de balisage, où nous devrions utiliser la syntaxe de l'élément de propriété au lieu de la syntaxe de l'attribut pour spécifier les ressources statiques, ce qui est verbeux et encombrant (j'explique mieux ce bug dans un précédent article de blog). Et même si ces deux limitations n'existaient pas, je préférerais quand même écrire la liste des styles utilisant seulement leurs noms - il est plus court et plus simple à lire qu'un StaticResource pour chacun.

La solution consiste à créer un StaticResourceExtension en utilisant du code. Étant donné une clé de style de type string et un fournisseur de services, Je Peux utiliser StaticResourceExtension pour récupérer l'instance de style réelle. Voici la syntaxe:

Style currentStyle = new StaticResourceExtension(currentResourceKey).ProvideValue(serviceProvider) as Style;

Maintenant, nous avons toutes les pièces nécessaires pour écrire la méthode ProvideValue:

public override object ProvideValue(IServiceProvider serviceProvider)
{
    Style resultStyle = new Style();

    foreach (string currentResourceKey in resourceKeys)
    {
        Style currentStyle = new StaticResourceExtension(currentResourceKey).ProvideValue(serviceProvider) as Style;

        if (currentStyle == null)
        {
            throw new InvalidOperationException("Could not find style with resource key " + currentResourceKey + ".");
        }

        resultStyle.Merge(currentStyle);
    }
    return resultStyle;
}

Voici un exemple complet de l'utilisation du Extension de balisage MultiStyle:

<Window.Resources>
    <Style TargetType="Button" x:Key="SmallButtonStyle">
        <Setter Property="Width" Value="120" />
        <Setter Property="Height" Value="25" />
        <Setter Property="FontSize" Value="12" />
    </Style>

    <Style TargetType="Button" x:Key="GreenButtonStyle">
        <Setter Property="Foreground" Value="Green" />
    </Style>

    <Style TargetType="Button" x:Key="BoldButtonStyle">
        <Setter Property="FontWeight" Value="Bold" />
    </Style>
</Window.Resources>

<Button Style="{local:MultiStyle SmallButtonStyle GreenButtonStyle BoldButtonStyle}" Content="Small, green, bold" />

entrez la description de l'image ici

37
répondu Wilka 2016-10-23 21:13:51

Mais vous pouvez étendre à partir d'un autre.. jetez un oeil à la propriété BasedOn

<Style TargetType="TextBlock">
      <Setter Property="Margin" Value="3" />
</Style>

<Style x:Key="AlwaysVerticalStyle" TargetType="TextBlock" 
       BasedOn="{StaticResource {x:Type TextBlock}}">
     <Setter Property="VerticalAlignment" Value="Top" />
</Style>
29
répondu Arcturus 2012-12-14 10:05:28

WPF / XAML ne fournit pas cette fonctionnalité nativement, mais il fournit l'extensibilité pour vous permettre de faire ce que vous voulez.

Nous avons rencontré le même besoin, et avons fini par créer notre propre extension de balisage XAML (que nous avons appelée "MergedStylesExtension") pour nous permettre de créer un nouveau Style à partir de deux autres styles (qui, si nécessaire, pourraient probablement être utilisés plusieurs fois de suite pour hériter d'encore plus de styles).

En raison d'un bogue WPF/XAML, nous devons utiliser la syntaxe de l'élément de propriété pour utilisez - le, mais à part cela, il semble fonctionner correctement. Par exemple,

<Button
    Content="This is an example of a button using two merged styles">
    <Button.Style>
      <ext:MergedStyles
                BasedOn="{StaticResource FirstStyle}"
                MergeStyle="{StaticResource SecondStyle}"/>
   </Button.Style>
</Button>

J'ai récemment écrit à ce sujet ici: http://swdeveloper.wordpress.com/2009/01/03/wpf-xaml-multiple-style-inheritance-and-markup-extensions/

17
répondu 2009-01-04 04:09:45

Ceci est possible en créant une classe d'aide à utiliser et à envelopper vos styles. CompoundStyle mentionné ici montre comment le faire. Il y a plusieurs façons, mais le plus simple est de faire ce qui suit:

<TextBlock Text="Test"
    local:CompoundStyle.StyleKeys="headerStyle,textForMessageStyle,centeredStyle"/>

J'espère que ça aide.

3
répondu Shahar Prish 2012-10-04 15:26:09

Si vous ne touchez aucune propriété spécifique, vous pouvez obtenir toutes les propriétés de base et communes au style dont le type cible serait FrameworkElement. ensuite, vous pouvez créer des saveurs spécifiques pour chaque type de cible dont vous avez besoin, sans avoir besoin de copier à nouveau toutes ces propriétés communes.

1
répondu Greg 2008-09-16 04:26:49

Vous pouvez probablement obtenir quelque chose de similaire si vous l'appliquez à une collection d'éléments en utilisant un StyleSelector, j'ai utilisé ceci pour aborder un problème similaire en utilisant différents styles sur TreeViewItems en fonction du type d'objet lié dans l'arborescence. Vous devrez peut-être modifier légèrement la classe ci-dessous pour vous adapter à votre approche particulière, mais j'espère que cela vous aidera à démarrer

public class MyTreeStyleSelector : StyleSelector
{
    public Style DefaultStyle
    {
        get;
        set;
    }

    public Style NewStyle
    {
        get;
        set;
    }

    public override Style SelectStyle(object item, DependencyObject container)
    {
        ItemsControl ctrl = ItemsControl.ItemsControlFromItemContainer(container);

        //apply to only the first element in the container (new node)
        if (item == ctrl.Items[0])
        {
            return NewStyle;
        }
        else
        {
            //otherwise use the default style
            return DefaultStyle;
        }
    }
}

Vous appliquez ensuite ceci comme suit

 <TreeView>
     <TreeView.ItemContainerStyleSelector
         <myassembly:MyTreeStyleSelector DefaultStyle="{StaticResource DefaultItemStyle}"
                                         NewStyle="{StaticResource NewItemStyle}" />
     </TreeView.ItemContainerStyleSelector>
  </TreeView>
1
répondu Dave 2008-10-15 11:22:59

Parfois, vous pouvez aborder cela en imbriquant des panneaux. Disons que vous avez un Style qui change de premier plan et un autre change de FontSize, vous pouvez appliquer ce dernier sur un TextBlock, et le mettre dans une grille dont son Style est le premier. Cela pourrait aider et pourrait être le moyen le plus simple dans certains cas, mais cela ne résoudra pas tous les problèmes.

1
répondu hillin 2013-11-24 18:55:52

Lorsque vous remplacez SelectStyle, vous pouvez obtenir la propriété GroupBy via la réflexion comme ci-dessous:

    public override Style SelectStyle(object item, DependencyObject container)
    {

        PropertyInfo p = item.GetType().GetProperty("GroupBy", BindingFlags.NonPublic | BindingFlags.Instance);

        PropertyGroupDescription propertyGroupDescription = (PropertyGroupDescription)p.GetValue(item);

        if (propertyGroupDescription != null && propertyGroupDescription.PropertyName == "Title" )
        {
            return this.TitleStyle;
        }

        if (propertyGroupDescription != null && propertyGroupDescription.PropertyName == "Date")
        {
            return this.DateStyle;
        }

        return null;
    }
1
répondu Sérgio Henrique 2014-02-05 03:31:31

Utiliser AttachedProperty pour définir plusieurs styles comme le code suivant:

public class Css
{

    public static string GetClass(DependencyObject element)
    {
        if (element == null)
            throw new ArgumentNullException("element");

        return (string)element.GetValue(ClassProperty);
    }

    public static void SetClass(DependencyObject element, string value)
    {
        if (element == null)
            throw new ArgumentNullException("element");

        element.SetValue(ClassProperty, value);
    }


    public static readonly DependencyProperty ClassProperty =
        DependencyProperty.RegisterAttached("Class", typeof(string), typeof(Css), 
            new PropertyMetadata(null, OnClassChanged));

    private static void OnClassChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var ui = d as FrameworkElement;
        Style newStyle = new Style();

        if (e.NewValue != null)
        {
            var names = e.NewValue as string;
            var arr = names.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
            foreach (var name in arr)
            {
                Style style = ui.FindResource(name) as Style;
                foreach (var setter in style.Setters)
                {
                    newStyle.Setters.Add(setter);
                }
                foreach (var trigger in style.Triggers)
                {
                    newStyle.Triggers.Add(trigger);
                }
            }
        }
        ui.Style = newStyle;
    }
}

Usege:

<Window x:Class="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:style_a_class_like_css"
        mc:Ignorable="d"
        Title="MainWindow" Height="150" Width="325">
    <Window.Resources>

        <Style TargetType="TextBlock" x:Key="Red" >
            <Setter Property="Foreground" Value="Red"/>
        </Style>

        <Style TargetType="TextBlock" x:Key="Green" >
            <Setter Property="Foreground" Value="Green"/>
        </Style>

        <Style TargetType="TextBlock" x:Key="Size18" >
            <Setter Property="FontSize" Value="18"/>
            <Setter Property="Margin" Value="6"/>
        </Style>

        <Style TargetType="TextBlock" x:Key="Bold" >
            <Setter Property="FontWeight" Value="Bold"/>
        </Style>

    </Window.Resources>
    <StackPanel>

        <Button Content="Button" local:Css.Class="Red Bold" Width="75"/>
        <Button Content="Button" local:Css.Class="Red Size18" Width="75"/>
        <Button Content="Button" local:Css.Class="Green Size18 Bold" Width="75"/>

    </StackPanel>
</Window>

Résultat:

entrez la description de l'image ici

1
répondu google dev 2017-09-12 04:56:24