This article has been localized into Polish by the community.
Zasoby
WPF wprowadziło bardzo użyteczne narzędzie: możliwość do przechowywania danych jako zasoby, zarówno lokalnie dla pojedynczej kontrolki, okna, lub globalnie dla całej aplikacji. Te dane mogą zawierać prawie wszystko, od konkretnych danych, po całą hierarchię kontrolek WPF. Umożliwia to umieszczenie danych w jednym miejscu i korzystanie z nich z wielu innych miejsc, co jest bardzo przydatne.
Pojęcie to jest wykorzystywane w stylach i szablonach, o których przeczytasz później w tym tutorialu, lecz jak zostanie to przedstawione w tym rozdziale, zasoby można wykorzystać również do wielu innych rzeczy. Pozwól że zademonstruję na prostym przykładzie:
<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>
Każdy zasób posiada klucz, który jest tworzony wykorzystując atrybut x:Key. Umożliwia on odnoszenie się do zasobów z innych części aplikacji, w połączeniu z rozszerzeniem znaczników StaticResource. W tym przykładzie, przechowuję prosty ciąg znaków, który wykorzystuję w dwóch kontrolkach TextBlock.
StaticResource vs DynamicResource
W dotychczasowych przykładach, używałem rozszerzenia znaczników StaticResource do odwoływania się do zasobu. Istnieje jednak alternatywa w postaci DynamicResource.
Główną różnicą jest to, że odwołanie do zasobu statycznego jest ustalane tylko raz, w momencie ładowania kodu XAML. Jeżeli zasób ten zostanie później zmieniony, zmiana ta nie zostanie uwzględniona w miejscach użycia tego zasobu.
Zasób dynamiczny określany jest w momencie jego pobierania lub gdy zostanie zmieniony. Zasób statyczny porównać można do wartości statycznej a zasób dynamiczny do funkcji monitorującej daną wartość i zwracającą tę wartość jeśli tylko zostanie zmieniona - nie działa to w ten sposób ale powinno to przybliżyć kiedy użyć jednej lub drugiej opcji. Zasoby dynamiczne umożliwiają również korzystanie z zasobów, które nawet nie istnieją podczas projektowania aplikacji, np. można je dodać w kodzie podczas uruchamiania aplikacji.
Więcej typów zasobów
Udostępnienie prostego ciągu znaków było łatwe, lecz można zrobić o wiele więcej. W następnym przykładzie przechowam tablicę ciągów znaków razem z gradientowym pędzlem, przeznaczonym do wykorzystania jako tło dla okna aplikacji. To powinno pozwolić Ci zobaczyć ile można osiągnąć wykorzystując zasoby.
<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>
Tym razem, dodaliśmy kilka dodatkowych zasobów i teraz nasze okno zawiera prosty ciąg znaków, tablicę ciągów znaków i LinearGradientBrush. Ciąg znaków jest wykorzystany jako etykieta, tablica ciągów znaków jako elementy kontrolki ComboBox a gradientowy pędzel jako tło dla całego okna. Jak więc widzisz, prawie wszystko może być wykorzystane jako zasób.
Zasoby lokalne i globalne
Dotychczas przechowywaliśmy zasoby na poziomie okna co pozwalało na dostęp do nich z każdego elementu podrzędnego.
Kiedy potrzebujesz pewnych zasobów tylko dla jednej kontrolki możesz je dodać tylko dla niej zamiast dla całego okna. Działa to dokładnie w ten sam sposób a jedyną różnicą jest to, że zasoby są dostępne tylko w obrębie kontrolki, która je posiada.
<StackPanel Margin="10">
<StackPanel.Resources>
<sys:String x:Key="ComboBoxTitle">Items:</sys:String>
</StackPanel.Resources>
<Label Content="{StaticResource ComboBoxTitle}" />
</StackPanel>
W tym przypadku dodaliśmy zasoby do StackPanel a następnie użyliśmy ich jako treść podległej kontroli - Label'a. Inne kontrolki wewnątrz tego StackPanel'a również mogą używać tych zasobów, tak samo zresztą jak te dzieci i potomkowie kontrolek będących wewnątrz StackPanel'a. Pamiętaj, że kontrolki poza StackPanel'em nie będą miałby dostępu do jego zasobów.
Możliwy jest również dostęp do tych samych zasobów z kilku okien. Plik App.xaml może zawierać w sobie zasoby zupełnie tak jak okno lub dowolna kontrolka WPF. Kiedy zasoby są przechowywane wewnątrz tego pliku są one dostępne globalnie i każda kontrolka użytkownika może z nich skorzystać. Działa to dokładnie w ten sam sposób, jak wcześniej.
<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>
Tych zasobów używa się w taki sam sposób - WPF samoczynnie ich poszuka - najpierw w kontrolce, potem w oknie w którym znajduje się nasza kontrolka, a następnie w pliku App.xaml.
<Label Content="{StaticResource ComboBoxTitle}" />
Dostęp do zasobów z kodu za-widokiem (code-behind)
Jak na razie dostęp do naszych zasobów uzyskiwaliśmy za pomocą XAML używając rozszerzeń znaczników (markup extension). Nie jest to jedyny sposób dostępu do tych danych – można go uzyskać również z kodu za-widokiem, co czasem bardzo się przydaje. Wcześniej zobaczyliśmy, że można przechowywać zasoby w kilku miejscach, więc teraz dostaniemy się do wszystkich trzech miejsc z kodu za-widokiem.
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>
kod za-widokiem (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());
}
}
}
Jak widzisz, przechowaliśmy trzy różne komunikaty „Hello, world!": jeden w pliku App.xaml, drugi wewnątrz okna a trzeci lokalnie dla głównego panelu. Sam widok natomiast składa się z przycisku i ListBox'a.
W kodzie za-widokiem obsługujemy zdarzenie naciśnięcia przycisku w którym dodajemy wszystkie trzy ciągi znakowe do ListBox'a – jak widać na zrzucie ekranu. Aby uzyskać dostęp do zasobów zapisanych w plikach XAML używamy funkcji FindResource() która zwraca żądane zasoby (jeśli je znaleziono). Tak uzyskane zasoby zamieniamy w ciągi znaków za pomocą metody ToString().
Zauważ w jaki sposób użyliśmy metody FindResource() w różnych zakresach: pierwszy – w panelu, drugi – w oknie i trzeci – w bieżącej aplikacji (Application). Wyszukiwanie zasobów, tam gdzie wiemy, że są jest dobrym rozwiązaniem. Choć, jak już wcześniej wspomniano, jeśli zasób nie zostanie znaleziony w danym miejscu to wyszukiwanie jest kontynuowane w górę hierarchii. Także użycie metody FindResource() na panelu również pozwoliłoby nam znaleźć wszystkie trzy ciągi znaków ponieważ wyszukiwanie byłoby kontynuowane w górę z panelu do okna a następnie z okna do aplikacji jeśli zasobu by nie znaleziono.
Szukanie zasobów w drugą stronę nie działa: wyszukiwanie zawsze jest kontynuowane w górę drzewa, nie w dół. Także nie ma sensu wywoływanie funkcji FindResource() na poziomie aplikacji oczekując znalezienia zasobu, który został zdefiniowany wewnątrz kontrolki umieszczonej w oknie.