So wenden Sie mehrere Stile in WPF an

153

Wie würde ich in WPF mehrere Stile auf a anwenden FrameworkElement? Zum Beispiel habe ich ein Steuerelement, das bereits einen Stil hat. Ich habe auch einen separaten Stil, den ich hinzufügen möchte, ohne den ersten wegzublasen. Die Stile haben unterschiedliche TargetTypes, daher kann ich nicht einfach einen miteinander erweitern.

MojoFilter
quelle
OP hat nie angegeben, ob sein erster Stil nur für ein einzelnes Steuerelement gilt. Bei den Antworten auf dieser Seite wird davon ausgegangen, dass beide Stile für mehrere Steuerelemente freigegeben werden müssen. Wenn Sie nach einer Möglichkeit suchen, Basisstile
lesen

Antworten:

154

Ich denke, die einfache Antwort ist, dass Sie (zumindest in dieser Version von WPF) nicht das tun können, was Sie versuchen.

Das heißt, für ein bestimmtes Element kann nur ein Stil angewendet werden.

Wie andere oben bereits erwähnt haben, können Sie möglicherweise BasedOnhelfen. Schauen Sie sich das folgende Stück lose Xaml an. Darin sehen Sie, dass ich einen Basisstil habe, der eine Eigenschaft festlegt, die für die Basisklasse des Elements vorhanden ist, auf das ich zwei Stile anwenden möchte. Und im zweiten Stil, der auf dem Basisstil basiert, habe ich eine andere Eigenschaft festgelegt.

Die Idee hier ... ist also, wenn Sie die Eigenschaften, die Sie festlegen möchten, irgendwie trennen können ... entsprechend der Vererbungshierarchie des Elements, für das Sie mehrere Stile festlegen möchten ... haben Sie möglicherweise eine Problemumgehung.

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


Hoffe das hilft.

Hinweis:

Eines ist besonders zu beachten. Wenn Sie den TargetTypeim zweiten Stil (im ersten Satz von xaml oben) in ändern ButtonBase, werden die beiden Stile nicht angewendet. Schauen Sie sich jedoch das folgende xaml an, um diese Einschränkung zu umgehen. Grundsätzlich bedeutet dies, dass Sie dem Stil einen Schlüssel geben und ihn mit diesem Schlüssel referenzieren müssen.

<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>
cplotts
quelle
10
Denken Sie daran ... ** Bestellung ist wichtig **. Das derivedStylemuss nach dembaseStyle
SliverNinja - MSFT
50

Bea Stollnitz hatte einen guten Blogbeitrag über die Verwendung einer Markup-Erweiterung unter der Überschrift "Wie kann ich mehrere Stile in WPF festlegen?"

Dieser Blog ist jetzt tot, also reproduziere ich den Beitrag hier


WPF und Silverlight bieten beide die Möglichkeit, einen Stil über die Eigenschaft „BasedOn“ von einem anderen Stil abzuleiten. Mit dieser Funktion können Entwickler ihre Stile mithilfe einer Hierarchie ähnlich der Klassenvererbung organisieren. Betrachten Sie die folgenden Stile:

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

Bei dieser Syntax wird für eine Schaltfläche, die RedButtonStyle verwendet, die Foreground-Eigenschaft auf Red und die Margin-Eigenschaft auf 10 festgelegt.

Diese Funktion gibt es in WPF schon lange und sie ist neu in Silverlight 3.

Was ist, wenn Sie mehr als einen Stil für ein Element festlegen möchten? Weder WPF noch Silverlight bieten eine sofort einsatzbereite Lösung für dieses Problem. Glücklicherweise gibt es Möglichkeiten, dieses Verhalten in WPF zu implementieren, die ich in diesem Blogbeitrag diskutieren werde.

WPF und Silverlight verwenden Markup-Erweiterungen, um Eigenschaften mit Werten zu versehen, für deren Abruf eine gewisse Logik erforderlich ist. Markup-Erweiterungen sind leicht an geschweiften Klammern in XAML zu erkennen. Beispielsweise enthält die Markup-Erweiterung {Binding} eine Logik zum Abrufen eines Werts aus einer Datenquelle und zum Aktualisieren, wenn Änderungen auftreten. Die Markup-Erweiterung {StaticResource} enthält Logik zum Abrufen eines Werts aus einem Ressourcenwörterbuch basierend auf einem Schlüssel. Zum Glück können Benutzer mit WPF ihre eigenen benutzerdefinierten Markup-Erweiterungen schreiben. Diese Funktion ist in Silverlight noch nicht vorhanden, daher gilt die Lösung in diesem Blog nur für WPF.

Andere haben großartige Lösungen geschrieben, um zwei Stile mithilfe von Markup-Erweiterungen zusammenzuführen. Ich wollte jedoch eine Lösung, die die Möglichkeit bietet, eine unbegrenzte Anzahl von Stilen zusammenzuführen, was etwas schwieriger ist.

Das Schreiben einer Markup-Erweiterung ist unkompliziert. Der erste Schritt besteht darin, eine Klasse zu erstellen, die von MarkupExtension abgeleitet ist, und das MarkupExtensionReturnType-Attribut zu verwenden, um anzugeben, dass der von Ihrer Markup-Erweiterung zurückgegebene Wert vom Typ Style sein soll.

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

Angeben von Eingaben für die Markup-Erweiterung

Wir möchten Benutzern unserer Markup-Erweiterung eine einfache Möglichkeit bieten, die zusammenzuführenden Stile anzugeben. Grundsätzlich gibt es zwei Möglichkeiten, wie der Benutzer Eingaben für eine Markup-Erweiterung angeben kann. Der Benutzer kann Eigenschaften festlegen oder Parameter an den Konstruktor übergeben. Da der Benutzer in diesem Szenario die Möglichkeit haben muss, eine unbegrenzte Anzahl von Stilen anzugeben, bestand mein erster Ansatz darin, einen Konstruktor zu erstellen, der eine beliebige Anzahl von Zeichenfolgen mit dem Schlüsselwort "params" verwendet:

public MultiStyleExtension(params string[] inputResourceKeys)
{
}

Mein Ziel war es, die Eingaben wie folgt schreiben zu können:

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

Beachten Sie das Komma zwischen den verschiedenen Stilschlüsseln. Leider unterstützen benutzerdefinierte Markup-Erweiterungen keine unbegrenzte Anzahl von Konstruktorparametern, sodass dieser Ansatz zu einem Kompilierungsfehler führt. Wenn ich im Voraus gewusst hätte, wie viele Stile ich zusammenführen wollte, hätte ich dieselbe XAML-Syntax mit einem Konstruktor verwenden können, der die gewünschte Anzahl von Zeichenfolgen verwendet:

public MultiStyleExtension(string inputResourceKey1, string inputResourceKey2)
{
}

Um dieses Problem zu umgehen, habe ich beschlossen, dass der Konstruktorparameter eine einzelne Zeichenfolge verwendet, die die durch Leerzeichen getrennten Stilnamen angibt. Die Syntax ist nicht schlecht:

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.");
    }
}

Berechnung der Ausgabe der Markup-Erweiterung

Um die Ausgabe einer Markup-Erweiterung zu berechnen, müssen Sie eine Methode aus MarkupExtension mit dem Namen "ProvideValue" überschreiben. Der von dieser Methode zurückgegebene Wert wird im Ziel der Markup-Erweiterung festgelegt.

Ich habe zunächst eine Erweiterungsmethode für Style erstellt, mit der zwei Stile zusammengeführt werden können. Der Code für diese Methode ist recht einfach:

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];
    }
}

Mit der obigen Logik wird der erste Stil so geändert, dass er alle Informationen aus dem zweiten enthält. Wenn es Konflikte gibt (z. B. haben beide Stile einen Setter für dieselbe Eigenschaft), gewinnt der zweite Stil. Beachten Sie, dass ich neben dem Kopieren von Stilen und Triggern auch die TargetType- und BasedOn-Werte sowie alle Ressourcen des zweiten Stils berücksichtigt habe. Für den TargetType des zusammengeführten Stils habe ich den Typ verwendet, der stärker abgeleitet ist. Wenn der zweite Stil einen BasedOn-Stil hat, füge ich seine Stilhierarchie rekursiv zusammen. Wenn es Ressourcen hat, kopiere ich sie in den ersten Stil. Wenn auf diese Ressourcen mit {StaticResource} verwiesen wird, werden sie statisch aufgelöst, bevor dieser Zusammenführungscode ausgeführt wird. Daher ist es nicht erforderlich, sie zu verschieben. Ich habe diesen Code hinzugefügt, falls wir DynamicResources verwenden.

Die oben gezeigte Erweiterungsmethode aktiviert die folgende Syntax:

style1.Merge(style2);

Diese Syntax ist nützlich, vorausgesetzt, ich habe Instanzen beider Stile in ProvideValue. Nun, ich nicht. Alles, was ich vom Konstruktor bekomme, ist eine Liste von Zeichenfolgenschlüsseln für diese Stile. Wenn die Konstruktorparameter Parameter unterstützen würden, hätte ich die folgende Syntax verwenden können, um die tatsächlichen Stilinstanzen abzurufen:

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

Das geht aber nicht Und selbst wenn die Parameterbeschränkung nicht vorhanden wäre, würden wir wahrscheinlich eine andere Einschränkung der Markup-Erweiterungen treffen, bei der wir die Eigenschaftselementsyntax anstelle der Attributsyntax verwenden müssten, um die statischen Ressourcen anzugeben, die ausführlich und umständlich sind (ich erkläre dies Fehler besser in einem früheren Blog-Beitrag ). Und selbst wenn diese beiden Einschränkungen nicht existieren würden, würde ich die Liste der Stile lieber nur mit ihren Namen schreiben - sie ist kürzer und einfacher zu lesen als eine StaticResource für jeden.

Die Lösung besteht darin, eine StaticResourceExtension mithilfe von Code zu erstellen. Bei einem Stilschlüssel vom Typ Zeichenfolge und einem Dienstanbieter kann ich StaticResourceExtension verwenden, um die tatsächliche Stilinstanz abzurufen. Hier ist die Syntax:

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

Jetzt haben wir alle Teile, die zum Schreiben der ProvideValue-Methode benötigt werden:

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

Hier ist ein vollständiges Beispiel für die Verwendung der MultiStyle-Markup-Erweiterung:

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

Geben Sie hier die Bildbeschreibung ein

Wilka
quelle
3
Wirklich gute Lösung, aber ich verstehe nicht, warum es keine einfache Lösung gibt, um 3 oder + Stil zusammenzuführen.
Herr Rubix
31

Sie können aber auch eine andere erweitern. Schauen Sie sich die BasedOn-Eigenschaft an

<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>
Arcturus
quelle
das war genug für mich. tnks!
David Lay
Dies funktioniert jedoch nur, wenn beide Stile vom gleichen Typ sind (XAML-Fehler: "Kann nur auf einem Stil mit dem
Zieltyp
17

WPF / XAML bietet diese Funktionalität nicht nativ, bietet jedoch die Erweiterbarkeit, mit der Sie tun können, was Sie möchten.

Wir hatten das gleiche Bedürfnis und haben schließlich unsere eigene XAML-Markup-Erweiterung (die wir "MergedStylesExtension" nannten) erstellt, damit wir aus zwei anderen Stilen einen neuen Stil erstellen können (der bei Bedarf wahrscheinlich mehrmals in einem verwendet werden kann Zeile, um von noch mehr Stilen zu erben).

Aufgrund eines WPF / XAML-Fehlers müssen wir die Eigenschaftselementsyntax verwenden, um es zu verwenden, aber ansonsten scheint es in Ordnung zu funktionieren. Z.B,

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

Ich habe kürzlich hier darüber geschrieben: http://swdeveloper.wordpress.com/2009/01/03/wpf-xaml-multiple-style-inheritance-and-markup-extensions/


quelle
3

Dies ist möglich, indem Sie eine Hilfsklasse erstellen, um Ihre Stile zu verwenden und zu verpacken. Der hier erwähnte CompoundStyle zeigt, wie es geht. Es gibt mehrere Möglichkeiten, aber die einfachste ist die folgende:

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

Hoffentlich hilft das.

Shahar Prish
quelle
2

Verwenden Sie AttachedPropertydiese Option , um mehrere Stile wie den folgenden Code festzulegen:

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

Verwendung:

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

Ergebnis:

Geben Sie hier die Bildbeschreibung ein

google dev
quelle
1

Wenn Sie keine bestimmten Eigenschaften berühren, können Sie alle Basis- und allgemeinen Eigenschaften für den Stil abrufen, dessen Zieltyp FrameworkElement wäre. Anschließend können Sie für jeden gewünschten Zieltyp bestimmte Geschmacksrichtungen erstellen, ohne alle allgemeinen Eigenschaften erneut kopieren zu müssen.

Greg
quelle
1

Sie können wahrscheinlich etwas Ähnliches erhalten, wenn Sie dies mithilfe eines StyleSelector auf eine Sammlung von Elementen anwenden. Ich habe dies verwendet, um ein ähnliches Problem bei der Verwendung unterschiedlicher Stile in TreeViewItems zu lösen, abhängig vom gebundenen Objekttyp im Baum. Möglicherweise müssen Sie die unten stehende Klasse geringfügig ändern, um sie an Ihren speziellen Ansatz anzupassen. Hoffentlich können Sie damit beginnen

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

Sie wenden dies dann so an

 <TreeView>
     <TreeView.ItemContainerStyleSelector
         <myassembly: MyTreeStyleSelector DefaultStyle = "{StaticResource DefaultItemStyle}"
                                         NewStyle = "{StaticResource NewItemStyle}" />
     </TreeView.ItemContainerStyleSelector>
  </ TreeView>
Dave
quelle
1

Manchmal können Sie dies erreichen, indem Sie Panels verschachteln. Angenommen, Sie haben einen Stil, der den Vordergrund ändert, und einen anderen, der die Schriftgröße ändert. Sie können den letzteren auf einen TextBlock anwenden und ihn in ein Raster einfügen, dessen Stil der erste ist. Dies kann hilfreich sein und in einigen Fällen der einfachste Weg sein, obwohl nicht alle Probleme gelöst werden.

Hillin
quelle
1

Wenn Sie SelectStyle überschreiben, können Sie die GroupBy-Eigenschaft über die folgende Reflektion abrufen:

    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;
    }
Sérgio Henrique
quelle
0

Wenn Sie versuchen, einen eindeutigen Stil auf nur ein einzelnes Element anzuwenden als Ergänzung zu einem Basisstil , gibt es eine völlig andere Möglichkeit, die meiner Meinung nach für lesbaren und wartbaren Code viel besser ist.

Es ist sehr häufig erforderlich, Parameter pro einzelnem Element anzupassen. Das Definieren von Wörterbuchstilen nur für die Verwendung mit einem Element ist äußerst umständlich zu pflegen oder sinnvoll. Lesen Sie hier meine Antwort auf meine eigene Frage, um zu vermeiden, dass Stile nur für einmalige Elementänderungen erstellt werden:

https://stackoverflow.com/a/54497665/1402498

JamesHoux
quelle