A InfoWiki wikiből
Tartalomjegyzék |
XAML
(ez csak vázlat, ide mentem, hogy itt is meglegyen - hz)
XAML (ejtsd zammel) az Extensible Application Markup Language rövidítése.
A XAML egy XML file. Egyetlen célja van: hogy .NET osztályok példányosítását végezze el. Mivel ez nem kód (nem C#), így a példányosítást deklaratív módon végezzük:
- elkészítünk egy XML bejegyzést, azonosítva melyik osztály (class) példányát kívánjuk elkészíteni.
- opcionálisan nevet is adunk ennek a példánynak, hogy a kódból ezen változónévvel (mezőnévvel) lehessen rá hivatkozni
- leírjuk melyik property-jének mi legyen a kezdőértéke (ez utóbbi nem triviális, mivel a property tetszőleges adattípus lehet, értéke ezért nem feltétlenül írható le egy string-el)
Persze a példányosítást tipikusan kódból szoktuk végezni. Ezért a WPF nem igényli kötelezően a XAML használatát. Minden elvégezhető kódból is, ahogy az korábban megszoktuk. A XAML használatának azonban az az előnye, hogy a XAML készítését a designer is el tudja végezni, hiszen kódolást neki nem kell végeznie, a xaml deklaratív. Így a design a kódkészítéssel párhuzamosan végezhető tevékenységgé vált.
A XAML-nak valójában több változata is van:
- WPF XAML: direkt WPF jellegű alkalmazások xml nyelve, tipikusan WPF-ben használt objektumok példányosítását és beállítását írjuk le benne.
- XPS XAML: (XML Paper Specification), az .xps dokumentumok által használt nyelv, elsősorban dokumentumokban használt
- WPF/E XAML: a SilverLight alkalmazások által használt. Tipikusan a WPF XAML-jének egy részhalmaza, csak bizonyos objektumok példányosítása megengedett, mivel a Silverlight alkalmazások böngészőben futtatandók.
- WF XAML: Workflow Foundation alkalmazások által használt xml.
Az XAML file-okat .xaml néven mentjük, és mivel ezek gyakorlatilag xml file-ok, ezért akár notepaddal is szerkeszthetőek. Gyakorlatilag érdemesebb azonban syntax highlight, és code completition eszközökkel ellátott xml szerkesztőt használni.
Az XAML file-ok a project fordításakor feldolgozásra kerülnek, és alapos szintaktikai ellenőrzés után bináris állománnyá (BAML - Binary XAML) formátumra fordulnak le. Ezen formátum belső szerkezete gyakorlatilag megegyezik az XAML belső szerkezetével, de ezen bináris formája az olvasást és feldolgozást segíti és gyorsítja. Ezen BAML file a DLL-be beszúródik erőforrás (resource) alakban, és futás közben kerül kiértékelésre (és végrehajtásra).
Ez sem kötelező elem. A WPF alkalmazás design-je maradhat eredeti szöveges XAML alakban is, külső file formájában csatolva a futtatható .exe mellé. Ezen XAML file-t ekkor kódból kell betölteni, feldolgozni, és kiértékelni.
XAML alapok
A XAML-nek egyetlen célja van: .NET osztályok példányosítását leírni. Ezért szerkezete erősen kötött, de a programozók (akik számára a példányosítás megszokott lépés, és általában ismerik az objektumokosztályok neveit, és a property-k neveit) számára gyorsan megérthető és megszokható.
Az osztályok nevét (amiből a példányt készíteni szeretnénk) nem csak egyszerűen le kell írni. A XAML-ben az osztályt a névtérrel kell azonosítani. Ez hasonló, mint a using szerepe a C# kódban. Azonban az xml file-okban ez úgy van, hogy nem végtelen sok using'-unk van, s amíg nincs névütközés, addig minden rendben. Egyetlen névterünk lehet az alapértelmezett. A további névtereinknek alias nevet kell adni, és minden esetben ezen alias neveket ki kell írni.
Egy átlagos XAML file-ban két két névtér szokott lenni:
- az első névtér magát a .NET osztályhierarchiát képviseli, azon belül is a WPF által használt osztályok elérhetőségét. Ez szokott az alapértelmezett névtér lenni:
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- a második névtér magát a XAML sémára hivatkozik. Ennek a tipikus alias neve az x:
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
A névterek megadásakor leírt érték úgy néz ki mintha az egy weblap címe lenne. Nem az. Ez egy URI (Uniform Resource Identifier), vagyis egy szabványos leírása egy egyedi azonosítónak. A schemas.microsoft.com egy olyan előtagja ennek a tagolt azonosítónak, ami a Microsoft-ra utal. Ezen azonosító értelmét és jelentését a XML feldolgozó érti. Ezen névtér megadása egyben sok C#-beli using-ot helyettesít az XAML file-ban. Ezért a fenti alapértelmezett névtér egyben magában foglalja, helyettesíti gyakorlatilag az összes C#-ban használt WPF-hez tartozó névteret.
Példa:
<Window x:Class="WindowsApplication1.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="WindowsApplication1" Height="300" Width="300"> <Grid> </Grid> </Window>
A példa magyarázata:
- definiáljuk a két névteret
- a Window objektumosztály egy leszármazott (gyerek) osztályát készítjük el
- a származtatott típus a WindowsApplication1 névtérbe kerül, Window1 névvel lesz
- ebből fog a XAML futás közben példányosítani
- a Class kulcsszó a XAML névtér által kezelt kulcsszó, ezért x: előtaggal rendelkezik
namespace WindowsApplication1 { partial class Window1:Window { } }
- a továbbiakban beállítjuk a példány Title property-jének értékét WindowsApplication1-re
- a Height property értékét 300 WPF Device Independent Unit-ra
- a Width értékét is 300-ra
- ezen példány Content-jét (tartalom) feltöltjük egy Grid osztálybeli példánnyal
Egy XAML file gyökéreleme az alábbi 3 elem egyike lehet:
- Window - form jellegű alkalmazás esetén egy ablak leírása
- Page - page alapú működés esetén a page leírása
- Application - alkalmazás leírása (tipikusan erőforrást tartalmaz)
Ha a gyökérelem egy window, akkor akár Main() függvényre sincs szükség az alkalmazáson belül. A window példány automatikusan létrejön, átadásra kerül az Application.Run()-nak - a program elindul.
A szokásos névteren túl a XAML file tetszőleges további névteret is tartalmazhat. Ez esetben az alábbi mintát lehet használni:
xmlns:Prefix="clr-namespace:Namespace;assembly=AssemblyName"
- Prefix: ide kell egy egyszerű azonosító, ami nem az "x:"
- Namespace: ide kell behelyettesíteni a saját névterünket
- AssemblyName: opcionális, a DLL neve
Példa:
xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:local="clr-namespace:MyNamespace"
x:Name vs Name
Korábban, a hagyományos Windows alkalmazásokban minden elemnek, ami a felületen megjelent - nevet kellett adni. Még akkor is, ha az a név csak annyi volt, hogy 'label12'.
WPF-ben nem szokás minden elemnek nevet adni, csak amelyekre a kódból ténylegesen hivatkozni kívánunk. A névadási szabályokra természetesen a szokásos azonosító elnevezési konvenciók vonatkoznak. A nevet az "x:Name" attribútumon keresztül lehet/kell megadni:
<Window x:Class="WindowsApplication1.Window1" ...> <Grid x:Name="grid1"> </Grid> </Window>
Ez megfelel az alábbi kódnak:
private System.Windows.Controls.Grid grid1;
Sok .NET osztályban van Name property, amely a példány azonosítóját tartalmazza (ilyen pl. a FrameworkElement osztály is). Ez esetben elvileg az alábbi szintaxist kellene használni:
<Grid x:Name="grid1" Name="grid1"> ... </Grid>
Ennek elkerülése végett a XAML fordítók intelligens módon bármelyiket elfogadják külön-külön is elegendőnek:
<Grid x:Name="grid1"> ... </Grid> <Button Name="OK_Button"> ... </Button>
Mindkét forma esetén felismerik hogy a példánynak nevet kívánunk adni, és ezen nevet el kell helyezni a Name property-ben mint érték. Ez a látszat, a valóságban azt használja ki a XAML fordító, hogy az objektumosztálnyak van RuntimeNameProperty attribútuma, és ezen keresztül működteti a névbeállítást.
Property értékének beállítása
A XAML file-ok egyetlen célja, hogy .NET osztályok példányosítását és beállítását írja le deklaratív módon. Ehhez azonosítani kell a .NET osztályt, opcionálisan megadni egy példánynevet (ha a kódoló ezt igényli), majd értékadó utasítássorozat formájában megadni a példány attribútumainak kezdőbeállítását.
String típusú property
Mindaddig, amíg a példány beállítandó atribútuma string típusú, a kezdőérték-megadás problémamentes, hiszen a XML file-ok attribútumjai is string-ek:
<Window ... Title="WindowsApplication1" ... />
Egyszerű típusú property
Bonyolultabb a helyzet, amikor a property típusa nem string:
<Window ... Width="300"... />
Ez azért működik jól, mert az értékekhez ún. típus-konverterek vannak megadva. A XAML fordító felismeri, hogy a Width property double típusú, így ehhez ezt a típuskonvertert kell használni. A fenti XAML részt az alábbi kód helyettesíthetné:
this.Width = Double.Parse(300);
Jegyezzük meg, hogy a XAML fordító nem C# kódot generál a XAML-ból, mindössze bináris formává alakítja át, és futás közben értékeli ki. Ennek megfelelően futás közben kell a bár bináris, de az attribútumértékeket továbbra is stringek formájában tároló BAML-ből a 300 értéket átrakni a Width property-be.
Hasonló típuskonverter van használva a felsorolás (enum) eseténis:
<... HorizontalAlignment="Stretch" ... />
A HorizontalAlignment property típusa egy enumeration, a HorizontalAlignment enum. Ebben van Stretch nevű tag, melyre hivatkozunk jelen példában. A helyettesítő kód az alábbi lehetne:
this.HorizontalAlignment = (HorizontalAlignment)Enum.Parse(typeof(HorizontalAlignment), "Stretch", true);
A típuskonverterek általában kisbetű-nagybetű érzékenyek!
Összetett típusú property
Amennyiben egy property értéke nem leírható egyetlen string-el, úgy a property értékadását ki kell bontani. Ez meghetető egyébként akkor is, ha a property értéke amúgy leírható lenne az előző egyszerű módon is. A kibontás során beágyazott XML bejegyzést kell készíteni, amelynek nevében utalnia kell a kibontandó property-re:
<Button Content="Klikk engem" .../>
... kibontva ...
<Button ...> <Button.Content>Klikk engem</Button.Content> </Button>
Amennyiben egy XAML tag beágyazott tartalommal bír, melyet nem azonosítottunk mint kibontott property, úgy az az adott tag Content-kének a kibontását jelenti. Ezért a fenti példa így is írható:
<Button ...>Klikk engem</Button>
Ez esetben a Content típusa egy, a megadott string lesz.
Bonyolultabb eset, amikor a property, akinek az értékét be akarjuk állítani - egy objektumpéldányt vár értékül. Ez esetben a kibontás elkerülhetetlen. Ilyen eset például a háttér beállítása:
<Button ...> <Button.Background> <LinearGradientBrush> ... </LinearGradientBrush> </ButtonBackground> </Button>
Kollekció-jellegű property
Ha megvizsgáljuk akár ezen LinearGradientBrush példányosításának és beállításának menetét, észrevehetjük, hogy van egy GradientStops property-je, amely lista-jellegűen tartalmaz további elemeket. Ezen property értékét az alábbi módon tudjuk kibontani:
<LinearGradientBrush> <LinearGradientBrush.GradientStops> <GradientStop Offset="0.00" Color="Red" /> <GradientStop Offset="0.50" Color="Indigo" /> <GradientStop Offset="1.00" Color="Violet" /> </LinearGradientBrush.GradientStops> </LinearGradientBrush>
A listába három elemet helyezünk el, mindegyik GradientStop típusú, és a példányok beállítása is leolvasható. Ez az alábbi C# kóddal lenne leírható:
LinearGradientBrush brush = new LinearGradientBrush(); GradientStop gradientStop1 = new GradientStop(); gradientStop1.Offset = 0; gradientStop1.Color = Colors.Red; brush.GradientStops.Add(gradientStop1); GradientStop gradientStop2 = new GradientStop(); gradientStop2.Offset = 0.5; gradientStop2.Color = Colors.Indigo; brush.GradientStops.Add(gradientStop2); GradientStop gradientStop3 = new GradientStop(); gradientStop3.Offset = 1; gradientStop3.Color = Colors.Violet; brush.GradientStops.Add(gradientStop3);
Kiterjesztések
A bonyolultabb esetek leírása kevéssé triviális. Például hogyan tudjuk leírni azt XAML-ben, hogy egymás után van 3 db Button, az elsőnél definiáltam egy Bacground-t, ami egy LinearGradiendtBrush példány lett, a másik két gomb esetén nem szeretném újra leírni az adatokat, hanem ugyanazt a LinearGradiendtBrush-t szeretném használni? Kódban:
LinearGradientBrush b = new LinearGradientBrush(); ... Button button1 = new Button(); button1.Background = b; Button button2 = new Button(); button1.Background = b; Button button3 = new Button(); button1.Background = b;
A nem triviális esetek leírását markup extension-k támogatják. A markup extension-okat onnan lehet felismerni, hogy értékadáskor a string tartalma kapcsol zárójeleket tartalmaz:
<Button ... Foreground="{x:Static SystemColors.ActiveCaptionBrush}" >
Ezeket a kiterjesztéseket gyakran használjuk erőforrások elérésénél, vagy adatkötésnél, és a megfelelő fejezetekben lesznek tárgyalva.
Speciális karakterek
Az XML nyelvben speicális jelentéssel bíró karakterek az alábbiak:
- < (kisebb) =>
< code>
- > (nagyobb) =>
> code>
- & (et-jel) =>
& code>
- " (idezojel) =>
" code>
Ezek miatt nem string-ek belsejében ezeket a karaktereket leírni helyettesítő karakterekkel kell. Helytelen az alábbi példa:
<Button ...> klikkelj >>ide<< rám</Button> code>
Helyes az alábbi:
<Button ...> klikkelj >>ide<< rám</Button> code>
Attached Property
Van olyan eset, amikor egy példány property-jét nem magában a példányban, hanem az abba ágyazott (gyerek) példányban állítjuk be. A szülő valamely property-jére vissza lehet utalni:
<Grid> <TextBox x:Name="txtQuestion" ... Grid.Row="0"> A kérdés: lenni vagy nem lenni?! </TextBox> </Grid>
Ez a visszautalás valójában nem property-re irányul, sokkal inkább függvényhívás lesz belőle. A fordító feltételezi, hogy ekkor van egy "Set...()" függvény a szülő osztályban (ez egy osztály-szintű függvényt feltételez!), melynek átadja a beállító példányt, és a beállítandó értéket:
Grid.SetRow(txtQuestion, 0);
A fenti függvény természetesen nem kommunikál magával a tartalmazó (szülő) Grid példánnyal, mivel azt nem ismerheti (a SetRow() osztályszintű!). Helyette a beállító példány (txtQuestion)-ban fogja beállítani a megfelelő értéket oly módon, hogy létrehoz benne egy DependecyProperty-t, és elhelyezi benne az értéket:
txtQuestion.SetValue(Grid.RowProperty, 0);
Kollekció kezelés
Amikor egy példány valamely attribútumába több más példányt helyezünk el, akkor a XAML értelmező azt kollekció-bővítés műveletekkel helyettesíti:
<LinearGradientBrush.GradientStops> <GradientStop Offset="0.00" Color="Red" /> <GradientStop Offset="0.50" Color="Indigo" /> <GradientStop Offset="1.00" Color="Violet" /> </LinearGradientBrush.GradientStops>
Amennyiben a kiválasztott property
- implementálta az IList interface-t, akkor .Add(...) műveleteket fog használni
- implementálta IDictionary, interface-t, akkor .Add(x:Key, ...) műveleteket fog használni
- ContentProperty-je van a parent-nek, akkor azt használja fel