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:Classam Wurzelelement ist Pflicht. Die Klasse erbt vom Typ des Wurzelelements, der nicht versiegelt sein darf. Der Generator erzeugtInitializeComponent(), die Felder und die Schlüsselklasse; die Klasse wird vom Anwender alspartialdeklariert (eine Wörterbuchdatei braucht kein Code-Behind, siehe unten).x:Namewird eininternal-Feld; an der Wurzel zeigt das Feld aufthis. In einer ControlTemplate wird es ein benannter Teil (PART_…); in DataTemplate und ItemsPanelTemplate gilt der Name nur innerhalb der Vorlage (fürElementNameundx:Reference), ein Feld gibt es dort nicht. Ein Name, der mit einem Mitglied der Klasse, einem sichtbaren Mitglied der Basis,InitializeComponentoder der SchlüsselklasseSchluesselkollidiert, ergibt KX0402.x:Namesetzt bei einemFrameworkElementausserdemName(wie WPF), auch in DataTemplate und ItemsPanelTemplate;FindNamefindet es. Teile einer ControlTemplate bekommen keinenName(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:Namein einer DataTemplate oder ItemsPanelTemplate ist erlaubt; der Name gilt nur dort (fürElementNameundx:Reference) und ergibt kein Feld. EinElementNamein 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
xdarf 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: ContentControlChildren: PanelChild: Border, PopupItems: ItemsControlInlines: TextBlock, SpanText: 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
ElementNameoderSource={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:
Hyperlinksteht als Inline imTextBlock(<Hyperlink NavigateUri="https://example.org/"><Run Text="Mehr" /></Hyperlink>;NavigateUriist ein Uri-Text,Hyperlink.RequestNavigate="…"an einem Vorfahren behandelt alle Adressen).HyperlinkButtonundLinkLabel(Textals Inhalt) sind gewöhnliche Elemente; die Links desLinkLabel(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 anUIElement. - Die Methode muss existieren (KX0401) und nicht statisch sein. Die Signatur prüft der C#-Übersetzer im erzeugten Code, die Meldung zeigt dank
#lineauf das Attribut. - In einer ControlTemplate werden keine Handler verdrahtet (KX0401).
Werte aus Text (Typkonverter)
| Typ | Beispiele |
|---|---|
| Zahlen, bool, char | 12, 1.5, Auto/NaN (double), True, * |
| Enum | Center, Bold; bei [Flags] A, B oder A \| B |
| Thickness | 8, 8,4, 1,2,3,4 |
| CornerRadius | 4, 0,24,0,24 |
| GridLength | Auto, *, 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) |
| PathGeometry | M0,0 L10,0 L5,8 Z (Schreibweise beim Bauen geprüft, höchstens 1 000 000 Zeichen) |
| PointCollection | 0,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) |
| DoubleCollection | 2 1, 4,2, 1 ,2 (Strichmuster; endliche Zahlen, höchstens 10 000; ob Strichlängen nicht negativ sein müssen, prüft die Form) |
| Matrix | 1,0,0,1,10,20 (M11,M12,M21,M22,OffsetX,OffsetY) oder Identity |
| TimeSpan | 0: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) |
| DateTime | 2026-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") |
| Uri | https://ui.kuonen.org/seite, seite.html (absolut oder relativ; leerer Text und Unlesbares sind ein Fehler) |
| Type | Button, local:MeinControl |
| FontWeight | Thin … 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 < stehen (Kleinschreibung), > (Grossschreibung) darf so stehen oder als >; & (beliebiges Zeichen) als &. 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
| Erweiterung | Bedeutung |
|---|---|
{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),FindAncestormitAncestorType(Pflicht) undAncestorLevel(1 bis 64, Standard 1; nur an einem FrameworkElement).{RelativeSource AncestorType=…}ohne Modus heißt FindAncestor;{RelativeSource AncestorLevel=2}ohne Modus und ohneAncestorTypeergibt eine eigene Meldung. Der Modus gilt ohne Rücksicht auf Groß- und Kleinschreibung,AncestorTypemuss einVisualsein (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:
DataGridhat[ContentProperty("Columns")]; die SpaltenelementeDataGridTextColumn,DataGridCheckBoxColumnundDataGridTemplateColumn(mitCellTemplate) stehen unmittelbar im Element.Widthgeht über den GridLength-Konverter (Auto,2*,120). - Zellbindung:
{Binding Pfad, Mode=OneWay|TwoWay}an einer Eigenschaft vom TypZellbindung(z. B.DataGridBoundColumn.Binding) erzeugtZellbindung.Aus<Q, W>(pfad, schreibbar).Q, die Zeilenklasse, kommt ausx:DataType(am Spaltenelement oder einem Vorfahren),Waus dem Pfad; der Pfad entsteht wie bei jeder Bindung ausBindingPath.Stufe(…), ohne Reflexion.{Binding}oder{Binding .}bindet die Zeile selbst (nur lesend). - Falle:
x:DataTypegehört an die Spalten (nicht ans DataGrid, sonst würde die eigene Bindung des Grids, etwaItemsSource, gegen die Zeilenklasse geprüft). Fehlt es an Spalte und Grid, gilt dasx:DataTypeder Seite:Qist dann die Klasse der Seite, die Zellen bleiben ohne Meldung leer. - Schreibbar ist ein Pfad mit öffentlichem Setter, solange
ModenichtOneWayist; ohneModegilt alsoTwoWay, wo es möglich ist, undOneWaysonst. - KX0600 (mit Stelle): kein
x:DataType; unbekannter Pfad;Mode=TwoWayan einem nur lesbaren Pfad (oder an der Zeile selbst);Modeausser OneWay und TwoWay; andere Argumente alsPathundMode(keinConverter,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.kxamlmit.kxaml.cs) zeigt eine Million Zeilen aus einer eigenenZeilenquelle, Sortieren an der Quelle, Bearbeiten, eine Kästchenspalte und eine Vorlagenspalte an einer asynchronen Quelle; ihre C#-Fassung baut denselben Baum (KxamlSeitenTests). - Die Eigenschaft
Bindingist 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:Keywird das FeldSchluessel.<Key>(RessourcenSchluessel<T>) der Klasse, je Datei eindeutig und ein gültiger C#-Bezeichner.Schluesselselbst und Mitglieder vonobject(Equals,ToString, …) sind als Schlüssel nicht erlaubt (KX0500). Pinsel haben den SchlüsseltypBrush?. InResourcesstehen nur Objekte mitx: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:
TargetTypeist Pflicht, dazuBasedOn({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ßerenx:DataType), Setter (Wert als Attribut, Erweiterung oder<Setter.Value>) undStyle.TriggersmitTrigger Property=… Value=…(eine Bedingung);MultiTriggermitMultiTrigger.ConditionsausCondition 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);MultiDataTriggermitMultiDataTrigger.ConditionsausCondition Binding=… Value=….
Werte von Bedingungen sind fest (Text,
x:Static,x:Null,StaticResource),DynamicResourcegibt es nur im Setter (als Bedingungswert KX0701 bei den neuen Triggerarten, KX0700 beim einfachen Trigger). Ein Trigger hat höchstens 64 Bedingungen, doppelteX.Conditionsergeben KX0701. Setter setzen nur Abhängigkeitseigenschaften des Zieltyps;TargetNamegibt es im Style nicht (KX0700). Der Zieltyp einesBasedOn-Styles muss passen, die Kette ist durchStyle.MaxBasisTiefebegrenzt. Kommt der Grund-Style ausx:Static(sein Zieltyp ist beim Bauen unbekannt), prüftKxamlHilfen.Stilzur 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 einControlTemplate.Triggers),DataTemplate,ItemsPanelTemplate(Wurzel ist ein Panel).TemplateBindinggibt es nur in einer ControlTemplate. In DataTemplate und ItemsPanelTemplate endet der Bereich vonx:DataTypeund Ressourcen an der Vorlage; jede Verwendung baut einen eigenen Baum. - ControlTemplate.Triggers:
TriggerundMultiTriggermit Bedingungen am Control; jeder Setter brauchtTargetName(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).Propertydarf mit Besitzer geschrieben werden (Border.BackgroundundBackgroundsind 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 ohneTargetNameim selben Trigger ergibt KX0701. DataTrigger gibt es hier nicht. EinDynamicResourceals 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
ResourceDictionaryab; ohne eigenen parameterlosen Konstruktor erzeugt der Generator ihn (InitializeComponentsetzt die Einträge), Code-Behind ist also nicht nötig. An der Wurzel steht nurx:Class. Sourceist 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:
StaticResourceliest den Wert aus der Instanz (KxamlHilfen.Ressource),DynamicResourceund Style-Setter nehmen das Schlüsselfeld der Datei (Farben.Schluessel.Akzent). Eigene Einträge gehen vor; unter den eingebundenen gewinnt die zuletzt eingebundene.MergedDictionariesstehen 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
ResourceDictionaryzuerst 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 undEinfrierenbesuchen dagegen jedes erreichbare Wörterbuch genau einmal (Laufmarke, ohne Zähldeckel). Ein Wörterbuch gehört dem Thread, der es zuerst schreibt;Einfriereneines 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.
| Regel | Bedeutung |
|---|---|
| KX0001 | Interner Fehler des Generators (bitte melden) |
| KX0002 | kein wohlgeformtes XML (auch DTD) |
| KX0003 | Datei zu groß (mehr als 2 Mi Zeichen) |
| KX0004 | zu tief verschachtelt (mehr als 64 Stufen) |
| KX0005 | zu 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) |
| KX0006 | Wert oder Text zu lang (mehr als 64 KiB) |
| KX0007 | kein Wurzelelement |
| KX0008 | Warnung: 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 |
| KX0010 | Markup-Erweiterung fehlerhaft, unbekannt oder an dieser Stelle nicht erlaubt (höchstens 8 tief; x:Reference außerhalb von Attributwerten und Source) |
| KX0100 | x:Class fehlt, ist ungültig, steht nicht am Wurzelelement oder die Basis ist versiegelt |
| KX0101 | unbekannter oder nicht erklärter Namensraum |
| KX0102 | Typ unbekannt, mehrdeutig, kein gültiger Typname oder nicht erzeugbar (abstrakt, kein öffentlicher parameterloser Konstruktor); auch: Kuonen.UI nicht referenziert |
| KX0200 | Eigenschaft oder Ereignis unbekannt, doppelt gesetzt, nicht beschreibbar, unbekannter Spracheintrag x:… |
| KX0201 | Inhalt passt nicht (keine Inhaltseigenschaft, falscher Typ, zu viele Werte) |
| KX0300 | Wert passt nicht zum Typ (Konverter, Farbname, {x:Null} für Werttyp, {x:Reference} mit falschem Typ oder als Inhalt) |
| KX0400 | x:Name ungültig oder doppelt im Namensbereich |
| KX0401 | Ereignis-Handler fehlt, ist statisch oder steht in einer ControlTemplate |
| KX0402 | x:Name kollidiert mit einem Mitglied der Klasse, ihrer Basis, InitializeComponent oder Schluessel |
| KX0403 | Name (ElementName, x:Reference, TargetName) im Namensbereich nicht gefunden |
| KX0500 | Ressource, Schlüssel oder statisches Mitglied nicht gefunden, unpassend, doppelt, als Element schon verwendet oder als Element von außerhalb einer Vorlage |
| KX0501 | Wö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 |
| KX0600 | Bindung nicht auflösbar (Quelltyp, Pfad, Modus, nicht beschreibbare Eigenschaft bei TwoWay, Konverter, Source/ElementName/RelativeSource zugleich, Validierung) |
| KX0601 | RelativeSource fehlerhaft (Modus, AncestorType, der ein Visual sein muss, AncestorLevel, TemplatedParent außerhalb einer ControlTemplate, auch in einer DataTemplate darin, FindAncestor an einem Nicht-FrameworkElement) |
| KX0700 | Style oder Vorlage fehlerhaft (TargetType, BasedOn, Setter, Trigger, TemplateBinding, Wurzel, TargetName im Style) |
| KX0701 | Trigger 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
StringFormatin Bindungen, Indexer und Anhängsel in Bindungspfaden,RelativeSource PreviousData- Ereignisse in Vorlagen (KX0401)
- Konstruktorargumente für unveränderliche Typen
x:Referenceprü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(nurSource) und Wörterbuchdateien aus anderen Projekten
Weitere Eigenheiten, die zur Sprache gehören:
- Ein UIElement als
StaticResourceist 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
FrameworkElementhä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.