Domanda

In WPF, come applicherei più stili a a FrameworkElement?Ad esempio, ho un controllo che ha già uno stile.Ho anche uno stile separato che vorrei aggiungere senza spazzare via il primo.Gli stili hanno TargetType diversi, quindi non posso semplicemente estenderne uno con l'altro.

È stato utile?

Soluzione

Penso che la risposta semplice sia che non puoi fare (almeno in questa versione di WPF) quello che stai cercando di fare.

Cioè, per ogni particolare elemento può essere applicato un solo Stile.

Tuttavia, come altri hanno affermato sopra, forse puoi usarlo BasedOn per aiutarti.Dai un'occhiata al seguente pezzo di xaml sciolto.In esso vedrai che ho uno stile base che imposta una proprietà che esiste nella classe base dell'elemento a cui voglio applicare due stili.E, nel secondo stile che è basato sullo stile base, ho impostato un'altra proprietà.

Quindi, l'idea qui...è se puoi in qualche modo separare le proprietà che vuoi impostare ...in base alla gerarchia di ereditarietà dell'elemento su cui si desidera impostare più stili...potresti avere una soluzione alternativa.

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


Spero che questo ti aiuti.

Nota:

Una cosa in particolare da notare.Se cambi il TargetType nel secondo stile (nel primo set di xaml sopra) a ButtonBase, i due stili non vengono applicati.Tuttavia, controlla il seguente xaml di seguito per aggirare questa restrizione.Fondamentalmente, significa che devi dare una chiave allo stile e farvi riferimento con quella chiave.

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

Altri suggerimenti

Bea Stollnitz l'aveva fatto un buon post sul blog sull'utilizzo di un'estensione di markup per questo, sotto l'intestazione "Come posso impostare più stili in WPF?"

Quel blog ora è morto, quindi riproduco il post qui


WPF e Silverlight offrono entrambi la possibilità di derivare uno stile da un altro stile tramite la proprietà "BasedOn".Questa funzionalità consente agli sviluppatori di organizzare i propri stili utilizzando una gerarchia simile all'ereditarietà delle classi.Considera i seguenti stili:

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

Con questa sintassi, un Button che utilizza RedButtonStyle avrà la proprietà Foreground impostata su Red e la proprietà Margin impostata su 10.

Questa funzionalità è presente in WPF da molto tempo ed è nuova in Silverlight 3.

Cosa succede se vuoi impostare più di uno stile su un elemento?Né WPF né Silverlight forniscono una soluzione immediata a questo problema.Fortunatamente esistono modi per implementare questo comportamento in WPF, di cui parlerò in questo post del blog.

WPF e Silverlight utilizzano estensioni di markup per fornire proprietà con valori che richiedono una logica per essere ottenuti.Le estensioni di markup sono facilmente riconoscibili dalla presenza di parentesi graffe che le circondano in XAML.Ad esempio, l'estensione di markup {Binding} contiene la logica per recuperare un valore da un'origine dati e aggiornarlo quando si verificano modifiche;l'estensione di markup {StaticResource} contiene la logica per acquisire un valore da un dizionario risorse in base a una chiave.Fortunatamente per noi, WPF consente agli utenti di scrivere le proprie estensioni di markup personalizzate.Questa funzionalità non è ancora presente in Silverlight, quindi la soluzione in questo blog è applicabile solo a WPF.

Altri hanno scritto ottime soluzioni per unire due stili utilizzando le estensioni di markup.Tuttavia, volevo una soluzione che fornisse la possibilità di unire un numero illimitato di stili, il che è un po’ più complicato.

Scrivere un'estensione di markup è semplice.Il primo passaggio consiste nel creare una classe che deriva da MarkupExtension e utilizzare l'attributo MarkupExtensionReturnType per indicare che si intende che il valore restituito dall'estensione di markup sia di tipo Style.

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

Specificare gli input per l'estensione di markup

Vorremmo offrire agli utenti della nostra estensione di markup un modo semplice per specificare gli stili da unire.Esistono essenzialmente due modi in cui l'utente può specificare gli input per un'estensione di markup.L'utente può impostare proprietà o passare parametri al costruttore.Poiché in questo scenario l'utente necessita della possibilità di specificare un numero illimitato di stili, il mio primo approccio è stato quello di creare un costruttore che accettasse un numero qualsiasi di stringhe utilizzando la parola chiave "params":

public MultiStyleExtension(params string[] inputResourceKeys)
{
}

Il mio obiettivo era quello di essere in grado di scrivere gli input come segue:

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

Notare la virgola che separa le diverse chiavi di stile.Sfortunatamente, le estensioni di markup personalizzate non supportano un numero illimitato di parametri del costruttore, quindi questo approccio genera un errore di compilazione.Se avessi saputo in anticipo quanti stili volevo unire, avrei potuto utilizzare la stessa sintassi XAML con un costruttore che prendeva il numero desiderato di stringhe:

public MultiStyleExtension(string inputResourceKey1, string inputResourceKey2)
{
}

Come soluzione alternativa, ho deciso di fare in modo che il parametro del costruttore accettasse una singola stringa che specifica i nomi degli stili separati da spazi.La sintassi non è poi così male:

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

Calcolo dell'output dell'estensione di markup

Per calcolare l'output di un'estensione di markup, dobbiamo sovrascrivere un metodo di MarkupExtension chiamato "ProvideValue".Il valore restituito da questo metodo verrà impostato nella destinazione dell'estensione di markup.

Ho iniziato creando un metodo di estensione per Style che sappia unire due stili.Il codice per questo metodo è abbastanza semplice:

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

Con la logica di cui sopra, il primo stile viene modificato per includere tutte le informazioni del secondo.Se ci sono conflitti (es.entrambi gli stili hanno un setter per la stessa proprietà), vince il secondo stile.Si noti che oltre a copiare stili e trigger, ho anche preso in considerazione i valori TargetType e BasedOn, nonché le eventuali risorse che il secondo stile potrebbe avere.Per TargetType dello stile unito, ho utilizzato il tipo più derivato.Se il secondo stile ha uno stile BasedOn, unisco ricorsivamente la sua gerarchia di stili.Se ha risorse, le copio nel primo stile.Se si fa riferimento a tali risorse utilizzando {StaticResource}, vengono risolte staticamente prima dell'esecuzione di questo codice di unione e pertanto non è necessario spostarle.Ho aggiunto questo codice nel caso in cui utilizziamo DynamicResources.

Il metodo di estensione mostrato sopra abilita la seguente sintassi:

style1.Merge(style2);

Questa sintassi è utile a condizione che disponga di istanze di entrambi gli stili all'interno di ProvideValue.Beh, non lo so.Tutto quello che ottengo dal costruttore è un elenco di chiavi stringa per quegli stili.Se ci fosse il supporto per i parametri nei parametri del costruttore, avrei potuto usare la seguente sintassi per ottenere le istanze di stile effettive:

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

Ma questo non funziona.E anche se la limitazione params non esistesse, probabilmente troveremmo un'altra limitazione delle estensioni di markup, dove dovremmo usare la sintassi degli elementi proprietà invece della sintassi degli attributi per specificare le risorse statiche, che è verbosa e macchinosa (lo spiego bug meglio in a post precedente del blog).E anche se entrambe queste limitazioni non esistessero, preferirei comunque scrivere l’elenco degli stili utilizzando solo i loro nomi: è più breve e più semplice da leggere rispetto a StaticResource per ciascuno di essi.

La soluzione è creare una StaticResourceExtension utilizzando il codice.Data una chiave di stile di tipo stringa e un fornitore di servizi, posso utilizzare StaticResourceExtension per recuperare l'istanza di stile effettiva.Ecco la sintassi:

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

Ora abbiamo tutti i pezzi necessari per scrivere il metodo 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;
}

Ecco un esempio completo dell'utilizzo dell'estensione di markup 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" />

enter image description here

Ma puoi estendere da un altro...dai un'occhiata alla proprietà 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>

WPF/XAML non fornisce questa funzionalità in modo nativo, ma fornisce l'estensibilità per consentirti di fare ciò che desideri.

Ci siamo imbattuti nella stessa esigenza e abbiamo finito per creare la nostra estensione di markup XAML (che abbiamo chiamato "MergedStylesExtension") per permetterci di creare un nuovo stile da altri due stili (che, se necessario, potrebbe probabilmente essere utilizzato più volte in un file riga per ereditare da ancora più stili).

A causa di un bug WPF/XAML, dobbiamo utilizzare la sintassi dell'elemento proprietà per usarlo, ma a parte questo sembra funzionare bene.Per esempio.,

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

Ne ho scritto recentemente qui:http://swdeveloper.wordpress.com/2009/01/03/wpf-xaml-multiple-style-inheritance-and-markup-extensions/

Ciò è possibile creando una classe helper per utilizzare e racchiudere i tuoi stili.CompoundStyle menzionato Qui mostra come farlo.Esistono diversi modi, ma il più semplice è procedere come segue:

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

Spero che aiuti.

Utilizzo AttachedProperty per impostare più stili come il seguente codice:

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

Utilizzo:

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

Risultato:

enter image description here

se non stai toccando alcuna proprietà specifica, puoi ottenere tutte le proprietà di base e comuni nello stile il cui tipo di destinazione sarebbe FrameworkElement.quindi, puoi creare caratteristiche specifiche per ogni tipo di target di cui hai bisogno, senza bisogno di copiare nuovamente tutte quelle proprietà comuni.

Probabilmente puoi ottenere qualcosa di simile se lo applichi a una raccolta di elementi utilizzando uno StyleSelector, l'ho usato per affrontare un problema simile nell'utilizzo di stili diversi su TreeViewItems a seconda del tipo di oggetto associato nell'albero.Potrebbe essere necessario modificare leggermente la lezione seguente per adattarla al tuo approccio particolare, ma speriamo che questo ti consenta di iniziare

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

Quindi applicalo in questo modo

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

A volte puoi avvicinarti a questo annidando i pannelli.Supponiamo che tu abbia uno stile che cambia primo piano e un altro cambia FontSize, puoi applicare quest'ultimo su un TextBlock e inserirlo in una griglia il cui stile è il primo.Questo potrebbe aiutare e potrebbe essere il modo più semplice in alcuni casi, anche se non risolverà tutti i problemi.

Quando esegui l'override di SelectStyle puoi ottenere la proprietà GroupBy tramite la riflessione come di seguito:

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

Se stai cercando di applicare uno stile unico a un solo elemento come aggiunta a uno stile di base, esiste un modo completamente diverso per farlo che è IMHO molto migliore per il codice leggibile e gestibile.

È estremamente comune la necessità di modificare i parametri per ogni singolo elemento.Definire gli stili del dizionario solo per l'uso su un elemento è estremamente complicato da mantenere o dargli un senso.Per evitare di creare stili solo per modifiche una tantum agli elementi, leggi la mia risposta alla mia domanda qui qui:

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

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top