Zum Inhalt
Kuonen.UI

KXAML

Die Sprache: Elemente, Eigenschaften, Bindungen, Ressourcen, Styles, Vorlagen und Trigger.

KXAML beschreibt Oberflächen von Kuonen.UI in XML. Der Quellgenerator tools/Kuonen.UI.Kxaml übersetzt jede .kxaml-Datei beim Bauen in C#. Zur Laufzeit gibt es keinen Parser und keine Reflexion; der erzeugte Code ist AOT- und Trimming-tauglich. Dieselbe Übersetzung nutzen die Sprachdienste und der Designer in VS Code (docs/designer.md).

Einbinden

<ItemGroup>
  <ProjectReference Include="..\..\tools\Kuonen.UI.Kxaml\Kuonen.UI.Kxaml.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
  <AdditionalFiles Include="**\*.kxaml" />
</ItemGroup>

Über das Paket (docs/auslieferung.md) entfällt beides: Kuonen.UI bringt den Generator mit und bindet **/*.kxaml selbst ein (abschaltbar mit <KuonenUIKxamlAutomatisch>false</KuonenUIKxamlAutomatisch>). Innerhalb dieser Lösung gilt der Projektverweis oben. Das Projekt braucht außerdem einen Verweis auf Kuonen.UI (sonst KX0102 am Wurzelelement). Der Generator selbst ist ein netstandard2.0-Analyzer mit Roslyn als einziger Paketreferenz; in src/ gibt es weiterhin keine Pakete.

Grundform

<StackPanel xmlns="https://ui.kuonen.org/kxaml"
            xmlns:x="https://ui.kuonen.org/kxaml/x"
            xmlns:local="using:Meine.App"
            x:Class="Meine.App.Hauptseite"
            Spacing="8">
  <TextBlock x:Name="Titel" Text="Hallo" FontWeight="SemiBold" />
  <Button Content="Weiter" Click="Weiter_Click" />
</StackPanel>
public sealed partial class Hauptseite
{
    public Hauptseite() => InitializeComponent();

    private void Weiter_Click(object sender, RoutedEventArgs e) => Titel.Text = "Weiter";
}
  • x:Class am Wurzelelement ist Pflicht. Die Klasse erbt vom Typ des Wurzelelements, der nicht versiegelt sein darf. Der Generator erzeugt InitializeComponent(), die Felder und die Schlüsselklasse; die Klasse wird vom Anwender als partial deklariert (eine Wörterbuchdatei braucht kein Code-Behind, siehe unten).
  • x:Name wird ein internal-Feld; an der Wurzel zeigt das Feld auf this. In einer ControlTemplate wird es ein benannter Teil (PART_…); in DataTemplate und ItemsPanelTemplate gilt der Name nur innerhalb der Vorlage (für ElementName und x:Reference), ein Feld gibt es dort nicht. Ein Name, der mit einem Mitglied der Klasse, einem sichtbaren Mitglied der Basis, InitializeComponent oder der Schlüsselklasse Schluessel kollidiert, ergibt KX0402.
  • x:Name setzt bei einem FrameworkElement ausserdem Name (wie WPF), auch in DataTemplate und ItemsPanelTemplate; FindName findet es. Teile einer ControlTemplate bekommen keinen Name (sie gehören dem Control, nicht dem Namensbereich der Datei).
  • Namensbereiche: Die Datei ist ein Namensbereich, jede Vorlage (ControlTemplate, DataTemplate, ItemsPanelTemplate) ein eigener. Ein Name steht je Bereich nur einmal (KX0400, auch in einer ControlTemplate schon beim Bauen) und ist nur in seinem Bereich sichtbar (sonst KX0403). x:Name in einer DataTemplate oder ItemsPanelTemplate ist erlaubt; der Name gilt nur dort (für ElementName und x:Reference) und ergibt kein Feld. Ein ElementName in einer ControlTemplate meint einen Teil derselben Vorlage.
  • Namensräume:
    • https://ui.kuonen.org/kxaml: Kuonen.UI, .Controls, .Layout, .Media, .Bindung, .Befehle, .Themes, .Shapes (Formen)
    • https://ui.kuonen.org/kxaml/x: Spracheinträge (x:Class, x:Name, x:Key, x:DataType, x:Null, x:Static, x:Reference)
    • using:Namensraum: eigene Typen; geschachtelte Typen mit Punkt (local:Aussen.Innen)
    • Der Präfix x darf anders heißen; maßgeblich ist die Adresse.

Eigenschaften und Inhalt

  • Attribute setzen Eigenschaften, angehängte als Besitzer.Eigenschaft (Grid.Row="1", ToolTipService.ToolTip="…", TextElement.FontStyle="Italic"). Ein Attribut kann auch ein Ereignis sein (siehe unten).
  • Eigenschaftselemente <Typ.Eigenschaft> nehmen Objekte auf (<Grid.RowDefinitions>, <Border.Style>, <ToolTipService.ToolTip>). Sie haben keine Attribute.
  • Kinder ohne Eigenschaftselement gehen in die Inhaltseigenschaft ([ContentProperty]):
    • Content: ContentControl
    • Children: Panel
    • Child: Border, Popup
    • Items: ItemsControl
    • Inlines: TextBlock, Span
    • Text: Run

    Text in einer Inline-Sammlung wird ein Run.

  • Reihenfolge: Der Baum wird von oben gebaut: erzeugen, einhängen, dann Attribute (in Dokumentreihenfolge), Eigenschaftselemente, Inhalt. Startwerte vor die Ereignis-Attribute schreiben. Bindungen mit ElementName oder Source={x:Reference …} und Zuweisungen aus {x:Reference} stehen am Ende ihres Namensbereichs (dann gibt es auch später erklärte Namen schon).
  • Leerraum wird auf ein Leerzeichen zusammengefasst und am Anfang und Ende gekürzt; genauer Leerraum steht in <Run Text="…" />.
  • Links: Hyperlink steht als Inline im TextBlock (<Hyperlink NavigateUri="https://example.org/"><Run Text="Mehr" /></Hyperlink>; NavigateUri ist ein Uri-Text, Hyperlink.RequestNavigate="…" an einem Vorfahren behandelt alle Adressen). HyperlinkButton und LinkLabel (Text als Inhalt) sind gewöhnliche Elemente; die Links des LinkLabel (Links, LinkArea) setzt der Code-Behind.
  • Sammlungen (RowDefinitions, Items, …) nehmen ihre Einträge als Kindelemente auf; ein Attribut kann sie nicht setzen (KX0200).
  • Eine Eigenschaft darf nur einmal gesetzt werden, auch nicht zusätzlich über den Inhalt (KX0200).

Ereignisse

Ein Attribut, das eine Ereignis-Eigenschaft benennt, verdrahtet eine Instanzmethode der x:Class (oder ihrer Basis, sofern sichtbar):

  • CLR-Ereignisse (Click="Weiter_Click") werden mit += verdrahtet.
  • Routed Events (auch angehängte) über AddHandler; sie gibt es nur an UIElement.
  • Die Methode muss existieren (KX0401) und nicht statisch sein. Die Signatur prüft der C#-Übersetzer im erzeugten Code, die Meldung zeigt dank #line auf das Attribut.
  • In einer ControlTemplate werden keine Handler verdrahtet (KX0401).

Werte aus Text (Typkonverter)

TypBeispiele
Zahlen, bool, char12, 1.5, Auto/NaN (double), True, *
EnumCenter, Bold; bei [Flags] A, B oder A \| B
Thickness8, 8,4, 1,2,3,4
CornerRadius4, 0,24,0,24
GridLengthAuto, *, 2*, 40
Color, Brush#RGB, #ARGB, #RRGGBB, #AARRGGBB oder ein Farbname wie bei WPF (Red, CornflowerBlue, Transparent; die 141 Namen von System.Windows.Media.Colors, Groß- und Kleinschreibung egal, nur ASCII; #Red ist ein Hex-Fehler)
PathGeometryM0,0 L10,0 L5,8 Z (Schreibweise beim Bauen geprüft, höchstens 1 000 000 Zeichen)
PointCollection0,0 40,30 80,0 oder 0 0, 40 30, 80 0 (Paare x,y; Leerraum oder ein Komma trennt; ungerade Zahl und nicht endliche Zahlen sind ein Fehler; höchstens 1 000 000 Punkte; bis 1000 Punkte steht die Liste im erzeugten Code, darüber liest PointCollection.Parse den beim Bauen geprüften Text)
DoubleCollection2 1, 4,2, 1 ,2 (Strichmuster; endliche Zahlen, höchstens 10 000; ob Strichlängen nicht negativ sein müssen, prüft die Form)
Matrix1,0,0,1,10,20 (M11,M12,M21,M22,OffsetX,OffsetY) oder Identity
TimeSpan0:0:1, 00:00:00.5, 1.02:03:04, -0:0:2 (WPF-Form [-][d.]hh:mm[:ss[.fffffff]], immer invariant gelesen; erzeugt TimeSpan.FromTicks)
DateTime2026-10-13, 2026-10-13T14:05, 2026-10-13T14:05:09 (ISO 8601, immer von Hand und unabhängig von der Sprache des Baurechners gelesen; erzeugt new DateTime(j, m, t[, h, mi, s]); ungültige Tage wie der 30. Februar, Jahr 0, Landesformen wie 13.10.2026 und Text sind ein Fehler mit Stelle). DayOfWeek geht als Aufzählung (FirstDayOfWeek="Monday")
Urihttps://ui.kuonen.org/seite, seite.html (absolut oder relativ; leerer Text und Unlesbares sind ein Fehler)
TypeButton, local:MeinControl
FontWeightThin … SemiBold … Black oder Zahl

Zahlen werden mit der invarianten Kultur gelesen; NaN und unendlich sind nur dort erlaubt, wo der Typ sie annimmt. Zahlenlisten trennen mit Komma oder Leerraum; ein Komma braucht beidseitig eine Zahl. Einfache Objekte aus Text: <Thickness x:Key="Abstand">4</Thickness>, <SolidColorBrush x:Key="Warnung">#C42B1C</SolidColorBrush>. Ein {} am Anfang eines Werts maskiert eine wörtliche Klammer. Meldungen der Konverter liegen auf dem Anfang des Werts; die Stelle im Pfad oder in der Liste steht im Text. Farbnamen stehen beim Bauen in einer festen Tabelle; erzeugt wird die Zahl.

Fehler in Listen und Pfaden nennen die Stelle (ab 1) im Wert, etwa „„NaN“ ist keine endliche Zahl (Stelle 3)“; die Meldung liegt auf dem Anfang des Werts (KX0300).

An einer Eigenschaft vom Typ UIElement (oder einem davon abgeleiteten) ist ein Name allein kein Wert: RightOf="Knopf" ergibt KX0300 mit dem Hinweis {x:Reference Knopf} verwenden, wenn Knopf ein x:Name im selben Namensbereich ist (auch ein später erklärter), sonst die allgemeine Meldung „kein Typkonverter“.

Formen

Die Formen (Rectangle, Ellipse, Line, Polyline, Polygon, Path) stehen im Standardnamensraum (Kuonen.UI.Shapes gehört zu den Namensräumen hinter https://ui.kuonen.org/kxaml). Path.Data nimmt die Pfadsprache (M0,0 L10,10 Z, wie PathIcon.Data), Points an Polyline und Polygon eine Punktliste, StrokeDashArray eine Zahlenliste:

<Path Data="M0,0 L40,0 L20,30 Z" Fill="#2B88D8" />
<Polyline Points="0,0 40,30 80,0" Stroke="#333" StrokeThickness="2" StrokeDashArray="2 1" />

Ein RelativePanel bezieht sich auf Geschwister mit {x:Reference}: <Button RelativePanel.RightOf="{x:Reference Knopf}" />.

Layout-Ergänzungen und Dialoge

UniformGrid (Rows, Columns, FirstColumn), RelativePanel, Viewbox (Stretch, StretchDirection), LayoutTransformControl, GridSplitter und SplitContainer sind gewöhnliche Elemente im Standardnamensraum. Eine KXAML-Datei kann ein UserControl als Wurzel haben (x:Class nennt die Klasse im Code-Behind, Beispiel samples/Kuonen.UI.Galerie/Seiten/UserControlSeite.kxaml). LayoutTransformControl.LayoutTransform nimmt eine Matrix als Text (LayoutTransform="0.7071,0.7071,-0.7071,0.7071,0,0"). Ein GridSplitter steht im selben Grid in einer eigenen Spur (Beispiel GridSplitterSeite.kxaml).

MessageBox, TaskDialog und DispatcherTimer sind keine Elemente der Oberfläche: Sie werden im Code-Behind benutzt (await MessageBox.ZeigeAsync(…), new DispatcherTimer { Interval = … }), nicht im KXAML und nicht in der Werkzeugkiste des Designers.

Datum und Zeit

Calendar, MonthCalendar, DatePicker, CalendarDatePicker, TimePicker und DateTimePicker stehen im Standardnamensraum. Daten werden als ISO 8601 geschrieben (DisplayDateStart="2026-01-01", Value="2026-10-13T14:30:00"), Zeiten im TimePicker als TimeSpan (SelectedTime="14:30"). Gesperrte Tage sind eine Sammlung von Bereichen:

<Calendar DisplayDateStart="2026-01-01" SelectedDatesChanged="Kalender_SelectedDatesChanged">
  <Calendar.BlackoutDates>
    <CalendarDateRange Start="2026-12-24" End="2026-12-26" />
  </Calendar.BlackoutDates>
</Calendar>
<TimePicker ClockIdentifier="12HourClock" MinuteIncrement="15" SelectedTime="09:15" />
<DateTimePicker Format="Custom" CustomFormat="dd.MM.yyyy HH:mm" ShowCheckBox="True" Value="2026-10-13T14:30:00" />

FirstDayOfWeek nimmt einen DayOfWeek-Namen. Das Anzeigeformat (Kalenderformat) setzt das Code-Behind (Kalenderformat.Fuer("de-DE")); ohne Angabe gilt das der Sprache aus Standardtexte mit Region CH. Beispiele: CalendarSeite, MonthCalendarSeite, DatePickerSeite, TimePickerSeite und DateTimePickerSeite in samples/Kuonen.UI.Galerie/Seiten.

Masken

MaskedTextBox.Mask nimmt die Maskensprache von WinForms als gewöhnlichen Attributtext: <MaskedTextBox Mask="756.0000.0000.00" />. Ein \ vor einem Zeichen macht es zum Literal und bleibt im Attribut unverändert (XML kennt kein \-Escape: Mask="\0\00"). < muss als &lt; stehen (Kleinschreibung), > (Grossschreibung) darf so stehen oder als &gt;; & (beliebiges Zeichen) als &amp;. Beispiel: samples/Kuonen.UI.Galerie/Seiten/MaskedTextBoxSeite.kxaml.

Klappknöpfe

SplitButton, ToggleSplitButton und DropDownButton tragen ihre Klappe in Flyout (ein UIElement): ein ContextMenu erscheint als Menü unter dem Knopf, jedes andere Element in einer eigenen Klappe. Als Eigenschaftselement:

<SplitButton Content="Einfügen" Click="Einfuegen_Click">
  <SplitButton.Flyout>
    <ContextMenu>
      <MenuItem Header="Nur Text" Click="Eintrag_Click" />
      <MenuItem Header="Mit Format" Click="Eintrag_Click" />
    </ContextMenu>
  </SplitButton.Flyout>
</SplitButton>

Ein Element hängt an höchstens einem Knopf (ein zweiter wirft beim Setzen). Freien Inhalt schliesst SchliesseKlappe() nach einer Wahl.

Markup-Erweiterungen

ErweiterungBedeutung
{x:Null}null (nur Verweistypen und Nullable<T>)
{x:Static local:Typ.Mitglied}öffentliches statisches Feld, Eigenschaft (öffentlicher Getter), Konstante oder Enum-Wert
{x:Reference Name}das Objekt mit diesem x:Name im selben Namensbereich (auch später erklärt); nur als Attributwert eines Elements oder als Source einer Bindung. Geprüft wird nur, dass der Typ passt und dass kein Element in die Inhaltseigenschaft oder eine Sammlung kommt (KX0300); ob das Ziel zur Laufzeit schon einen anderen Elternteil hat, prüft der Generator nicht. Bezüge im RelativePanel stehen so: RelativePanel.RightOf="{x:Reference Name}" (angehängte Eigenschaften vom Typ UIElement)
{StaticResource Key}Ressource dieser Datei (Element oder Vorfahr) oder einer eingebundenen Wörterbuchdatei, beim Bauen aufgelöst
{DynamicResource Key} / {DynamicResource Fluent.Farben.Rand}folgt Theme- und Ressourcenwechseln (SetResourceReference); nur an Abhängigkeitseigenschaften von FrameworkElement
{Binding Pfad, Mode=TwoWay, Converter={StaticResource K}, FallbackValue=…, TargetNullValue=…, Source=…, ElementName=…, RelativeSource=…, Validierung=Methode, ValidatesOnExceptions=True}typisierte Bindung
{RelativeSource Self}, {RelativeSource TemplatedParent}, {RelativeSource FindAncestor, AncestorType=Typ, AncestorLevel=n}Quelle einer Bindung relativ zum Element
{TemplateBinding Eigenschaft}in einer ControlTemplate: Eigenschaft des Controls

Bindungen brauchen den Quelltyp, und zwar genau eine dieser Angaben:

  • x:DataType="local:Kunde" am Element, an einem Vorfahren oder an der DataTemplate (die Quelle ist dann der DataContext);
  • Source ({StaticResource …}, {x:Static …} oder {x:Reference Name});
  • ElementName=Name (ein x:Name im selben Namensbereich, auch später erklärt; KX0403, wenn es ihn dort nicht gibt);
  • RelativeSource: Self (das Element), TemplatedParent (nur in einer ControlTemplate, typisiert mit deren TargetType), FindAncestor mit AncestorType (Pflicht) und AncestorLevel (1 bis 64, Standard 1; nur an einem FrameworkElement). {RelativeSource AncestorType=…} ohne Modus heißt FindAncestor; {RelativeSource AncestorLevel=2} ohne Modus und ohne AncestorType ergibt eine eigene Meldung. Der Modus gilt ohne Rücksicht auf Groß- und Kleinschreibung, AncestorType muss ein Visual sein (KX0601). Gesucht wird zur Laufzeit (über den Besitzer eines Popups hinweg) beim Anhängen und nach jedem Einhängen des Elements oder eines Vorfahren; beim Entfernen bleibt die Quelle stehen.

Mehr als eine Angabe ergibt KX0600, Fehler in RelativeSource KX0601. Der Pfad wird beim Bauen geprüft (nur Eigenschaftsnamen mit Punkt, keine Indexer; nur die letzte Stufe darf ein Werttyp sein) und als BindingPath.Stufe(…) erzeugt; über eine Abhängigkeitseigenschaft eines Elements (KuonenObject) als BindingPath.Eigenschaft(Feld), die Änderungen ohne INotifyPropertyChanged meldet. TwoWay über eine solche Stufe braucht denselben Typ an beiden Seiten. Für TwoWay und OneWayToSource muss die Eigenschaft beschreibbar sein (KX0600): ein öffentlicher Setter der CLR-Eigenschaft; ohne sie das Feld einer Abhängigkeitseigenschaft, außer der Besitzer hält zu ihr einen Schlüssel KuonenPropertyKey<T> namens <Name>Key oder <Name>PropertyKey (Regel für nur lesbare Eigenschaften; ein nur lesbares Feld ohne diesen Schlüsselnamen bleibt beim Bauen unerkannt, der Kern verweigert das Schreiben dann zur Laufzeit). Konverter sind IValueConverter<TQuelle, TZiel> mit TZiel = Typ der Eigenschaft. Source und Converter brauchen eine Markup-Erweiterung, nicht {x:Null}. Validierung benennt eine Methode der x:Class. Markup-Erweiterungen dürfen höchstens 8 Stufen tief geschachtelt sein.

DataGrid und Zellbindung

<DataGrid ItemsSource="{Binding Leute}">
  <DataGridTextColumn x:DataType="local:Person" Header="Name" Width="2*" Binding="{Binding Name}" />
  <DataGridTextColumn x:DataType="local:Person" Header="Ort" Width="120" Binding="{Binding Adresse.Ort, Mode=OneWay}" />
  <DataGridCheckBoxColumn x:DataType="local:Person" Header="Aktiv" Width="Auto" Binding="{Binding Aktiv}" />
  <DataGridTemplateColumn Header="Person">
    <DataGridTemplateColumn.CellTemplate>
      <DataTemplate x:DataType="local:Person"><TextBlock Text="{Binding Name}" /></DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
  </DataGridTemplateColumn>
</DataGrid>
  • Spalten als Inhalt: DataGrid hat [ContentProperty("Columns")]; die Spaltenelemente DataGridTextColumn, DataGridCheckBoxColumn und DataGridTemplateColumn (mit CellTemplate) stehen unmittelbar im Element. Width geht über den GridLength-Konverter (Auto, 2*, 120).
  • Zellbindung: {Binding Pfad, Mode=OneWay|TwoWay} an einer Eigenschaft vom Typ Zellbindung (z. B. DataGridBoundColumn.Binding) erzeugt Zellbindung.Aus<Q, W>(pfad, schreibbar). Q, die Zeilenklasse, kommt aus x:DataType (am Spaltenelement oder einem Vorfahren), W aus dem Pfad; der Pfad entsteht wie bei jeder Bindung aus BindingPath.Stufe(…), ohne Reflexion. {Binding} oder {Binding .} bindet die Zeile selbst (nur lesend).
  • Falle: x:DataType gehört an die Spalten (nicht ans DataGrid, sonst würde die eigene Bindung des Grids, etwa ItemsSource, gegen die Zeilenklasse geprüft). Fehlt es an Spalte und Grid, gilt das x:DataType der Seite: Q ist dann die Klasse der Seite, die Zellen bleiben ohne Meldung leer.
  • Schreibbar ist ein Pfad mit öffentlichem Setter, solange Mode nicht OneWay ist; ohne Mode gilt also TwoWay, wo es möglich ist, und OneWay sonst.
  • KX0600 (mit Stelle): kein x:DataType; unbekannter Pfad; Mode=TwoWay an einem nur lesbaren Pfad (oder an der Zeile selbst); Mode ausser OneWay und TwoWay; andere Argumente als Path und Mode (kein Converter, Source, ElementName, …); {TemplateBinding} und {DynamicResource} an einer Zellbindung.
  • Abweichung vom Plan: {StaticResource}, {x:Static} und {x:Null} gehen an einer Zellbindung durch, weil sie einen fertigen, typgeprüften Wert liefern (bei falschem Typ KX0300 bzw. KX0500); der Plan wollte jede andere Erweiterung mit KX0600 abweisen.
  • Beispiel: Die Galerie-Seite „DataGrid“ (samples/Kuonen.UI.Galerie/Seiten/DataGridSeite.kxaml mit .kxaml.cs) zeigt eine Million Zeilen aus einer eigenen Zeilenquelle, Sortieren an der Quelle, Bearbeiten, eine Kästchenspalte und eine Vorlagenspalte an einer asynchronen Quelle; ihre C#-Fassung baut denselben Baum (KxamlSeitenTests).
  • Die Eigenschaft Binding ist eine Abhängigkeitseigenschaft, die Zellbindung selbst folgt aber keinem Datenkontext: Sie wird einmal beim Bauen gesetzt und liest die Zeile, die das Grid ihr gibt.

Ressourcen, Styles, Vorlagen

<StackPanel.Resources>
  <SolidColorBrush x:Key="Warnung">#C42B1C</SolidColorBrush>
  <Style x:Key="Karte" TargetType="Border" x:DataType="local:Aufgabe">
    <Setter Property="Padding" Value="12" />
    <Setter Property="BorderBrush" Value="{DynamicResource Fluent.Farben.Rand}" />
    <Style.Triggers>
      <Trigger Property="IsPointerOver" Value="True">
        <Setter Property="Background" Value="{StaticResource Warnung}" />
      </Trigger>
      <MultiTrigger>
        <MultiTrigger.Conditions>
          <Condition Property="IsEnabled" Value="False" />
          <Condition Property="Opacity" Value="1" />
        </MultiTrigger.Conditions>
        <Setter Property="BorderThickness" Value="2" />
      </MultiTrigger>
      <DataTrigger Binding="{Binding Erledigt}" Value="True">
        <Setter Property="Background" Value="LightGreen" />
      </DataTrigger>
    </Style.Triggers>
  </Style>
  <ControlTemplate x:Key="Rund" TargetType="ToggleButton">
    <Border x:Name="PART_Rand" CornerRadius="8" Background="{TemplateBinding Background}">
      <ContentPresenter Content="{TemplateBinding Content}" />
    </Border>
    <ControlTemplate.Triggers>
      <Trigger Property="IsChecked" Value="True">
        <Setter TargetName="PART_Rand" Property="Background" Value="LightBlue" />
      </Trigger>
    </ControlTemplate.Triggers>
  </ControlTemplate>
  <DataTemplate x:Key="Zeile" x:DataType="local:Kunde">
    <TextBlock Text="{Binding Name}" />
  </DataTemplate>
</StackPanel.Resources>
  • Schlüssel: Jeder x:Key wird das Feld Schluessel.<Key> (RessourcenSchluessel<T>) der Klasse, je Datei eindeutig und ein gültiger C#-Bezeichner. Schluessel selbst und Mitglieder von object (Equals, ToString, …) sind als Schlüssel nicht erlaubt (KX0500). Pinsel haben den Schlüsseltyp Brush?. In Resources stehen nur Objekte mit x:Key, oder genau ein <ResourceDictionary> (ohne Attribute) mit Einträgen und <ResourceDictionary.MergedDictionaries>.
  • StaticResource wird beim Bauen aufgelöst; die Ressource muss vorher in dieser Datei stehen (am Element oder an einem Vorfahren) oder in einer vorher eingebundenen Wörterbuchdatei. Ein UIElement als StaticResource ist nur einmal verwendbar (ein Element hat nur einen Elternteil; die zweite Verwendung ergibt KX0500) und in einer Vorlage nur aus deren eigenen Resources (jede Verwendung der Vorlage baut einen neuen Baum; sonst KX0500).
  • Style: TargetType ist Pflicht, dazu BasedOn ({StaticResource …} oder {x:Static …}), x:DataType (Datentyp der DataTrigger; ist er nicht auflösbar, gibt es nur diese eine Meldung, ohne Folgemeldungen und ohne Rückfall auf einen äußeren x:DataType), Setter (Wert als Attribut, Erweiterung oder <Setter.Value>) und Style.Triggers mit
    • Trigger Property=… Value=… (eine Bedingung);
    • MultiTrigger mit MultiTrigger.Conditions aus Condition Property=… Value=… (alle müssen gelten);
    • DataTrigger Binding="{Binding Pfad}" Value=… (Pfad auf dem DataContext, typisiert über x:DataType; der Wert im Typ der letzten Stufe);
    • MultiDataTrigger mit MultiDataTrigger.Conditions aus Condition Binding=… Value=….

    Werte von Bedingungen sind fest (Text, x:Static, x:Null, StaticResource), DynamicResource gibt es nur im Setter (als Bedingungswert KX0701 bei den neuen Triggerarten, KX0700 beim einfachen Trigger). Ein Trigger hat höchstens 64 Bedingungen, doppelte X.Conditions ergeben KX0701. Setter setzen nur Abhängigkeitseigenschaften des Zieltyps; TargetName gibt es im Style nicht (KX0700). Der Zieltyp eines BasedOn-Styles muss passen, die Kette ist durch Style.MaxBasisTiefe begrenzt. Kommt der Grund-Style aus x:Static (sein Zieltyp ist beim Bauen unbekannt), prüft KxamlHilfen.Stil zur Laufzeit und meldet einen unpassenden Grund-Style genau einmal mit Datei, Zeile und Spalte; behandelt, gilt der Style ohne Grund-Style.

  • Vorlagen: ControlTemplate (TargetType, genau eine Wurzel und höchstens ein ControlTemplate.Triggers), DataTemplate, ItemsPanelTemplate (Wurzel ist ein Panel). TemplateBinding gibt es nur in einer ControlTemplate. In DataTemplate und ItemsPanelTemplate endet der Bereich von x:DataType und Ressourcen an der Vorlage; jede Verwendung baut einen eigenen Baum.
  • ControlTemplate.Triggers: Trigger und MultiTrigger mit Bedingungen am Control; jeder Setter braucht TargetName (ein x:Name der Vorlage) und setzt dort eine Abhängigkeitseigenschaft. Der zuletzt stehende aktive Trigger gewinnt. Ohne aktiven Trigger gilt der Grundwert: das Attribut am Teil (fest, TemplateBinding, DynamicResource, StaticResource, x:Static; keine Bindung) oder, ohne Attribut, der eigene Wert des Teils (Style, Theme, geerbt). Eine Eigenschaft, die ein Trigger setzt, steht am Teil als Attribut, nicht als Eigenschaftselement (KX0701). Property darf mit Besitzer geschrieben werden (Border.Background und Background sind dieselbe Eigenschaft und bilden eine Gruppe); maßgeblich ist die aufgelöste Eigenschaft, nicht der Kurzname: zwei angehängte Eigenschaften gleichen Namens verschiedener Besitzer (local:Eins.Wert, local:Zwei.Wert) sind zwei Eigenschaften mit je eigenem Grundwert. Ein Setter mit und einer ohne TargetName im selben Trigger ergibt KX0701. DataTrigger gibt es hier nicht. Ein DynamicResource als Grundwert oder Setter-Wert folgt Änderungen im Wörterbuch, auch in einem eingebundenen.

Wörterbuchdateien

Eine .kxaml mit ResourceDictionary als Wurzel ist ein Wörterbuch für mehrere Dateien:

<ResourceDictionary xmlns="https://ui.kuonen.org/kxaml"
                    xmlns:x="https://ui.kuonen.org/kxaml/x"
                    x:Class="Meine.App.Farben">
  <ResourceDictionary.MergedDictionaries>
    <ResourceDictionary Source="Gemeinsam/Masse.kxaml" />
  </ResourceDictionary.MergedDictionaries>
  <SolidColorBrush x:Key="Akzent">CornflowerBlue</SolidColorBrush>
  <Style x:Key="Karte" TargetType="Border" BasedOn="{StaticResource Grund}" />
</ResourceDictionary>
<StackPanel.Resources>
  <ResourceDictionary>
    <ResourceDictionary.MergedDictionaries>
      <ResourceDictionary Source="Farben.kxaml" />
    </ResourceDictionary.MergedDictionaries>
  </ResourceDictionary>
</StackPanel.Resources>
<Border Background="{StaticResource Akzent}" Style="{StaticResource Karte}" />
  • Die Klasse leitet von ResourceDictionary ab; ohne eigenen parameterlosen Konstruktor erzeugt der Generator ihn (InitializeComponent setzt die Einträge), Code-Behind ist also nicht nötig. An der Wurzel steht nur x:Class.
  • Source ist der Pfad einer .kxaml-Datei desselben Projekts (unter den AdditionalFiles), relativ zur einbindenden Datei (../Gemeinsam/Farben.kxaml); URIs und absolute Pfade gibt es nicht. Groß- und Kleinschreibung zählen (die Meldung nennt eine ähnliche Datei).
  • Die Schlüssel eingebundener Dateien sind beim Bauen bekannt und typisiert, auch mittelbar eingebundene: StaticResource liest den Wert aus der Instanz (KxamlHilfen.Ressource), DynamicResource und Style-Setter nehmen das Schlüsselfeld der Datei (Farben.Schluessel.Akzent). Eigene Einträge gehen vor; unter den eingebundenen gewinnt die zuletzt eingebundene. MergedDictionaries stehen vor den Einträgen, die sie benutzen.
  • In einer Wörterbuchdatei stehen keine Elemente (UIElement; KX0501), für Elemente eine Vorlage nehmen. Kreise (eine Datei bindet sich mittelbar selbst ein), fremde Dateien, Dateien ohne Wörterbuch-Wurzel, mehr als 64 mittelbar eingebundene Dateien und Schlüssel, deren Typ nicht eindeutig aufzulösen ist (nicht gefunden, in zwei Assemblies, nicht benannt), ergeben KX0501; die einbindende Datei meldet es mit dem Namen der Wörterbuchdatei statt „nicht erklärt“. Die Suche über eingebundene Dateien besucht jede Tabelle höchstens einmal je Suche (verzweigte Einbindungen laufen nicht in eine Vervielfachung).
  • Zur Laufzeit sucht ResourceDictionary zuerst in den eigenen Einträgen, dann in den eingebundenen (die letzte zuerst); Änderungen in einem eingebundenen Wörterbuch erreichen jedes Element, das es (mittelbar) einbindet. Verschachtelung (16), Zahl je Wörterbuch (64) und durchsuchte Wörterbücher je Suche (256) sind begrenzt. Meldungen und Einfrieren besuchen dagegen jedes erreichbare Wörterbuch genau einmal (Laufmarke, ohne Zähldeckel). Ein Wörterbuch gehört dem Thread, der es zuerst schreibt; Einfrieren eines noch nicht eingefrorenen Wörterbuchs prüft den Besitzer zuerst, ein eingefrorenes lässt sich von jedem Thread lesen und noch einmal einfrieren.
  • Inkrementell: Je Datei steht ihre Schlüsseltabelle (Name, Typ, Schlüsseltyp, Style-Zieltyp, Grund-Style) in einem eigenen Schritt. Ändert sich nur ein Wert in einem Wörterbuch, bleibt die Tabelle gleich und keine einbindende Datei wird neu übersetzt; ändert sich die Tabelle, werden nur die Dateien neu übersetzt, die das Wörterbuch (mittelbar) einbinden.

Meldungen

Jede Eingabe ergibt Code oder Meldungen mit Zeile und Spalte, nie einen Absturz des Generators. Alle Meldungen sind Fehler, außer KX0008 (Warnung). C#-Fehler im erzeugten Code zeigen dank #line ebenfalls auf die KXAML-Stelle.

RegelBedeutung
KX0001Interner Fehler des Generators (bitte melden)
KX0002kein wohlgeformtes XML (auch DTD)
KX0003Datei zu groß (mehr als 2 Mi Zeichen)
KX0004zu tief verschachtelt (mehr als 64 Stufen)
KX0005zu viele Elemente und Texte (mehr als 20 000), zu viele Attribute (mehr als 128 an einem Element) oder zu viele Verweise auf Namen (ElementName, x:Reference; mehr als 20 000 in einem Namensbereich, die übrigen werden nicht gebunden)
KX0006Wert oder Text zu lang (mehr als 64 KiB)
KX0007kein Wurzelelement
KX0008Warnung: der Dateipfad enthält ein Anführungszeichen oder einen Zeilenwechsel und wird nicht in #line geschrieben; C#-Fehler liegen dann im erzeugten Code statt in der KXAML-Datei
KX0010Markup-Erweiterung fehlerhaft, unbekannt oder an dieser Stelle nicht erlaubt (höchstens 8 tief; x:Reference außerhalb von Attributwerten und Source)
KX0100x:Class fehlt, ist ungültig, steht nicht am Wurzelelement oder die Basis ist versiegelt
KX0101unbekannter oder nicht erklärter Namensraum
KX0102Typ unbekannt, mehrdeutig, kein gültiger Typname oder nicht erzeugbar (abstrakt, kein öffentlicher parameterloser Konstruktor); auch: Kuonen.UI nicht referenziert
KX0200Eigenschaft oder Ereignis unbekannt, doppelt gesetzt, nicht beschreibbar, unbekannter Spracheintrag x:…
KX0201Inhalt passt nicht (keine Inhaltseigenschaft, falscher Typ, zu viele Werte)
KX0300Wert passt nicht zum Typ (Konverter, Farbname, {x:Null} für Werttyp, {x:Reference} mit falschem Typ oder als Inhalt)
KX0400x:Name ungültig oder doppelt im Namensbereich
KX0401Ereignis-Handler fehlt, ist statisch oder steht in einer ControlTemplate
KX0402x:Name kollidiert mit einem Mitglied der Klasse, ihrer Basis, InitializeComponent oder Schluessel
KX0403Name (ElementName, x:Reference, TargetName) im Namensbereich nicht gefunden
KX0500Ressource, Schlüssel oder statisches Mitglied nicht gefunden, unpassend, doppelt, als Element schon verwendet oder als Element von außerhalb einer Vorlage
KX0501Wörterbuchdatei fehlerhaft: Source nicht gefunden, keine Wörterbuchdatei, kein relativer Pfad, Kreis, Element im Wörterbuch, fremde Angaben, mehr als 64 mittelbar eingebundene Dateien, Schlüsseltyp nicht auflösbar
KX0600Bindung nicht auflösbar (Quelltyp, Pfad, Modus, nicht beschreibbare Eigenschaft bei TwoWay, Konverter, Source/ElementName/RelativeSource zugleich, Validierung)
KX0601RelativeSource fehlerhaft (Modus, AncestorType, der ein Visual sein muss, AncestorLevel, TemplatedParent außerhalb einer ControlTemplate, auch in einer DataTemplate darin, FindAncestor an einem Nicht-FrameworkElement)
KX0700Style oder Vorlage fehlerhaft (TargetType, BasedOn, Setter, Trigger, TemplateBinding, Wurzel, TargetName im Style)
KX0701Trigger fehlerhaft (MultiTrigger, DataTrigger, MultiDataTrigger, Condition, doppelte Conditions, mehr als 64 Bedingungen, Fehler im Value, ControlTemplate.Triggers, Setter ohne TargetName in einer Vorlage)

Grenzen (noch nicht unterstützt)

Folgendes gibt es noch nicht; es ergibt in der Regel eine Meldung (meist KX0010, KX0200 oder KX0700):

  • EventTrigger, Storyboards; DataTrigger in ControlTemplate.Triggers
  • StringFormat in Bindungen, Indexer und Anhängsel in Bindungspfaden, RelativeSource PreviousData
  • Ereignisse in Vorlagen (KX0401)
  • Konstruktorargumente für unveränderliche Typen
  • x:Reference prüft beim Bauen nur Typ und Verwendungsort (siehe Markup-Erweiterungen), nicht den Elternteil des Ziels
  • Array-Typen als Schlüsseltyp in Wörterbuchdateien (in KXAML nicht herstellbar; der Schutz ist ohne eigenen Test)
  • Wörterbücher inline in MergedDictionaries (nur Source) und Wörterbuchdateien aus anderen Projekten

Weitere Eigenheiten, die zur Sprache gehören:

  • Ein UIElement als StaticResource ist nur einmal verwendbar; für mehrere Verwendungen eine Vorlage nehmen.
  • Ändert sich C#, werden alle .kxaml-Dateien neu übersetzt (die Typen könnten sich geändert haben); das erzeugte Ergebnis bleibt gleich. Ändert sich nur eine .kxaml, wird nur sie neu übersetzt, bei einer Wörterbuchdatei mit geänderter Schlüsseltabelle auch die Dateien, die sie einbinden.
  • Enthält der Pfad einer Datei ein Anführungszeichen oder einen Zeilenwechsel (U+000A, U+000D, U+0085, U+2028, U+2029), schreibt der Generator für sie kein #line (KX0008); Fehler des C#-Übersetzers liegen dann im erzeugten Code.
  • Jedes FrameworkElement hält ein Feld für seine Vorfahr-Bindungen (8 Byte), auch ohne solche Bindungen; das ist bewusst so gelassen. Die Suche selbst kostet ohne Vorfahr-Bindungen nichts.
  • Gleich lautende Hinweisnamen mehrerer Dateien bekommen das Suffix _n; die Namen bleiben nach Änderungen gleich.
  • Bedingungen in ControlTemplate-Triggern und ihre Werte werden je Vorlagen-Instanz einmal angelegt; jede Änderung einer Bedingungs-Eigenschaft rechnet die betroffenen Teile neu.