Как применить несколько стилей в WPF
в WPF, как бы я применил несколько стилей к FrameworkElement? Например, у меня есть элемент управления, который имеет стиль. У меня также есть отдельный стиль, который я хотел бы добавить к нему, не сдувая первый. Стили имеют разные целевые типы, поэтому я не могу просто расширить один с другим.
10 ответов:
Я думаю, что простой ответ заключается в том, что вы не можете сделать (по крайней мере, в этой версии 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, эти два стиля не применяются. Однако, проверьте следующий код ниже, чтобы обойти это ограничение. В принципе, это означает, что вам нужно дать стилю ключ и ссылаться на него с помощью этого ключа.<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, будет иметь свойство переднего плана, установленное в 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". Значение, возвращаемое этим методом, будет установлено в целевой объект расширения разметки.
я начал с создания метода расширения для стиля, который знает, как объединить два стиля. Код для этого метода достаточно просто:
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. Все, что я получаю от конструктора-это список строковых ключей для этих стилей. Если бы в параметрах конструктора была поддержка params, я мог бы использовать следующий синтаксис для получения фактических экземпляров стиля:
<Button Style="{local:MultiStyle {StaticResource BigButtonStyle}, {StaticResource GreenButtonStyle}}" … />public MultiStyleExtension(params Style[] styles) { }но это не сработает. И даже если бы ограничение params не существовало, мы, вероятно, попали бы в другое ограничение расширений разметки, где нам пришлось бы использовать синтаксис элемента свойства вместо синтаксиса атрибута указать статические ресурсы, который является подробным и громоздким (я объясняю эту ошибку лучше в предыдущий пост в блоге). И даже если бы оба эти ограничения не существовали, я все равно предпочел бы написать список стилей, используя только их имена – он короче и проще для чтения, чем StaticResource для каждого из них.
решение заключается в создании StaticResourceExtension с помощью кода. Учитывая ключ стиля типа string и поставщика услуг, I можно использовать 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" />
но вы можете расширить от другого.. взгляните на свойство 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"), чтобы позволить нам создать новый стиль из двух других стилей (которые, при необходимости, возможно, можно было бы использовать несколько раз подряд, чтобы наследовать от еще большего количества стилей).
из-за ошибки 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"/>надеюсь, что это поможет.
Если вы не касаетесь каких-либо конкретных свойств, вы можете получить все базовые и общие свойства для стиля, целевой тип которого будет 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>
иногда вы можете подойти к этому, вложив панели. Скажем, у вас есть стиль, который меняет передний план, а другой изменяет размер шрифта, вы можете применить последний к текстовому блоку и поместить его в сетку, стиль которой является первым. Это может помочь и может быть самым простым способом в некоторых случаях, хотя это не решит всех проблем.
при переопределении 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; }
использовать
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>результат:


Comments