.net - redondeados - estilos datagrid wpf
Cómo aplicar múltiples estilos en WPF (10)
A veces puedes acercarte esto al anidar paneles. Supongamos que tiene un estilo que cambia de primer plano y otro cambia FontSize, puede aplicar el último en un bloque de texto y colocarlo en una cuadrícula cuyo estilo es el primero. Esto podría ayudar y podría ser la forma más fácil en algunos casos, aunque no resolverá todos los problemas.
En WPF, ¿cómo podría aplicar varios estilos a FrameworkElement
? Por ejemplo, tengo un control que ya tiene un estilo. También tengo un estilo separado que me gustaría agregar sin eliminar el primero. Los estilos tienen diferentes TargetTypes, por lo que no puedo extender uno con el otro.
Bea Stollnitz publicó una buena publicación sobre el uso de una extensión de marcado para esto, bajo el título "¿Cómo puedo configurar varios estilos en WPF?"
Ese blog está muerto ahora, así que estoy reproduciendo el post aquí
WPF y Silverlight ofrecen la posibilidad de derivar un Estilo de otro Estilo a través de la propiedad "Basado en". Esta característica permite a los desarrolladores organizar sus estilos usando una jerarquía similar a la herencia de clases. Considera 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 usa RedButtonStyle tendrá su propiedad de primer plano establecida en rojo y su propiedad de margen establecida en 10.
Esta característica ha existido en WPF durante mucho tiempo, y es nueva en Silverlight 3.
¿Qué sucede si desea establecer más de un estilo en un elemento? Ni WPF ni Silverlight brindan una solución para este problema de manera inmediata. Afortunadamente, hay formas de implementar este comportamiento en WPF, que analizaré en esta publicación de blog.
WPF y Silverlight usan extensiones de marcado para proporcionar propiedades con valores que requieren cierta lógica para obtener. Las extensiones de marcado son fácilmente reconocibles por la presencia de corchetes que las rodean en XAML. Por ejemplo, la extensión de marcado {Binding} contiene lógica para recuperar un valor de un origen de datos y actualizarlo cuando se producen cambios; la extensión de marcado {StaticResource} contiene lógica para tomar un valor de un diccionario de recursos basado en una clave. Afortunadamente para nosotros, WPF permite a los usuarios escribir sus propias extensiones de marcado personalizadas. Esta característica aún no está presente en Silverlight, por lo que la solución en este blog solo se aplica a WPF.
Others han escrito excelentes soluciones para fusionar dos estilos utilizando extensiones de marcado. Sin embargo, quería una solución que brindara la posibilidad de fusionar un número ilimitado de estilos, lo que es un poco más complicado.
Escribir una extensión de marcado es sencillo. El primer paso es crear una clase que se derive de MarkupExtension, y usar el atributo MarkupExtensionReturnType para indicar que el valor que devuelve la extensión de marcado es de tipo Estilo.
[MarkupExtensionReturnType(typeof(Style))]
public class MultiStyleExtension : MarkupExtension
{
}
Especificación de entradas a la extensión de marcado
Nos gustaría dar a los usuarios de nuestra extensión de marcado una forma simple de especificar los estilos que se fusionarán. Hay esencialmente dos formas en que el usuario puede especificar entradas para una extensión de marcado. El usuario puede establecer 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 enfoque fue crear un constructor que tome cualquier cantidad de cadenas usando la palabra clave "params":
public MultiStyleExtension(params string[] inputResourceKeys)
{
}
Mi objetivo era poder escribir las entradas de la siguiente manera:
<Button Style="{local:MultiStyle BigButtonStyle, GreenButtonStyle}" … />
Observe la coma que separa las diferentes teclas de estilo. Desafortunadamente, las extensiones de marcado personalizadas no admiten un número ilimitado de parámetros de constructor, por lo que este enfoque genera un error de compilación. Si supiera de antemano cuántos estilos quería fusionar, podría haber usado la misma sintaxis XAML con un constructor que tomara el número deseado de cadenas:
public MultiStyleExtension(string inputResourceKey1, string inputResourceKey2)
{
}
Como solución alternativa, decidí que el parámetro constructor tomara una sola cadena que especificara los nombres de estilo separados por espacios. La sintaxis no es tan mala:
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.");
}
}
Cálculo de la salida de la extensión de marcado
Para calcular el resultado de una extensión de marcado, necesitamos anular un método de MarkupExtension llamado "ProvideValue". El valor devuelto por este método se establecerá en el objetivo de la extensión de marcado.
Empecé creando un método de extensión para Style que sabe cómo combinar dos estilos. El código para este método es bastante 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 del segundo. Si hay conflictos (por ejemplo, ambos estilos tienen un setter para la misma propiedad), el segundo estilo gana. Tenga en cuenta que, aparte de copiar estilos y activadores, también tomé en cuenta los valores TargetType y BasedOn, así como también los recursos que pueda tener el segundo estilo. Para TargetType del estilo fusionado, utilicé el tipo que sea más derivado. Si el segundo estilo tiene un estilo Basado en, fusiono su jerarquía de estilos recursivamente. Si tiene recursos, los copio al primer estilo. Si se hace referencia a esos recursos utilizando {StaticResource}, se resuelven estáticamente antes de que se ejecute este código de combinación, y por lo tanto, no es necesario moverlos. Agregué este código en caso de que usemos DynamicResources.
El método de extensión que se muestra arriba permite la siguiente sintaxis:
style1.Merge(style2);
Esta sintaxis es útil siempre que tenga instancias de ambos estilos dentro de ProvideValue. Bueno, yo no. Todo lo que obtengo del constructor es una lista de claves de cadena para esos estilos. Si hubiera soporte para params en los parámetros del constructor, podría haber usado la siguiente sintaxis para obtener las instancias de estilo reales:
<Button Style="{local:MultiStyle {StaticResource BigButtonStyle}, {StaticResource GreenButtonStyle}}" … />
public MultiStyleExtension(params Style[] styles)
{
}
Pero eso no funciona. E incluso si la limitación de params no existiera, probablemente tocáramos con otra limitación de extensiones de marcado, donde tendríamos que usar la sintaxis de elementos de propiedad en lugar de la sintaxis de atributos para especificar los recursos estáticos, que es detallado y engorroso (explico esto error mejor en una publicación de blog anterior ). E incluso si ambas limitaciones no existieran, aún así preferiría escribir la lista de estilos usando solo sus nombres: es más corta y más simple de leer que un StaticResource para cada uno.
La solución es crear un código usando StaticResourceExtension. Dada una clave de estilo de tipo cadena y un proveedor de servicios, puedo usar StaticResourceExtension para recuperar la instancia de estilo real. Aquí está la sintaxis:
Style currentStyle = new StaticResourceExtension(currentResourceKey).ProvideValue(serviceProvider) as Style;
Ahora tenemos todas las piezas necesarias para escribir el 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;
}
Aquí hay un ejemplo completo del uso de la extensión de marcado 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" />
Cuando anula SelectStyle, puede obtener la propiedad GroupBy a través de la reflexión, como se muestra a continuación:
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;
}
Esto es posible mediante la creación de una clase auxiliar para usar y ajustar sus estilos. CompoundStyle mencionado here 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.
Pero puede ampliar desde otro ... eche un vistazo a la propiedad 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>
Probablemente pueda obtener algo similar si aplica esto a una colección de elementos mediante el uso de StyleSelector, lo he usado para abordar un problema similar al usar diferentes estilos en TreeViewItems, dependiendo del tipo de objeto enlazado en el árbol. Es posible que deba modificar ligeramente la clase siguiente para ajustarse a su enfoque particular, pero espero que esto lo ayude a comenzar
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, aplica esto como tal
<TreeView> <TreeView.ItemContainerStyleSelector <myassembly:MyTreeStyleSelector DefaultStyle="{StaticResource DefaultItemStyle}" NewStyle="{StaticResource NewItemStyle}" /> </TreeView.ItemContainerStyleSelector> </TreeView>
Utilice 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:
WPF / XAML no proporciona esta funcionalidad de forma nativa, pero proporciona la capacidad de ampliación para permitirle hacer lo que desea.
Nos encontramos con la misma necesidad, y terminamos creando nuestra propia extensión de marcado XAML (que llamamos "MergedStylesExtension") para permitirnos crear un nuevo estilo a partir de otros dos estilos (que, de ser necesario, probablemente podría usarse varias veces en un fila para heredar de incluso más estilos).
Debido a un error de WPF / XAML, necesitamos usar la sintaxis del elemento de propiedad para usarlo, pero aparte de eso, parece funcionar bien. P.ej,
<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>
Recientemente escribí al respecto aquí: http://swdeveloper.wordpress.com/2009/01/03/wpf-xaml-multiple-style-inheritance-and-markup-extensions/
si no está tocando ninguna propiedad específica, puede obtener todas las propiedades básicas y comunes para el estilo cuyo tipo de destino sería FrameworkElement. luego, puede crear sabores específicos para cada tipo de objetivo que necesite, sin necesidad de copiar todas esas propiedades comunes nuevamente.
Creo que la respuesta simple es que no puedes hacer (al menos en esta versión de WPF) lo que intentas hacer.
Es decir, para cualquier elemento en particular, solo se puede aplicar un Estilo.
Sin embargo, como otros han indicado anteriormente, tal vez puedas usar BasedOn
para ayudarte. Mira la siguiente pieza de xaml suelto. En él, verá que tengo un estilo base que establece una propiedad que existe en la clase base del elemento al que deseo aplicar dos estilos. Y, en el segundo estilo, que se basa en el estilo base, configuré otra propiedad.
Entonces, la idea aquí ... es si de alguna manera puede separar las propiedades que desea establecer ... de acuerdo con la jerarquía de herencia del elemento sobre el que desea establecer múltiples estilos ... es posible que tenga una solución 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 esto ayude.
Nota:
Una cosa en particular para tener en cuenta. Si cambia TargetType
en el segundo estilo (en el primer conjunto de xaml anterior) a ButtonBase
, los dos estilos no se aplican. Sin embargo, consulte la siguiente xaml a continuación para evitar esa restricción. Básicamente, significa que debe darle una clave al Estilo y referenciarlo 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>