Pregunta

En WPF, ¿cómo puedo aplicar varios estilos a un FrameworkElement?Por ejemplo, yo tengo un control que ya tiene un estilo.También tengo un estilo independiente que me gustaría añadir a sin soplar lejos de la primera.Los estilos tienen diferentes TargetTypes, de manera que no puedo extender el uno con el otro.

¿Fue útil?

Solución

Creo que la respuesta simple es que usted no puede hacer (al menos en esta versión de WPF) lo que usted está tratando de hacer.

Esto es, para cualquier elemento en particular sólo un Estilo que se puede aplicar.

Sin embargo, como otros han dicho más arriba, tal vez usted puede utilizar BasedOn para ayudarle a salir.Echa un vistazo a la siguiente pieza suelta xaml.En él podrás ver que tengo un estilo de la base de que es la configuración de una propiedad que existe en la base de la clase de elemento que quiero aplicar dos estilos.Y, en el segundo estilo que se basa en el estilo de la base, me puse otra propiedad.

Así, la idea aquí ...es que si de alguna manera se puede separar las propiedades que desea establecer ...según la jerarquía de herencia del elemento que desea definir varios estilos de ...usted podría tener una solución.

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


Espero que esto ayude.

Nota:

Una cosa en particular a la nota.Si cambia el TargetType en el segundo estilo (en el primer set de xaml de arriba) ButtonBase, los dos Estilos no se aplican.Sin embargo, consulte el siguiente xaml de abajo para evitar que la restricción.Básicamente, esto significa que usted necesita para dar el Estilo de clave de referencia y con esa clave.

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

Otros consejos

Bea Stollnitz había un buen blog sobre el uso de una extensión de marcado para este, bajo el título "¿Cómo puedo establecer varios estilos en WPF?"

Que el blog está muerto ahora, así que estoy reproduciendo el post aquí


WPF y Silverlight ambos ofrecen la posibilidad de obtener un Estilo de otro Estilo a través de la "Barra" de la propiedad.Esta característica permite a los desarrolladores organizar sus estilos utilizando una jerarquía similar a la de la herencia de clases.Considere los siguientes estilos:

<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 esta sintaxis, un Botón que utiliza RedButtonStyle tendrá su primer plano de conjunto de propiedades a Rojo y su Margen de conjunto de propiedades a 10.

Esta característica ha sido de alrededor de WPF para un largo tiempo, y es nuevo en Silverlight 3.

Lo que si desea establecer más de un estilo de un elemento?Ni WPF ni Silverlight proporcionar una solución para este problema fuera de la caja.Afortunadamente, hay maneras de implementar este comportamiento en WPF, que voy a discutir en este blog.

WPF y Silverlight uso marcado de extensiones para proporcionar propiedades con valores que requiere un poco de lógica a obtener.Extensiones de marcado son fácilmente reconocibles por la presencia de llaves que les rodea en XAML.Por ejemplo, el {Binding} extensión de marcado que contiene la lógica de la obtención de un valor a partir de un origen de datos y actualizarla cuando se producen cambios;el {StaticResource} extensión de marcado que contiene la lógica para tomar un valor de un diccionario de recursos basada en una clave.Afortunadamente para nosotros, WPF permite a los usuarios escribir sus propias costumbres extensiones de marcado.Esta característica aún no está presente en Silverlight, por lo que la solución en este blog sólo es aplicable a WPF.

Otros se han escrito grandes soluciones para combinar dos estilos usando extensiones de marcado.Sin embargo, yo quería una solución que proporciona la capacidad de combinar un número ilimitado de estilos, que es un poco más complicado.

La escritura de una extensión de marcado es sencillo.El primer paso es crear una clase que deriva de MarkupExtension, y el uso de la MarkupExtensionReturnType atributo para indicar que desea que el valor devuelto de su extensión de marcado de tipo de Estilo.

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

La especificación de insumos para la extensión de marcado

Nos gustaría dar a los usuarios de nuestra extensión de marcado de una forma sencilla para especificar los estilos para combinar.Básicamente hay dos formas en las que el usuario puede especificar entradas para una extensión de marcado.El usuario puede establecer las propiedades o pasar parámetros al constructor.Dado que en este escenario el usuario necesita la capacidad de especificar un número ilimitado de estilos, mi primer acercamiento fue crear un constructor que toma cualquier número de cadenas utilizando el "params" palabra clave:

public MultiStyleExtension(params string[] inputResourceKeys)
{
}

Mi objetivo era ser capaz de escribir las entradas de la siguiente manera:

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

Aviso de la coma para separar las diferentes claves de estilo.Por desgracia, la costumbre extensiones de marcado no admite un número ilimitado de parámetros del constructor, por lo que este enfoque se traduce en un error de compilación.Si yo sabía de antemano cómo muchos estilos quería combinar, podría haber utilizado la misma sintaxis XAML con un constructor que toma el número deseado de cadenas:

public MultiStyleExtension(string inputResourceKey1, string inputResourceKey2)
{
}

Como una solución, decidí tener el parámetro del constructor tomar una sola cadena que especifica el estilo de nombres separados por espacios.La sintaxis no es tan malo:

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

El cálculo de la salida de la extensión de marcado

Para calcular el resultado de una extensión de marcado, tenemos que sobreescribir un método de MarkupExtension llamado "ProvideValue".El valor devuelto por este método será el fijado en el objetivo de la extensión de marcado.

Empecé por la creación de un método de extensión para el Estilo que sabe cómo combinar dos estilos.El código de este método es muy simple:

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 lógica anterior, el primer estilo se modifica para incluir toda la información de la segunda.Si hay conflictos (por ejemplo,ambos estilos tienen un definidor de la misma propiedad), el segundo estilo es el que gana.Observe que aparte de copiar estilos y desencadenantes, yo también tomó en cuenta el TargetType y Barra de valores así como de los recursos de la segunda manera.Para el TargetType de la fusión de estilo, he utilizado sea cual sea el tipo es más de la que deriva.Si el segundo estilo tiene una Barra de estilo, puedo combinar su jerarquía de estilos de forma recursiva.Si tiene recursos, me copiar a la primera de estilo.Si esos recursos se refieren al uso de {StaticResource}, están resueltos estáticamente antes de esta fusión se ejecuta el código, y por lo tanto no es necesario moverlos.He añadido este código en caso de que estamos usando DynamicResources.

El método de extensión que se muestra más arriba permite la siguiente sintaxis:

style1.Merge(style2);

Esta sintaxis es útil siempre que puedo tener instancias de ambos estilos dentro de ProvideValue.Bueno, yo no.Todo lo que se obtiene desde el constructor es una lista de claves de cadena para esos estilos.Si hay apoyo para los parámetros en el constructor con parámetros, podría haber utilizado la siguiente sintaxis para obtener el estilo real instancias:

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

Pero eso no funciona.E incluso si los parámetros de limitación no existe, que probablemente iba a golpear otra limitación de las extensiones de marcado, donde habría que utilizar la propiedad de un elemento de sintaxis en lugar de la sintaxis de atributo para especificar los recursos estáticos, que es detallado y complicado (lo explico mejor en un error entrada en el blog anterior).Y aunque tanto las limitaciones no existen, todavía se prefiere escribir la lista de estilos usando sólo sus nombres – es más corto y más fácil de leer que un StaticResource para cada uno.

La solución es crear un StaticResourceExtension mediante código.Dado un estilo de clave de tipo string y un proveedor de servicios, puede utilizar StaticResourceExtension para recuperar el estilo real de la instancia.Esta es la sintaxis:

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

Ahora ya tenemos todas las piezas necesarias para escribir el ProvideValue método:

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

Aquí está un ejemplo completo del uso de la MultiStyle extensión de marcado:

<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

Pero usted puede extender desde otro..echa un vistazo a la Barra de la propiedad

<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 no proporcionar esta funcionalidad de forma nativa, pero ofrece la capacidad de ampliación para permitir que usted haga lo que usted quiere.

Nos encontramos con la misma necesidad, y acabó creando nuestro propio Marcado XAML Extensión (que hemos denominado "MergedStylesExtension") que nos permiten crear un nuevo Estilo de otros dos estilos (que, si es necesario, probablemente, podría ser utilizado varias veces en una fila para heredar de estilos).

Debido a una WPF/XAML error, tenemos que utilizar la propiedad sintaxis de elemento a utilizar, pero por lo demás parece que funciona ok.E. g.,

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

Hace poco escribí sobre ello aquí:http://swdeveloper.wordpress.com/2009/01/03/wpf-xaml-multiple-style-inheritance-and-markup-extensions/

Esto es posible mediante la creación de una clase auxiliar para usar y ajustar sus estilos.CompoundStyle mencionado aquí muestra cómo hacerlo.Hay varias formas, pero la más fácil es hacer lo siguiente:

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

Espero que ayude.

Uso AttachedProperty para establecer varios estilos como el siguiente código:

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>

Resultado:

enter image description here

si usted no estén en contacto con propiedades específicas, usted puede conseguir toda la base y propiedades comunes para el estilo del tipo de destino sería FrameworkElement.a continuación, puede crear aromas específicos para cada uno de los tipos de destino que usted necesita, sin necesidad de copiar todas aquellas propiedades comunes de nuevo.

Usted probablemente puede conseguir algo similar en el caso de la aplicación de esta a una colección de elementos mediante el uso de un StyleSelector, he utilizado este enfoque un problema similar en el uso de diferentes estilos en TreeViewItems dependiendo de la envolvente del tipo de objeto en el árbol.Puede que tenga que modificar la clase de abajo ligeramente para adaptarse a su particular enfoque, pero esperemos que esto va a empezar

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

A continuación, aplicar esta como para

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

A veces, usted puede acercarse a esta por la anidación de los paneles.Digamos que tienes un Estilo que los cambios de primer plano y otra de los cambios de Tamaño, se puede aplicar el último en un TextBlock, y lo puso en una Cuadrícula que su Estilo es el primero.Esto puede ayudar y puede ser la forma más sencilla en algunos casos, aunque no resuelve todos los problemas.

Cuando se reemplaza SelectStyle usted puede conseguir GroupBy la propiedad a través de la reflexión, como el de abajo:

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

Si usted está tratando de aplicar un estilo único a un solo elemento como una adición a una base de estilo, hay una forma completamente diferente de hacer esto en mi humilde opinión es mucho más legible y mantenible código.

Es muy común a la necesidad de ajustar los parámetros de cada elemento individual.La definición de diccionario de estilos sólo para uso en un elemento muy engorroso para mantener o dar sentido.Para evitar la creación de estilos para un sólo elemento ajustes, lee mi respuesta a mi propia pregunta aquí:

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

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top