Markup ExtCzęść NR.4Po długiej przerwie zastanawiam się czy uzupełniać ten kurs dalej, zwłaszcza że ma on swoje wady, ale co tam.
W poprzednim wpisie omówiłem właściwości elementów z punktu widzenia języka XAML.
Czyli w wielkim skrócie możemy ustawić właściwości elementów na dwa sposoby.
Przy pomocy atrybutów.
<Button xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
Background="AliceBlue"
Content="OK">
</Button>
Przy pomocy elementów.
<Button xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<Button.Background>
Wheat
</Button.Background>
</Button>
Warto jednak powiedzieć coś o konwersji, która tutaj zachodzi. W XAML jak widzimy nie musimy pisać całej przestrzeni nazw by uzyskać kolekcję statycznych kolorów dostępnych w “System.Windows.Media.Brushes”.
System.Windows.Controls.Button b = new System.Windows.Controls.Button();
b.Content = "TEXT";
b.Background = System.Windows.Media.Brushes.White;
Kolory w XAML są deklarowane jako napisy stringi, ale czy na pewno tak jest?
Okej głupie pytanie.
Na pewno tak nie jest, ale trzeba przyznać, że jest to wygodne. Zwłaszcza że mamy Intellisense.
W takich wpadkach XAML wyszukuje odpowiednich konwerterów typu, które tłumaczą zapis string na dany typ danych.
WPF posiada wiele domyślnych konwerterów dla typów danych jak: Brush, Color, FontWeight,Point i tak dalej.
Wszystkie te klasy dziedziczą po “TypeConverter” co oznacza, że możesz napisać swój własny konwerter. Konwerter rozumieją zapisy wartości nie zależnie od wielkości znaków. Możesz n.p zapisać tło małymi literami a XAML nadal zrozumie o co ci chodzi.
<Button Background="aliceblue"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" />
Bez konwerterów kolor tła musiał być deklarowany w następujący sposób.
<Button xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
Content="OK">
<Button.Background>
<SolidColorBrush Color="AliceBlue"/>
</Button.Background>
</Button>
Z konwerterami możesz zapisać kolor jako wyrażenie hexadecymalne RGB.
<Button xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
Background="#F1FAAA"/>
Bez z nich musiałbyś użyć następującego zapisu.
<Button xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<Button.Background>
<SolidColorBrush>
<SolidColorBrush.Color>
<Color A="255" R="241" G="250" B="170"/>
</SolidColorBrush.Color>
</SolidColorBrush>
</Button.Background>
</Button>
Nawet ten powyższy zapis jest możliwy, ponieważ istnieje konwerter, który tłumaczy stringi na typ “byte”. Bo takie wartości przyjmuje konstruktor typu “Color”. Będą pozbawieni tego konwertera w sumie nie możemy już w żaden wygodny sposób zadeklarować koloru dla tego przycisku.
Jak kompilator XAML znajduje odpowiednie konwertery i jak on kojarzy je z daną klasą. Klasy .NET używają w tym celu atrybutu “System.ComponentModel.TypeConverterAttribute”.
Przykładowo “BrushConverter” jest używany przez XAML do właściwości Background. Dzieje się tak, ponieważ właściwości ta jest typu Brush, a klasa ta ma właśnie ten atrybut wskazujący na ten konwerter.
[Localizability(LocalizationCategory.None, Readability = Readability.Unreadable)]
[TypeConverter(typeof(BrushConverter))]
[ValueSerializer(typeof(BrushValueSerializer))]
public abstract class Brush : Animatable, IFormattable, DUCE.IResource
A co z właściwościami, które nie są reprezentowane przez klasy tylko przez typy wartościowe jak n.p double? W takim wypadku właściwości przykładowo “FontSize” ma przypisany ten atrybut.
[TypeConverter(typeof(FontSizeConverter))]
public double FontSize
{
get;
set;
}
Oczywiście typ double może określać nie tylko rozmiar czcionki dlatego ten konwerter nie jest powiązany z każdą właściwością mającą typ double. W WPF typ double zwykle jest powiązany z konwerterem “LenghtConverter”.
Markup Extensions
Markup Extensions podobnie jak konwertery typów rozszerzają one wyrażenia XAML o kolejny poziom. Markup Extension są to wyrażenia typu string, które są konwertowane na odpowiednie obiekty. Podobnie jak z konwerterami typów w WPF mamy kilka gotowych wyrażeń Markup Extensions, ale możemy także napisać swoje własne.
Podstawowa różnica pomiędzy konwerterami typów, a Markup Extension polega na tym, że Markup Extensions mają swoją określoną składnie. Użycie ich ma więc tylko sens w składni XAML. Powstały one w wyniku pewnych ograniczeń związanymi z konwerterami typów.
Powiedzmy, że chcesz stworzyć tło przycisku jako gradient. Normalnie nie jest to możliwe używając prostego wyrażenia string. Gdybyś jednak stworzył swój własny Markup Extension, który zwracałby taki złożony typ byłoby to możliwe.
Za każdy razem, gdy wartość właściwości jest pokryta nawiasami klamrowymi, parser XAML tratuje to wrażenie jako Marku Extension niż jako zapis string, który być może musi zostać skonwertowany .
<Button
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Background="{x:Null}"
Height="{x:Static SystemParameters.IconGridHeight}"
Content="{Binding Path=Height, RelativeSource={RelativeSource Self}}" />
Pierwsze dwa wyrażenie odwołują się do klas z przestrzeni nazw “System.Windows.Markup” przy użyciu prefiksu “X”. Pierwsze wyrażenie korzystające z klasy “NullExtension” zwróci wartości “null” drugie odwołuje się do statycznych wartości dostępnych z klasy “StaticExtension”
Ostatnie wyrażenie wykonuje powiązanie (Binding). Nie ma ono prefiksu “x”, gdyż nie korzysta z przestrzeni nazwy “System.Windows.Markup” tylko z “System.Windows.Data”, które jest dostępne w domyślnej przestrzeni nazw języka XAML.
Co dokładnie się dzieje w dwóch pierwszych wrażeniach.
Na podstawie wyrażeń string, które korzystają z parametrów oddzielonymi kropkami XAML zwraca odpowiednie wartość. Parametry oddzielone kropkami mogą zachowywać się jak właściwości rzeczywistych klas w C#. W rzeczywistości w tych wrażeniach odwołujesz się konstruktorów, które przy odpowiednich parametrach zwracają odpowiednie obiekty.
[TypeForwardedFrom("PresentationFramework, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35")]
public class StaticExtension : MarkupExtension
{
[TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
public StaticExtension();
public StaticExtension(string member);
[ConstructorArgument("member")]
public string Member { get; set; }
[DefaultValue("")]
public Type MemberType { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider);
}
Konwertery typów nie wspierają wartości null, ale przy użyciu Markup Extension jesteśmy wstanie taki typ zwrócić. Puste tło nie jest użyteczne, ale jest dobre jako przykład.
StaticExtension pozwala na odwoływanie się do statycznych pól, właściwości, stałych i typów wyliczeniowych dostępnych w frameworku.
Przykładowo statyczna właściwość “IconGridHeight” zwraca wysokość siatki, w którym mieszczą się duże ikony.
Binding to bardziej zaawansowany topiki, na który trzeba poświęci więcej czasu.
Nawiasy klamrowe są specjalny wrażeniem jak więc zapisać nawiasy klamrowe jako czysty string.
Na wszystko jest sposób. Używając nie uzupełnionych nawiasów klamrowych jako prefiks jesteś wstanie napisać wyrażenie posiadające nawiasy klamrowe jako zwykły napis.
<Button xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Content="{}{Działa}"/>
Alternatywnie możesz użyć uzupełnienia przy pomocy elementów. One domyślnie nie korzystają z markup extensions w taki sposób jak wyrażenia atrybutowe.
<Button xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<Button.Content>
{Działa}
</Button.Content>
</Button>
Wyrażenie markup extension są tylko klasami z domyślnymi konstruktorami. Mogą być używane w wyrażeniu elementowym w następujący sposób.
<Button xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Button.Background>
<x:Null/>
</Button.Background>
<Button.Height>
<x:Static Member="SystemParameters.IconGridHeight"/>
</Button.Height>
<Button.Content>
<Binding Path="Height">
<Binding.RelativeSource>
<RelativeSource Mode="Self"/>
</Binding.RelativeSource>
</Binding>
</Button.Content>
</Button>
Jak to wyrażenie działa? Klasy Markup Extension mają właściwości przypisane parametrom swojego konstruktora. Przykładowo klasa “StaticExtension” ma właściwość “Member”, która działa tak samo, jak argument przekazywany w konstruktorze. Mechanizm ten jest bardziej jasny, gdy spojrzysz na ciekawostkę poniżej i kod klasy “StaticExtension” pokazanej wyżej.
Zapis taki nie jest jednak wspierany przez Intellisense. Oznacza to, że brak pomocy w uzupełnianiu wyrażeń.