如何申请在WPF多个样式

问题描述 投票:140回答:11

在WPF中,我怎么会应用多个样式的FrameworkElement?举例来说,我已经有一个风格的控制。我也有一个独立的风格,我想给它添加不吹走的第一个。款式有不同的TargetTypes,所以我不能只是延长一个与其他。

.net wpf styles
11个回答
147
投票

我认为答案很简单,你不能这样做(至少在这个版本的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>

1
投票

当你重写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;
    }

0
投票

如果你正在尝试一种独特的风格适用于只是一个单一的元素作为除基本样式,有一种完全不同的方式来做到这一点,是恕我直言多的可读和可维护的代码更好。

这是非常常见的需要调整每个单独的元素参数。定义字典的风格只为一个元件上使用非常麻烦的维护或使之感。为了避免只为一次性因素调整创造风格,看了我的回答我自己的问题在这里在这里:

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


43
投票

BEA Stollnitz有a good blog post有关使用此标记扩展的标题下,“我怎么能在WPF中设置多个样式?”

该博客现在已经死了,所以我在这里再现后


WPF和Silverlight都提供通过“支持算法FMP”属性派生从另一个风格样式的功能。该功能使开发人员使用的层次结构类似于类继承来组织自己的风格。考虑以下样式:

<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将其前景属性设置为红色和Margin属性设置为10的按钮。

此功能已经出现在WPF中很长一段时间,并在Silverlight 3的新特性。

如果你想设置多个样式的元素?无论是WPF的Silverlight也提供了一个解决这个问题的开箱。幸运的是,实施这一行为在WPF中,我将在这篇博客文章讨论如何。

WPF和Silverlight使用标记扩展以提供与需要一些逻辑来获得值的属性。标记扩展是通过在XAML它们周围大括号的存在很容易辨认。例如,{结合}标记扩展包含逻辑,用于从数据源获取的值,并且在发生变化时更新它;的{}的StaticResource标记扩展包含逻辑以抓住从基于键资源字典的值。幸运的是,WPF允许用户编写自己的自定义标记扩展。此功能尚未出现在Silverlight,所以在这个博客上的解决方案只适用于WPF。

Others已经写了很大的解决方案,以合并使用标记扩展两种风格。不过,我想,提供给合并无限数量的风格,这是一个有点麻烦的能力的解决方案。

编写标记扩展非常简单。第一步是创建一个从的MarkupExtension派生的类,并使用MarkupExtensionReturnType属性表明您打算从您的标记扩展返回为类型样式的值。

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

Specifying inputs to the markup extension

我们想给我们的标记扩展的用户一个简单的方法来指定要合并的样式。基本上有在其中用户可以指定输入标记扩展两种方式。用户可以设置属性或参数传递给构造。因为在这种情况下,用户需要指定款式不限数量的能力,我的第一个方法是创建一个构造函数任意数量的使用“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.");
    }
}

Calculating the output of the markup extension

为了计算标记扩展的输出,我们需要重写从的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的和支持算法FMP值以及第二样式可能具有的任何资源。对于合并后的风格TargetType的,我无论采用哪种类型的派生程度更高。如果第二风格有支持算法FMP的风格,我递归合并其风格的层次结构。如果有资源,我在将它们复制到第一个样式。如果这些资源是指使用{}的StaticResource,他们是静态解析本次合并代码执行之前,因此没有必要移动它们。我加入这个代码的情况下,我们使用DynamicResources。

上面示出的扩展方法允许的语法如下:

style1.Merge(style2);

只要我有内ProvideValue两种风格的情况下,这种语法是很有用的。好吧,我不知道。所有我从构造得到的是那些风格字符串键列表。如果是在构造函数的参数PARAMS的支持,我可以用下面的语法来获得实际的作风实例:

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

但是,这并不工作。而且即使PARAMS限制是不存在的,我们可能会打标记扩展的另一个限制,我们必须使用属性 - 元素语法而不是属性语法来指定静态资源,这是冗长和繁琐的(我解释臭虫在previous blog post更好)。即使不存在这两个限制,我仍然宁愿写的只用自己的名字样式列表中 - 这是更短,更简单的比为每一个StaticResource的阅读。

的解决方案是创建使用代码StaticResourceExtension。鉴于字符串类型和服务提供商的风格的关键,我可以用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


31
投票

但是你可以从其他扩展..看看的支持算法FMP财产

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

17
投票

WPF / XAML本身不提供此功能,但它确实提供了可扩展性,让你做你想要的。

我们遇到同样的需求,并最终创建自己的XAML标记扩展(我们称之为“MergedStylesExtension”),使我们能够从其他两种款式(其中,如果需要的话,也许可以在多次使用创建新样式行从更加风格继承)。

由于WPF / XAML的错误,我们需要使用属性元素语法使用它,但比它似乎工作OK等。例如。,

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


3
投票

这通过创建一个辅助类使用和包装你的风格是可能的。 CompoundStyle提到here展示了如何做到这一点。有多种方式,但最简单的是做到以下几点:

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

希望帮助。


2
投票

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

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>

结果:

enter image description here


1
投票

如果你不碰任何特定的属性,你可以得到所有基地和公共属性,其是目标类型将FrameworkElement的风格。然后,你可以创建你需要每个目标类型的特定口味,而无需再次复制所有这些共同的属性。


1
投票

你也许可以得到类似的东西,如果将其应用到通过使用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>

1
投票

有时你可以通过嵌套板接近这一点。假设你有一个风格,改变前景和另一个变化字号,你可以申请在一个TextBlock后者,并把它放在一个网格,其它的风格是第一位的。这可能会帮助并可能在某些情况下,最简单的方法,但它并不能解决所有的问题。

© www.soinside.com 2019 - 2024. All rights reserved.