Benutzerdefinierte angefügte Eigenschaften

Eine angefügte Eigenschaft ist ein XAML-Konzept. Angefügte Eigenschaften werden in der Regel als spezielle Form der Abhängigkeitseigenschaft definiert. Dieses Thema erläutert, wie eine angefügte Eigenschaft als Abhängigkeitseigenschaft implementiert und die Accessorkonvention definiert wird, die erforderlich ist, damit die angefügte Eigenschaft in XAML verwendet werden kann.

Voraussetzungen

Wir gehen davon aus, dass Sie Abhängigkeitseigenschaften aus Sicht eines Consumers vorhandener Abhängigkeitseigenschaften verstehen und dass Sie die Übersicht über Abhängigkeitseigenschaften gelesen haben. Außerdem sollten Sie die Übersicht über angefügte Eigenschaften gelesen haben. Um den Beispielen in diesem Thema zu folgen, sollten Sie auch XAML verstehen und wissen, wie Sie eine einfache Windows-Runtime-App mit C++, C# oder Visual Basic schreiben.

Szenarien für angefügte Eigenschaften

Sie können eine angefügte Eigenschaft erstellen, wenn ein Mechanismus für Eigenschafteneinstellungen für andere Klassen als die definierende Klasse zur Verfügung stehen muss. Die häufigsten Szenarien hierfür sind Layout- und Dienstunterstützung. Beispiele für vorhandene Layouteigenschaften sind Canvas.ZIndex und Canvas.Top. In einem Layout-Szenario können Elemente, die als untergeordnete Elemente für das Layout steuernde Elemente vorhanden sind, können Layoutanforderungen einzeln an die übergeordneten Elemente stellen, für jede Einstellung einen Eigenschaftswert, den das übergeordnete Element als angefügte Eigenschaft definiert. Ein Beispiel für das Dienstunterstützungsszenario in der Windows-Runtime-API ist der Satz der angefügten Eigenschaften von ScrollViewer, z. B. ScrollViewer.IsZoomChainingEnabled.

Warnung

Eine vorhandene Einschränkung der Windows-Runtime-XAML-Implementierung besteht darin, dass Sie Ihre benutzerdefinierte angefügte Eigenschaft nicht animieren können.

Registrieren einer benutzerdefinierten angefügten Eigenschaft

Wenn Sie die angefügte Eigenschaft nur für die Verwendung für andere Typen definieren, muss die Klasse, in der die Eigenschaft registriert ist, nicht von DependencyObject abgeleitet sein. Sie müssen jedoch den Zielparameter für Zugriffsmethoden DependencyObject verwenden, wenn Sie dem typischen Modell folgen, bei dem die angehängte Eigenschaft auch eine Abhängigkeitseigenschaft ist, so dass Sie den unterstützenden Property Store verwenden können.

Definieren Sie die angefügte Eigenschaft als Abhängigkeitseigenschaft, indem Sie eine öffentlichestatischeschreibgeschützte Eigenschaft vom Typ DependencyProperty deklarieren. Sie definieren diese Eigenschaft mithilfe des Rückgabewerts der RegisterAttached-Methode. Der Eigenschaftenname muss mit dem Namen der angefügten Eigenschaft übereinstimmen, den Sie als Parameter für den RegisterAttached-Namen angeben, wobei die Zeichenkette „Eigenschaft“ am Ende hinzugefügt wird. Dies ist die etablierte Konvention zum Benennen der Bezeichner von Abhängigkeitseigenschaften in Bezug auf die Eigenschaften, die sie darstellen.

Der Hauptbereich, in dem sich die Definition einer benutzerdefinierten angefügten Eigenschaft von einer benutzerdefinierten Abhängigkeitseigenschaft unterscheidet, ist die Art und Weise, wie Sie die Zugriffsmethoden oder Wrapper definieren. Anstatt die in benutzerdefinierten Abhängigkeitseigenschaften beschriebene Wrapper-Technik zu verwenden, müssen Sie auch statische GetPropertyName- und SetPropertyName-Methoden als Zugriffsmethoden für die angefügte Eigenschaft bereitstellen. Die Zugriffsmethoden werden hauptsächlich vom XAML-Parser verwendet, obwohl alle anderen aufrufende Funktion sie auch verwenden können, um Werte in Nicht-XAML-Szenarien festzulegen.

Wichtig

Wenn Sie die Zugriffsmethoden nicht richtig definieren, kann der XAML-Prozessor nicht auf Ihre angefügte Eigenschaft zugreifen, und jeder, der versucht, ihn zu verwenden, erhält wahrscheinlich einen XAML-Parserfehler. Außerdem verlassen sich Entwurfs- und Codierungstools häufig auf die Konventionen „*Property“ für die Benennung von Bezeichnern, wenn sie auf eine benutzerdefinierte Abhängigkeitseigenschaft in einer referenzierten Assembly stoßen.

Accessoren

Die Signatur für den GetPropertyName-Zugriff muss wie folgt lauten.

public staticvalueTypeGetPropertyName(DependencyObject target)

Für Microsoft Visual Basic ist dies der Fall.

Public Shared Function GetPropertyName(ByVal target As DependencyObject) As valueType)

Das Zielobjekt kann in Ihrer Implementierung einen spezifischeren Typ aufweisen, muss jedoch von DependencyObject abgeleitet werden. Der Rückgabewert valueType kann in Ihrer Implementierung auch von einem spezifischeren Typ sein. Der grundlegende Objekttyp ist akzeptabel, aber häufig möchten Sie, dass Ihre angefügte Eigenschaft die Typsicherheit erzwingt. Die Verwendung der Eingabe in den Getter- und Settersignaturen ist eine empfohlene Typsicherheitstechnik.

Für die Zugriffsmethode SetPropertyName muss die Signatur wie folgt lauten.

public static void SetPropertyName(DependencyObject target ,valueType value)

Für Visual Basic ist dies der Fall.

Public Shared Sub SetPropertyName(ByVal target As DependencyObject, ByVal value AsvalueType)

Das Zielobjekt kann in Ihrer Implementierung einen spezifischeren Typ aufweisen, muss jedoch von DependencyObject abgeleitet werden. Das Wertobjekt und sein valueType können in Ihrer Implementierung einen spezifischeren Typ haben. Denken Sie daran, dass der Wert für diese Methode die Eingabe ist, die vom XAML-Prozessor kommt, wenn er auf Ihre angehängte Eigenschaft im Markup trifft. Es muss eine Typkonvertierung oder eine bestehende Auszeichnungserweiterung für den von Ihnen verwendeten Typ vorhanden sein, damit der entsprechende Typ aus einem Attributwert (der letztendlich nur eine Zeichenfolge ist) erstellt werden kann. Der grundlegende Objekttyp ist akzeptabel, aber häufig möchten Sie weitere Typsicherheiten. Um dies zu erreichen, setzen Sie die Typerzwingung in die Zugriffsmethoden.

Hinweis

Es ist auch möglich, eine angefügte Eigenschaft zu definieren, bei der der beabsichtigte Verbrauch über die Eigenschaftselementsyntax erfolgt. In diesem Fall benötigen Sie keine Typkonvertierung für die Werte, aber Sie müssen sicherstellen, dass die von Ihnen beabsichtigten Werte in XAML erstellt werden können. VisualStateManager.VisualStateGroups ist ein Beispiel für eine vorhandene angefügte Eigenschaft, die nur den Verbrauch von Eigenschaftselementen unterstützt.

Code-Beispiel

Dieses Beispiel zeigt die Registrierung von Abhängigkeitseigenschaften (mithilfe der RegisterAttached-Methode) sowie die Zugriffsmethoden Get und Set für eine benutzerdefinierte angefügte Eigenschaft. Im Beispiel ist der Name der angefügten Eigenschaft IsMovable. Deshalb müssen die Accessoren GetIsMovable und SetIsMovable genannt werden. Der Besitzer der angefügten Eigenschaft ist eine Dienstklasse namens GameService, die keine eigene Benutzeroberfläche hat. Der Zweck besteht nur darin, die angefügten Eigenschaftendienste bereitzustellen, wenn die angefügte Eigenschaft GameService.IsMovable Eigenschaft verwendet wird.

Das Definieren der angefügten Eigenschaft in C++/CX ist etwas komplexer. Sie müssen entscheiden, wie Sie zwischen der Header- und der Codedatei unterscheiden. Außerdem sollten Sie den Bezeichner als Eigenschaft mit nur einer Get-Zugriffsmethode verfügbar machen, aus Gründen, die in benutzerdefinierten Abhängigkeitseigenschaften erläutert werden. In C++/CX müssen Sie diese Eigenschaftsfeldbeziehung explizit definieren, anstatt sich auf das schreibgeschützte Schlüsselwort (keyword) in .NET und die implizite Sicherung einfacher Eigenschaften zu verlassen. Sie müssen auch die Registrierung der angefügten Eigenschaft innerhalb einer Hilfsfunktion ausführen, die nur einmal ausgeführt wird, wenn die App gestartet wird, aber bevor XAML-Seiten geladen werden, die die angefügte Eigenschaft benötigen. Der typische Ort zum Aufrufen der Hilfsfunktionen für die Eigenschaftsregistrierung für eine oder alle Abhängigkeiten oder angefügten Eigenschaften befindet sich im App / -Anwendungskonstruktor im Code für Ihre app.xaml-Datei.

public class GameService : DependencyObject
{
    public static readonly DependencyProperty IsMovableProperty = 
    DependencyProperty.RegisterAttached(
      "IsMovable",
      typeof(Boolean),
      typeof(GameService),
      new PropertyMetadata(false)
    );
    public static void SetIsMovable(UIElement element, Boolean value)
    {
        element.SetValue(IsMovableProperty, value);
    }
    public static Boolean GetIsMovable(UIElement element)
    {
        return (Boolean)element.GetValue(IsMovableProperty);
    }
}
Public Class GameService
    Inherits DependencyObject

    Public Shared ReadOnly IsMovableProperty As DependencyProperty = 
        DependencyProperty.RegisterAttached("IsMovable",  
        GetType(Boolean), 
        GetType(GameService), 
        New PropertyMetadata(False))

    Public Shared Sub SetIsMovable(ByRef element As UIElement, value As Boolean)
        element.SetValue(IsMovableProperty, value)
    End Sub

    Public Shared Function GetIsMovable(ByRef element As UIElement) As Boolean
        GetIsMovable = CBool(element.GetValue(IsMovableProperty))
    End Function
End Class
// GameService.idl
namespace UserAndCustomControls
{
    [default_interface]
    runtimeclass GameService : Windows.UI.Xaml.DependencyObject
    {
        GameService();
        static Windows.UI.Xaml.DependencyProperty IsMovableProperty{ get; };
        static Boolean GetIsMovable(Windows.UI.Xaml.DependencyObject target);
        static void SetIsMovable(Windows.UI.Xaml.DependencyObject target, Boolean value);
    }
}

// GameService.h
...
    static Windows::UI::Xaml::DependencyProperty IsMovableProperty() { return m_IsMovableProperty; }
    static bool GetIsMovable(Windows::UI::Xaml::DependencyObject const& target) { return winrt::unbox_value<bool>(target.GetValue(m_IsMovableProperty)); }
    static void SetIsMovable(Windows::UI::Xaml::DependencyObject const& target, bool value) { target.SetValue(m_IsMovableProperty, winrt::box_value(value)); }

private:
    static Windows::UI::Xaml::DependencyProperty m_IsMovableProperty;
...

// GameService.cpp
...
Windows::UI::Xaml::DependencyProperty GameService::m_IsMovableProperty =
    Windows::UI::Xaml::DependencyProperty::RegisterAttached(
        L"IsMovable",
        winrt::xaml_typename<bool>(),
        winrt::xaml_typename<UserAndCustomControls::GameService>(),
        Windows::UI::Xaml::PropertyMetadata{ winrt::box_value(false) }
);
...
// GameService.h
#pragma once

#include "pch.h"
//namespace WUX = Windows::UI::Xaml;

namespace UserAndCustomControls {
    public ref class GameService sealed : public WUX::DependencyObject {
    private:
        static WUX::DependencyProperty^ _IsMovableProperty;
    public:
        GameService::GameService();
        void GameService::RegisterDependencyProperties();
        static property WUX::DependencyProperty^ IsMovableProperty
        {
            WUX::DependencyProperty^ get() {
                return _IsMovableProperty;
            }
        };
        static bool GameService::GetIsMovable(WUX::UIElement^ element) {
            return (bool)element->GetValue(_IsMovableProperty);
        };
        static void GameService::SetIsMovable(WUX::UIElement^ element, bool value) {
            element->SetValue(_IsMovableProperty,value);
        }
    };
}

// GameService.cpp
#include "pch.h"
#include "GameService.h"

using namespace UserAndCustomControls;

using namespace Platform;
using namespace Windows::Foundation;
using namespace Windows::Foundation::Collections;
using namespace Windows::UI::Xaml;
using namespace Windows::UI::Xaml::Controls;
using namespace Windows::UI::Xaml::Data;
using namespace Windows::UI::Xaml::Documents;
using namespace Windows::UI::Xaml::Input;
using namespace Windows::UI::Xaml::Interop;
using namespace Windows::UI::Xaml::Media;

GameService::GameService() {};

GameService::RegisterDependencyProperties() {
    DependencyProperty^ GameService::_IsMovableProperty = DependencyProperty::RegisterAttached(
         "IsMovable", Platform::Boolean::typeid, GameService::typeid, ref new PropertyMetadata(false));
}

Festlegen der benutzerdefinierten angefügten Eigenschaft aus XAML-Markup

Nachdem Sie Ihre angefügte Eigenschaft definiert und die Supportmitglieder als Teil eines benutzerdefinierten Typs eingeschlossen haben, müssen Sie die Definitionen für die XAML-Verwendung verfügbar machen. Dazu müssen Sie einen XAML-Namespace zuordnen, der auf den Codenamespace verweist, der die relevante Klasse enthält. In Fällen, in denen Sie die angefügte Eigenschaft als Teil einer Bibliothek definiert haben, müssen Sie diese Bibliothek als Teil des App-Pakets für die App einschließen.

Eine XML-Namespacezuordnung für XAML wird in der Regel im Stammelement einer XAML-Seite platziert. Für die Klasse mit dem Namen GameService im Namespace UserAndCustomControls, die die in den vorangegangenen Ausschnitten gezeigten angehängten Eigenschaftsdefinitionen enthält, könnte die Zuordnung beispielsweise wie folgt aussehen.

<UserControl
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:uc="using:UserAndCustomControls"
  ... >

Mithilfe der Zuordnung können Sie Ihre angefügte Eigenschaft GameService.IsMovable für jedes Element festlegen, das Ihrer Zieldefinition entspricht, einschließlich eines vorhandenen Typs, der definiert Windows-Runtime definiert.

<Image uc:GameService.IsMovable="True" .../>

Wenn Sie die Eigenschaft für ein Element festlegen, das sich auch innerhalb desselben zugeordneten XML-Namespace befindet, müssen Sie das Präfix weiterhin im Namen der angefügten Eigenschaft einschließen. Dies liegt daran, dass das Präfix den Besitzertyp qualifiziert. Das Attribut der angefügten Eigenschaft kann nicht innerhalb desselben XML-Namespaces wie das Element verwendet werden, in dem das Attribut enthalten ist, auch wenn Attribute durch normale XML-Regeln Namespaces von Elementen erben können. Wenn Sie beispielsweise GameService.IsMovable auf einen benutzerdefinierten Typ von ImageWithLabelControl setzen (Definition nicht gezeigt) und selbst wenn beide im selben Code-Namespace definiert wären, der demselben Präfix zugeordnet ist, würde die XAML trotzdem so aussehen.

<uc:ImageWithLabelControl uc:GameService.IsMovable="True" .../>

Hinweis

Wenn Sie eine XAML-Benutzeroberfläche mit C++/CX schreiben, müssen Sie den Header für den benutzerdefinierten Typ einschließen, der die angefügte Eigenschaft definiert, jedes Mal, wenn eine XAML-Seite diesen Typ verwendet. Jede XAML-Seite verfügt über einen zugeordneten CodeBehind-Header (.xaml.h). Hier sollten Sie den Header (mit #include) für die Definition des Besitzertyps der angefügten Eigenschaft einschließen.

Imperatives Festlegen der benutzerdefinierten angefügten Eigenschaft

Sie können auch über imperativen Code auf eine benutzerdefinierte angefügte Eigenschaft zugreifen. Der folgende Code zeigt, wie das geht.

<Image x:Name="gameServiceImage"/>
// MainPage.h
...
#include "GameService.h"
...

// MainPage.cpp
...
MainPage::MainPage()
{
    InitializeComponent();

    GameService::SetIsMovable(gameServiceImage(), true);
}
...

Werttyp einer benutzerdefinierten angefügten Eigenschaft

Der Typ, der als Werttyp einer benutzerdefinierten angefügten Eigenschaft verwendet wird, wirkt sich auf die Verwendung, die Definition oder sowohl die Verwendung als auch die Definition aus. Der Werttyp der angefügten Eigenschaft wird an mehreren Orten deklariert: in den Signaturen der Zugriffsmethoden Get und Set, und auch als propertyType-Parameter des RegisterAttached-Aufrufs.

Der am häufigsten verwendete Werttyp für angefügte Eigenschaften (benutzerdefiniert oder anderweitig) ist eine einfache Zeichenfolge. Dies liegt daran, dass angefügte Eigenschaften in der Regel für die Verwendung von XAML-Attributen vorgesehen sind und durch die Verwendung einer Zeichenkette als Wertetyp bleiben die Eigenschaften schlank. Andere Grundtypen, die über eine systemeigene Konvertierung in Zeichenfolgenmethoden verfügen, z. B. ganze Zahl, Doppel- oder Enumerationswert, werden auch als Werttypen für angefügte Eigenschaften verwendet. Sie können andere Werttypen, die keine native Zeichenfolgenkonvertierung unterstützen, als Wert der angefügten Eigenschaft verwenden. Dies führt jedoch dazu, eine Entscheidung über die Verwendung oder die Implementierung zu treffen:

  • Sie können die angefügte Eigenschaft beibehalten, aber die angefügte Eigenschaft kann die Verwendung nur unterstützen, wenn die angefügte Eigenschaft ein Eigenschaftselement ist und der Wert als Objektelement deklariert wird. In diesem Fall muss der Eigenschaftstyp die XAML-Verwendung als Objektelement unterstützen. Überprüfen Sie bei vorhandenen Windows-Runtime-Referenzklassen die XAML-Syntax, um sicherzustellen, dass der Typ die Verwendung von XAML-Objektelementen unterstützt.
  • Sie können die angefügte Eigenschaft unverändert lassen, aber sie nur in einer Attributverwendung über eine XAML-Referenztechnik wie eine Bindung oder StaticResource verwenden, die als Zeichenkette ausgedrückt werden kann.

Weitere Informationen zum Beispiel Canvas.Left

In früheren Beispielen für die Verwendung angefügter Eigenschaften haben wir verschiedene Möglichkeiten zum Festlegen der angefügten Canvas.Left-Eigenschaft gezeigt. Aber was ändert das daran, wie ein Canvas mit Ihrem Objekt interagiert, und wann geschieht das? Wir werden dieses spezielle Beispiel weiter untersuchen, denn wenn Sie eine angefügte Eigenschaft implementieren, ist es interessant zu sehen, was sonst eine typische angefügte Eigenschaftsbesitzerklasse mit ihren angefügten Eigenschaftswerten tun will, wenn sie sie für andere Objekte findet.

Die Hauptfunktion eines Canvas ist ein absolut positionierter Layoutcontainer in der Benutzeroberfläche. Die untergeordneten Elemente eines Canvas werden in einer von der Basisklasse definierten Eigenschaft Kinder gespeichert. Von allen Bereichen ist Canvas der einzige, der absolute Positionierung verwendet. Es hätte das Objektmodell des allgemeinen UIElement-Typs aufgebläht, um Eigenschaften hinzuzufügen, die nur für Canvas und die speziellen UIElement-Fälle von Bedeutung sind, in denen es sich um untergeordnete Elemente eines UIElements handelt. Durch die Definition der Layout-Steuereigenschaften einer Canvas als angefügte Eigenschaften, die jedes UIElement verwenden kann, bleibt das Objektmodell übersichtlicher.

Um ein praktischer Bereich zu sein, verfügt Canvas über ein Verhalten, das die Measure- und Arrange-Methoden auf Frameworkebene überschreibt. Hier sucht Canvas tatsächlich nach angefügten Eigenschaftswerten für die untergeordneten Elemente. Ein Teil der Muster Measure und Arrange ist ein Loop, das alle Inhalte durchläuft, und ein Bereich verfügt über die Kinder-Eigenschaft, die es explizit macht, was als untergeordnetes Element eines Bereichs betrachtet werden soll. Das Layoutverhalten von Canvas durchläuft also diese untergeordneten Elemente und führt statische Canvas.GetLeft- und Canvas.GetTop-Aufrufe für jedes untergeordnete Element aus, um festzustellen, ob diese angefügten Eigenschaften einen nicht standardmäßigen Wert enthalten (Standardwert ist 0). Diese Werte werden dann verwendet, um jedes untergeordnete Element im verfügbaren Canvas-Layoutbereich gemäß den spezifischen Werten, die von jedem untergeordneten Element bereitgestellt werden, absolut zu positionieren und mithilfe von Arrange festzulegen.

Der Code sieht in etwa so aus wie dieser Pseudocode.

protected override Size ArrangeOverride(Size finalSize)
{
    foreach (UIElement child in Children)
    {
        double x = (double) Canvas.GetLeft(child);
        double y = (double) Canvas.GetTop(child);
        child.Arrange(new Rect(new Point(x, y), child.DesiredSize));
    }
    return base.ArrangeOverride(finalSize); 
    // real Canvas has more sophisticated sizing
}

Hinweis

Weitere Informationen zur Funktionsweise von Bereichen finden Sie in der Übersicht über benutzerdefinierte XAML-Bereiche.