ugrás a tartalomhoz

XAML bemutatása

chikk · 2010. Okt. 31. (V), 18.09
XAML bemutatása

Ha valaki egy előzetes áttekintés útán úgy gondolja, hogy az XAML mindössze az n+1-edik módja annak, hogy grafikus felületeket írjunk le valami elvont XML formátumban, az nagyon téved. Az XAML igazából egy olyan deklaratív, XML alapú .NET nyelvjárás, aminek segítségével objektumgráfok deklarációja írható le olyan formátumban, ami az XML jellegéből adódóan ember és gép számára nagyon könnyen olvasható, megérthető és kezelhető.

Kialakulásának előzményei a .NET Win Forms technológiájáig nyúlnak vissza. A Win Forms felületleíró rendszere úgy oldotta meg a feladatot, hogy egyszerűen C# vagy VB.NET kódot használt.


this.button1 = new System.Windows.Forms.Button();
this.checkBox1 = new System.Windows.Forms.CheckBox();
this.SuspendLayout();
// 
// button1
// 
this.button1.Location = new System.Drawing.Point(13, 13);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(75, 23);
this.button1.TabIndex = 0;
this.button1.Text = "button1";
this.button1.UseVisualStyleBackColor = true;
// 
// checkBox1
// 
this.checkBox1.AutoSize = true;
this.checkBox1.Location = new System.Drawing.Point(95, 18);
this.checkBox1.Name = "checkBox1";
this.checkBox1.Size = new System.Drawing.Size(80, 17);
this.checkBox1.TabIndex = 1;
this.checkBox1.Text = "checkBox1";
this.checkBox1.UseVisualStyleBackColor = true;
// 
// Form1
// 
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(284, 262);
this.Controls.Add(this.checkBox1);
this.Controls.Add(this.button1);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);
this.PerformLayout();

Ez a megoldás, bár működik, rengeteg problémához vezet. Elég csak belegondolni, hogy a form designernek képesnek kell lennie C# vagy VB.NET kód értelmezésére, minden a designerben beállítható layout állapot és felületelem tulajdonság kezelésére, ezek kóddá alakítására, és a kód tervezőfelület nézetben való ábrázolására. Még leírni is bonyolult, nem ám megcsinálni! Ebből következik, hogy a Win Forms tervező lassú, nehezen kezelhető, illetve elég csak egy kicsit belenyúlni az általa definiált kódba ahhoz, hogy az egész végleg megboruljon.

További probléma az elgondolással, hogy a .NET jellegéből adódóan nyelvfüggetlen platform. Viszont ahhoz, hogy egy nyelvet adjunk a rendszerhez, majd azzal Win Forms alkalmazásokat fejlesszünk, a nyelv fejlesztőjének rendelkezésre kell bocsátani azt a modult, ami képesség teszi a designerrel való együttműködésre, és ez szükségtelenül nagy feladat lehet.

Tehát szükség volt egy .NET nyelvre, aminek feladata csak és kizárólag az objektumok deklarálása, hiszen ha belegondolunk, az összes ma létező tervező, legyen az egy grafikus felületé, szerveroldali munkafolyamaté, objektumok definiálását végzi. Ahhoz, hogy ez a nyelv ember és gép számára könnyen kezelhető, platformok, szoftverek közt átvihető legyen, az XML formátum alkalmazása volt a legkézenfekvőbb választás.

Nézzük meg XAML nyelven az előbbi űrlap definícióját:


<Window x:Class="WpfApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Button Content="Button" 
                Height="23" 
                HorizontalAlignment="Left" 
                Margin="12,12,0,0" 
                Name="button1" 
                VerticalAlignment="Top" 
                Width="75" />
        <CheckBox Content="CheckBox" 
                  Height="16" 
                  HorizontalAlignment="Left" 
                  Margin="93,16,0,0" 
                  Name="checkBox1" 
                  VerticalAlignment="Top" />
    </Grid>
</Window>

Ez egy olyan ember számára is áttekinthető, aki még soha életében nem látott .NET-et, de programozottan sem túl összetett dolog elemezni, lévén 100% XML-ről van szó.

Mit definiálhatunk XAML nyelven?

Bár az XAML-t elsődlegesen a Windows Presentation Foundation, majd a Silverlight felületekeket leíró objektumgráfok definiálására találták ki, segítségével bármilyen, az XAML-lel együttműködésre képes objektum, illetve objektumgráf leírható. Az együttműködésre képesség annyit tesz, hogy az adott objektum publikus konstruktor(ok) hívásával és publikus tulajdonságok értékeinek állításával inicializálható.

Nézzünk egy egyszerű objektumot:


namespace WpfApp
{
    public class Stuff
    {
        public Stuff()
        {
            Values = new List<int>();
        }

        public string Name { get; set; }

        public List<int> Values { get; private set; }
    }
}

Ezt C# kódból a következőképen hozhatjuk létre:


var stuff = new Stuff();
stuff.Name = "Hello";
stuff.Values.Add(1);
stuff.Values.Add(2);
stuff.Values.Add(3);
stuff.Values.Add(4);
stuff.Values.Add(5);

Ugyanez XAML jelöléssel:


<Stuff xmlns="clr-namespace:WpfApp;assembly=WpfApp"
       xmlns:s="clr-namespace:System;assembly=mscorlib"
       Name="Hello">
    <Stuff.Values>
        <s:Int32>1</s:Int32>
        <s:Int32>2</s:Int32>
        <s:Int32>3</s:Int32>
        <s:Int32>4</s:Int32>
        <s:Int32>5</s:Int32>
    </Stuff.Values>
</Stuff>

Az XAML alapvető elemei

Elemezzük a fenti definíciót!

Először is a szabvány XML névterekkel tudunk hivatkozni .NET-es névterekre a clr-namespace névtérdefiníció segítségével. Magyarán azzal, hogy a gyökér névtérnek felveszem a következőt: xmlns="clr-namespace:WpfApp;assembly=WpfApp". Jelzem, hogy a névtér prefix nélküli XML elemek olyan objektumtípusokat fognak jelölni, amelyek a WpfApp nevű assembly WpfApp névterében vannak. A Stuff objektum ilyen, tehát a Stuff gyökérelem egy Stuff típusú objektum létrehozását írja elő. Behívom továbbá a .NET-es System névteret is az s XML névtérebe, mert szükség lesz rá (erről később).

A Stuff objektum publikus tulajdonságainak beállítását XAML-lel két módszerrel tudom megtenni. Elsőként attribútumként, vagyis XML attibútum jelzi az adott tulajdonság értékét: Name="Hello". Erre akkor van lehetőség, ha az adott tulajdonság típusa stringből és stringre konvertálható. Ilyen az összes .NET primitív érték típus és néhány, a rendszer által kezelt beépített típus (DateTime, TimeSpan, Uri stb.). Saját típusok string konverziójának definiálására is van lehetőség Type Converter-ek megadásával (lásd még: WPF Type Converters, Silverlight Type Converters).

A másik módszer az ún. property element nyelvtan, ami a következőképp fest:


<Stuff>
    <Stuff.Name>Hello</Stuff.Name>
</Stuff>

Ebben az esetben a tulajdonságot „kibontom” egy új XML elembe, és úgy adok neki értéket. Ennek a módszernek akkor van jelentősége, ha egy-egy tulajdonság értékének egy újabb objektumot akarok megadni, amit XAML-ből definiálok.


<Button>
    <Button.Content>
        <Rectangle Fill="Red" Width="100" Height="100" />
    </Button.Content>
</Button>

A definíció egy olyan gombot ír le, amin szöveg helyett egy piros téglalap jelenik meg. A Content a gomb objektum azon tulajdonsága, aminek értéke a gomb tartalmát jelenti. Tehát ha azt akarom, hogy ez legyen „Hello”, akkor írhatom attribútum szintaxissal:


  <Button Content="Hello"/>

vagy element szintaxissal is:


<Button>
    <Button.Content>
        Hello
    </Button.Content>
</Button>

Hogy még egyszerűbb legyen, minden objektum rendelkezhet default content tulajdonsággal is, így az XML definícióban lévő első, nem element nyelvtanú elem ezen tulajdonság értékét fogja jelölni. A gombomat, aminek a default content tulajdonsága maga a Content, írhatom így is:


<Button>Hello</Button>

Ahol az első nem element szintaxisú XML elem a „Hello” string node, így ez meg a default content tulajdonságba, ami a Button objektumnál a Content.

Nézzük tovább a Stuff XAML definíciót!


<Stuff.Values>
    <s:Int32>1</s:Int32>
    <s:Int32>2</s:Int32>
    <s:Int32>3</s:Int32>
    <s:Int32>4</s:Int32>
    <s:Int32>5</s:Int32>
</Stuff.Values>

A Suff.Values nem más, mint egy olyan csak olvasható lista tulajdonság, ami számokat tartalmazhat. A számok tulajdonképpen System.Int32 struktúrák, amik XAML-ből való definiálásához be kell hítvatkozni a System .NET névteret (megtettem az s névtérben). Innentől kezdve az s:Int32 XML elem az XAML leírásban egy System.Int32 definíciót fog jelenteni. Az XAML van olyan intelligens, hogy ha egy lista tulajdonságot jelölő property element definícióba (Stuff.Values) olyan XAML elemeket sorolunk fel, ami az adott listának eleme lehet, akkor ezeket az elemeket be fogja tenni a listába. Ha a lista tulajdonság csak olvasható, akkor az XAML formátum feltételezi, hogy ott már létezik egy előzetesen inicializált lista (ahogy én azt a Stuff objektum konstruktorában létrehoztam). Ha írható és olvasható, és ha a lista tulajdonság értéke null, akkor létre fog hozni egy újat, amibe pakolja az elemeket.


namespace WpfApp
{
    public class Stuff
    {
        public string Name { get; set; }

        public List<int> Values { get; set; }
    }
}

Ez objektum XAML definíciójának értelmezése során csak abban az esetben lesz a Values tulajdonság nem null, ha XAML-ből definiáljuk, és/vagy elemeket rakunk bele (ilyenkor implicit definíció történik).

Stuff.Values = null esetén:


<Stuff xmlns="clr-namespace:WpfApp;assembly=WpfApp"
       xmlns:s="clr-namespace:System;assembly=mscorlib"
       Name="Hello"/>

Üres lista (explicit):


<Stuff xmlns="clr-namespace:WpfApp;assembly=WpfApp"
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       xmlns:s="clr-namespace:System;assembly=mscorlib"
       xmlns:coll="clr-namespace:System.Collections.Generic;assembly=mscorlib"
       Name="Hello">
    <Stuff.Values>
        <coll:List x:TypeArguments="s:Int32"/>
    </Stuff.Values>
</Stuff>

Lista elemekkel (exlicit):


<Stuff xmlns="clr-namespace:WpfApp;assembly=WpfApp"
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       xmlns:s="clr-namespace:System;assembly=mscorlib"
       xmlns:coll="clr-namespace:System.Collections.Generic;assembly=mscorlib"
       Name="Hello">
    <Stuff.Values>
        <coll:List x:TypeArguments="s:Int32">
            <s:Int32>1</s:Int32>
            <s:Int32>2</s:Int32>
            <s:Int32>3</s:Int32>
        </coll:List>
    </Stuff.Values>
</Stuff>

Lista elemekkel (implicit):


<Stuff xmlns="clr-namespace:WpfApp;assembly=WpfApp"
       xmlns:s="clr-namespace:System;assembly=mscorlib"
       Name="Hello">
    <Stuff.Values>
        <s:Int32>1</s:Int32>
        <s:Int32>2</s:Int32>
        <s:Int32>3</s:Int32>
    </Stuff.Values>
</Stuff>

Az explicit lista objektum definíció érdekében két dologra van szükség. Az egyik, hogy behívjuk azt a névteret, ahol maga a List .NET osztály található (System.Collections.Generic), a másik – mivel a List generikus – behívjuk magát az XAML névteret – neve: x, ami a különböző (XAML) nyelvi sajátosságokat tartalmazza, többek közt az x:TypeArguments kiterjesztést, ami a generikus tulajdonságok típusparaméterének megadására szolgál, ami jelen esetben az int, XAML esetén jelenleg s:Int32.

Az XAML nyelvi feature-örökről itt lehet nagyokat okosodni.

Ojektumgráfok és az XAML

Ennyiből már nagyjából mindenki tisztában van azzal, hogyan lehet tetszőleges objektumfákat XAML-lel definiálni, de hogyan lesz ebből gráf, kérdezhetnénk. Kereszthivatkozások XAML definíciójának eszköze az ún. markup extension. Ez egy speciális XAML elem, ami az XAML parser kiterjesztéseként működve lehetővé teszi, hogy egy adott XAML jelölés egy speciális műveletet hajtson végre, illetve egy speciális működésű objektumot hozzon létre az adott ponton. Nézzünk egy pédát (Silverlight)!


<Grid x:Name="LayoutRoot" Background="White">
    <Grid.Resources>
        <SolidColorBrush Color="Red" x:Key="RedBrush"/>
    </Grid.Resources>
    <Button Background="{StaticResource RedBrush}">Hello</Button>
</Grid>

Először is, minden WPF és Silverlight vezérlő rendelkezik egy Resources tulajdonsággal, ami leegyszerűsítve nem más, mint egy egyszerű dictionary, amibe XAML-ből az x:Key kulcs jelöléssel gyakorlatilag bármilyen objektumot betehetünk.

Ezen erőforrásokat aztán bármelyik a definiáló elemen belül lévő objektum képes elérni a StaticResource markup extension segítségével. Az XAML lehetővé teszi az egyszerűsítés érdekében, hogy minden markup extensiont attribútum szintaxissal is definiálhatunk. Ilyenkor az XML elemek {}-között vannak, az XML tag neve az első azonosító, a második azonosító a default content tulajdonság értéke (jelen esetben a ResourceKey) – ez elhagyható, az attribútumok pedig attribútum=érték pároként felsorolva adhatók meg az elem definíción belül. Magyarán a fenti definíció standard XML-re bontva így néz ki:


<Grid x:Name="LayoutRoot" Background="White">
    <Grid.Resources>
        <SolidColorBrush Color="Red" x:Key="RedBrush"/>
    </Grid.Resources>
    <Button>
        <Button.Background>
            <StaticResource ResourceKey="RedBrush"/>
        </Button.Background>
        Hello
    </Button>
</Grid>

De így is írhatom, mintha a ResourceKey nem lenne default content tulajdonság:


<Grid x:Name="LayoutRoot" Background="White">
    <Grid.Resources>
        <SolidColorBrush Color="Red" x:Key="RedBrush"/>
    </Grid.Resources>
    <Button Background="{StaticResource ResourceKey=RedBrush}">
        Hello
    </Button>
</Grid>

A másik fontos WPF és Silverlight markup extension, a Binding. Ennek segítségével bizonyos objektumok tulajdonságait össze tudom kötni más objektumok megfelelő tulajdonságaival, például:


<StackPanel x:Name="LayoutRoot" Background="White">
    <TextBox Name="tbEnter"/>
    <TextBlock Text="{Binding Text, ElementName=tbEnter}"/>
</StackPanel>

Ebben a példában a Binding segítségével összekötöm a tbEnter nevű TextBox Text tulajdonságát az alatta lévő TextBlock Text tulajdonságával, vagyis az alsó szövegboxban megjelenik, amit a felhasználó beír felül, mindenféle plusz kódolás nélkül! A Binding itt is attribútum szintaxissal van megadva. A default content tulajdonsága ennek az objektumnak a Path, vagyis a tulajdonságelérési útvonal, ami most Text lesz, majd a Binding.ElementName magadásával szólok, hogy egy tbEnter nevű elemhez kötök, így létre fog jönni az objektumok közt kapcsolat. Az érthetőség kedvéért a fenti definíciót leírom kibontva.


<StackPanel x:Name="LayoutRoot" Background="White">
    <TextBox Name="tbEnter"/>
    <TextBlock>
        <TextBlock.Text>
            <Binding Path="Text" ElementName="tbEnter"/>
        </TextBlock.Text>
    </TextBlock>
</StackPanel>


A jelölés kiterjesztések, mint a Binding és a StaticResource nem az XAML nyelv, hanem egy az őt használó rendszer termékei. Lehetőség van sajátot definiálni, miknek segítségével a legösszetetteb működésre képes objektumgráf felépítése is lehetővéválik az XAML nyelv segítségével.

XAML != UI

Az XAML, mint láthattuk, nem csak felhasználói felületek és grafikai elemek leírására, hanem bármilyen .NET-es objektumgráf leírására képes. Ezt a lehetőségét használja ki a Microsoft Workflow Foundation 4.0 (értsd 2.0) is, ahol szintén XAML-t használnak a kliens és a szerveroldali munkafolyamatok leírására. Ennek köszönhetően az új verzióra nem csak a WF rendszer sebessége, hanem az XAML képességéire és a WPF tudására épült vadonatúj Workflow Designer is nagyon sokat fejlődött az előző, XAML-t csak részben (és rendkívül bután) használó változathoz képest. Így néz ki egy egyszerű while ciklus az új rendszerben:

while ciklus Workflow Designerben

Minek XAML megfelelője a következő:


<While sap:VirtualizedContainerService.HintSize="464,399" 
        Condition="[index < 10]">
  <Sequence sap:VirtualizedContainerService.HintSize="438,283">
    <sap:WorkflowViewStateService.ViewState>
      <scg3:Dictionary x:TypeArguments="x:String, x:Object">
        <x:Boolean x:Key="IsExpanded">True</x:Boolean>
      </scg3:Dictionary>
    </sap:WorkflowViewStateService.ViewState>
    <WriteLine sap:VirtualizedContainerService.HintSize="242,61" 
                Text="[index.ToString()]" />
    <Assign sap:VirtualizedContainerService.HintSize="242,58">
      <Assign.To>
        <OutArgument x:TypeArguments="x:Int32">[index]</OutArgument>
      </Assign.To>
      <Assign.Value>
        <InArgument x:TypeArguments="x:Int32">[index + 1]</InArgument>
      </Assign.Value>
    </Assign>
  </Sequence>
</While>

Az, hogy miért és mikor van szükség egy „egyszeri” while ciklushoz külön XAML dialektusra és designerre, már egy másik történet, talán egy következő alkalommal elmesélem.

Az új .NET-es nyelv

Sajnos nincs módom mindent leírni az XAML-ről, de remélem, hogy sikerült úgy bemutatni, hogy innentől kezdve ne csak egy hetvenhetedik XML formátumot, hanem egy speciális területre fejlesztett, de teljes értékű .NET nyelvjárást tiszteljünk benne.

Sajnos jelenleg a Silverlight által támogatott XAML dialektus nem teljesen ekvivalens a .NET Framework 4.0-ban lévő XAML-lel, így néhány nagyon hasznos funkcióról le kell mondanunk egyelőre. Ennek köszönhetően jelenleg két különböző XAML-t bemutató MSDN oldal létezik, minek tüzetes áttanulmányozását mindenkinek meleg szívvel ajánlom, mielőtt komolyabban elkezdene foglalkozni a nyelvre építő rendszerek bármelyikével.

A cikk ikonjához Zen Sutherland fotóját vettük kölcsön.

 
chikk arcképe
chikk
2000 óta programozásból él. C++ alapú multimédiás fejlesztésekkel kezdte a debreceni Siker-Film Stúdióban, majd a VVMF Hungária Kft. programozójaként dolgozott. 2010-től a Carnation .NET feljesztője. 2004 óta megszállott .NET hívő, 2008-tól a Microsoft „Most Valuable Professional Award” kitüntetettje. Hobbija a programozás és az elektronikus zene.
1

Jó cucc

janoszen · 2010. Okt. 31. (V), 18.22
Jól sikerült, gyorsan átolvastam. Kicsit az az érzésem ezzel kapcsolatban, hogy ez olyan, mint amilyennek a XUL-nak kéne lennie. (Az ugyanis nem sikerült annyira királyul, mint lehetett volna.) Igazából aminek nagyon örülnék (hátha van rá kapacitásod) az egy leírás, hogy ha egy 1.0-s webfejlesztő el szeretne kezdeni Silverlightozni, akkor mire lesz szüksége, mi az ami ingyen van, mi az ami fizetős, hogy működik az egész technológia, ergó egy áttekintés az egész Silverlightról.
2

Kezdet

orionstar · 2010. Okt. 31. (V), 23.14
Ha Silverlighttal szeretnél foglalkozni, akkor a Microsoft magyar fejlesztői portálját, a http://devportal.hu/-t illetve az akadémiai aloldalát kezd el nézegetni.
Egyik jó poszt induláshoz: http://devportal.hu/groups/silverlight/pages/silverlight-alapok.aspx

Mi az ami ingyen van?
A Visual Studio letölthető ingyenes Express Edition formában, de az ingyenes verzió nem az igazi komolyabb appokhoz. A Visual Studio mellé még a Silverlight4 SDK illetve Silverlight4 Toolkit-et ajánlott letölteni.
Magyar tananyagnak C#, XAML téren a, devportálon kívül, használható Reiter István régi C# jegyzete: http://people.inf.elte.hu/reiter_i/oldsharp.pdf. Ha esetleg az újabb verziót vagy a könyvet megtalálod valahol, azt ne olvasd végig, hiányzik a WPF témakör belőle.
Angolul frissen elérhető egy komplett áttekintés Silverlightról: http://blogs.msdn.com/b/jmeier/archive/2010/10/26/silverlight-developer-guidance-map.aspx.

Mi az ami fizetős?
A Silverlight-WPF alkalmazások grafikai tervezésében, az effektek stb kialakításához Expression Blend ajánlott, ami ingyenesen nem elérhető, trial verzió van, azzal lehet próbálkozni.
3

Rettentő sok kód

saxus · 2010. Nov. 1. (H), 18.42
Bevallom őszintén, nem foglalkoztam túl sokat XAML-l, de azok alapján, amit eddig láttam belőle, nekem az jött le, hogy valami eszméletlen mennyiségű kódot kell mögéhányni az alkalmazásban, főleg a bindingek miatt.

Elismerem, hogy hatalmas projektnél valószínűleg megtérül ez az agyonrétegezettség, de elsőre nekem borzasztó munkásnak tűnik.

Valahogy a Delphi ActionManager-e jut eszembe, mint távoli rokon. Annál viszont nem éreztem azt, hogy feleslegesen kell kódot írnom, hanem inkább ténylegesen a feladat megoldására koncentrálhattam.
4

nekem az jött le, hogy valami

inf3rno · 2010. Nov. 2. (K), 13.29
nekem az jött le, hogy valami eszméletlen mennyiségű kódot kell mögéhányni az alkalmazásban

Én egyáltalán nem foglalkoztam még a témával, viszont nekem meg az jött le, hogy kattintasz kettőt, aztán a XAML-t generálja neked egy program, szóval kódírásról szó sincs. Lehet én értettem félre valamit?

Egyébként egész jó a cikk bevezetőnek.
5

Így igaz

orionstar · 2010. Nov. 2. (K), 15.58
Jól jött le a dolog, vagy összekattingatod amit akarsz vagy kódolsz. A fejlesztő döntése!
6

Nem XAML, C# kód

saxus · 2010. Nov. 3. (Sze), 00.31
Nem az XAML-re gondoltam, hanem a háttérben lévő C# kódról, ami a bindingeket tartalmazza. View Model-nek vagy minek hívják az MVVM patternben.
7

Bindings

orionstar · 2010. Nov. 3. (Sze), 06.10
Bindingseket is meg tudod adni kódolás nélkül, a propreties fülön, csak figyelni kell, hogy milyen bindingset adsz meg, de olykor bugos a VS és nem jeleníti meg a tulajdonságok fület miután rákattintottál a kívánt controllra... meg vannak más bugok is.