Вопрос

Как в WPF применить несколько стилей к FrameworkElement?Например, у меня есть элемент управления, у которого уже есть стиль.У меня также есть отдельный стиль, который я хотел бы добавить к нему, не отбрасывая при этом первый.У стилей разные TargetTypes, поэтому я не могу просто расширить один другим.

Это было полезно?

Решение

Я думаю, что простой ответ заключается в том, что вы не можете (по крайней мере, в этой версии WPF) делать то, что пытаетесь сделать.

То есть к любому конкретному элементу можно применить только один Стиль.

Однако, как уже говорили выше, возможно, вы можете использовать BasedOn чтобы помочь вам.Ознакомьтесь со следующим фрагментом свободного xaml.В нем вы увидите, что у меня есть базовый стиль, который устанавливает свойство, существующее в базовом классе элемента, к которому я хочу применить два стиля.А во втором стиле, основанном на базовом стиле, я установил еще одно свойство.

Итак, идея здесь...это если вы можете как-то разделить свойства, которые хотите установить...в соответствии с иерархией наследования элемента, для которого вы хотите установить несколько стилей...у вас может быть обходной путь.

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


Надеюсь это поможет.

Примечание:

Особо следует отметить одну вещь.Если вы измените TargetType во втором стиле (в первом наборе xaml выше) для ButtonBase, два стиля не применяются.Однако ознакомьтесь со следующим xaml ниже, чтобы обойти это ограничение.По сути, это означает, что вам нужно дать стилю ключ и ссылаться на него с помощью этого ключа.

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

Другие советы

Беа Стольниц имела хороший пост в блоге об использовании для этого расширения разметки в разделе «Как установить несколько стилей в WPF?»

Этот блог уже мертв, поэтому я воспроизвожу этот пост здесь.


И WPF, и Silverlight предлагают возможность получить стиль из другого стиля с помощью свойства «BasedOn».Эта функция позволяет разработчикам организовывать свои стили, используя иерархию, аналогичную наследованию классов.Рассмотрим следующие стили:

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

При таком синтаксисе для кнопки, использующей RedButtonStyle, для свойства Foreground установлено значение Red, а для свойства Margin — 10.

Эта функция уже давно присутствует в WPF и является новой в Silverlight 3.

Что делать, если вы хотите установить для элемента более одного стиля?Ни WPF, ни Silverlight не предоставляют готового решения этой проблемы.К счастью, есть способы реализовать такое поведение в WPF, о которых я расскажу в этой статье блога.

WPF и Silverlight используют расширения разметки для предоставления свойствам значений, для получения которых требуется определенная логика.Расширения разметки легко узнать по наличию фигурных скобок, окружающих их в XAML.Например, расширение разметки {Binding} содержит логику для извлечения значения из источника данных и его обновления при возникновении изменений;Расширение разметки {StaticResource} содержит логику для получения значения из словаря ресурсов на основе ключа.К счастью для нас, WPF позволяет пользователям писать свои собственные расширения разметки.Эта функция пока отсутствует в Silverlight, поэтому решение в этом блоге применимо только к WPF.

Другие написали отличные решения для объединения двух стилей с помощью расширений разметки.Однако мне нужно было решение, обеспечивающее возможность объединения неограниченного количества стилей, что немного сложнее.

Написать расширение разметки несложно.Первым шагом является создание класса, производного от MarkupExtension, и использование атрибута MarkupExtensionReturnType, чтобы указать, что вы хотите, чтобы значение, возвращаемое из вашего расширения разметки, имело тип Style.

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

Указание входных данных для расширения разметки

Мы хотели бы предоставить пользователям нашего расширения разметки простой способ указать стили, которые необходимо объединить.По сути, существует два способа, с помощью которых пользователь может указать входные данные для расширения разметки.Пользователь может устанавливать свойства или передавать параметры конструктору.Поскольку в этом сценарии пользователю нужна возможность указывать неограниченное количество стилей, мой первый подход заключался в создании конструктора, который принимает любое количество строк, используя ключевое слово «params»:

public MultiStyleExtension(params string[] inputResourceKeys)
{
}

Моя цель состояла в том, чтобы иметь возможность писать входные данные следующим образом:

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

Обратите внимание на запятую, разделяющую разные клавиши стиля.К сожалению, пользовательские расширения разметки не поддерживают неограниченное количество параметров конструктора, поэтому такой подход приводит к ошибке компиляции.Если бы я заранее знал, сколько стилей хочу объединить, я мог бы использовать тот же синтаксис XAML с конструктором, принимающим желаемое количество строк:

public MultiStyleExtension(string inputResourceKey1, string inputResourceKey2)
{
}

В качестве обходного пути я решил, чтобы параметр конструктора принимал одну строку, определяющую имена стилей, разделенные пробелами.Синтаксис не так уж и плох:

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

Вычисление вывода расширения разметки

Чтобы вычислить выходные данные расширения разметки, нам нужно переопределить метод из MarkupExtension под названием «ProvideValue».Значение, возвращаемое этим методом, будет установлено в цели расширения разметки.

Я начал с создания метода расширения Style, который умеет объединять два стиля.Код этого метода довольно прост:

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

Используя приведенную выше логику, первый стиль модифицируется, чтобы включить всю информацию из второго.Если есть конфликты (напр.оба стиля имеют установщик для одного и того же свойства), побеждает второй стиль.Обратите внимание, что помимо копирования стилей и триггеров я также принял во внимание значения TargetType и BasedOn, а также любые ресурсы, которые может иметь второй стиль.Для TargetType объединенного стиля я использовал более производный тип.Если второй стиль имеет стиль BasedOn, я рекурсивно объединяю его иерархию стилей.Если у него есть ресурсы, я копирую их в первый стиль.Если на эти ресурсы ссылаются с помощью {StaticResource}, они статически разрешаются до выполнения этого кода слияния, и поэтому нет необходимости перемещать их.Я добавил этот код на случай, если мы используем DynamicResources.

Показанный выше метод расширения допускает следующий синтаксис:

style1.Merge(style2);

Этот синтаксис полезен при условии, что у меня есть экземпляры обоих стилей в пределах ProvideValue.Ну, я нет.Все, что я получаю от конструктора, — это список строковых ключей для этих стилей.Если бы в параметрах конструктора была поддержка параметров, я мог бы использовать следующий синтаксис для получения реальных экземпляров стиля:

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

Но это не работает.И даже если бы ограничения на параметры не существовало, мы, вероятно, столкнулись бы с другим ограничением расширений разметки, когда нам пришлось бы использовать синтаксис элементов свойств вместо синтаксиса атрибутов для указания статических ресурсов, что является многословным и громоздким (я объясняю это). ошибка лучше в предыдущая запись в блоге).И даже если бы обоих этих ограничений не существовало, я бы все равно предпочел написать список стилей, используя только их имена — он короче и проще для чтения, чем StaticResource для каждого из них.

Решение состоит в том, чтобы создать StaticResourceExtension с помощью кода.Имея ключ стиля типа string и поставщика услуг, я могу использовать StaticResourceExtension для получения фактического экземпляра стиля.Вот синтаксис:

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

Теперь у нас есть все необходимое для написания метода 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;
}

Вот полный пример использования расширения разметки 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

Но можно продлить и от другого..взгляните на свойство 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 не предоставляет эту функциональность изначально, но обеспечивает расширяемость, позволяющую вам делать то, что вы хотите.

Мы столкнулись с той же необходимостью и в итоге создали собственное расширение разметки XAML (которое мы назвали «MergedStylesExtension»), позволяющее нам создавать новый стиль из двух других стилей (которые, при необходимости, вероятно, можно было бы использовать несколько раз в row для наследования еще большего количества стилей).

Из-за ошибки WPF/XAML нам нужно использовать синтаксис элемента свойства, чтобы использовать его, но в остальном он работает нормально.Например.,

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

Недавно я писал об этом здесь:http://swdeveloper.wordpress.com/2009/01/03/wpf-xaml-multiple-style-inheritance-and-markup-extensions/

Это возможно путем создания вспомогательного класса для использования и переноса ваших стилей.Упоминание CompoundStyle здесь показывает, как это сделать.Есть несколько способов, но самый простой — сделать следующее:

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

Надеюсь, это поможет.

Использовать AttachedProperty чтобы установить несколько стилей, например следующий код:

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

Использование:

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

Результат:

enter image description here

если вы не затрагиваете какие-либо конкретные свойства, вы можете передать все базовые и общие свойства стилю, целевым типом которого будет FrameworkElement.затем вы можете создавать отдельные варианты для каждого нужного вам целевого типа без необходимости повторного копирования всех этих общих свойств.

Вероятно, вы можете получить что-то подобное, если примените это к коллекции элементов с помощью StyleSelector. Я использовал это для решения аналогичной проблемы при использовании разных стилей в TreeViewItems в зависимости от типа привязанного объекта в дереве.Возможно, вам придется немного изменить приведенный ниже класс, чтобы приспособиться к вашему конкретному подходу, но, надеюсь, это поможет вам начать работу.

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

Затем вы применяете это так

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

Иногда это можно сделать путем вложения панелей.Допустим, у вас есть стиль, который меняет передний план, а другой меняет размер шрифта, вы можете применить последний к TextBlock и поместить его в сетку, стиль которой является первым.Это может помочь и в некоторых случаях может оказаться самым простым способом, хотя и не решит всех проблем.

Когда вы переопределяете SelectStyle, вы можете получить свойство GroupBy через отражение, как показано ниже:

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

Если вы пытаетесь применить уникальный стиль только к одному элементу В качестве дополнения к базовому стилю существует совершенно другой способ сделать это, который, ИМХО, намного лучше для читаемого и поддерживаемого кода.

Чрезвычайно часто возникает необходимость настройки параметров для каждого отдельного элемента.Определение стилей словаря только для использования с одним элементом чрезвычайно сложно поддерживать или понимать.Чтобы не создавать стили только для разовой настройки элемента, прочитайте мой ответ на мой собственный вопрос здесь:

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top