Jak zastosować wiele stylów w WPF

W WPF, jak zastosować wiele stylów do FrameworkElement? Na przykład, mam kontrolę, która już ma styl. Mam też osobny styl, który chciałbym dodać do niego, nie zdmuchując pierwszego. Style mają różne TargetTypes, więc nie mogę po prostu rozszerzyć jeden z drugim.

Author: MojoFilter, 2008-08-19

10 answers

Myślę, że prosta odpowiedź jest taka, że nie możesz zrobić (przynajmniej w tej wersji WPF) tego, co próbujesz zrobić.

oznacza to, że dla każdego konkretnego elementu można zastosować tylko jeden styl.

Jednak, jak inni stwierdzili powyżej, może możesz użyć BasedOn, aby ci pomóc. Sprawdź poniższy kawałek luźnego xaml. W nim zobaczysz, że mam styl bazowy, który ustawia właściwość, która istnieje na klasie bazowej elementu, który chcę zastosować dwa style za. I w drugim stylu, który jest oparty na stylu bazowym, ustawiam inną właściwość.

Więc, pomysł tutaj ... jest, jeśli możesz jakoś oddzielić właściwości, które chcesz ustawić ... zgodnie z hierarchią dziedziczenia elementu, na którym chcesz ustawić wiele stylów ... możesz mieć obejście.

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


Mam nadzieję, że to pomoże.

Uwaga:

Na szczególną uwagę zasługuje jedna rzecz. Jeśli zmienisz TargetType w drugim stylu (w pierwszym zestawie xaml powyżej) do ButtonBase, oba style nie są stosowane. Sprawdź jednak poniższe xaml poniżej, aby obejść to ograniczenie. Zasadniczo oznacza to, że musisz nadać stylowi klucz i odnieść się do niego za pomocą tego klucza.
<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
Author: cplotts,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-14 10:04:22

Bea Stollnitz miała dobry post na blogu o używaniu rozszerzenia znaczników w tym celu, pod nagłówkiem "jak Mogę ustawić wiele stylów w WPF?"

Ten blog jest teraz martwy, więc reprodukuję post tutaj


WPF i Silverlight oferują możliwość wyprowadzenia stylu z innego stylu poprzez właściwość "BasedOn". Ta funkcja umożliwia programistom organizowanie ich stylów za pomocą hierarchii podobnej do dziedziczenia klas. Rozważ następujące kwestie style:
<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>

W tej składni przycisk, który używa RedButtonStyle, będzie miał właściwość Foreground ustawioną na Red i właściwość marginesu ustawioną na 10.

[13]}Ta funkcja istnieje w WPF od dawna i jest nowa w Silverlight 3.

Co zrobić, jeśli chcesz ustawić więcej niż jeden styl na elemencie? Ani WPF, ani Silverlight nie zapewniają rozwiązania tego problemu po wyjęciu z pudełka. Na szczęście istnieją sposoby implementacji tego zachowania w WPF, które omówię w ten wpis na blogu.

WPF i Silverlight używają rozszerzeń znaczników, aby zapewnić właściwości z wartościami, które wymagają pewnej logiki do uzyskania. Rozszerzenia znaczników są łatwo rozpoznawalne dzięki obecności nawiasów klamrowych otaczających je w XAML. Na przykład rozszerzenie znaczników {Binding} zawiera logikę pobierania wartości ze źródła danych i aktualizowania jej w przypadku wystąpienia zmian; rozszerzenie znaczników {StaticResource} zawiera logikę pobierania wartości ze słownika zasobów opartego na kluczu. Na szczęście dla nas, WPF pozwala użytkownikom pisać własne niestandardowe rozszerzenia znaczników. Ta funkcja nie jest jeszcze obecna w Silverlight, więc rozwiązanie na tym blogu ma zastosowanie tylko do WPF.

Inni napisali świetne rozwiązania do łączenia dwóch stylów za pomocą rozszerzeń znaczników. Jednak chciałem rozwiązania, które zapewniło możliwość łączenia nieograniczonej liczby stylów, co jest nieco trudniejsze.

Pisanie rozszerzenia znaczników jest proste. Pierwszym krokiem jest utworzenie klasy to pochodzi z markup extension i używa atrybutu Markup Extensionreturntype, aby wskazać, że wartość zwracana z rozszerzenia znaczników ma być w stylu type.

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

Określanie wejść do rozszerzenia znaczników

Chcielibyśmy dać użytkownikom naszego rozszerzenia znaczników prosty sposób na określenie stylów do scalenia. Istnieją zasadniczo dwa sposoby, na jakie użytkownik może określić dane wejściowe rozszerzenia znaczników. Użytkownik może ustawić właściwości lub przekazać parametry do konstruktor. Ponieważ w tym scenariuszu użytkownik potrzebuje możliwości określenia nieograniczonej liczby stylów, moim pierwszym podejściem było stworzenie konstruktora, który przyjmie dowolną liczbę ciągów za pomocą słowa kluczowego" params": {]}

public MultiStyleExtension(params string[] inputResourceKeys)
{
}

Moim celem było umieć pisać wejścia w następujący sposób:

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

Zwróć uwagę na przecinek oddzielający różne klucze stylu. Niestety, niestandardowe rozszerzenia znaczników nie obsługują nieograniczonej liczby parametrów konstruktora, więc takie podejście skutkuje błąd kompilacji. Gdybym wiedział wcześniej, ile stylów chcę scalić, mógłbym użyć tej samej składni XAML z konstruktorem przyjmującym żądaną liczbę łańcuchów:

public MultiStyleExtension(string inputResourceKey1, string inputResourceKey2)
{
}

Jako obejście postanowiłem, że parametr konstruktora przyjmie pojedynczy ciąg znaków, który określa nazwy stylów oddzielone spacjami. Składnia nie jest taka zła:

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

Obliczanie wyjścia rozszerzenia znaczników

Aby obliczyć wynik rozszerzenia znaczników, musimy nadpisuje metodę z MarkupExtension o nazwie "ProvideValue". Wartość zwracana z tej metody zostanie ustawiona w celu rozszerzenia znaczników.

Zacząłem od stworzenia metody rozszerzenia stylu, która wie, jak połączyć dwa style. Kod tej metody jest dość prosty:

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

Z logiką powyżej, pierwszy styl jest modyfikowany tak, aby zawierał wszystkie informacje z drugiego. Jeśli występują konflikty (np. oba style mają Ustawiacz dla tej samej właściwości), drugi styl wygrywa. Zauważ, że oprócz kopiowania stylów i wyzwalaczy, wziąłem również pod uwagę wartości TargetType i BasedOn, a także wszelkie zasoby, które może mieć drugi styl. Dla TargetType scalonego stylu użyłem tego, który typ jest bardziej Pochodny. Jeśli drugi styl ma styl BasedOn, scalam rekurencyjnie jego hierarchię stylów. Jeśli ma zasoby, kopiuję je do pierwszego stylu. Jeśli te zasoby są odwoływane za pomocą {StaticResource}, są statycznie rozwiązywane przed tym kod scalający jest wykonywany, dlatego nie jest konieczne ich przenoszenie. Dodałem ten kod na wypadek, gdybyśmy używali DynamicResources.

Przedstawiona powyżej metoda rozszerzenia umożliwia następującą składnię:

style1.Merge(style2);

Ta składnia jest przydatna pod warunkiem, że mam instancje obu stylów w ProvideValue. Od konstruktora dostaję tylko listę kluczy łańcuchowych dla tych stylów. Jeśli w parametrach konstruktora było wsparcie dla params, mogłem użyć następującego składnia, aby uzyskać rzeczywiste instancje stylu:

<Button Style="{local:MultiStyle {StaticResource BigButtonStyle}, {StaticResource GreenButtonStyle}}" … />
public MultiStyleExtension(params Style[] styles)
{
}
Ale to nie działa. I nawet jeśli ograniczenie params nie istniało, prawdopodobnie trafilibyśmy na inne ograniczenie rozszerzeń znaczników, gdzie musielibyśmy używać składni właściwości-element zamiast składni atrybutów, aby określić statyczne zasoby, co jest gadatliwe i kłopotliwe(wyjaśniam ten błąd lepiej w poprzedni wpis na blogu ). I nawet gdyby oba te ograniczenia nie istniały, to i tak wolałbym napisać listę stylów używających tylko ich nazw – jest krótszy i prostszy do odczytania niż StaticResource dla każdego z nich.

Rozwiązaniem jest utworzenie rozszerzenia StaticResourceExtension za pomocą kodu. Biorąc pod uwagę klucz stylu typu string i dostawcę usług, mogę użyć StaticResourceExtension, aby pobrać rzeczywistą instancję stylu. Oto składnia:

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

Teraz mamy wszystkie elementy potrzebne do napisania metody 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;
}

Oto kompletny przykład użycia Rozszerzenie znaczników 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" />

Tutaj wpisz opis obrazka

 37
Author: Wilka,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-23 21:13:51

Ale można rozszerzyć od innego.. zapoznaj się z właściwością 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
Author: Arcturus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-14 10:05:28

WPF / XAML nie zapewnia tej funkcjonalności natywnie, ale zapewnia rozszerzalność, aby umożliwić Ci robienie tego, co chcesz.

Natknęliśmy się na tę samą potrzebę i ostatecznie stworzyliśmy własne rozszerzenie znaczników XAML (które nazwaliśmy "MergedStylesExtension"), aby umożliwić nam tworzenie nowego stylu z dwóch innych stylów (które, jeśli zajdzie taka potrzeba, prawdopodobnie mogłyby być używane wiele razy z rzędu, aby dziedziczyć z jeszcze większej liczby stylów).

Ze względu na błąd WPF/XAML, musimy użyć składni property element do użyj go, ale poza tym wydaje się działać ok. Np.,

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

Ostatnio pisałem o tym tutaj: http://swdeveloper.wordpress.com/2009/01/03/wpf-xaml-multiple-style-inheritance-and-markup-extensions/

 17
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-04 04:09:45

Jest to możliwe poprzez utworzenie klasy pomocniczej do użycia i zawijania stylów. CompoundStyle wymienione tutaj pokazuje jak to zrobić. Istnieje wiele sposobów, ale najprostszym jest wykonanie następujących czynności:

<TextBlock Text="Test"
    local:CompoundStyle.StyleKeys="headerStyle,textForMessageStyle,centeredStyle"/>
Mam nadzieję, że to pomoże.
 3
Author: Shahar Prish,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-04 15:26:09

Jeśli nie dotykasz żadnych konkretnych właściwości, możesz uzyskać wszystkie podstawowe i wspólne właściwości do stylu, którego docelowym typem będzie Framkelement. następnie możesz tworzyć określone smaki dla każdego potrzebnego typu docelowego, bez potrzeby ponownego kopiowania wszystkich tych wspólnych właściwości.

 1
Author: Greg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-16 04:26:49

Prawdopodobnie możesz uzyskać coś podobnego, jeśli zastosujesz to do kolekcji przedmiotów za pomocą selektora stylów, użyłem tego, aby podejść do podobnego problemu w używaniu różnych stylów na TreeViewItems w zależności od typu obiektu związanego w drzewie. Być może będziesz musiał nieco zmodyfikować poniższą klasę, aby dostosować się do twojego konkretnego podejścia, ale mam nadzieję, że to pomoże Ci zacząć

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

Następnie zastosuj to tak

 <TreeView>
     <TreeView.ItemContainerStyleSelector
         <myassembly:MyTreeStyleSelector DefaultStyle="{StaticResource DefaultItemStyle}"
                                         NewStyle="{StaticResource NewItemStyle}" />
     </TreeView.ItemContainerStyleSelector>
  </TreeView>
 1
Author: Dave,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-15 11:22:59

Czasami można podejść do tego przez zagnieżdżanie paneli. Powiedzmy, że masz styl, który zmienia pierwszy plan, a inny zmienia FontSize, możesz zastosować ten drugi na bloku tekstowym i umieścić go w siatce, której styl jest pierwszy. Może to pomóc i może być najprostszym sposobem w niektórych przypadkach, choć nie rozwiąże wszystkich problemów.

 1
Author: hillin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-11-24 18:55:52

Po nadpisaniu SelectStyle możesz uzyskać Właściwość GroupBy poprzez odbicie jak poniżej:

    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
Author: Sérgio Henrique,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-05 03:31:31

Użyj AttachedProperty, aby ustawić wiele stylów, takich jak następujący kod:

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>

Wynik:

Tutaj wpisz opis obrazka

 1
Author: google dev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-12 04:56:24