XAML- und benutzerdefinierte Klassen für WPF

Die Implementierung von XAML in Common Language Runtime (CLR)-Frameworks unterstützt die Möglichkeit zum Definieren einer benutzerdefinierten Klasse oder Struktur in einer beliebigen Common Language Runtime (CLR)-Sprache und den anschließenden Zugriff auf diese Klasse unter Verwendung von XAML-Markup. Sie können eine Mischung aus Windows Presentation Foundation (WPF)-Typen und ihren benutzerdefinierten Typen innerhalb derselben Markupdatei verwenden. Die übliche Vorgehensweise hierbei ist, Ihren benutzerdefinierten Typen ein XAML-Namespace-Präfix zuzuordnen. Dieses Thema beschreibt die Anforderungen, die eine benutzerdefinierte Klasse erfüllen muss, um als XAML-Element verwendet werden zu können.

Benutzerdefinierte Klassen in Anwendungen oder Assemblys

Benutzerdefinierte Klassen, die in XAML verwendet werden, können auf zwei verschiedene Arten definiert werden: im CodeBehind oder in anderem Code, der die primäre Windows Presentation Foundation (WPF)-Anwendung erzeugt, oder als Klasse in einer separaten Assembly, z.B. einer ausführbare Datei oder einer als Klassenbibliothek verwendeten DLL. Jeder dieser Ansätze hat bestimmte Vor- und Nachteile.

  • Der Vorteil der Erstellung einer Klassenbibliothek ist, dass alle diese benutzerdefinierten Klassen über viele verschiedene mögliche Anwendungen gemeinsam genutzt werden können. Eine separate Bibliothek gibt Ihnen auch mehr Kontrolle über die Versionierung von Anwendungen und vereinfacht das Erstellen einer Klasse, in denen die gewünschten Klasse als Stammelement auf einer XAML-Seite verwendet wird.

  • Der Vorteil bei der Definition der benutzerdefinierten Klassen in der Anwendung ist, dass diese Technik relativ schlank ist und Probleme bei Bereitstellung und Tests minimiert, die auftreten, wenn Sie separate Assemblys zusätzlich zur ausführbaren Hauptdatei der Anwendung einführen.

  • Egal, ob sie in derselben oder einer anderen Assembly definiert sind: benutzerdefinierte Klassen benötigen eine Zuordnung zwischen dem CLR-Namespace und dem XML-Namespace, um in XAML als Elemente verwendet werden zu können. Siehe XAML-Namespaces und Namespacezuordnung für WPF-XAML.

Anforderungen an benutzerdefinierten Klassen als XAML-Elemente

Um als Objektelement instanziiert werden zu können, muss die Klasse die folgenden Anforderungen erfüllen:

  • Ihre benutzerdefinierte Klasse muss öffentlich sein und über einen parameterlosen öffentlichen Standardkonstruktor verfügen. (Hinweise zu Strukturen finden Sie im folgenden Abschnitt.)

  • Die benutzerdefinierte Klasse darf keine geschachtelte Klasse sein. Geschachtelte Klassen und der "Punkt" in ihrer allgemeinen CLR-Notation verursachen Konflikte mit anderen WPF- und/oder XAML-Funktionen wie z. B. angefügten Eigenschaften.

Ihre Objektdefinition ermöglicht nicht nur die Verwendung von Objektelement-Syntax, sondern auch von Eigenschaftenelement-Syntax für alle öffentlichen Eigenschaften, die dieses Objekt als Werttyp haben. Dies liegt darin begründet, dass das Objekt jetzt als Objektelement instanziiert werden kann und somit den Eigenschaftenelementwert einer solchen Eigenschaft befüllen kann.

Strukturen

Strukturen, die Sie als benutzerdefinierte Typen definieren, können immer in XAML in WPF erstellt werden. Grund hierfür ist, dass die CLR-Compiler implizit einen parameterlosen Konstruktor für Strukturen erstellen, die alle Eigenschaftswerte mit ihren Standardwerten initialisieren. In einigen Fällen sind für Strukturen das standardmäßige Konstruktionsverhalten und/oder die Verwendung als Objektelement nicht ratsam. Möglicherweise sollen die Struktur und ihre befüllten Werte konzeptionell als Einheit fungieren, wobei die enthaltenen Werte möglicherweise sich gegenseitig ausschließende Interpretationen haben und daher keiner ihrer Eigenschaften ein Wert zugewiesen werden kann. Ein WPF-Beispiel für eine solche Struktur ist GridLength. Im Allgemeinen sollten solche Strukturen einen Typkonverter implementieren, sodass die Werte in Attributform ausgedrückt werden können, mit String-Konventionen, welche die verschiedenen Interpretationen oder Modi der Strukturwerte erstellen. Die Struktur muss über einen parameterlosen Konstruktor ein ähnliches Verhalten auch für die Codekonstruktion verfügbar machen.

Anforderungen an Eigenschaften einer benutzerdefinierten Klasse als XAML-Attribute

Eigenschaften müssen einen Werttypen referenzieren (z. B. einen primitiven Typen) oder eine Klasse verwenden, die entweder einen parameterlosen Konstruktor oder einen dedizierten Typkonverter besitzt, auf den ein XAML-Prozessor zugreifen kann. In der CLR-XAML-Implementierung finden XAML-Prozessoren entweder diese Konverter durch native Unterstützung für Sprachprimitive, oder durch Anwenden von TypeConverterAttribute auf einen Typ oder ein Member in unterstützenden Typdefinitionen.

Alternativ kann die Eigenschaft auf einen abstrakten Klassentyp oder eine Schnittstelle verweisen. Bei abstrakten Klassen oder Schnittstellen wird bei der XAML-Analyse erwartet, dass der Eigenschaftswert mit praktischen Klasseninstanzen befüllt sein muss, die die Schnittstelle implementieren, oder Instanzen von Typen, die von der abstrakten Klasse abgeleitet sind.

Eigenschaften können für eine abstrakte Klasse deklariert werden, können jedoch nur in praktischen Klassen festgelegt werden, die von der abstrakten Klasse abgeleitet sind. Dies liegt daran, dass die Erstellung des Objektelements für die Klasse einen öffentlichen Konstruktor ohne Parameter für die Klasse erfordert.

TypeConverter-fähige Attributsyntax

Wenn Sie einen dedizierten, mit Attributen versehenen Typkonverter auf Klassenebene bereitstellen, wird durch die angewendete Typumwandlung Attributsyntax für jede Eigenschaft ermöglicht, die diesen Typ instanziieren muss. Ein Typkonverter ermöglicht keine Objektelementverwendung des Typs; nur das Vorhandensein eines parameterlosen Konstruktors für diesen Typ ermöglicht die Verwendung von Objektelementen. Aus diesem Grund sind Eigenschaften, für die ein Typkonverter aktiviert ist, im Allgemeinen nicht in Eigenschaftensyntax verwendbar, es sei denn, der Typ selbst unterstützt ebenfalls Objektelementsyntax. Die Ausnahme ist, wenn Sie eine Eigenschaftenelement-Syntax angeben, das Eigenschaftenelement jedoch eine Zeichenfolge enthält. Eine solche Praxis ist im Wesentlichen äquivalent zur Nutzung der Attributsyntax und weitgehend unüblich, es sei denn, Sie benötigen eine stabilere Behandlung von Leerzeichen in Attributwerten. Nachfolgend sehen sie z.B. ein Eigenschaftenelement, das eine Zeichenfolge akzeptiert, und das dazugehörige Äquivalent in Attributsyntax:

<Button>Hallo!
  <Button.Language>
    de-DE
  </Button.Language>
</Button>
<Button Language="de-DE">Hallo!</Button>

Beispiele für Eigenschaften, bei denen in XAML Attributsyntax zulässig ist, Eigenschaftenelement-Syntax jedoch nicht, wenn sie ein Objektelement enthält, sind verschiedene Eigenschaften, die den Cursor-Typ annehmen. Die Cursor-Klasse hat einen eigenen Typkonverter CursorConverter, bietet aber keinen parameterlosen Konstruktor, sodass die Cursor-Eigenschaft nur über eine Attributsyntax festgelegt werden kann, obwohl der eigentliche Cursor-Typ ein Verweistyp ist.

Typkonverter auf Eigenschaftenebene

Alternativ kann die Eigenschaft selbst auf der Eigenschaftenebene einen Typkonverter deklarieren. Dies ermöglicht eine „Minisprache”, die Objekte vom Typ der Eigenschaft inline instanziiert, indem eingehende Zeichenfolgenwerte des Attributs als Eingabe für einen ConvertFrom-Vorgang basierend auf den entsprechenden Typ dienen. Dies erfolgt in der Regel, um einen zweckmäßigen Accessor bereitzustellen und nicht als alleiniges Mittel zum Festlegen einer Eigenschaft in XAML. Es ist jedoch auch möglich, Typkonverter für Attribute dort einzusetzen, wo Sie vorhandene CLR-Typen verwenden möchten, die entweder keinen parameterlosen Konstruktor oder keinen attribuierten Typkonverter bereitstellen. Beispiele aus der WPF-API sind bestimmte Eigenschaften, die den Typ CultureInfo annehmen. In diesem Fall verwendete WPF den vorhandenen Microsoft .NET Framework-CultureInfo-Typ für eine bessere Kompatibilität und für Migrationsszenarios, die in früheren Versionen von Frameworks verwendet wurden, bei denen aber der CultureInfo-Typ nicht die erforderlichen Konstruktoren unterstützte, oder keine direkte Verwendung als XAML-Eigenschaftswert von Typumwandlungen auf Typebene zuließ.

Wenn Sie eine Eigenschaft verfügbar machen, die in XAML Verwendung findet, insbesondere dann, wenn Sie Autor eines Steuerelements sind, sollten Sie unbedingt die Eigenschaft durch eine Abhängigkeitseigenschaft unterstützen. Dies gilt insbesondere dann, wenn Sie die vorhandene Windows Presentation Foundation (WPF)-Implementierung des XAML-Prozessors nutzen, da die Verwendung einer Unterstützung durch DependencyProperty die Leistung verbessert. Eine Abhängigkeitseigenschaft wird für Ihre Eigenschaft Funktionen des Eigenschaftensystems verfügbar machen, die Benutzer von einer XAML-zugänglichen Eigenschaft erwarten. Zu diesen Funktionen gehören z.B. Animationen, Datenbindungen und die Unterstützung von Stilen. Weitere Informationen finden Sie unter Benutzerdefinierte Abhängigkeitseigenschaften und Laden von XAML und Abhängigkeitseigenschaften.

Schreiben und Attribuieren eines Typkonverters

In gewissen Fällen wird es unumgänglich sein, eine benutzerdefinierte, von TypeConverter abgeleitete Klasse zu schreiben, um Typkonvertierungen für Ihren Eigenschaftstyp zur Verfügung zu haben. Anweisungen zum Ableiten und Erstellen eines Typkonverters, der XAML-Verwendungen unterstützen kann, und zum Anwenden der TypeConverterAttribute-Klasse finden Sie unter TypeConverter und XAML.

Anforderungen an die Attributsyntax für XAML-Ereignishandler bei Ereignissen einer Benutzerdefinierten Klasse

Damit ein Ereignis als CLR-Ereignis verwendet werden kann, muss es als öffentliches Ereignis einer Klasse verfügbar gemacht werden, die einen parameterlosen Konstruktor unterstützt, oder einer abstrakten Klasse, in der auf das Ereignis über abgeleitete Klassen zugegriffen werden kann. Um es auf einfache Art als Routingereignis zu verwenden, muss Ihr CLR-Ereignis explizit add- und remove-Methoden implementieren, die Handler für die CLR-Ereignissignatur hinzufügen oder entfernen, und dann diese Handler an die Methoden AddHandler und RemoveHandler weiterleiten. Diese Methoden fügen die Handler zum Routingereignis-Handlerspeicher auf der Instanz, der das Ereignis zugeordnet ist, hinzu oder entfernen sie.

Hinweis

Es ist möglich, Handler für Routingereignisse mithilfe von AddHandler direkt zu registrieren, und absichtlich kein CLR-Ereignis zu definieren, das das Routingereignis verfügbar macht. Davon wird im Allgemeinen abgeraten, da das Ereignis in diesem Fall keine XAML-Attributsyntax zum Anhängen von Handlern ermöglicht, und die resultierende Klasse eine weniger transparente XAML-Ansicht der Möglichkeiten dieses Typs bieten wird.

Schreiben von Auflistungseigenschaften

Eigenschaften, die einen Auflistungstyp annehmen, haben eine XAML-Syntax, die Ihnen ermöglicht, Objekte anzugeben, die der Auflistung hinzugefügt werden. Diese Syntax hat zwei interessante Features.

  • Das Objekt, das das Auflistungsobjekt darstellt, muss nicht in Objektelementsyntax angegeben werden. Dieses Auflistungstyps ist implizit dann vorhanden, wenn Sie eine Eigenschaft in XAML angeben, die einen Auflistungstyp annimmt.

  • Untergeordnete Elemente der Auflistungseigenschaft im Markup werden so verarbeitet, dass sie Mitglieder der Auflistung werden. Normalerweise erfolgt der Codezugriff auf die Mitglieder einer Auflistung über Listen- bzw. Wörterbuch-Methoden wie z.B. Add, oder über einen Indexer. XAML-Syntax unterstützt jedoch weder Methoden noch Indexer (Ausnahme: XAML 2009 kann Methoden unterstützen, aber die Verwendungen von XAML 2009 schränkt die Verwendungsmöglichkeiten mit WPF ein; siehe XAML 2009-Sprachfunktionen). Auflistungen sind offensichtlich eine durchaus übliche Anforderung beim Erstellen einer Struktur von Elementen, und Sie benötigen eine Methode zum Befüllen dieser Auflistungen im deklarativen XAML-Code. Aus diesem Grund werden untergeordnete Elemente einer Auflistungseigenschaft verarbeitet, indem sie der Auflistung hinzugefügt werden, die dem Typwert der Auflistungseigenschaft entspricht.

Die Implementierung von .NET Framework-XAML-Diensten und somit der WPF XAML-Prozessor verwenden die folgende Definition für eine Auflistungseigenschaft. Die Eigenschaftentyp der Eigenschaft muss einen der folgenden implementieren:

Jeder dieser Typen in CLR verfügt über eine Add-Methode, die vom XAML-Prozessor verwendet wird, um der zugrunde liegenden Auflistung bei der Erstellung des Objektdiagramms Elemente hinzuzufügen.

Hinweis

Die generischen List- und Dictionary-Schnittstellen (IList<T> und IDictionary<TKey,TValue>) werden vom WPF XAML-Prozessor für die Sammlungserkennung nicht unterstützt. Sie können die Klasse jedoch als List<T>-Basisklasse verwenden, da sie IList direkt implementiert, oder Dictionary<TKey,TValue> als Basisklasse, da sie IDictionary direkt implementiert.

Wenn Sie eine Eigenschaft, die eine Auflistung annimmt, deklarieren, sollten Sie sorgfältig darauf achten, wie dieser Eigenschaftswert in neuen Instanzen des Typs initialisiert wird. Wenn Sie die Eigenschaft nicht als Abhängigkeitseigenschaft implementieren, ist es angebracht, der Eigenschaft ein unterstützendes Feld zuzuweisen, das den Typkonstruktor der Auflistung aufruft. Wenn die Eigenschaft eine Abhängigkeitseigenschaft ist, müssen Sie die Auflistungseigenschaft als Teil des standardmäßigen Typkonstruktors initialisieren. Der Grund dafür ist, dass eine Abhängigkeitseigenschaft ihren Standardwert aus Metadaten übernimmt, und Sie in der Regel nicht möchten, dass der Anfangswert einer Auflistungseigenschaft eine statische, gemeinsam genutzte Auflistung ist. Es sollte eine Auflistungsinstanz pro enthaltender Typinstanz geben. Weitere Informationen finden Sie unter Benutzerdefinierte Abhängigkeitseigenschaften.

Sie können für Ihre Auflistungseigenschaft einen benutzerdefinierten Auflistungstyp implementieren. Aufgrund der impliziten Behandlung der Auflistungseigenschaft muss der benutzerdefinierte Auflistungstyp keinen parameterlosen Konstruktor bereitstellen, um implizit in XAML verwendet werden zu können. Allerdings können Sie für den Auflistungstyp optional einen parameterlosen Konstruktor bereitstellen. Dies ist eine durchaus ratsame Praxis. Solange Sie keinen parameterlosen Konstruktor bereitstellen, können Sie die Auflistung nicht explizit als Element deklarieren. Einige Markupautoren sehen die explizite Auflistung möglicherweise als guten und zu bevorzugenden Markupstil. Darüber hinaus kann ein parameterloser Konstruktor die Initialisierungsanforderungen vereinfachen, wenn Sie neue Objekte erstellen, die Ihren Auflistungstyp als Eigenschaftswert verwenden.

Deklarieren von XAML-Inhaltseigenschaften

Die XAML-Sprache definiert das Konzept einer XAML-Inhaltseigenschaft. Jede Klasse, die in Objektsyntax verwendet werden kann, hat genau eine XAML-Inhaltseigenschaft. Um eine Eigenschaft als die XAML-Inhaltseigenschaft Ihrer Klasse zu deklarieren, wenden Sie ContentPropertyAttribute als Teil der Klassendefinition an. Geben Sie den Namen der gewünschten XAML-Inhaltseigenschaft als Name im Attribut an. Die Eigenschaft wird durch Ihren Namen als Zeichenfolge angegeben, nicht als ein über Reflektion erhaltenes Konstrukt, wie dies bei PropertyInfo der Fall ist.

Sie können eine Auflistungseigenschaft als XAML-Inhaltseigenschaft angeben. Dies führt zu einer Verwendung dieser Eigenschaft, bei der das Objektelement ein oder mehrere untergeordnete Elemente ohne dazwischenliegende Auflistungs-Objektelemente oder Eigenschaftenelement-Tags haben kann. Diese Elemente werden dann als Wert für die XAML-Inhaltseigenschaft behandelt und der unterstützenden Auflistungsinstanz hinzugefügt.

Einige vorhandene XAML-Inhaltseigenschaften verwenden den Eigenschaftentyp Object. Dies ermöglicht eine XAML-Inhaltseigenschaft, die primitive Werte wie z. B. String, sowie einen einzelnen Verweisobjektwert annehmen kann. Befolgen Sie dieses Modell, ist Ihr Typ sowohl für die Typbestimmung als auch für die Behandlung der möglichen Typen zuständig. Der Hauptgrund für einen Inhaltstyp vom Typ Object ist, dass man damit sowohl eine einfache Möglichkeit des Hinzufügens von Objektinhalten als Zeichenfolge (die eine standardmäßige WPF-Behandlung erhält), als auch eine fortgeschrittene Möglichkeit zum Hinzufügen von zusätzlichen Nicht-Standarddaten zur Verfügung hat.

Serialisieren von XAML

Für bestimmte Szenarios, z.B. Wenn Sie Autor eines Steuerelements sind, möchten Sie möglicherweise auch sicherstellen, dass jede Objektdarstellung, die in XAML instanziiert werden kann, auch wieder zurück in das entsprechende XAML-Markup serialisiert werden kann. Serialisierungsanforderungen werden in diesem Thema nicht beschrieben. Informationen hierzu finden Sie unter Übersicht über das Erstellen von Steuerelementen und Elementstruktur und Serialisierung.

Siehe auch