Budowa aplikacji w technologii .NET
wykład 05 – Polecenia i zasoby Commands – polecenia
• Polecenie jest wyższym poziomem abstrakcji, niż zdarzenie – reprezentuje zadanie, które chce wykonać użytkownik (np. otworzyć plik, skopiować tekst do schowka, wydrukować dokument).
• Zdarzenia są akcjami, które użytkownik musi podjąć, aby wykonać zadanie. W standardowym scenariuszu każde polecenie może być wydane na wiele sposobów:
menu, przycisk na pasku narzędzi, skrót klawiaturowy, etc.
• System poleceń w WPF pozwala wydzielić abstrakcję zadań i utworzyć połączenie miedzy akcją (lub kontrolką) uruchamiającą polecenie a wykonującą je metodą.
Pozwala to przenieść kod wykonujący zadania poza kod kontrolek i zdarzeń (w dobrze zaprojektowanej aplikacji logika nie powinna być umieszczona w
handlerach zdarzeń, ale metodach wyższego poziomu), a jedno polecenie może być powiązane z wieloma elementami interfejsu użytkownika.
• Polecenie może być wyłączone, co dezaktywuje uruchamiające je kontrolki.
Rozwiązanie przy użyciu zdarzeń (handler jest tylko przekaźnikiem między interfejsem a logiką):
rozwiązanie przy użyciu poleceń:
(za: Pro WPF in C# 2010 Windows Presentation Foundation in .NET 4, Matthew MacDonald)
Składniki modelu poleceń w WPF:
• Command – obiekt reprezentujący polecenie/ zadanie do wykonania (nie zawiera jednak kodu je wykonującego)
• CommandSource – kontrolka lub akcja wywołująca polecenie
• CommandBinding – obiekt łączący polecenie z jego wykonawcą (handlerem)
• CommandTarget – element, na którym wykonywana jest akcja – np. pole tekstowe na którym wykonywane jest Copy
Interfejs ICommand
public interface ICommand {
void Execute(object parameter);
bool CanExecute(object parameter);
event EventHandler CanExecuteChanged;
}
• Execute – uruchamia polecenie (ale nie zawiera logiki zadania)
• CanExecute – zwraca stan polecenia (czy jest włączone)
• CanExecuteChanged – zdarzenie wywoływane, gdy stan polecenia ulega zmianie
Interfejs ICommand
• RoutedCommand jest jedyną klasą bezpośrednio implementującą interfejs ICommand.
• Daje ona wsparcie dla zdarzeń typu tunneling oraz bubbling.
• Dodaje nazwę oraz kolekcję akcji myszy i klawiatury wywołujących polecenie public class RoutedCommand : ICommand
{
public InputGestureCollection InputGestures { get; } public string Name { get; }
public Type OwnerType { get; }
public bool CanExecute(Object parameter, IInputElement target) { } public void Execute(Object parameter, IInputElement target) { } }
• Klasa RoutedUICommand dodaje własność Text, odpowiadającą za tekst wyświetlany w kontrolkach powiązanych z poleceniem.
public class RoutedUICommand : RoutedCommand {
public string Text { get; set; }
Biblioteka poleceń WPF:
WPF udostępnia zastaw predefiniowanych poleceń do użytku twórcy aplikacji. Są one dostępne poprzez statyczne właściwości poniższych klas:
• ApplicationCommands – polecenia związane ze schowkiem (np. Copy, Cut, Paste), zarządzaniem dokumentami (np. New, Open, Save, SaveAs) i operacjami poziomu aplikacji (np. Properties, Help, Print, Undo, Redo)
• NavigationCommands – polecenia używane do nawigacji w aplikacjach o interfejsie opartym na stronach (np. BrowseBack, BrowseForward, NextPage, Refresh, IncreaseZoom)
• EditingCommands – polecenia związane z edycją dokumentów, dotyczą poruszania się po dokumencie (MoveToLineEnd, MoveLeftByWord,
MoveUpByPage), wyboru zawartości (SelectToLineEnd, SelectLeftByWord) i zmian formatowania (ToggleBold, ToggleUnderline)
• ComponentCommands – polecenia używane przez komponenty interfejsu użytkownika (np. MoveLeft, ScrollPageUp)
• MediaCommands – polecenia związane z zarządzaniem multimediami (np. Play, Pause, NextTrack, IncreaseVolume)
Część z predefiniowanych poleceń ma już zdefiniowane domyślne połączenie z akcją (popularnymi skrótami klawiaturowymi: Ctrl+O, Ctrl+S, Ctrl+Z, etc.).
Używanie poleceń:
1. Wybór polecenia
• jednego z gotowych lub zdefiniowanie własnej klasy polecenia
Używanie poleceń:
1. Wybór polecenia
2. Przypisanie polecenia uruchamiającej je kontrolce lub/i akcji klawiatury/ myszy
• polecenia mogą być wywoływane z kodu poprzez metodę Execute
• przeważnie jednak przypisujemy je do kontrolek implementujących interfejs ICommandSource: kontrolki dziedziczące z ButtonBase, odnośniki
(Hyperlink), elementy list (ListBoxItem) i menu (MenuItem)
<Button Command="ApplicationCommands.Help">Help</Button>
• lub:
<Button Command="Help">Help</Button>
• można też dodać:
◦ CommandParameter – dodatkowy parametr dla polecenia
◦ CommandTarget – element na którym ma być wykonane polecenie
Używanie poleceń:
1. Wybór polecenia
2. Przypisanie polecenia uruchamiającej je kontrolce lub/i akcji klawiatury/ myszy
• polecenia mogą być wywoływane z kodu poprzez metodę Execute
• przeważnie jednak przypisujemy je do kontrolek implementujących interfejs ICommandSource: kontrolki dziedziczące z ButtonBase, odnośniki
(Hyperlink), elementy list (ListBoxItem) i menu (MenuItem)
• możemy dodać wiele źródeł jednego polecenia:
<DockPanel>
<Menu DockPanel.Dock="Top">
...
<MenuItem Header="Help">
<MenuItem Command="Help"></MenuItem>
</MenuItem>
</Menu>
<StackPanel> ... </StackPanel>
</DockPanel>
kontrolka MenuItem wykorzystuje własność Text oraz przypisany skrót do wyświetlania
Używanie poleceń:
1. Wybór polecenia
2. Przypisanie polecenia uruchamiającej je kontrolce lub/i akcji klawiatury/ myszy
• polecenia mogą być wywoływane z kodu poprzez metodę Execute
• przeważnie jednak przypisujemy je do kontrolek implementujących interfejs ICommandSource
• przypisanie polecenia akcji myszy lub klawiatury:
ApplicationCommands.Help.InputGestures.Add(
new MouseGesture(MouseAction.LeftDoubleClick));
ApplicationCommands.Help.InputGestures.Add(
new KeyGesture(Key.H, ModifierKeys.Control));
• lub:
<Window.InputBindings>
<KeyBinding Command="Help" Key="H" Modifiers="Ctrl"/>
<MouseBinding Command="Help" MouseAction="LeftDoubleClick"/>
</Window.InputBindings>
Używanie poleceń:
1. Wybór polecenia
2. Przypisanie polecenia uruchamiającej je kontrolce lub/i akcji klawiatury/ myszy 3. Stworzenie metody wykonującej polecenie
private void MyHelp(object sender, ExecutedRoutedEventArgs e) {
MessageBox.Show("Udało ci się uruchomić pomoc.", "Pomoc",
MessageBoxButton.OK,
MessageBoxImage.Information);
e.Handled = true;
}
• e.Command – polecenie, które wywołało tę metodę
• e.Parameter – dodatkowy parametr, który mogliśmy przekazać u źródła
Używanie poleceń:
1. Wybór polecenia
2. Przypisanie polecenia uruchamiającej je kontrolce lub/i akcji klawiatury/ myszy 3. Stworzenie metody wykonującej polecenie
4. Stworzenie powiązania (CommandBinding) łączącego polecenie z wykonawcą
• wiąże logikę z poleceniem – bez tego polecenie jest wyłączone, a kontrolki nieaktywne
5. Dodanie stworzonego powiązania do kolekcji poleceń okna // stworzenie dowiązania
CommandBinding bind = new CommandBinding();
bind.Command = ApplicationCommands.Help;
// przypisanie handlera do obsługi zdarzenia bind.Executed += MyHelp;
// rejestracja dowiązania
this.CommandBindings.Add(bind);
• powiązanie można dodać do dowolnego elementu, ale zazwyczaj dodawane jest do okna najwyższego poziomu
• zdarzenie odpalane jest przez przypisany poleceniu element, ale dzięki bubbling dotrze do elementu zawierającego powiązanie
Używanie poleceń:
1. Wybór polecenia
2. Przypisanie polecenia uruchamiającej je kontrolce lub/i akcji klawiatury/ myszy 3. Stworzenie metody wykonującej polecenie
4. Stworzenie powiązania (CommandBinding) łączącego polecenie z wykonawcą
• wiąże logikę z poleceniem – bez tego polecenie jest wyłączone, a kontrolki nieaktywne
5. Dodanie stworzonego powiązania do kolekcji poleceń okna this.CommandBindings.Add(
new CommandBinding(ApplicationCommands.Help, MyHelp));
Używanie poleceń:
1. Wybór polecenia
2. Przypisanie polecenia uruchamiającej je kontrolce lub/i akcji klawiatury/ myszy 3. Stworzenie metody wykonującej polecenie
4. Stworzenie powiązania (CommandBinding) łączącego polecenie z wykonawcą
• wiąże logikę z poleceniem – bez tego polecenie jest wyłączone, a kontrolki nieaktywne
5. Dodanie stworzonego powiązania do kolekcji poleceń okna
<Window ...>
<Window.CommandBindings>
<CommandBinding Command="Help" Executed="MyHelp" />
</Window.CommandBindings>
...
</Window>
• wiązanie wykonuje się raz, niezależnie od liczby źródeł
Wyłączanie poleceń
• Przydatne w przypadku poleceń, które mogą być wywoływane tylko w określonych sytuacjach
• Włączanie/ wyłączanie odbywa się w obsłudze zdarzenia CanExecute:
CommandBinding bind = new CommandBinding();
...
bind.CanExecute += MyHelpCanExecute;
...
this.CommandBindings.Add(bind);
• lub:
new CommandBinding(ApplicationCommands.Help, MyHelp,
MyHelpCanExecute)
• lub:
<CommandBinding Command="Help" Executed="MyHelp"
CanExecute="MyHelpCanExecute"/>
Wyłączanie poleceń
• Przydatne w przypadku poleceń, które mogą być wywoływane tylko w określonych sytuacjach
• Włączanie/ wyłączanie odbywa się w obsłudze zdarzenia CanExecute:
private void MyHelpCanExecute(object sender,
CanExecuteRoutedEventArgs e) {
if( /*... help dostępny ...*/ ) e.CanExecute = true;
else
e.CanExecute = false;
}
• uwaga: ta metoda może uruchamiać się często – gdy WPF uzna, że mogła zajść jakaś zmiana
• można też uruchomić ręcznie, aby wymusić odświeżenie kontrolek:
CommandManager.InvalidateRequerySuggested();
Przykład użycia:
<Window ...>
<Window.CommandBindings>
<CommandBinding Command="Save" Executed="MySave"
CanExecute="MySaveCanExecute" />
</Window.CommandBindings>
<StackPanel>
<TextBox TextChanged="MyTextChanged"></TextBox>
<Button Command="Save">Save</Button>
</StackPanel>
</Window>
public partial class Window1 : Window {
public Window1() {
InitializeComponent();
IsDirty = false;
}
private bool IsDirty { get; set; } private void MySave(...)
{
IsDirty = false;
}
private void MySaveCanExecute(...) {
e.CanExecute = IsDirty;
}
private void MyTextChanged(...) {
IsDirty = true;
} }
Techniki zaawansowane – definiowanie własnych poleceń public class PizzaCommands
{
private static RoutedUICommand piecz;
static PizzaCommands() {
piecz = new RoutedUICommand(
"Upiecz Pizzę", "Piecz", typeof(PizzaCommands));
// dobrym pomysłem jest dodanie tutaj // skrótów klawiatury:
piecz.InputGestures.Add(new KeyGesture(Key.P,
ModifierKeys.Control));
}
public static RoutedUICommand Piecz {
get { return piecz; } }
}
Techniki zaawansowane – definiowanie własnych poleceń
<Window ...
xmlns:app="clr-namespace:WpfApplication1" ... >
<Grid>
...
<Button Command="app:PizzaCommands.Piecz">
Upiecz Pizzę </Button>
...
</Grid>
</Window>
• Przed przypisaniem polecenia kontrolce, musimy zmapować własną przestrzeń nazw do zrozumiałej przez XAMLa (WpfApplication1 to nazwa projektu, app to wybrany alias).
Techniki zaawansowane – wykorzystanie własności Text
• własność Text zawiera opisową nazwę polecenia; niektóre kontrolki (np.
MenuItem) same go wykorzystują, w wypadku Buttonów możemy zrobić to ręcznie:
<Button Command="SaveAs"
Content="{x:Static ApplicationCommands.SaveAs}"></Button>
• jednak w ten sposób uzyskamy jedynie nazwę pobraną z ToString, a nie opisowy tekst polecenia; do tego lepiej wykorzystać wiązanie (o wiązaniach więcej na kolejnym wykładzie):
<Button Command="SaveAs"
Content="{Binding RelativeSource={RelativeSource Self}, Path=Command.Text}"></Button>
• można go użyć również np. w tooltipie:
<Button Command="SaveAs"
ToolTip="{Binding RelativeSource={RelativeSource Self}, Path=Command.Text}">
Techniki zaawansowane – kontrolki z wbudowaną obsługą poleceń
• Niektóre kontrolki do wprowadzania danych mają wbudowaną obsługę poleceń (np. TextBox obsługuje polecenia Cut, Copy, Paste).
• Przyciski z przypisanymi poleceniami, umieszczone w toolbarze lub menu automatycznie to obsłużą, bez dowiązywania jakiejkolwiek logiki.
• Kontrolki same (po focusie) rozpoznają kiedy polecenie może być wywołane oraz co jest elementem docelowym.
<StackPanel>
<ToolBar>
...
<Button Command="Paste">Paste</Button>
</ToolBar>
<TextBox Name="txtBox"></TextBox>
<WrapPanel>
...
<Button Command="Paste">Paste</Button>
</WrapPanel>
</StackPanel>
Techniki zaawansowane – kontrolki z wbudowaną obsługą poleceń
• Niektóre kontrolki do wprowadzania danych mają wbudowaną obsługę poleceń (np. TextBox obsługuje polecenia Cut, Copy, Paste).
• Przyciski z przypisanymi poleceniami, umieszczone w toolbarze lub menu automatycznie to obsłużą, bez dowiązywania jakiejkolwiek logiki.
• Kontrolki same (po focusie) rozpoznają kiedy polecenie może być wywołane oraz co jest elementem docelowym.
<StackPanel>
<ToolBar>
...
<Button Command="Paste">Paste</Button>
</ToolBar>
<TextBox Name="txtBox"></TextBox>
<WrapPanel>
...
<Button Command="Paste">Paste</Button>
</WrapPanel>
</StackPanel>
Techniki zaawansowane – kontrolki z wbudowaną obsługą poleceń
• W wypadku kontrolek poza menu czy toolbarem możemy samodzielnie wskazać element docelowy polecenia:
<TextBox Name="txtBox"></TextBox>
<WrapPanel>
...
<Button Command="Paste"
CommandTarget="{Binding ElementName=txtBx}">
Paste</Button>
</WrapPanel>
• lub kazać poszukiwać go w elementach nadrzędnych (hierarchii zagnieżdżenia)
<WrapPanel FocusManager.IsFocusScope="True">
...
<Button Command="Paste">Paste</Button>
</WrapPanel>
Techniki zaawansowane – kontrolki z wbudowaną obsługą poleceń
• W wypadku kontrolek poza menu czy toolbarem możemy samodzielnie wskazać element docelowy polecenia:
<TextBox Name="txtBox"></TextBox>
<WrapPanel>
...
<Button Command="Paste"
CommandTarget="{Binding ElementName=txtBx}">
Paste</Button>
</WrapPanel>
• lub kazać poszukiwać go w elementach nadrzędnych (hierarchii zagnieżdżenia)
<WrapPanel FocusManager.IsFocusScope="True">
...
<Button Command="Paste">Paste</Button>
</WrapPanel>
Techniki zaawansowane – kontrolki z wbudowaną obsługą poleceń
Gdy chcemy wyłączyć polecenie kontrolki:
• niekiedy mamy gotową flagę, np. TextBox pozwala na ustawienie IsUndoEnabled, aby wyłączyć Undo
• możemy też dodać własne wiązanie do tego polecenia, które dostarczy CanExecuted zawsze zwracające false:
CommandBinding commandBinding = new CommandBinding(
ApplicationCommands.Cut, null, SuppressCommand);
txtBox.CommandBindings.Add(commandBinding);
• i:
private void SuppressCommand(...) {
e.CanExecute = false;
e.Handled = true;
}
Techniki zaawansowane – kontrolki z wbudowaną obsługą poleceń
Gdy chcemy wyłączyć polecenie kontrolki:
• niekiedy mamy gotową flagę, np. TextBox pozwala na ustawienie IsUndoEnabled, aby wyłączyć Undo
• możemy też dodać własne wiązanie do tego polecenia, które dostarczy CanExecuted zawsze zwracające false
• akcję (np. skrót klawiaturowy) uruchamiające to polecenie możemy związać poleceniem „to nie jest polecenie”:
KeyBinding keyBinding = new KeyBinding(
ApplicationCommands.NotACommand, Key.C, ModifierKeys.Control);
txt.InputBindings.Add(keyBinding);
Techniki zaawansowane – wykorzystanie tego samego polecenia w kilku miejscach
• Efekt polecenia zależy od elementu docelowego (np. gdy mamy dwa pola tekstowe w oknie, Cut, Copy, Paste działają dla jednego z nich, zależnie od focusa)
<StackPanel>
<ToolBar>
<Button Command="Cut">Cut</Button>
<Button Command="Copy">Copy</Button>
<Button Command="Paste">Paste</Button>
</ToolBar>
<TextBox ></TextBox>
<TextBox ></TextBox>
</StackPanel>
Techniki zaawansowane – wykorzystanie tego samego polecenia w kilku miejscach
• Efekt polecenia zależy od elementu docelowego (np. gdy mamy dwa pola tekstowe w oknie, Cut, Copy, Paste działają dla jednego z nich, zależnie od focusa)
• Jak uzyskać ten efekt dla innych poleceń?
<Window ...>
<Window.CommandBindings>
<CommandBinding Command="Save" Executed="MySave"
CanExecute="MySaveCanExecute" />
</Window.CommandBindings>
<StackPanel>
<ToolBar>
...
<Button Command="Save">Save</Button>
</ToolBar>
<TextBox TextChanged="MyTextChanged"></TextBox>
<TextBox TextChanged="MyTextChanged"></TextBox>
</StackPanel>
</Window>
Techniki zaawansowane – wykorzystanie tego samego polecenia w kilku miejscach
• Efekt polecenia zależy od elementu docelowego (np. gdy mamy dwa pola tekstowe w oknie, Cut, Copy, Paste działają dla jednego z nich, zależnie od focusa)
• Jak uzyskać ten efekt dla innych poleceń?
<Window ...>
<Window.CommandBindings>
<CommandBinding Command="Save" Executed="MySave"
CanExecute="MySaveCanExecute" />
</Window.CommandBindings>
<StackPanel>
<ToolBar>
...
<Button Command="Save">Save</Button>
</ToolBar>
<TextBox TextChanged="MyTextChanged"></TextBox>
<TextBox TextChanged="MyTextChanged"></TextBox>
</StackPanel>
</Window>
Techniki zaawansowane – wykorzystanie tego samego polecenia w kilku miejscach
• Efekt polecenia zależy od elementu docelowego (np. gdy mamy dwa pola tekstowe w oknie, Cut, Copy, Paste działają dla jednego z nich, zależnie od focusa)
• Jak uzyskać ten efekt dla innych poleceń?
private Dictionary<object, bool> isDirty = new ...;
private void MySave(...) {
isDirty[e.Source] = false;
}
private void MySaveCanExecute(...) {
if (isDirty.ContainsKey(e.Source) && isDirty[e.Source]) e.CanExecute = true;
else
e.CanExecute = false;
}
private void MyTextChanged(...) {
isDirty[e.Source] = true;
Techniki zaawansowane – wykorzystanie tego samego polecenia w kilku miejscach
• Efekt polecenia zależy od elementu docelowego (np. gdy mamy dwa pola tekstowe w oknie, Cut, Copy, Paste działają dla jednego z nich, zależnie od focusa)
• Jak uzyskać ten efekt dla innych poleceń?
• e.Source zwraca element docelowy polecenia, jeżeli:
◦ kontrolka wywołująca polecenie znajduje się w toolbarze
◦ przypisaliśmy ręcznie CommandTarget
◦ nakażemy poszukiwania elementu poprzez FocusManager.IsFocusScope
• w przeciwnym wypadku e.Source będzie kontrolką, która wywołała zdarzenie
• Możemy również tworzyć osobne wiązanie dla każdego pola tekstowego:
<TextBox TextChanged="MyTextChanged">
<TextBox.CommandBindings>
<CommandBinding Command="Save" Executed="MySave"
CanExecute="MySaveCanExecute" />
</TextBox.CommandBindings>
</TextBox>
Techniki zaawansowane – wykorzystanie tego samego polecenia w kilku miejscach
• Efekt polecenia zależy od elementu docelowego (np. gdy mamy dwa pola tekstowe w oknie, Cut, Copy, Paste działają dla jednego z nich, zależnie od focusa)
• Jak uzyskać ten efekt dla innych poleceń?
• e.Source zwraca element docelowy polecenia, jeżeli:
◦ kontrolka wywołująca polecenie znajduje się w toolbarze
◦ przypisaliśmy ręcznie CommandTarget
◦ nakażemy poszukiwania elementu poprzez FocusManager.IsFocusScope
• w przeciwnym wypadku e.Source będzie kontrolką, która wywołała zdarzenie
• Możemy również tworzyć osobne wiązanie dla każdego pola tekstowego.
• Teraz sender powie nam dla którego pola została wywołana metoda:
private void MySave(object sender, ExecutedRoutedEventArgs e) {
string text = ((TextBox)sender).Text;
MessageBox.Show("Dane do zapisania: " + text);
...
}
Techniki zaawansowane – wykorzystanie tego samego polecenia w kilku miejscach
• Efekt polecenia zależy od elementu docelowego (np. gdy mamy dwa pola tekstowe w oknie, Cut, Copy, Paste działają dla jednego z nich, zależnie od focusa)
• Jak uzyskać ten efekt dla innych poleceń?
• Nieco nieeleganckie jest tworzenie wiązania osobno dla każdego pola, ale da się to rozwiązać definiując zasób:
<Window.Resources>
<CommandBinding x:Key="binding" Command="Save"
Executed="MySave" CanExecute="MySaveCanExecute">
</CommandBinding>
</Window.Resources>
• i używając go:
<TextBox.CommandBindings>
<StaticResource ResourceKey="binding"></StaticResource>
</TextBox.CommandBindings>
Techniki zaawansowane – parametr przekazywany do Command
• niektóre polecenia wymagają dodatkowego parametru
• można odczytać go z innej kontrolki:
<Button Command="NavigationCommands.Zoom"
CommandParameter="{Binding ElementName=txtZoom, Path=Text}">
Zoom To Value
</Button>
Ograniczenia systemu poleceń:
• Polecenie może zmieniać automatycznie jedynie własność IsEnabled. Użyteczne mogłoby być też IsChecked, niestety, należy robić to ręcznie.
• Nie ma zbudowanych mechanizmów do śledzenia historii wykonywanych poleceń (do Undo/Redo).
Resources – zasoby
W WPF można wyróżnić dwa rodzaje zasobów:
• Assembly resources (binary resources)
◦ pliki z danymi (binarne), wbudowane w skompilowany podzespół aplikacji (np.
obrazki)
◦ działają prawie identycznie jak assembly resources w innych aplikacjach .NET, jedyną różnicą jest system adresujący, wykorzystywany przy odwoływaniu się od zasobów
◦ zasobem typu assembly w aplikacjach WPF jest np. plik BAML (skompilowany plik XAML)
• Object resources
◦ mogą być nimi dowolne obiekty, zdefiniowany przeważnie w XAMLu, umożliwiają przechowywanie różnego rodzaju informacji w centralnym miejscu, zapobiegając powtarzaniu kodu
Dodawanie zasobów typu assembly
• Należy dodać odpowiednie pliki do projektu i ustawić i właściwość Build Action na Resource (w celu lepszej organizacji, pliki mogą być grupowane w foldery).
Odwoływanie się do zasobów typu assembly
• Obiekt StreamResourceInfo daje dostęp m. in. do typu oraz strumienia danych zasobu:
StreamResourceInfo sri = Application.GetResourceStream(
new Uri("images/info.png", UriKind.Relative));
◦ ContentType zwraca string opisujący typ danych
◦ Stream – strumień (typu UnmanagedMemoryStream), z którego można odczytać bajty danych
• Niektóre klasy mają wbudowaną obsługę zasobów i potrafią z nimi współpracować (odnajdują je przez adres URI):
<Image Source="images/info.png" />
• lub:
image1.Source = new BitmapImage(
new Uri("images/info.png", UriKind.Relative));
Odwoływanie się do zasobów typu assembly
• Przez adres URI w przypadku zasobów zawartych w innym podzespole (bibliotece dll, z której korzysta nasza aplikacja):
img.Source = new BitmapImage(
new Uri("ImageLibrary;component/images/winter.jpg", UriKind.Relative));
Dodawanie zasobów z Build Action na Content (i Copy to Output Directory)
• Warto używać, gdy:
◦ chcemy zmieniać zasób bez ponownej kompilacji
◦ zasób jest bardzo duży
◦ plik jest opcjonalny i chcemy dostarczać aplikację również bez niego
◦ jest to plik dźwiękowy
• Content jest wygodniejsze, niż po prostu zwykłe dostarczenie plików z aplikacją i odczytywanie ich z dysku.
Object Resources
• Nowy system zasobów zintegrowany z XAML
• Możliwość definiowania zasobów w różnych miejscach (kontrolki, okna, cała aplikacja)
• Zasoby obiektowe (deklaratywne, logiczne):
◦ pozwalają na zdefiniowanie obiektu raz i używanie go w wielu miejscach kodu
◦ umożliwiają przeniesienie np. szczegółów formatujących kontrolek do centralnego miejsca, w którym mogą być w łatwy sposób zmieniane
◦ gdy pewna informacja jest oddzielona od reszty aplikacja, może być modyfikowana dynamicznie
Przykład wykorzystania Object resources definiowanie zasobu:
<Window.Resources>
<SolidColorBrush x:Key="zielony" Color="Green" />
</Window.Resources>
korzystanie z zasobu:
<Button Background="{StaticResource zielony}">
Statycznie
</Button>
<Button Background="{DynamicResource zielony}">
Dynamicznie
</Button>
Kolekcja zasobów
• Każdy element posiada właściwość Resources, która przechowuje kolekcję zasobów (ResourceDictionary)
• Kolekcja zasobów może przechowywać dowolne typy obiektów
• Przeważnie zasoby definiowane są na poziomie okna, gdyż wszystkie dzieci mają dostęp do zasobów ojca
Zasoby statyczne i dynamiczne
• Zasoby statyczne, w przeciwieństwie do dynamicznych, są pobierane raz (po deklaracji w kodzie XAML) i nie reagują na zmianę zasobu z poziomu kodu
this.Resources["zielony"] =
new SolidColorBrush(Colors.Yellow);
• Zasoby dynamiczne pobierane są za każdym razem, gdy są potrzebne. Ponieważ wiąże się to z dodatkowym narzutem, z zasobów dynamicznych należy korzystać tylko wtedy, gdy:
◦ zasób zależy od ustawień systemowych (np. kolory systemowe)
◦ planujemy podmieniać obiekty dynamicznie
Zasoby statyczne i dynamiczne
• Dlaczego jednak to działa?
SolidColorBrush brush =
(SolidColorBrush)this.Resources["zielony"];
brush.Color = Colors.Red;
• Ponieważ nie podmieniliśmy obiektu, a jedynie zmieniliśmy jego stan wewnętrzny.
Zaś Brush informuje każdą używającą go kontrolkę o swojej zmianie.
Techniki zaawansowane – zasoby niewspółdzielone
• Zazwyczaj powstaje jeden obiekt, z którego korzystają wszyscy używajacy tego zasobu.
• Możemy jednak udostępniać każdemu jego własny obiekt – można rozważać wykorzystanie tego, jeśli każdy użytkownik chce osobno modyfikować zasób lub zasobem jest coś, czego nie możemy dzielić (np. element) – są jednak lepsze sposoby by to osiągnąć.
<SolidColorBrush x:Key="TileBrush" x:Shared="False" ...>
</SolidColorBrush>
Techniki zaawansowane – dostęp do zasobów w kodzie
• W ten sposób mamy dostęp do zasobów zdefiniowanych w tej kontrolce:
Button cmd = (Button)sender;
Brush brush = (Brush)cmd.Resources["zielony"];
• Dzięki temu nie musimy znać położenia zasobu – nastąpi poszukiwanie, jak w wypadku korzystania z zasobu w XAMLu:
Brush brush = (Brush)cmd.FindResource("zielony");
• Jest też TryFindResource(), które w razie niepowodzenia nie rzuca wyjątku, tylko zwraca null.
Zasoby aplikacji:
<Application ...>
<Application.Resources>
<SolidColorBrush x:Key="zielony" Color="Green" />
<SolidColorBrush x:Key="czerwony" Color="Red" />
<SolidColorBrush x:Key="niebieski" Color="Blue" />
</Application.Resources>
</Application>
• Są dostępne w całej aplikacji
◦ są trochę jak zmienne globalne: nie należy z nimi przesadzać
• Jeszcze wyżej w drzewie poszukiwania znajdują się zasoby systemowe
Zasoby systemowe:
• Udostępniane są poprzez trzy klasy (przestrzeń nazw System.Windows):
SystemColors, SystemFonts, SystemParameters label.Foreground =
new SolidBrush(SystemColors.WindowTextColor);
• lub:
label.Foreground = SystemColors.WindowTextBrush;
• lub:
<Label Foreground="{x:Static SystemColors.WindowTextBrush}">
Napis
</Label>
• lub:
<Label Foreground="{DynamicResource
{x:Static SystemColors.WindowTextBrushKey}}">
Napis</Label>
Organizacja zasobów
• W celu umożliwienia współdzielenia zasobów między różnymi projektami tworzy się słowniki zasobów. Są one zapisywane w plikach XAML (dołączanych do aplikacji, z Build Action ustawionym na Page.
w pliku AppBrushes.xaml:
<ResourceDictionary ... >
<SolidColorBrush x:Key="zielony" Color="Green" />
<SolidColorBrush x:Key="czerwony" Color="Red" />
<SolidColorBrush x:Key="niebieski" Color="Blue" />
</ResourceDictionary>
Organizacja zasobów
• Następnie powinny być dołączone do jakiejś kolekcji zasobów w aplikacji.
<Application ... >
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="AppBrushes.xaml"/>
<ResourceDictionary Source="..."/>
</ResourceDictionary.MergedDictionaries>
<SolidColorBrush x:Key="inne zasoby" ... />
...
</ResourceDictionary>
</Application.Resources>
</Application>