Pergunta

No WPF, como eu aplicaria vários estilos a um FrameworkElement?Por exemplo, tenho um controle que já possui um estilo.Eu também tenho um estilo separado que gostaria de adicionar sem estragar o primeiro.Os estilos têm TargetTypes diferentes, então não posso simplesmente estender um com o outro.

Foi útil?

Solução

Acho que a resposta simples é que você não pode fazer (pelo menos nesta versão do WPF) o que está tentando fazer.

Ou seja, para qualquer elemento específico apenas um estilo pode ser aplicado.

No entanto, como outros afirmaram acima, talvez você possa usar BasedOn para ajudá-lo.Confira o seguinte pedaço de xaml solto.Nele você verá que tenho um estilo base que está definindo uma propriedade que existe na classe base do elemento ao qual desejo aplicar dois estilos.E, no segundo estilo que é baseado no estilo base, defini outra propriedade.

Então, a ideia aqui...é se você puder de alguma forma separar as propriedades que deseja definir ...de acordo com a hierarquia de herança do elemento no qual você deseja definir vários estilos ...você pode ter uma solução alternativa.

<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 isto ajude.

Observação:

Uma coisa em particular a ser observada.Se você mudar o TargetType no segundo estilo (no primeiro conjunto de xaml acima) para ButtonBase, os dois estilos não serão aplicados.No entanto, verifique o seguinte xaml abaixo para contornar essa restrição.Basicamente, isso significa que você precisa dar uma chave ao estilo e referenciá-lo com essa chave.

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

Outras dicas

Bea Stollnitz tinha uma boa postagem no blog sobre o uso de uma extensão de marcação para isso, sob o título "Como posso definir vários estilos no WPF?"

Esse blog está morto agora, então estou reproduzindo o post aqui


WPF e Silverlight oferecem a capacidade de derivar um estilo de outro estilo por meio da propriedade “BasedOn”.Este recurso permite que os desenvolvedores organizem seus estilos usando uma hierarquia semelhante à herança de classes.Considere os seguintes 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>

Com esta sintaxe, um Button que usa RedButtonStyle terá sua propriedade Foreground definida como Red e sua propriedade Margin definida como 10.

Esse recurso já existe no WPF há muito tempo e é novo no Silverlight 3.

E se você quiser definir mais de um estilo em um elemento?Nem o WPF nem o Silverlight fornecem uma solução pronta para uso para esse problema.Felizmente, existem maneiras de implementar esse comportamento no WPF, que discutirei nesta postagem do blog.

WPF e Silverlight usam extensões de marcação para fornecer propriedades com valores que requerem alguma lógica para serem obtidos.As extensões de marcação são facilmente reconhecíveis pela presença de chaves ao seu redor em XAML.Por exemplo, a extensão de marcação {Binding} contém lógica para buscar um valor de uma fonte de dados e atualizá-lo quando ocorrerem alterações;a extensão de marcação {StaticResource} contém lógica para obter um valor de um dicionário de recursos com base em uma chave.Felizmente para nós, o WPF permite que os usuários escrevam suas próprias extensões de marcação personalizadas.Esse recurso ainda não está presente no Silverlight, portanto a solução neste blog só é aplicável ao WPF.

Outros escreveram ótimas soluções para mesclar dois estilos usando extensões de marcação.No entanto, eu queria uma solução que oferecesse a capacidade de mesclar um número ilimitado de estilos, o que é um pouco mais complicado.

Escrever uma extensão de marcação é simples.A primeira etapa é criar uma classe derivada de MarkupExtension e usar o atributo MarkupExtensionReturnType para indicar que você pretende que o valor retornado de sua extensão de marcação seja do tipo Style.

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

Especificando entradas para a extensão de marcação

Gostaríamos de oferecer aos usuários da nossa extensão de marcação uma maneira simples de especificar os estilos a serem mesclados.Existem essencialmente duas maneiras pelas quais o usuário pode especificar entradas para uma extensão de marcação.O usuário pode definir propriedades ou passar parâmetros para o construtor.Como neste cenário o usuário precisa especificar um número ilimitado de estilos, minha primeira abordagem foi criar um construtor que aceita qualquer número de strings usando a palavra-chave “params”:

public MultiStyleExtension(params string[] inputResourceKeys)
{
}

Meu objetivo era ser capaz de escrever as entradas da seguinte forma:

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

Observe a vírgula que separa as diferentes teclas de estilo.Infelizmente, as extensões de marcação personalizada não suportam um número ilimitado de parâmetros de construtor, portanto, essa abordagem resulta em um erro de compilação.Se eu soubesse antecipadamente quantos estilos queria mesclar, poderia ter usado a mesma sintaxe XAML com um construtor usando o número desejado de strings:

public MultiStyleExtension(string inputResourceKey1, string inputResourceKey2)
{
}

Como solução alternativa, decidi fazer com que o parâmetro construtor assuma uma única string que especifique os nomes dos estilos separados por espaços.A sintaxe não é tão ruim:

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

Calculando a saída da extensão de marcação

Para calcular a saída de uma extensão de marcação, precisamos substituir um método de MarkupExtension chamado “ProvideValue”.O valor retornado deste método será definido no destino da extensão de marcação.

Comecei criando um método de extensão para Style que sabe mesclar dois estilos.O código para este método é bastante simples:

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

Com a lógica acima, o primeiro estilo é modificado para incluir todas as informações do segundo.Se houver conflitos (por ex.ambos os estilos possuem um setter para a mesma propriedade), o segundo estilo vence.Observe que além de copiar estilos e gatilhos, também levei em consideração os valores TargetType e BasedOn, bem como quaisquer recursos que o segundo estilo possa ter.Para o TargetType do estilo mesclado, usei o tipo mais derivado.Se o segundo estilo tiver um estilo BasedOn, mesclo sua hierarquia de estilos recursivamente.Se tiver recursos, copio-os para o primeiro estilo.Se esses recursos forem referidos usando {StaticResource}, eles serão resolvidos estaticamente antes da execução desse código de mesclagem e, portanto, não será necessário movê-los.Adicionei este código caso estejamos usando DynamicResources.

O método de extensão mostrado acima permite a seguinte sintaxe:

style1.Merge(style2);

Essa sintaxe é útil desde que eu tenha instâncias de ambos os estilos em ProvideValue.Bem, eu não.Tudo o que recebo do construtor é uma lista de chaves de string para esses estilos.Se houvesse suporte para parâmetros nos parâmetros do construtor, eu poderia ter usado a seguinte sintaxe para obter as instâncias reais do estilo:

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

Mas isso não funciona.E mesmo que a limitação de params não existisse, provavelmente encontraríamos outra limitação de extensões de marcação, onde teríamos que usar sintaxe de elemento de propriedade em vez de sintaxe de atributo para especificar os recursos estáticos, o que é detalhado e complicado (eu explico isso bug melhor em um postagem anterior do blog).E mesmo que essas duas limitações não existissem, eu ainda preferiria escrever a lista de estilos usando apenas seus nomes – é mais curto e mais simples de ler do que um StaticResource para cada um.

A solução é criar um StaticResourceExtension usando código.Dada uma chave de estilo do tipo string e um provedor de serviços, posso usar StaticResourceExtension para recuperar a instância de estilo real.Aqui está a sintaxe:

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

Agora temos todas as peças necessárias para escrever o método 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;
}

Aqui está um exemplo completo do uso da extensão de marcação 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

Mas você pode estender de outro..dê uma olhada na propriedade 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>

O WPF/XAML não fornece essa funcionalidade nativamente, mas fornece a extensibilidade para permitir que você faça o que quiser.

Encontramos a mesma necessidade e acabamos criando nossa própria extensão de marcação XAML (que chamamos de "MergedStylesExtension") para nos permitir criar um novo estilo a partir de dois outros estilos (que, se necessário, provavelmente poderiam ser usados ​​várias vezes em um linha para herdar ainda mais estilos).

Devido a um bug do WPF/XAML, precisamos usar a sintaxe do elemento de propriedade para usá-lo, mas fora isso parece funcionar bem.Por exemplo.,

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

Recentemente escrevi sobre isso aqui:http://swdeveloper.wordpress.com/2009/01/03/wpf-xaml-multiple-style-inheritance-and-markup-extensions/

Isso é possível criando uma classe auxiliar para usar e agrupar seus estilos.CompoundStyle mencionado aqui mostra como fazer.Existem várias maneiras, mas a mais fácil é fazer o seguinte:

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

Espero que ajude.

Usar AttachedProperty para definir vários estilos como o seguinte 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;
    }
}

Uso:

<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

se você não estiver tocando em nenhuma propriedade específica, poderá obter todas as propriedades básicas e comuns para o estilo cujo tipo de destino seria FrameworkElement.então, você pode criar variações específicas para cada tipo de destino necessário, sem a necessidade de copiar todas as propriedades comuns novamente.

Você provavelmente pode obter algo semelhante se aplicar isso a uma coleção de itens usando um StyleSelector. Usei isso para abordar um problema semelhante ao usar estilos diferentes em TreeViewItems, dependendo do tipo de objeto vinculado na árvore.Você pode ter que modificar um pouco a classe abaixo para ajustá-la à sua abordagem específica, mas espero que isso ajude você a começar

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

Você então aplica isso assim

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

Às vezes você pode abordar isso aninhando painéis.Digamos que você tenha um Style que altera o Foreground e outro altera o FontSize, você pode aplicar este último em um TextBlock, e colocá-lo em uma Grid cujo Style seja o primeiro.Isso pode ajudar e ser a maneira mais fácil em alguns casos, embora não resolva todos os problemas.

Ao substituir SelectStyle, você pode obter a propriedade GroupBy por meio de reflexão como abaixo:

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

Se você está tentando aplicar um estilo único a apenas um elemento como adição a um estilo base, existe uma maneira completamente diferente de fazer isso que é IMHO muito melhor para código legível e de fácil manutenção.

É extremamente comum precisar ajustar parâmetros por elemento individual.Definir estilos de dicionário apenas para uso em um elemento é extremamente complicado de manter ou entender.Para evitar a criação de estilos apenas para ajustes pontuais de elementos, leia minha resposta à minha própria pergunta aqui:

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

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top