This article has been localized into Spanish by the community.
Recursos
WPF introduce un concepto muy práctico: la habilidad de almacenar data como un recurso, ya sea de manera local para un control, local para toda la ventana o global para toda la aplicación. Esta data puede ser prácticamente cualquier cosa, desde información a una jerarquía de controles WPF. Esto te permite ubicar data en un lugar y luego utilizarla en distintas partes lo cual es muy útil.
Este concepto es muy utilizado para estilos y plantillas, las cuales discutiremos más adelante en este tutorial, pero como será ilustrado en este capítulo, éstas pueden ser utilizadas para muchas otras cosas. Una demostración con un ejemplo simple:
<Window x:Class="WpfTutorialSamples.WPF_Application.ResourceSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="ResourceSample" Height="150" Width="350">
<Window.Resources>
<sys:String x:Key="strHelloWorld">Hello, world!</sys:String>
</Window.Resources>
<StackPanel Margin="10">
<TextBlock Text="{StaticResource strHelloWorld}" FontSize="56" />
<TextBlock>Just another "<TextBlock Text="{StaticResource strHelloWorld}" />" example, but with resources!</TextBlock>
</StackPanel>
</Window>
Los recursos tienen asignados una llave, utilizando el atributo x:Key, el cual permite la referencia en otras partes de la aplicación utilizando esta llave, en conjunto con la extensión de marcado StaticResource. En este ejemplo, solo almacenamos un string, el cual es utilizado en dos controles TextBlock distintos.
Recurso estático (StaticResource) vs Recurso dinámico (DynamicResource)
En los ejemplos hasta el momento, he utilizado la extensión de marcado StaticResource para hacer referencia a un recurso. Sin embargo, existe una alternativa, en la forma de DynamicResource.
La diferencia principal es que un recurso estático es resuelto una sola vez, esto ocurre cuando el XAML es cargado inicialmente. Este recurso si es cambiado luego no será reflejado donde ha sido utilizado el StaticResource.
Por otro lado, un DynamicResource es resuelto cuando es necesitado y nuevamente cuando el recurso cambia. Una forma de verlo es como una referencia estática contra una referencia a una función que observa valores y los envía cada vez que estos cambien - no es exactamente como funcionan, pero debe darle una mejor idea de cual utilizar. DynamicResource permite utilizar recursos que no se encuentran en el momento de diseño, e.x. Si se agregan desde Code-behind en el inicio de la aplicación.
Mas tipos de recurso
Compartir un string fue simple, pero se pueden hacer muchas otras cosas. En este siguiente ejemplo, almacenaremos un arreglo completo de string, y un gradiente para utilizarlo como fondo en la aplicación. Esto debería darte una buena idea de cuánto puedes hacer con los recursos.
<Window x:Class="WpfTutorialSamples.WPF_Application.ExtendedResourceSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="ExtendedResourceSample" Height="160" Width="300"
Background="{DynamicResource WindowBackgroundBrush}">
<Window.Resources>
<sys:String x:Key="ComboBoxTitle">Items:</sys:String>
<x:Array x:Key="ComboBoxItems" Type="sys:String">
<sys:String>Item #1</sys:String>
<sys:String>Item #2</sys:String>
<sys:String>Item #3</sys:String>
</x:Array>
<LinearGradientBrush x:Key="WindowBackgroundBrush">
<GradientStop Offset="0" Color="Silver"/>
<GradientStop Offset="1" Color="Gray"/>
</LinearGradientBrush>
</Window.Resources>
<StackPanel Margin="10">
<Label Content="{StaticResource ComboBoxTitle}" />
<ComboBox ItemsSource="{StaticResource ComboBoxItems}" />
</StackPanel>
</Window>
Esta vez hemos agregado unos recursos adicionales para que nuestra ventana ahora contenga un simple string, un arreglo de strings y un LinearGradientBrush. El string es utilizado como un label, el arreglo de strings es utilizado como los items del control ComboBox y el LinearGradientBrush es utilizado para el fondo de la ventana. Así podemos observar que casi todo puede ser almacenado como un recurso.
Recursos locales y compartidos en la aplicación.
Hasta el momento solo hemos almacenado recursos a nivel de ventana, lo cual significa que podemos utilizarlos en toda la ventana.
Si solo necesitas acceso a un recurso en particular en un control específico, puedes hacer que sea más local al agregarlo al control en específico, en lugar de la ventana. Esto funciona de la misma manera, la única diferencia será que ahora solo podrá ser utilizada desde el ámbito del control.
<StackPanel Margin="10">
<StackPanel.Resources>
<sys:String x:Key="ComboBoxTitle">Items:</sys:String>
</StackPanel.Resources>
<Label Content="{StaticResource ComboBoxTitle}" />
</StackPanel>
En este caso, debemos agregar el recurso al StackPanel y utilizarlo desde el control hijo, el Label. Otros controles dentro del StackPanel pueden utilizarlo. Pero controles fuera de este StackPanel en particular no pueden utilizar estos recursos.
Si necesitas la habilidad de acceder a estos recursos desde otras ventanas, esto también es posible. El archivo App.xaml puede contener varios recursos como la ventana y cualquier control WPF, y cuando son almacenados en App.xaml estos son disponibles de manera global en todas las ventanas y controles de usuario del proyecto. Funciona exactamente de la misma manera a si lo estuviera almacenando y utilizando desde una ventana.
<Application x:Class="WpfTutorialSamples.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
StartupUri="WPF application/ExtendedResourceSample.xaml">
<Application.Resources>
<sys:String x:Key="ComboBoxTitle">Items:</sys:String>
</Application.Resources>
</Application>
Usarlo también es lo mismo - WPF automáticamente ira a este ámbito desde el control local a la ventana y luego a App.xaml, para encontrar el recurso:
<Label Content="{StaticResource ComboBoxTitle}" />
Recursos desde Code-behind
Hasta el momento hemos accedido a nuestros recursos directamente desde XAML utilizando extensión de marcado. Pero por supuesto también podemos acceder a estos recursos desde Code-behind, lo cual puede ser útil en varias situaciones. En el ejemplo anterior, pudimos observar como almacenar varios recursos en distintos lugares, así que en este ejemplo, accederemos a tres distintos recursos utilizando Code-behind, cada uno almacenado en un ámbito distinto.
App.xaml:
<Application x:Class="WpfTutorialSamples.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
StartupUri="WPF application/ResourcesFromCodeBehindSample.xaml">
<Application.Resources>
<sys:String x:Key="strApp">Hello, Application world!</sys:String>
</Application.Resources>
</Application>
Window:
<Window x:Class="WpfTutorialSamples.WPF_Application.ResourcesFromCodeBehindSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="ResourcesFromCodeBehindSample" Height="175" Width="250">
<Window.Resources>
<sys:String x:Key="strWindow">Hello, Window world!</sys:String>
</Window.Resources>
<DockPanel Margin="10" Name="pnlMain">
<DockPanel.Resources>
<sys:String x:Key="strPanel">Hello, Panel world!</sys:String>
</DockPanel.Resources>
<WrapPanel DockPanel.Dock="Top" HorizontalAlignment="Center" Margin="10">
<Button Name="btnClickMe" Click="btnClickMe_Click">Click me!</Button>
</WrapPanel>
<ListBox Name="lbResult" />
</DockPanel>
</Window>
Code-behind:
using System;
using System.Windows;
namespace WpfTutorialSamples.WPF_Application
{
public partial class ResourcesFromCodeBehindSample : Window
{
public ResourcesFromCodeBehindSample()
{
InitializeComponent();
}
private void btnClickMe_Click(object sender, RoutedEventArgs e)
{
lbResult.Items.Add(pnlMain.FindResource("strPanel").ToString());
lbResult.Items.Add(this.FindResource("strWindow").ToString());
lbResult.Items.Add(Application.Current.FindResource("strApp").ToString());
}
}
}
Así que como pueden observar, hemos almacenado el mensaje "Hello, world!" en tres lugares distintos: uno en App.xaml, uno dentro de la ventana y el último en panel principal. La interfaz consiste de un botón y un ListBox.
En Code-behind, manejamos el evento click del botón, en el cual agregamos cada uno de los strings al ListBox, como se observa en la imagen. Utilizamos el método FindResource(), con el cual retornamos el recurso como un objeto(si lo encontramos) y lo convertimos en un string utilizando el método ToString().
Nótese como utilizamos el método FindResource() en distintos ámbitos - primero en el panel, luego en la ventana y después en el objeto Application actual. Tiene sentido buscar los recursos donde sabemos que se encuentran ubicados, pero como ha sido mencionado anteriormente, si un recurso no es encontrado, la búsqueda progresara a través de la jerarquía, así que en pricipio, podríamos haber utilizado el método FindResource() en el panel en los tres casos ya que estos hubieran buscado a lo largo de la ventana y luego a nivel de la aplicación si no encontraban el recurso.
Lo mismo no ocurre de la otra manera - la búsqueda no navega de manera descendiente en el árbol así que no podemos buscar un recurso desde el nivel de la aplicación si este ha sido definido a nivel de ventana o de control.