Bea Stollnitz teve um bom post sobre como usar 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
O WPF e o Silverlight oferecem a capacidade de derivar um estilo de outro estilo por meio da propriedade “BasedOn”. Esse recurso permite que os desenvolvedores organizem seus estilos usando uma hierarquia semelhante à herança de classe. 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 essa sintaxe, um Button que usa RedButtonStyle terá sua propriedade Foreground definida como Red e sua propriedade Margin definida como 10.
Esse recurso 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 para esse problema imediatamente. Felizmente, existem maneiras de implementar esse comportamento no WPF, que discutirei nesta postagem do blog.
O WPF e o 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 colchetes ao redor deles no 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 gravem suas próprias extensões de marcação personalizadas. Esse recurso ainda não está presente no Silverlight, portanto, a solução neste blog é aplicável apenas ao WPF.
Outros criaram ó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ê deseja que o valor retornado da 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 basicamente 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 nesse cenário o usuário precisa especificar um número ilimitado de estilos, minha primeira abordagem foi criar um construtor que use 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 maneira:
<Button Style="{local:MultiStyle BigButtonStyle, GreenButtonStyle}" … />
Observe a vírgula separando as diferentes teclas de estilo. Infelizmente, as extensões de marcação personalizadas não oferecem suporte a um número ilimitado de parâmetros do construtor; portanto, essa abordagem resulta em um erro de compilação. Se eu soubesse com antecedência quantos estilos queria mesclar, poderia ter usado a mesma sintaxe XAML com um construtor que obtém o número desejado de strings:
public MultiStyleExtension(string inputResourceKey1, string inputResourceKey2)
{
}
Como solução alternativa, decidi fazer com que o parâmetro construtor usasse uma única sequência que especifique os nomes de 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 desse método será definido no destino da extensão de marcação.
Comecei criando um método de extensão para o 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 exemplo, ambos os estilos têm um setter para a mesma propriedade), o segundo estilo vence. Observe que, além de copiar estilos e gatilhos, também considerei os valores TargetType e BasedOn, bem como quaisquer recursos que o segundo estilo possa ter. Para o TargetType do estilo mesclado, usei o tipo que for mais derivado. Se o segundo estilo tiver um estilo BasedOn, mesclar sua hierarquia de estilos recursivamente. Se houver recursos, eu os copio para o primeiro estilo. Se esses recursos forem referidos como {StaticResource}, eles serão resolvidos estaticamente antes da execução desse código de mesclagem e, portanto, não será necessário movê-los. Eu adicionei esse código no caso de estarmos usando DynamicResources.
O método de extensão mostrado acima habilita a seguinte sintaxe:
style1.Merge(style2);
Essa sintaxe é útil, desde que eu tenha instâncias de ambos os estilos no 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 de estilo reais:
<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 parâmetros não existisse, provavelmente atingiríamos outra limitação de extensões de marcação, onde teríamos que usar a sintaxe de elemento de propriedade em vez da sintaxe de atributo para especificar os recursos estáticos, que são detalhados e pesados (eu explico isso bug melhor em uma postagem anterior do blog ). E mesmo que essas duas limitações não existissem, eu 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 o 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" />