PwT.N W5

Transkrypt

PwT.N W5
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.
1/52
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)
2/52
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
3/52
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
4/52
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; }
}
5/52
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.).
6/52
Używanie poleceń:
1. Wybór polecenia
• jednego z gotowych lub zdefiniowanie własnej klasy polecenia
7/52
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
8/52
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
informacji dla użytkownika
9/52
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>
10/52
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
11/52
Używanie poleceń:
1.
2.
3.
4.
Wybór polecenia
Przypisanie polecenia uruchamiającej je kontrolce lub/i akcji klawiatury/ myszy
Stworzenie metody wykonującej polecenie
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
12/52
Używanie poleceń:
1.
2.
3.
4.
Wybór polecenia
Przypisanie polecenia uruchamiającej je kontrolce lub/i akcji klawiatury/ myszy
Stworzenie metody wykonującej polecenie
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));
13/52
Używanie poleceń:
1.
2.
3.
4.
Wybór polecenia
Przypisanie polecenia uruchamiającej je kontrolce lub/i akcji klawiatury/ myszy
Stworzenie metody wykonującej polecenie
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ł
14/52
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"/>
15/52
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();
16/52
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>
17/52
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;
}
}
18/52
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; }
}
}
19/52
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).
20/52
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}">
<Image ... /> </Button>
21/52
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>
22/52
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>
23/52
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>
24/52
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>
25/52
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;
}
26/52
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);
27/52
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>
28/52
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>
29/52
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>
30/52
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;
}
31/52
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>
32/52
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);
...
}
33/52
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>
34/52
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).
35/52
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
36/52
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).
37/52
38/52
39/52
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));
40/52
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.
41/52
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
42/52
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>
43/52
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
44/52
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
45/52
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.
46/52
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>
47/52
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.
48/52
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
49/52
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>
50/52
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>
51/52
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>
52/52