Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
La extensibilidad del modelo de control de Windows Presentation Foundation (WPF) reduce considerablemente la necesidad de crear un nuevo control. Sin embargo, en determinados casos es posible que tenga que crear un control personalizado. En este tema se describen las características que minimizan la necesidad de crear un control personalizado y los diferentes modelos de creación de controles en Windows Presentation Foundation (WPF). En este tema también se muestra cómo crear un nuevo control.
Alternativas a escribir un nuevo control
Históricamente, si quería obtener una experiencia personalizada de un control existente, se limitaba a cambiar las propiedades estándar del control, como el color de fondo, el ancho del borde y el tamaño de fuente. Si desea ampliar la apariencia o el comportamiento de un control más allá de estos parámetros predefinidos, tendría que crear un nuevo control, normalmente heredando de un control existente y reemplazando el método responsable de dibujar el control. Aunque sigue siendo una opción, WPF le permite personalizar los controles existentes mediante su modelo de contenido enriquecido, estilos, plantillas y desencadenadores. En la lista siguiente se proporcionan ejemplos de cómo se pueden usar estas características para crear experiencias personalizadas y coherentes sin tener que crear un nuevo control.
Contenido enriquecido. Muchos de los controles estándar de WPF admiten contenido enriquecido. Por ejemplo, la propiedad de contenido de un Button es de tipo Object, por lo que teóricamente todo se puede mostrar en un Button. Para que un botón muestre una imagen y un texto, puede agregar una imagen y un TextBlock a un StackPanel y asignar StackPanel a la propiedad Content. Dado que los controles pueden mostrar elementos visuales de WPF y datos arbitrarios, es menos necesario crear un nuevo control o modificar un control existente para admitir una visualización compleja. Para obtener más información sobre el modelo de contenido para Button y otros modelos de contenido en WPF, vea Modelo de contenido de WPF.
Estilos. A Style es una colección de valores que representan las propiedades de un control . Mediante el uso de estilos, puede crear una representación reutilizable de una apariencia y comportamiento de control deseado sin escribir un nuevo control. Por ejemplo, supongamos que desea que todos los TextBlock controles tengan una fuente roja y Arial con un tamaño de fuente de 14. Puede crear un estilo como un recurso y establecer las propiedades adecuadas en consecuencia. A continuación, todos los TextBlock que agregue a la aplicación tendrán la misma apariencia.
Plantillas de datos. Un DataTemplate permite personalizar cómo se muestran los datos en un control . Por ejemplo, un elemento en DataTemplate puede utilizarse para especificar cómo se muestran los datos en un ListBox. Para obtener un ejemplo de esto, consulte Información general sobre plantillas de datos. Además de personalizar la apariencia de los datos, puede DataTemplate incluir elementos de la interfaz de usuario, lo que proporciona una gran flexibilidad en las interfaces de usuario personalizadas. Por ejemplo, mediante un DataTemplate, puede crear un ComboBox en el que cada elemento contiene una casilla.
Plantillas de control. Muchos controles de WPF usan un ControlTemplate para definir la estructura y apariencia del control, lo que separa la apariencia de un control de la funcionalidad del control. Puede cambiar drásticamente la apariencia de un control redefinindo su ControlTemplate. Por ejemplo, supongamos que desea un control que tenga un aspecto similar a un semáforo. Este control tiene una sencilla interfaz de usuario y funcionalidad. El control es de tres círculos, solo uno de los cuales se puede iluminar a la vez. Tras reflexionar, puede darse cuenta de que un RadioButton ofrece la funcionalidad de que solo se pueda seleccionar uno a la vez, pero la apariencia predeterminada de RadioButton no se parece en nada a las luces de un semáforo. Dado que RadioButton usa una plantilla de control para definir su apariencia, es fácil redefinir ControlTemplate para que se ajuste a los requisitos del control y usar botones de radio para hacer su semáforo.
Nota:
Aunque un RadioButton puede usar un DataTemplate, un DataTemplate no es suficiente en este ejemplo. DataTemplate define la apariencia del contenido de un control. En el caso de , RadioButtonel contenido es lo que aparece a la derecha del círculo que indica si RadioButton está seleccionado. En el ejemplo de la luz de parada, el botón de radio solo debe ser un círculo que pueda "encenderse". Dado que el requisito de apariencia de la luz de parada es tan diferente del aspecto predeterminado de RadioButton, es necesario redefinir .ControlTemplate En general DataTemplate , se usa para definir el contenido (o los datos) de un control y ControlTemplate se usa para definir cómo se estructura un control.
Desencadenantes. Un Trigger permite cambiar dinámicamente la apariencia y el comportamiento de un control sin crear un nuevo control. Por ejemplo, supongamos que tiene varios controles ListBox en su aplicación y quiere que los elementos de cada ListBox sean en negrita y rojos cuando estén seleccionados. Su primer instinto podría ser crear una clase que herede de ListBox y sobrescribir el método OnSelectionChanged para cambiar la apariencia del elemento seleccionado, pero un enfoque mejor es agregar un desencadenador en un estilo de ListBoxItem que cambia la apariencia del elemento seleccionado. Un desencadenador permite cambiar los valores de propiedad o realizar acciones en función del valor de una propiedad. Un EventTrigger permite realizar acciones cuando se produce un evento.
Para obtener más información sobre los estilos, las plantillas y los desencadenadores, vea Aplicar estilos y plantillas.
En general, si el control refleja la funcionalidad de un control existente, pero desea que el control tenga un aspecto diferente, primero debe considerar si puede usar cualquiera de los métodos descritos en esta sección para cambiar la apariencia del control existente.
Modelos para la autoría de controles
El modelo de contenido enriquecido, los estilos, las plantillas y los desencadenadores minimizan la necesidad de crear un nuevo control. Sin embargo, si necesita crear un nuevo control, es importante comprender los diferentes modelos de creación de controles en WPF. WPF proporciona tres modelos generales para crear un control, cada uno de los cuales proporciona un conjunto diferente de características y nivel de flexibilidad. Las clases base de los tres modelos son UserControl, Controly FrameworkElement.
Derivando de UserControl
La manera más sencilla de crear un control en WPF es derivar de UserControl. Al compilar un control que hereda de UserControl, agrega componentes existentes a UserControl, asigna un nombre a los componentes y hace referencia a UserControl controladores de eventos en XAML. A continuación, puede hacer referencia a los elementos con nombre y definir los controladores de eventos en el código. Este modelo de desarrollo es muy similar al que se usa para el desarrollo de aplicaciones en WPF.
Si se construye correctamente, un UserControl puede aprovechar las ventajas de contenido enriquecido, estilos y desencadenadores. Sin embargo, si el control hereda de UserControl, las personas que usan el control no podrán usar un DataTemplate o ControlTemplate para personalizar su apariencia. Es necesario derivar de Control la clase o de una de sus clases derivadas (distintas de UserControl) para crear un control personalizado que admita plantillas.
Ventajas de derivar de UserControl
Considere la posibilidad de derivar de UserControl si se aplican todas las siguientes opciones:
Quiere compilar el control de forma similar a cómo se compila una aplicación.
El control se compone únicamente de componentes existentes.
No es necesario admitir la personalización compleja.
Origen en el Control
Derivar de la Control clase es el modelo utilizado por la mayoría de los controles WPF existentes. Cuando se crea un control que hereda de la Control clase , se define su apariencia mediante plantillas. Al hacerlo, se separa la lógica operativa de la representación visual. También puede garantizar el desacoplamiento de la Interfaz de Usuario y la lógica mediante comandos y enlaces en lugar de eventos y evitar hacer referencia a elementos en ControlTemplate siempre que sea posible. Si la interfaz de usuario y la lógica del control están desacopladas correctamente, un usuario del control puede volver a definir el control ControlTemplate para personalizar su apariencia. Aunque la creación de un personalizado Control no es tan simple como la creación de un UserControl, un personalizado Control proporciona la mayor flexibilidad.
Ventajas de la obtención del control
Considere la posibilidad de derivar de Control en lugar de usar la UserControl clase si se aplica alguna de las siguientes opciones:
Desea que la apariencia del control sea personalizable a través de ControlTemplate.
Quieres que tu control soporte diferentes temas.
Derivación de FrameworkElement
Controles que derivan de UserControl o Control dependen de la composición de elementos existentes. En muchos escenarios, se trata de una solución aceptable, ya que cualquier objeto que hereda de FrameworkElement puede estar en .ControlTemplate Sin embargo, hay ocasiones en las que la apariencia de un control requiere más que la funcionalidad de la composición de elementos simple. En estos escenarios, basar un componente en FrameworkElement es la opción correcta.
Hay dos métodos estándar para crear componentes basados en FrameworkElement: representación directa y composición de elementos personalizados. La representación directa implica sobrescribir el OnRender método de FrameworkElement y proporcionar DrawingContext operaciones que definen explícitamente los aspectos visuales del componente. Este es el método utilizado por Image y Border. La composición de elementos personalizados implica el uso de objetos de tipo Visual para componer la apariencia del componente. Para obtener un ejemplo, vea Usar objetos DrawingVisual. Track es un ejemplo de un control en WPF que usa la composición de elementos personalizados. También es posible mezclar la representación directa y la composición de elementos personalizados en el mismo control.
Ventajas de derivar de FrameworkElement
Considere la posibilidad de derivar de FrameworkElement si se aplica alguna de las siguientes opciones:
Desea tener un control preciso sobre la apariencia de su control más allá de lo que proporciona la composición simple de elementos.
Quieres definir la apariencia de tu control definiendo tu propia lógica de representación.
Quiere componer elementos existentes de formas innovadoras que van más allá de lo que es posible con UserControl y Control.
Conceptos básicos de creación de controles
Como se explicó anteriormente, una de las características más eficaces de WPF es la capacidad de ir más allá de establecer propiedades básicas de un control para cambiar su apariencia y comportamiento, pero aún no necesita crear un control personalizado. El sistema de propiedades y el sistema de eventos de WPF permiten las funciones de estilo, enlace de datos y desencadenador. En las secciones siguientes se describen algunas prácticas que debe seguir, independientemente del modelo que use para crear el control personalizado, de modo que los usuarios del control personalizado puedan usar estas características igual que para un control que se incluya con WPF.
Usar propiedades de dependencia
Cuando una propiedad es una propiedad de dependencia, es posible hacer lo siguiente:
Establezca la propiedad dentro de un estilo.
Enlace la propiedad a un origen de datos.
Use un recurso dinámico como valor de la propiedad.
Animar la propiedad.
Si desea que una propiedad del control admita cualquiera de esta funcionalidad, debe implementarla como una propiedad de dependencia. En el ejemplo siguiente se define una propiedad de dependencia denominada Value
haciendo lo siguiente:
Defina un identificador DependencyProperty llamado
ValueProperty
como un campopublic
static
readonly
.Registre un nombre de propiedad con el sistema de propiedades, llamando a DependencyProperty.Register, para especificar lo siguiente:
El nombre de la propiedad.
Tipo de la propiedad.
El tipo de propietario del inmueble.
Los metadatos de la propiedad. Los metadatos contienen el valor predeterminado de la propiedad, un CoerceValueCallback y un PropertyChangedCallback.
Defina una propiedad contenedora CLR denominada
Value
, que tenga el mismo nombre que se usa para registrar la propiedad de dependencia, implementando los descriptores de accesoget
yset
para la propiedad. Tenga en cuenta que los accesoresget
yset
solo llaman a GetValue y SetValue respectivamente. Se recomienda que los accesores de las propiedades de dependencia no contengan lógica adicional porque los clientes y WPF pueden omitir los accesores y llamar GetValue y SetValue directamente. Por ejemplo, cuando una propiedad está enlazada a un origen de datos, no se llama al accesor de la propiedadset
. En lugar de agregar lógica adicional a los accesores get y set, use los ValidateValueCallback, CoerceValueCallback y PropertyChangedCallback delegados para responder y comprobar el valor cuando cambie. Para obtener más información sobre estos callbacks, consulte Callbacks y validación de propiedades de dependencia.Defina un método para el CoerceValueCallback denominado
CoerceValue
.CoerceValue
garantiza queValue
sea mayor o igual queMinValue
y menor o igual queMaxValue
.Defina un método para , PropertyChangedCallbackdenominado
OnValueChanged
.OnValueChanged
crea un objeto RoutedPropertyChangedEventArgs<T> y se prepara para generar el evento enrutadoValueChanged
. Los eventos enrutados se describen en la sección siguiente.
/// <summary>
/// Identifies the Value dependency property.
/// </summary>
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(
"Value", typeof(decimal), typeof(NumericUpDown),
new FrameworkPropertyMetadata(MinValue, new PropertyChangedCallback(OnValueChanged),
new CoerceValueCallback(CoerceValue)));
/// <summary>
/// Gets or sets the value assigned to the control.
/// </summary>
public decimal Value
{
get { return (decimal)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
private static object CoerceValue(DependencyObject element, object value)
{
decimal newValue = (decimal)value;
NumericUpDown control = (NumericUpDown)element;
newValue = Math.Max(MinValue, Math.Min(MaxValue, newValue));
return newValue;
}
private static void OnValueChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
NumericUpDown control = (NumericUpDown)obj;
RoutedPropertyChangedEventArgs<decimal> e = new RoutedPropertyChangedEventArgs<decimal>(
(decimal)args.OldValue, (decimal)args.NewValue, ValueChangedEvent);
control.OnValueChanged(e);
}
''' <summary>
''' Identifies the Value dependency property.
''' </summary>
Public Shared ReadOnly ValueProperty As DependencyProperty = DependencyProperty.Register("Value", GetType(Decimal), GetType(NumericUpDown), New FrameworkPropertyMetadata(MinValue, New PropertyChangedCallback(AddressOf OnValueChanged), New CoerceValueCallback(AddressOf CoerceValue)))
''' <summary>
''' Gets or sets the value assigned to the control.
''' </summary>
Public Property Value() As Decimal
Get
Return CDec(GetValue(ValueProperty))
End Get
Set(ByVal value As Decimal)
SetValue(ValueProperty, value)
End Set
End Property
Private Shared Overloads Function CoerceValue(ByVal element As DependencyObject, ByVal value As Object) As Object
Dim newValue As Decimal = CDec(value)
Dim control As NumericUpDown = CType(element, NumericUpDown)
newValue = Math.Max(MinValue, Math.Min(MaxValue, newValue))
Return newValue
End Function
Private Shared Sub OnValueChanged(ByVal obj As DependencyObject, ByVal args As DependencyPropertyChangedEventArgs)
Dim control As NumericUpDown = CType(obj, NumericUpDown)
Dim e As New RoutedPropertyChangedEventArgs(Of Decimal)(CDec(args.OldValue), CDec(args.NewValue), ValueChangedEvent)
control.OnValueChanged(e)
End Sub
Para obtener más información, consulte Propiedades de dependencia personalizadas.
Uso de eventos enrutados
Al igual que las propiedades de dependencia amplían la noción de propiedades CLR con funcionalidad adicional, los eventos enrutados amplían la noción de eventos CLR estándar. Al crear un nuevo control WPF, también se recomienda implementar el evento como un evento enrutado porque un evento enrutado admite el siguiente comportamiento:
Los eventos pueden gestionarse en un control principal de varios controles. Si un evento es un evento burbujeante, un único elemento padre del árbol de componentes puede suscribirse al evento. A continuación, los autores de aplicaciones pueden usar un controlador para responder al evento de varios controles. Por ejemplo, si el control forma parte de cada elemento de un ListBox (porque se incluye en un DataTemplate), el desarrollador de la aplicación puede definir el controlador de eventos para el evento del control en el ListBox. Siempre que se produzca el evento en cualquiera de los controles, se llama al controlador de eventos.
Los eventos enrutados se pueden usar en , EventSetterlo que permite a los desarrolladores de aplicaciones especificar el controlador de un evento dentro de un estilo.
Los eventos enrutados se pueden usar en un EventTrigger, lo que resulta útil para animar propiedades mediante XAML. Para obtener más información, vea Información general sobre animaciones.
En el ejemplo siguiente se define un evento enrutado haciendo lo siguiente:
Defina un identificador RoutedEvent llamado
ValueChangedEvent
como un campopublic
static
readonly
.Registre el evento enrutado llamando al método EventManager.RegisterRoutedEvent. En el ejemplo se especifica la siguiente información cuando llama a RegisterRoutedEvent:
El nombre del evento es
ValueChanged
.La estrategia de enrutamiento es Bubble, lo que significa que se llama primero a un controlador de eventos en el origen (el objeto que genera el evento) y, a continuación, se llama a controladores de eventos en los elementos primarios del origen en sucesión, empezando por el controlador de eventos en el elemento primario más cercano.
El tipo del controlador de eventos es RoutedPropertyChangedEventHandler<T>, construido con un tipo Decimal.
El tipo propietario del evento es
NumericUpDown
.
Declare un evento público denominado
ValueChanged
e incluya declaraciones de descriptor de acceso de eventos. El ejemplo llama a AddHandler en la declaración del descriptor de accesoadd
y a RemoveHandler en la declaración del descriptor de accesoremove
para usar los servicios de eventos WPF.Cree un método virtual protegido denominado
OnValueChanged
que genere elValueChanged
evento.
/// <summary>
/// Identifies the ValueChanged routed event.
/// </summary>
public static readonly RoutedEvent ValueChangedEvent = EventManager.RegisterRoutedEvent(
"ValueChanged", RoutingStrategy.Bubble,
typeof(RoutedPropertyChangedEventHandler<decimal>), typeof(NumericUpDown));
/// <summary>
/// Occurs when the Value property changes.
/// </summary>
public event RoutedPropertyChangedEventHandler<decimal> ValueChanged
{
add { AddHandler(ValueChangedEvent, value); }
remove { RemoveHandler(ValueChangedEvent, value); }
}
/// <summary>
/// Raises the ValueChanged event.
/// </summary>
/// <param name="args">Arguments associated with the ValueChanged event.</param>
protected virtual void OnValueChanged(RoutedPropertyChangedEventArgs<decimal> args)
{
RaiseEvent(args);
}
''' <summary>
''' Identifies the ValueChanged routed event.
''' </summary>
Public Shared ReadOnly ValueChangedEvent As RoutedEvent = EventManager.RegisterRoutedEvent("ValueChanged", RoutingStrategy.Bubble, GetType(RoutedPropertyChangedEventHandler(Of Decimal)), GetType(NumericUpDown))
''' <summary>
''' Occurs when the Value property changes.
''' </summary>
Public Custom Event ValueChanged As RoutedPropertyChangedEventHandler(Of Decimal)
AddHandler(ByVal value As RoutedPropertyChangedEventHandler(Of Decimal))
MyBase.AddHandler(ValueChangedEvent, value)
End AddHandler
RemoveHandler(ByVal value As RoutedPropertyChangedEventHandler(Of Decimal))
MyBase.RemoveHandler(ValueChangedEvent, value)
End RemoveHandler
RaiseEvent(ByVal sender As System.Object, ByVal e As RoutedPropertyChangedEventArgs(Of Decimal))
End RaiseEvent
End Event
''' <summary>
''' Raises the ValueChanged event.
''' </summary>
''' <param name="args">Arguments associated with the ValueChanged event.</param>
Protected Overridable Sub OnValueChanged(ByVal args As RoutedPropertyChangedEventArgs(Of Decimal))
MyBase.RaiseEvent(args)
End Sub
Para obtener más información, vea Información general sobre eventos enrutados y Creación de un evento enrutado personalizado.
Uso de la vinculación
Para desacoplar la interfaz de usuario del control desde su lógica, considere la posibilidad de usar el enlace de datos. Esto es especialmente importante si define la apariencia del control mediante un ControlTemplate. Al usar el enlace de datos, es posible que pueda eliminar la necesidad de hacer referencia a partes específicas de la interfaz de usuario desde el código. Es recomendable evitar hacer referencia a elementos que están en ControlTemplate porque cuando el código hace referencia a elementos que están en ControlTemplate y ControlTemplate se cambia, el elemento al que se hace referencia debe incluirse en el nuevo ControlTemplate.
En el ejemplo siguiente se actualiza el TextBlockNumericUpDown
del control , se asigna un nombre a él y se hace referencia al cuadro de texto por nombre en el código.
<Border BorderThickness="1" BorderBrush="Gray" Margin="2"
Grid.RowSpan="2" VerticalAlignment="Center" HorizontalAlignment="Stretch">
<TextBlock Name="valueText" Width="60" TextAlignment="Right" Padding="5"/>
</Border>
private void UpdateTextBlock()
{
valueText.Text = Value.ToString();
}
Private Sub UpdateTextBlock()
valueText.Text = Value.ToString()
End Sub
En el ejemplo siguiente se usa la vinculación para conseguir el mismo objetivo.
<Border BorderThickness="1" BorderBrush="Gray" Margin="2"
Grid.RowSpan="2" VerticalAlignment="Center" HorizontalAlignment="Stretch">
<!--Bind the TextBlock to the Value property-->
<TextBlock
Width="60" TextAlignment="Right" Padding="5"
Text="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type local:NumericUpDown}},
Path=Value}"/>
</Border>
Para obtener más información sobre el enlace de datos, consulte la Descripción general del enlace de datos.
Diseño para diseñadores
Para recibir compatibilidad con controles WPF personalizados en WPF Designer para Visual Studio (por ejemplo, la edición de propiedades con la ventana Propiedades), siga estas instrucciones. Para obtener más información sobre el desarrollo para WPF Designer, consulta Diseñar XAML en Visual Studio.
Propiedades de dependencia
Asegúrese de implementar los accesores CLR get
y set
como se describió anteriormente, en "Usar propiedades de dependencia". Los diseñadores pueden utilizar el contenedor para detectar la presencia de una propiedad de dependencia, pero, al igual que WPF y los clientes del control, no están obligados a llamar a los accesores al obtener o establecer la propiedad.
Propiedades adjuntas
Debe implementar propiedades adjuntas en controles personalizados siguiendo las siguientes directrices.
Tenga un
public
static
readonly
DependencyProperty de la forma PropertyNameProperty
que estaba creando mediante el RegisterAttached método . El nombre de propiedad que se pasa a RegisterAttached debe coincidir con PropertyName.Implemente un par de
public
static
métodos CLR denominadosSet
PropertyName yGet
PropertyName. Ambos métodos deben aceptar una clase derivada de DependencyProperty como primer argumento. ElSet
método PropertyName también acepta un argumento cuyo tipo coincide con el tipo de datos registrado para la propiedad . ElGet
método PropertyName debe devolver un valor del mismo tipo. Si falta elSet
método PropertyName, la propiedad se marca como de solo lectura.Set
PropertyName yGet
PropertyName deben enrutarse directamente a los métodos GetValue y SetValue en el objeto de dependencia de destino, respectivamente. Los diseñadores pueden acceder a la propiedad adjunta llamando a través del contenedor de métodos o realizando una llamada directa al objeto de dependencia de destino.
Para obtener más información sobre las propiedades adjuntas, vea Información general sobre las propiedades adjuntas.
Definir y usar recursos compartidos
Puede incluir el control en el mismo ensamblado que la aplicación, o puede empaquetar el control en un ensamblado independiente que se puede usar en varias aplicaciones. En la mayor parte, la información que se describe en este tema se aplica independientemente del método que use. Sin embargo, hay una diferencia que merece la pena destacar. Cuando colocas un control en el mismo ensamblado que una aplicación, puedes agregar recursos globales al archivo App.xaml. Pero un ensamblado que contiene solo los controles no tiene un Application objeto asociado, por lo que un archivo App.xaml no está disponible.
Cuando una aplicación busca un recurso, examina tres niveles en el orden siguiente:
El nivel del elemento.
El sistema comienza con el elemento que hace referencia al recurso y, a continuación, busca en los recursos del elemento primario lógico, etc. hasta que se alcance el elemento raíz.
Nivel de aplicación.
Recursos definidos por el Application objeto .
El nivel del tema.
Los diccionarios de nivel de tema se almacenan en una subcarpeta denominada Temas. Los archivos de la carpeta Temas corresponden a temas. Por ejemplo, podrías tener Aero.NormalColor.xaml, Luna.NormalColor.xaml, Royale.NormalColor.xaml, etc. También puedes tener un archivo denominado generic.xaml. Cuando el sistema busca un recurso en el nivel de temas, primero lo busca en el archivo específico del tema y, a continuación, lo busca en generic.xaml.
Cuando su control está en un ensamblado separado de la aplicación, puede colocar los recursos globales en el nivel de elemento o en el nivel de tema. Ambos métodos tienen sus ventajas.
Definición de recursos en el nivel de elemento
Puede definir recursos compartidos en el nivel de elemento mediante la creación de un diccionario de recursos personalizado y su combinación con el diccionario de recursos del control. Al usar este método, puede asignar un nombre al archivo de recursos lo que quiera y puede estar en la misma carpeta que los controles. Los recursos en el nivel de elemento también pueden usar cadenas simples como claves. En el ejemplo siguiente se crea un LinearGradientBrush archivo de recursos denominado Dictionary1.xaml.
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<LinearGradientBrush
x:Key="myBrush"
StartPoint="0,0" EndPoint="1,1">
<GradientStop Color="Red" Offset="0.25" />
<GradientStop Color="Blue" Offset="0.75" />
</LinearGradientBrush>
</ResourceDictionary>
Una vez que haya definido su diccionario, debe combinarlo con el diccionario de recursos del control. Puedes hacerlo mediante XAML o código.
En el ejemplo siguiente se combina un diccionario de recursos mediante XAML.
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Dictionary1.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
La desventaja de este enfoque es que se crea un ResourceDictionary objeto cada vez que se hace referencia a él. Por ejemplo, si tienes 10 controles personalizados en la biblioteca y combinas los diccionarios de recursos compartidos para cada control mediante XAML, creas 10 objetos idénticos ResourceDictionary . Para evitarlo, cree una clase estática que combine los recursos en el código y devuelva el resultado ResourceDictionary.
En el ejemplo siguiente se crea una clase que devuelve un objeto compartido ResourceDictionary.
internal static class SharedDictionaryManager
{
internal static ResourceDictionary SharedDictionary
{
get
{
if (_sharedDictionary == null)
{
System.Uri resourceLocater =
new System.Uri("/ElementResourcesCustomControlLibrary;component/Dictionary1.xaml",
System.UriKind.Relative);
_sharedDictionary =
(ResourceDictionary)Application.LoadComponent(resourceLocater);
}
return _sharedDictionary;
}
}
private static ResourceDictionary _sharedDictionary;
}
En el ejemplo siguiente se combina el recurso compartido con los recursos de un control personalizado en el constructor del control antes de llamar a InitializeComponent
. Dado que la SharedDictionaryManager.SharedDictionary
es una propiedad estática, la ResourceDictionary se crea una sola vez. Dado que el diccionario de recursos se fusionó antes de que se llamara a InitializeComponent
, los recursos están disponibles para el control en su archivo XAML.
public NumericUpDown()
{
this.Resources.MergedDictionaries.Add(SharedDictionaryManager.SharedDictionary);
InitializeComponent();
}
Definición de recursos en el nivel de tema
WPF le permite crear recursos para diferentes temas de Windows. Como autor de un control, puede definir un recurso para un tema específico para cambiar la apariencia del control en función del tema que esté en uso. Por ejemplo, la apariencia de un Button elemento en el tema clásico de Windows (el tema predeterminado para Windows 2000) difiere de un Button en el tema de Windows Luna (el tema predeterminado para Windows XP) porque cada tema utiliza un Button diferente ControlTemplate.
Los recursos específicos de un tema se conservan en un diccionario de recursos con un nombre de archivo específico. Estos archivos deben estar en una carpeta denominada Themes
que sea una subcarpeta de la carpeta que contiene el control . En la tabla siguiente se enumeran los archivos de diccionario de recursos y el tema asociado a cada archivo:
Nombre del archivo del diccionario de recursos | Tema de Windows |
---|---|
Classic.xaml |
Aspecto clásico de Windows 9x/2000 en Windows XP |
Luna.NormalColor.xaml |
Tema azul predeterminado en Windows XP |
Luna.Homestead.xaml |
Tema de oliva en Windows XP |
Luna.Metallic.xaml |
Tema silver en Windows XP |
Royale.NormalColor.xaml |
Tema predeterminado en Windows XP Media Center Edition |
Aero.NormalColor.xaml |
Tema predeterminado en Windows Vista |
No es necesario definir un recurso para cada tema. Si no se define un recurso para un tema específico, entonces el control comprueba el recurso en Classic.xaml
. Si el recurso no está definido en el archivo que corresponde al tema actual o en Classic.xaml
, el control usa el recurso genérico, que se encuentra en un archivo de diccionario de recursos denominado generic.xaml
. El generic.xaml
archivo se encuentra en la misma carpeta que los archivos de diccionario de recursos específicos del tema. Aunque generic.xaml
no se corresponde con un tema específico de Windows, sigue siendo un diccionario de nivel de tema.
El control personalizado NumericUpDown de ejemplo de C# o Visual Basic con soporte para tema y automatización de interfaz de usuario contiene dos diccionarios de recursos del control NumericUpDown
: uno se encuentra en generic.xaml y el otro en Luna.NormalColor.xaml.
Cuando colocas un ControlTemplate en cualquiera de los archivos de diccionario de recursos específicos del tema, debes crear un constructor estático para tu control y llamar al método OverrideMetadata(Type, PropertyMetadata) en el DefaultStyleKey, como se muestra en el ejemplo siguiente.
static NumericUpDown()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(NumericUpDown),
new FrameworkPropertyMetadata(typeof(NumericUpDown)));
}
Shared Sub New()
DefaultStyleKeyProperty.OverrideMetadata(GetType(NumericUpDown), New FrameworkPropertyMetadata(GetType(NumericUpDown)))
End Sub
Definir y hacer referencia a claves para recursos de tema
Al definir un recurso en el nivel de elemento, puede asignar una cadena como su clave y acceder al recurso a través de la cadena. Al definir un recurso a nivel de tema, debe usar ComponentResourceKey como clave. En el ejemplo siguiente se define un recurso en generic.xaml.
<LinearGradientBrush
x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type local:Painter},
ResourceId=MyEllipseBrush}"
StartPoint="0,0" EndPoint="1,0">
<GradientStop Color="Blue" Offset="0" />
<GradientStop Color="Red" Offset="0.5" />
<GradientStop Color="Green" Offset="1"/>
</LinearGradientBrush>
En el siguiente ejemplo se hace referencia al recurso especificando ComponentResourceKey como la clave.
<RepeatButton
Grid.Column="1" Grid.Row="0"
Background="{StaticResource {ComponentResourceKey
TypeInTargetAssembly={x:Type local:NumericUpDown},
ResourceId=ButtonBrush}}">
Up
</RepeatButton>
<RepeatButton
Grid.Column="1" Grid.Row="1"
Background="{StaticResource {ComponentResourceKey
TypeInTargetAssembly={x:Type local:NumericUpDown},
ResourceId=ButtonBrush}}">
Down
</RepeatButton>
Especificar la ubicación de los recursos del tema
Para buscar los recursos de un control, la aplicación de hospedaje debe saber que el ensamblado contiene recursos específicos del control. Para lograr eso, puede agregar el ThemeInfoAttribute al ensamblado que contiene el control. ThemeInfoAttribute tiene una GenericDictionaryLocation propiedad que especifica la ubicación de los recursos genéricos y una ThemeDictionaryLocation propiedad que especifica la ubicación de los recursos específicos del tema.
En el ejemplo siguiente se establecen las propiedades GenericDictionaryLocation y ThemeDictionaryLocation en SourceAssembly, para especificar que los recursos genéricos y específicos del tema están en el mismo ensamblado que el control.
[assembly: ThemeInfo(ResourceDictionaryLocation.SourceAssembly,
ResourceDictionaryLocation.SourceAssembly)]
<Assembly: ThemeInfo(ResourceDictionaryLocation.SourceAssembly, ResourceDictionaryLocation.SourceAssembly)>
Consulte también
- Diseño de XAML en Visual Studio
- URIs de paquete en WPF
- Personalización de controles
.NET Desktop feedback