Grundlagen - Xaml-Syntax

    • WPF

      Grundlagen - Xaml-Syntax

      Ich gehe hier mal eingehender auf die Xaml-Syntax selbst ein. Das meiste versteht man vielleicht intuitiv, bzw. kopiert man immer unverändert mit rüber, aber vlt. ist auch mal interessant, genauer zu wissen, warum man etwas so formulieren kann oder so, und anderes vlt. grad auch eben nicht.
      Evtl. ist auch nicht alles 100% korrekt, oder vollständig, also Einstieg in weitere Lektüre kann dieser MSDN-Artikel sein: MSDN-Artikel



      Xml-Namespaces
      Also wenn man in Wpf ein neues Window anfängt, sieht das mit den Xml-Namespaces zunächstmal so aus:

      XML-Quellcode

      1. <Window x:Class="View.Window1"
      2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      4. [...]>
      5. <!-- ... -->
      6. </Window>
      Der erste xmlns (zeile#2) hat keinen Namen, der zweite heißt x.
      Namespaces sind ganz allgemein etwas, worin definiert ist, welche Schlüsselworte erkannt werden.
      Obige NS verweisen auf zwei Sachen, die im Schema-Store mit dem Framework mit-installiert ist.
      Der ohne Namen enthält zB. lauter Controls, und weil er namenlos ist, kann man im Xaml die Standard-Controls auch einfach so hinschreiben.
      Nutzt man Schlüsselworte aus benannten NS, muss man den NS-Namen mit angeben, sieht man gleich hier in zeile#1: x:Class="View.Window1" - das Class-Schlüsselwort (aus dem Xml-Namespace x) bewirkt, dass "Window1" eine eigene Klasse ist, im Namespace "View".

      Neben Namespaces aus dem Schema-Store kann man auch auf Assembly-Namespaces verweisen - das ist dann nicht obige http-Syntax, sondern sowas:

      XML-Quellcode

      1. xmlns:vm="clr-namespace:XamlSyntax.Viewmodel"
      2. xmlns:sys="clr-namespace:System;assembly=mscorlib"
      Also clr-namespace sagt an, dass es ein Assembly-Namespace ist, und assembly benennt erforderlichenfalls die Assembly auch - es muss eine Bibliothek sein, auf die ein Projekt-Verweis gesetzt ist.
      Bei xmlns:vm="clr-namespace:XamlSyntax.Viewmodel" kann die assembly-Angabe weggelassen werden, XamlSyntax ist nämlich nichts eingebundenes, sondern ist das Projekt selber. Und Viewmodel ist innerhalb meines Projektes der Namespace, wo meine Viewmodel-Klassen eingeordnet sind. Also vm: bezeichnet alles, was ich extra gecodet habe, um Xaml daran binden zu können.
      clr-namespace:System;assembly=mscorlib hingegen bezeichnet den grundlegenden .Net-Namespace, da sind so Sachen wie String, Integer, DateTime etc. drin. Braucht man in Xaml normal nicht, aber für dieses Tut im Weiteren doch.
      Wir haben bisher also 4 xmlns' deklariert:
      1. den Namenlosen
      2. x
      3. vm (Viewmodel)
      4. sys (System)
      Damit haben wir einen ausreichenden Wortschatz festgelegt für unsere weiteren Tests.



      Schachtel-Syntax
      Ein grundlegendes zur Xaml-Syntax ist damit schon gesagt: Man muss die Namespaces angeben, sonst kapiert der Xaml-Parser gar nix.
      Xaml ist nun ein Xml-Dialekt, und bei Xml ist alles ineinander verschachtelt - ich zeige hier mal ganz "rohes" Xaml:

      XML-Quellcode

      1. <Window x:Class="wndSample1"
      2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      4. xmlns:sys="clr-namespace:System;assembly=mscorlib"
      5. xmlns:hlp="clr-namespace:System.Windows.Controls;assembly=WpfHelpers"
      6. xmlns:vm="clr-namespace:XamlSyntax.Viewmodel"
      7. DataContext="{Binding Source={x:Static vm:MainViewModel.Instance}}"
      8. >
      9. <Window.Content>
      10. <StackPanel>
      11. <StackPanel.Children>
      12. <DataGrid>
      13. <DataGrid.Background>
      14. <Brush>#123456</Brush>
      15. </DataGrid.Background>
      16. <DataGrid.ItemsSource>
      17. <Binding>
      18. <Binding.Source>
      19. <x:StaticExtension>
      20. <x:StaticExtension.Member>
      21. <sys:String>vm:MainViewModel.Instance</sys:String>
      22. </x:StaticExtension.Member>
      23. </x:StaticExtension>
      24. </Binding.Source>
      25. <Binding.Path>
      26. <PropertyPath>Persons</PropertyPath>
      27. </Binding.Path>
      28. </Binding>
      29. </DataGrid.ItemsSource>
      30. </DataGrid>
      31. </StackPanel.Children>
      32. </StackPanel>
      33. </Window.Content>
      34. </Window>

      Ich nenne es "roh", weil auf jede Syntax-Verkürzung verzichtet ist (ausser zeile#7).
      Jedenfalls im Rohling sieht man, wie beim Einschachteln eines inneren Elements zunächst die Property eingeschachtelt zu bezeichnen ist, durch . mit dem Property-OwnerTyp verbunden (<Window.Content>). Es entspricht einer Zuweisung, also:

      XML-Quellcode

      1. <Window x:Class="wndSample1"... >
      2. <Window.Content>
      3. <StackPanel>
      4. ...
      ist zu verstehen als:

      VB.NET-Quellcode

      1. Public Class wndSample1 : Inherits Window
      2. Public Sub New()
      3. Me.Content = New StackPanel())
      4. '...
      wie gesagt: wndSample1 erbt von Window und gibt sich im Konstruktor als Content ein StackPanel.



      TypeConverter
      Neben den Deklarationen aus den Namespaces versteht der Xaml-Parser auch Worte, für die ein TypConverter existiert. Das ist ein Mechanismus, der noch weiter im Hintergrund werkelt.
      Unsere roh-Formulierung

      XML-Quellcode

      1. <DataGrid.Background>
      2. <Brush>#123456</Brush>
      3. </DataGrid.Background>
      lässt keinen Zweifel, in was #123456 zu konvertieren ist, aber es geht auch ohne die Brush-Angabe:

      XML-Quellcode

      1. <DataGrid.Background>#123456</DataGrid.Background>
      Der XamlParser kennt ja den erwarteten Typ der DataGrid.Background-Property (ja, ein Brush), und dann guckt er, obs für Brush einen TypeConverter gibt, der mit "123456" was anfangen kann.



      Xml-Attribut-Syntax
      Im Falle einfacher Properties kann auch ganz ohne die lästige Schachtelei gearbeitet werden, nämlich indem man die Property als Xml-Attribut formuliert und zuweist:

      XML-Quellcode

      1. <DataGrid Background="#123456"/>
      Wie gesagt: geht bei singulären Properties, bei Auflistungen - wie etwa <StackPanel.Children> - muss man nachwievor die Schachtel-Syntax bemühen.

      Also der Xaml-Parser kann einen String lesen, wenn er einen Typ erwartet, und wenn es für diesen Typen einen TypConverter gibt, der letztlich den String lesen kann.



      Markup-Extensions
      Darüberhinaus gibt es noch die Markup-Extensions, die können noch kompliziertere Strings lesbar machen. Betrachten wir dieses umständliche Binding:

      XML-Quellcode

      1. <Binding>
      2. <Binding.Source>
      3. <x:StaticExtension>
      4. <x:StaticExtension.Member>
      5. <sys:String>vm:MainViewModel.Instance</sys:String>
      6. </x:StaticExtension.Member>
      7. </x:StaticExtension>
      8. </Binding.Source>
      9. <Binding.Path>
      10. <PropertyPath>Persons</PropertyPath>
      11. </Binding.Path>
      12. </Binding>
      Es hat 2 Properties, .Source und .Path, wobei .Source auch nochmal ein komplexes Objekt ist, was selbst wieder eine Property hat, nämlich .Member As String.
      All das kann man auch als Einzeiler formulieren:

      XML-Quellcode

      1. <DataGrid ItemsSource="{Binding Source={x:Static vm:MainViewModel.Instance},Path= Persons}"/>
      Also ItemsSource ist vom Typ Object, und dafür ist kein TypeConverter verfügbar. Aber die Markup-Extenion {Binding}, mit ihren Properties Source und Path stellt etwas Xaml-interpretierbares dar, eben ein Binding. Also im Grunde ist {Binding} im Einzeiler und <Binding> eingeschachtelt was ganz verschiedenes.
      {Binding} ist kein Binding, sondern ist eine Markup-Extension, von der ein Binding abgerufen werden kann.
      Und darin das x:Static ist auch kein wirkliches Objekt, sondern ist auch eine Markup-Extension, nämlich eine, von der (in diesem Beispiel) die Mainmodel-Instanz abgerufen werden kann.
      Wir haben hier also eine Markup-Extension innerhalb einer Markup-Extension - auch eine Verschachtelung von Properties, aber viel kompakter formuliert, einfach mit {}.
      Übrigens kann bei MarkupExtensions das "Extension"-Anhängsel weggelassen werden, also wie man sieht kann man x:Static schreiben, wenn man x:StaticExtension meint.



      Weitere Vereinfachungen
      Bestimmte Xaml-Elemente haben eine Default-Property, sodass man direkt reinschachteln kann:

      XML-Quellcode

      1. <Window x:Class="View.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
      2. <StackPanel>
      <Window.Content> erübrigt sich. Oder hier:

      XML-Quellcode

      1. <StackPanel>
      2. <TextBlock Text="Hallo"/>
      3. <TextBlock>Bla Bla</TextBlock>
      <StackPanel.Children> ist verzichtbar.
      Also bei Window ist .Content die Default-Property, und bei StackPanel ists .Children, die zu notieren weggelassen werden kann.

      Also unser "Roh-Xaml-Syntax"-Window aus Listing#3 schrumpft doch erheblich zusammen:

      XML-Quellcode

      1. <Window x:Class="Window2"
      2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      4. xmlns:vm="clr-namespace:XamlSyntax.Viewmodel">
      5. <StackPanel>
      6. <DataGrid Background="#123456" ItemsSource="{Binding Source={x:Static vm:MainViewModel.Instance},Path= Persons}"/>
      7. </StackPanel>
      8. </Window>




      Resources, und ein Tip
      Wie gesagt, Auflistungen kann man nicht "inlinen", und eine besonders wichtige Auflistungs-Property sind die Resources. Jedes FrameworkElement hat Resources, und was man da reintut, ist für alle eingeschachtelten Elemente mit-verfügbar, bzw. wirkt sogar auf diese.
      Hier habe ich einen Style in die Window-Resources gelegt, der alle Textblöcke gelb macht:

      XML-Quellcode

      1. <Window x:Class="wndSample1"... >
      2. <Window.Resources>
      3. <Style TargetType="TextBlock">
      4. <Setter Property="Background" Value="Yellow"/>
      5. </Style>
      6. </Window.Resources>
      7. <StackPanel>
      8. <StackPanel Orientation="Horizontal">
      9. <TextBlock Text="Hallo"/>
      10. <TextBlock>Bla Bla</TextBlock>
      11. </StackPanel>
      12. <DockPanel >
      13. <TextBlock Text="Hallo"/>
      14. <TextBlock>Bla Bla</TextBlock>
      15. </DockPanel>
      16. </StackPanel>
      17. </Window>

      Dazu hätte ich noch einen Tip: Immer die Basisklasse angeben, der eine Property angehört. Resources etwa ist von FrameworkElement geerbt, ich muss also nicht das hochspezialisierte Window angeben, sondern kann ebensogut lowlevel über FrameworkElement addressieren:

      XML-Quellcode

      1. <Window x:Class="wndSample1"... >
      2. <FrameworkElement.Resources>
      3. <Style TargetType="TextBlock">
      4. <Setter Property="Background" Value="Yellow"/>
      5. </Style>
      6. </FrameworkElement.Resources>
      7. <StackPanel>
      8. <StackPanel Orientation="Horizontal">
      9. <TextBlock Text="Hallo"/>
      10. <TextBlock>Bla Bla</TextBlock>
      11. </StackPanel>
      12. <DockPanel >
      13. <TextBlock Text="Hallo"/>
      14. <TextBlock>Bla Bla</TextBlock>
      15. </DockPanel>
      16. </StackPanel>
      17. </Window>

      Was hab ich davon? Nun, lowlevel-addressierte Properties kann ich leichter verschieben. Obiger Style wirkt sich ja auf alle TextBlöcke im Scope aus, aber ich kann die Resources komplett auch ins DockPanel schieben. Dadurch werden nur noch die Textblöcke auffm DockPanel gelb.

      XML-Quellcode

      1. <Window x:Class="wndSample1"... >
      2. <StackPanel>
      3. <StackPanel Orientation="Horizontal">
      4. <TextBlock Text="Hallo"/>
      5. <TextBlock>Bla Bla</TextBlock>
      6. </StackPanel>
      7. <DockPanel >
      8. <FrameworkElement.Resources>
      9. <Style TargetType="TextBlock">
      10. <Setter Property="Background" Value="Yellow"/>
      11. </Style>
      12. </FrameworkElement.Resources>
      13. <TextBlock Text="Hallo"/>
      14. <TextBlock>Bla Bla</TextBlock>
      15. </DockPanel>
      16. </StackPanel>
      17. </Window>
      Das Verschieben der Resourcen-Property geht, weil DockPanel ebenso von FrameworkElement erbt wie Window - also FrameworkElement.Resources kann ich verschieben, was mit Window.Resources nicht gegangen wäre, denn ein DockPanel ist zwar ein FrameworkElement, aber ist kein Window.

      (Im Anhang die Code-Beispiele)
      Dateien
      • XamlSyntax.zip

        (13,98 kB, 289 mal heruntergeladen, zuletzt: )

      Dieser Beitrag wurde bereits 10 mal editiert, zuletzt von „ErfinderDesRades“ ()