Frage

In WPF würde, wie ich verschiedene Stile zu einer FrameworkElement?Zum Beispiel habe ich ein Steuerelement, das bereits einen Stil.Ich habe auch einen separaten Stil, die ich hinzufügen möchte, ohne es blies der erste.Die Stile haben verschiedene TargetTypes, so kann ich nicht nur verlängern die einen mit den anderen.

War es hilfreich?

Lösung

Ich denke, die einfache Antwort ist, dass Sie nicht tun können (zumindest in dieser version der WPF), was Sie zu tun versuchen.

Das heißt, für jedes element nur ein Stil angewendet werden kann.

Jedoch, wie andere oben gesagt haben, vielleicht können Sie verwenden BasedOn um Ihnen zu helfen.Schauen Sie sich das folgende Stück lose xaml.In ihm werden Sie sehen, dass ich eine Basis-Stil, ist das festlegen einer Eigenschaft, die vorhanden ist, auf der Basis-Klasse für das element, das ich anwenden möchten zwei Stile.Und, im zweiten Stil basiert auf der Basis-Stil, habe ich eine andere Eigenschaft.

Also, die Idee, hier ...ist, wenn Sie irgendwie trennen Sie die Eigenschaften, die Sie festlegen möchten ...nach der Vererbungshierarchie von dem element, das Sie einstellen möchten mehrere Stile auf ...haben Sie vielleicht einen workaround.

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

Eine Sache, die besonders zu beachten.Wenn Sie ändern die TargetType in der zweiten Stil (im ersten Satz von xaml oben) ButtonBase, die beiden Stile nicht angewendet werden.Aber, schauen Sie sich die folgende xaml-Code unten, um zu bekommen um diese Einschränkung auf.Das bedeutet im Grunde, müssen Sie geben Sie dem Stil einen Schlüssel, und verweisen Sie mit dieser Taste.

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

Andere Tipps

Bea Stollnitz hatte ein guter blog-Beitrag über die Verwendung einer markup-Erweiterung unter der überschrift "Wie kann ich mehrere Stile in WPF?"

Das blog ist jetzt tot, also bin ich der Reproduktion der post hier


WPF-und Silverlight-beides bieten die Möglichkeit, daraus einen Stil aus einem anderen Stil durch den "BasedOn" - Eigenschaft.Dieses feature ermöglicht Entwicklern, sich zu organisieren, Ihre Stile über eine Hierarchie, ähnlich wie Klasse, Vererbung.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>

Mit dieser syntax, eine Schaltfläche, die verwendet RedButtonStyle wird Ihre Vordergrund-Eigenschaft auf Rot und der Margin-Eigenschaft auf 10 gesetzt.

Diese Funktion wurde um in WPF für eine lange Zeit, und es ist neu in Silverlight 3.

Was ist, wenn Sie festlegen möchten mehr als einen Stil auf ein element?Weder WPF noch Silverlight bieten eine Lösung für dieses problem aus der box.Glücklicherweise gibt es Möglichkeiten, um dieses Verhalten zu implementieren, die in WPF, die werde ich in diesem blog-post.

WPF und Silverlight verwenden Sie markup-Erweiterungen, um die Eigenschaften mit den Werten, erfordert es einige Logik zu erhalten.Markup-Erweiterungen sind leicht erkennbar durch die Anwesenheit von geschweiften Klammern um Sie herum in XAML.Für Beispiel, die {Binding} markup extension enthält die Logik zum abrufen eines Wertes aus einer Datenquelle und aktualisieren, wenn änderungen auftreten;die {StaticResource} markup extension enthält die Logik, um schnappen Sie sich ein Wert aus einer Ressource-Wörterbuch basiert auf einer Taste.Zum Glück für uns, WPF ermöglicht die Benutzer zu schreiben Ihre eigenen benutzerdefinierten markup-Erweiterungen.Diese Funktion ist noch nicht in Silverlight, so ist die Lösung in diesem blog gilt nur für WPF.

Andere geschrieben haben tolle Lösungen zum Zusammenführen von zwei Stile die Verwendung von markup extensions.Allerdings wollte ich eine Lösung, die die Fähigkeit zum Zusammenführen einer unbegrenzten Anzahl von Stilen, die ist ein wenig komplizierter.

Schreiben Sie eine markup-Erweiterung ist einfach.Der erste Schritt ist das erstellen einer Klasse, die sich von MarkupExtension, und verwenden Sie die MarkupExtensionReturnType-Attribut, um anzugeben, dass Sie beabsichtigen, den zurückgegebenen Wert aus Ihren markup-Erweiterung-Typ-Stil.

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

Die Angabe Eingänge, um die markup-Erweiterung

Wir möchten zu geben Sie die Benutzer unserer markup-Erweiterung eine einfache Möglichkeit zum angeben der Stile, die zusammengeführt werden.Es gibt im wesentlichen zwei Wege, auf denen der Benutzer festlegen kann, um Eingänge zu einem markup-Erweiterung.Der Benutzer kann festlegen von Eigenschaften oder Parameter an den Konstruktor übergeben wird.Da in diesem Szenario muss der Benutzer die Fähigkeit zu geben Sie eine unbegrenzte Anzahl von Stilen, mein Erster Ansatz war es, erstellen Sie einen Konstruktor, eine beliebige Anzahl von strings mit dem "params" keyword:

public MultiStyleExtension(params string[] inputResourceKeys)
{
}

Mein Ziel war es, in der Lage zu schreiben Sie die Eingänge wie folgt:

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

Beachten Sie das Komma trennt die verschiedene Stil-Tasten.Leider benutzerdefinierte markup-Erweiterungen nicht unterstützt eine unbegrenzte Anzahl von Konstruktor-Parameter, so dass dieser Ansatz führt zu einem Kompilierungsfehler.Wenn ich wüsste im Voraus, wie viele Stile habe ich verknüpfen möchte, hätte ich die gleichen XAML-syntax mit einem Konstruktor, wobei die gewünschte Anzahl der Saiten:

public MultiStyleExtension(string inputResourceKey1, string inputResourceKey2)
{
}

Als workaround habe ich beschlossen, die constructor-parameter nehmen Sie eine einzelne Zeichenfolge, die den style-Namen durch Leerzeichen getrennt.Die syntax ist nicht zu 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 Leistung der markup-Erweiterung

Zur Berechnung der Ausgabe eines markup-Erweiterung, brauchen wir eine Methode überschreiben von MarkupExtension genannt "ProvideValue".Der Rückgabewert dieser Methode wird im Ziel von den markup-Erweiterung.

Ich begann, indem eine extension-Methode für Stil, der weiß, wie man Zusammenführen von zwei Stile.Der code für diese Methode ist ganz 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 Logik oben, der erste Stil ist geändert, um alle Informationen aus der zweiten.Wenn es Konflikte gibt (z.B.beide Stile haben einen setter für die gleiche Eigenschaft), die zweite-Stil gewinnt.Beachten Sie, dass neben dem kopieren von Stilen und löst, auch ich nahm in Konto die TargetType und basiert auf Werten, sowie alle Ressourcen, die der zweiten Stil haben können.Für die TargetType der zusammengeführten Stil, die ich verwendet, je nachdem welcher Typ ist mehr abgeleitet werden.Wenn der zweite Stil verfügt über eine BasedOn Stil, ich Zusammenführen Ihrer Hierarchie von Stilen rekursiv.Wenn es über Ressourcen verfügt, kopiere ich Sie an den ersten Stil.Wenn diese Ressourcen sind bezeichnet mit {StaticResource}, Sie sind statisch aufgelöst werden, bevor das Zusammenführen von code ausgeführt wird, und daher ist es nicht notwendig, um Sie zu verschieben.Ich fügte diesen code in Fall verwenden wir DynamicResources.

Die Erweiterung oben gezeigten Methode können Sie die folgende syntax:

style1.Merge(style2);

Diese syntax ist nützlich, vorausgesetzt, habe ich Instanzen der beiden Stile innerhalb ProvideValue.Gut, ich glaube nicht.Alle, die ich von der Konstruktor ist eine Liste von string-Schlüssel für diejenigen Stile.Wenn es Unterstützung für die Parameter in den Konstruktor-Parameter, hätte ich die folgende syntax, um die tatsächliche Stil-Instanzen:

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

Aber das funktioniert nicht.Und auch wenn die params-Beschränkung nicht gäbe, würden wir wahrscheinlich auf eine weitere Einschränkung von markup extensions, wo wir müssten property-element-syntax anstelle von Attribut-syntax, um anzugeben, die statische Ressourcen, die ist ausführlich und umständlich (ich erkläre diese Fehler besser ist, in einem vorherigen blog-post).Und selbst wenn diese Beschränkungen nicht gäbe, würde ich immer noch eher schreiben der Liste der Formatvorlagen können Sie nur Ihren Namen – Sie ist kürzer und einfacher zu Lesen als eine StaticResource für jeden.

Die Lösung ist das erstellen einer StaticResourceExtension mit code.Gegeben ein Stil Schlüssel vom Typ string und einen service-provider, die ich verwenden kann StaticResourceExtension zum abrufen der eigentlichen Stil Beispiel.Hier ist die syntax:

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

Jetzt haben wir alle die Teile benötigt zu schreiben, die ProvideValue-Methode:

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

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

enter image description here

Aber Sie können Sie erweitern von anderen..werfen Sie einen Blick auf die BasedOn-Eigenschaft

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

WPF/XAML nicht bieten diese Funktionalität nativ, aber es stellt die Erweiterbarkeit zu ermöglichen Sie zu tun, was Sie wollen.

Wir liefen in die gleiche not, und am Ende schaffen unsere eigenen XAML-Markup-Erweiterung (die wir als "MergedStylesExtension") zu erlauben uns zu schaffen eine neue Stil von zwei anderen Arten (die, wenn nötig, könnte wahrscheinlich verwendet werden mehrere mal in Folge, Erben, sogar noch mehr Stile).

Aufgrund einer WPF/XAML-Fehler, die wir verwenden müssen, die property-element-syntax zu verwenden, aber andere als, dass es scheint zu funktionieren ok.E. g.,

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

Vor kurzem schrieb ich darüber hier:http://swdeveloper.wordpress.com/2009/01/03/wpf-xaml-multiple-style-inheritance-and-markup-extensions/

Dies ist möglich durch die Schaffung einer Helfer-Klasse zu verwenden, und wickeln Sie Ihre Stile.CompoundStyle erwähnt hier zeigt, wie es geht.Es gibt mehrere Möglichkeiten, aber die einfachste ist Folgendes zu tun:

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

Hoffe, das hilft.

Verwenden AttachedProperty mehrere Stile wie der folgende code:

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>

Ergebnis:

enter image description here

wenn Sie sind nicht berühren die spezifischen Eigenschaften, Sie können alle Basis-und Allgemeine Eigenschaften der Stil ist Ziel-Typ wäre FrameworkElement.dann, Sie können bestimmte Geschmacksrichtungen für jeden Gegner-Typen, die Sie benötigen, ohne die Notwendigkeit zu kopieren, alle jene Allgemeinen Eigenschaften wieder.

Sie können wahrscheinlich etwas ähnliches, wenn dies für eine Sammlung von Elementen, die durch die Nutzung der StyleSelector, ich habe diesen Ansatz ein ähnliches problem bei der Nutzung unterschiedlicher Stile, die auf TreeViewItems je nach gebundenen Objekt-Typ in der Baumstruktur.Sie müssen möglicherweise ändern Sie den Kurs etwas anpassen, um Ihren besonderen Ansatz, aber hoffentlich wird dies Ihnen den Einstieg

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

Dann wenden Sie diese als so

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

Manchmal kann man diesen Ansatz durch die Verschachtelung von Platten.Sagen Sie, Sie haben einen Stil, die änderungen Vordergrund und anderen änderungen, die Schriftgröße, die Sie anwenden können, das letztere auf einen TextBlock, und legen Sie Sie in ein Raster, das seinen Stil ist die erste.Dies könnte helfen-und der einfachste Weg sein kann, in einigen Fällen, obwohl es wird nicht alle Probleme lösen.

Wenn Sie überschreiben SelectStyle, die Sie bekommen können GroupBy Eigenschaft über die Reflexion, wie unten:

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

Wenn Sie versuchen, die Anwendung eines einzigartigen Stil, um nur ein einzelnes element als Ergänzung zu den Basis-Stil, es ist eine ganz andere Art und Weise das zu tun, ist IMHO viel besser lesbaren und wartbaren code.

Es ist sehr üblich, müssen tweak Parameter pro element.Definieren Wörterbuch-styles nur für den Einsatz auf ein-element ist äußerst umständlich zu pflegen und Sinn.Um zu vermeiden, Stile nur für one-off element tweaks, Lesen Sie meine Antwort auf meine eigene Frage hier:

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

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top