Benutzerdefinierte Abhängigkeitseigenschaften

Hier wird erläutert, wie Sie Ihre eigenen Abhängigkeitseigenschaften für eine Windows-Runtime-App mit C++, C# oder Visual Basic definieren und implementieren. Es werden Gründe aufgeführt, warum App-Entwickler und Komponentenautoren möglicherweise benutzerdefinierte Abhängigkeitseigenschaften erstellen möchten. Wir beschreiben die Implementierungsschritte für eine benutzerdefinierte Abhängigkeitseigenschaft sowie einige bewährte Methoden, die die Leistung, Benutzerfreundlichkeit oder Vielseitigkeit der Abhängigkeitseigenschaft verbessern können.

Voraussetzungen

Es wird davon ausgegangen, dass Sie die Übersicht über Abhängigkeitseigenschaften gelesen haben und abhängigkeitseigenschaften aus der Sicht eines Verbrauchers vorhandener Abhängigkeitseigenschaften verstehen. 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.

Was ist eine Abhängigkeitseigenschaft?

Um Formatierung, Datenbindung, Animationen und Standardwerte für eine Eigenschaft zu unterstützen, sollte sie als Abhängigkeitseigenschaft implementiert werden. Abhängigkeitseigenschaftswerte werden nicht als Felder der Klasse gespeichert, sie werden vom XAML-Framework gespeichert und mithilfe eines Schlüssels referenziert, der abgerufen wird, wenn die Eigenschaft beim Windows-Runtime Eigenschaftensystem registriert wird, indem die DependencyProperty.Register-Methode aufgerufen wird. Abhängigkeitseigenschaften können nur von Typen verwendet werden, die von DependencyObject abgeleitet werden. DependencyObject ist jedoch ziemlich hoch in der Klassenhierarchie, sodass die meisten Klassen, die für die Ui- und Präsentationsunterstützung vorgesehen sind, Abhängigkeitseigenschaften unterstützen können. Weitere Informationen zu Abhängigkeitseigenschaften und einigen der Terminologie und Konventionen, die für die Beschreibung in dieser Dokumentation verwendet werden, finden Sie in der Übersicht über Abhängigkeitseigenschaften.

Beispiele für Abhängigkeitseigenschaften in der Windows-Runtime sind: Control.Background, FrameworkElement.Width und TextBox.Text, unter anderem.

Konvention ist, dass jede Abhängigkeitseigenschaft, die von einer Klasse verfügbar gemacht wird, über eine entsprechende öffentliche statische Readonly-Eigenschaft vom Typ DependencyProperty verfügt, die für dieselbe Klasse verfügbar gemacht wird, die den Bezeichner für die Abhängigkeitseigenschaft bereitstellt. Der Name des Bezeichners folgt dieser Konvention: der Name der Abhängigkeitseigenschaft, wobei die Zeichenfolge "Property" am Ende des Namens hinzugefügt wird. Der entsprechende DependencyProperty-Bezeichner für die Control.Background-Eigenschaft lautet beispielsweise "Control.BackgroundProperty". Der Bezeichner speichert die Informationen zur Abhängigkeitseigenschaft, wie sie registriert wurde, und kann dann für andere Vorgänge verwendet werden, die die Abhängigkeitseigenschaft betreffen, z. B. das Aufrufen von SetValue.

Eigenschaftenwrapper

Abhängigkeitseigenschaften weisen in der Regel eine Wrapperimplementierung auf. Ohne den Wrapper besteht die einzige Möglichkeit zum Abrufen oder Festlegen der Eigenschaften darin, die Hilfsmethoden getValue und SetValue der Abhängigkeitseigenschaft zu verwenden und den Bezeichner als Parameter an sie zu übergeben. Dies ist eine ziemlich unnatürliche Verwendung für etwas, das angeblich eine Eigenschaft ist. Mit dem Wrapper können Ihr Code und jeder andere Code, der auf die Abhängigkeitseigenschaft verweist, jedoch eine einfache Objekteigenschaftssyntax verwenden, die für die von Ihnen verwendete Sprache natürlich ist.

Wenn Sie eine benutzerdefinierte Abhängigkeitseigenschaft selbst implementieren und möchten, dass sie öffentlich und einfach aufgerufen werden soll, definieren Sie auch die Eigenschaftenwrapper. Die Eigenschaftenwrapper sind auch hilfreich, um grundlegende Informationen über die Abhängigkeitseigenschaft zu Spiegelungs- oder statischen Analyseprozessen zu melden. Der Wrapper ist insbesondere der Ort, an dem Sie Attribute wie ContentPropertyAttribute platzieren.

Wann eine Eigenschaft als Abhängigkeitseigenschaft implementiert werden soll

Wenn Sie eine öffentliche Lese-/Schreibeigenschaft für eine Klasse implementieren, sofern Ihre Klasse von DependencyObject abgeleitet ist, haben Sie die Möglichkeit, ihre Eigenschaft als Abhängigkeitseigenschaft zu verwenden. Manchmal ist die typische Technik der Sicherung Ihrer Immobilie mit einem privaten Feld angemessen. Das Definieren der benutzerdefinierten Eigenschaft als Abhängigkeitseigenschaft ist nicht immer erforderlich oder angemessen. Die Wahl hängt von den Szenarien ab, die Ihre Eigenschaft unterstützen soll.

Sie können die Implementierung Ihrer Eigenschaft als Abhängigkeitseigenschaft in Betracht ziehen, wenn sie eines oder mehrere dieser Features der Windows-Runtime oder von Windows-Runtime-Apps unterstützen soll:

  • Festlegen der Eigenschaft über eine Formatvorlage
  • Dient als gültige Zieleigenschaft für die Datenbindung mit {Binding}
  • Unterstützen animierter Werte über ein Storyboard
  • Berichterstellung, wenn der Wert der Eigenschaft geändert wurde durch:
    • Aktionen, die vom Eigenschaftensystem selbst ausgeführt werden
    • Die -Umgebung
    • Benutzeraktionen
    • Lese- und Schreibstile

Prüfliste zum Definieren einer Abhängigkeitseigenschaft

Das Definieren einer Abhängigkeitseigenschaft kann als eine Reihe von Konzepten betrachtet werden. Diese Konzepte sind nicht unbedingt Verfahrensschritte, da mehrere Konzepte in einer einzigen Codezeile in der Implementierung behandelt werden können. Diese Liste bietet nur einen schnellen Überblick. Wir erläutern jedes Konzept weiter unten in diesem Thema ausführlicher, und wir zeigen Ihnen Beispielcode in mehreren Sprachen.

  • Registrieren Sie den Eigenschaftennamen beim Eigenschaftensystem (Call Register), geben Sie einen Besitzertyp und den Typ des Eigenschaftswerts an.
    • Es gibt einen erforderlichen Parameter für Register , der Eigenschaftsmetadaten erwartet. Geben Sie null für dieses Verhalten an, oder wenn Sie ein Verhalten ändern möchten, oder einen metadatenbasierten Standardwert, der durch Aufrufen von ClearValue wiederhergestellt werden kann, geben Sie eine Instanz von PropertyMetadata an.
  • Definieren Sie einen DependencyProperty-Bezeichner als öffentliches statisches readonly-Eigenschaftsmememmemmmemm für den Besitzertyp.Define a DependencyProperty identifier as a public static readonly property member on the owner type.
  • Definieren Sie eine Wrappereigenschaft nach dem Eigenschaftsaccessormodell, das in der von Ihnen implementierten Sprache verwendet wird. Der Name der Wrappereigenschaft sollte mit der Namenzeichenfolge übereinstimmen, die Sie in Register verwendet haben. Implementieren Sie die Get- und Set-Accessoren, um den Wrapper mit der umbrochenen Abhängigkeitseigenschaft zu verbinden, indem Sie GetValue und SetValue aufrufen und den Bezeichner Ihrer eigenen Eigenschaft als Parameter übergeben.
  • (Optional) Platzieren Sie Attribute wie ContentPropertyAttribute auf dem Wrapper.

Hinweis

Wenn Sie eine benutzerdefinierte angefügte Eigenschaft definieren, wird der Wrapper im Allgemeinen weggelassen. Stattdessen schreiben Sie einen anderen Accessorstil, den ein XAML-Prozessor verwenden kann. Siehe benutzerdefinierte angefügte Eigenschaften

Registrieren der Eigenschaft

Damit Ihre Eigenschaft eine Abhängigkeitseigenschaft ist, müssen Sie die Eigenschaft in einem Eigenschaftenspeicher registrieren, der vom Windows-Runtime Eigenschaftensystem verwaltet wird. Um die Eigenschaft zu registrieren, rufen Sie die Register-Methode auf.

Für Microsoft .NET-Sprachen (C# und Microsoft Visual Basic) rufen Sie "Register " im Textkörper Ihrer Klasse auf (innerhalb der Klasse, aber außerhalb von Memberdefinitionen). Der Bezeichner wird vom Register-Methodenaufruf als Rückgabewert bereitgestellt. Der Register-Aufruf wird in der Regel als statischer Konstruktor oder als Teil der Initialisierung einer öffentlichen statischen readonly-Eigenschaft vom Typ DependencyProperty als Teil der Klasse ausgeführt. Diese Eigenschaft macht den Bezeichner für Ihre Abhängigkeitseigenschaft verfügbar. Hier sind Beispiele für den Registeranruf.

Hinweis

Das Registrieren der Abhängigkeitseigenschaft als Teil der Bezeichnereigenschaftsdefinition ist die typische Implementierung, Sie können aber auch eine Abhängigkeitseigenschaft im statischen Klassenkonstruktor registrieren. Dieser Ansatz kann sinnvoll sein, wenn Sie mehr als eine Codezeile zum Initialisieren der Abhängigkeitseigenschaft benötigen.

Für C++/CX haben Sie Optionen, wie Sie die Implementierung zwischen der Kopfzeile und der Codedatei aufteilen. Die typische Aufteilung besteht darin, den Bezeichner selbst als öffentliche statische Eigenschaft im Header zu deklarieren, mit einer Get-Implementierung , aber keine Menge. Die Get-Implementierung bezieht sich auf ein privates Feld, bei dem es sich um eine nicht initialisierte DependencyProperty-Instanz handelt. Sie können auch die Wrapper deklarieren und die Get- und Set-Implementierungen des Wrappers festlegen. In diesem Fall enthält der Header eine minimale Implementierung. Wenn der Wrapper Windows-Runtime Attribut in der Kopfzeile benötigt. Platzieren Sie den Registeraufruf in der Codedatei in einer Hilfsfunktion, die nur ausgeführt wird, wenn die App das erste Mal initialisiert. Verwenden Sie den Rückgabewert von Register , um die statischen, aber nicht initialisierten Bezeichner auszufüllen, die Sie in der Kopfzeile deklariert haben, die Sie zunächst im Stammbereich der Implementierungsdatei auf Nullptr festlegen.

public static readonly DependencyProperty LabelProperty = DependencyProperty.Register(
  nameof(Label),
  typeof(String),
  typeof(ImageWithLabelControl),
  new PropertyMetadata(null)
);
Public Shared ReadOnly LabelProperty As DependencyProperty = 
    DependencyProperty.Register("Label", 
      GetType(String), 
      GetType(ImageWithLabelControl), 
      New PropertyMetadata(Nothing))
// ImageWithLabelControl.idl
namespace ImageWithLabelControlApp
{
    runtimeclass ImageWithLabelControl : Windows.UI.Xaml.Controls.Control
    {
        ImageWithLabelControl();
        static Windows.UI.Xaml.DependencyProperty LabelProperty{ get; };
        String Label;
    }
}

// ImageWithLabelControl.h
...
struct ImageWithLabelControl : ImageWithLabelControlT<ImageWithLabelControl>
{
...
public:
    static Windows::UI::Xaml::DependencyProperty LabelProperty()
    {
        return m_labelProperty;
    }

private:
    static Windows::UI::Xaml::DependencyProperty m_labelProperty;
...
};

// ImageWithLabelControl.cpp
...
Windows::UI::Xaml::DependencyProperty ImageWithLabelControl::m_labelProperty =
    Windows::UI::Xaml::DependencyProperty::Register(
        L"Label",
        winrt::xaml_typename<winrt::hstring>(),
        winrt::xaml_typename<ImageWithLabelControlApp::ImageWithLabelControl>(),
        Windows::UI::Xaml::PropertyMetadata{ nullptr }
);
...
//.h file
//using namespace Windows::UI::Xaml::Controls;
//using namespace Windows::UI::Xaml::Interop;
//using namespace Windows::UI::Xaml;
//using namespace Platform;

public ref class ImageWithLabelControl sealed : public Control
{
private:
    static DependencyProperty^ _LabelProperty;
...
public:
    static void RegisterDependencyProperties();
    static property DependencyProperty^ LabelProperty
    {
        DependencyProperty^ get() {return _LabelProperty;}
    }
...
};

//.cpp file
using namespace Windows::UI::Xaml;
using namespace Windows::UI::Xaml.Interop;

DependencyProperty^ ImageWithLabelControl::_LabelProperty = nullptr;

// This function is called from the App constructor in App.xaml.cpp
// to register the properties
void ImageWithLabelControl::RegisterDependencyProperties()
{ 
    if (_LabelProperty == nullptr)
    { 
        _LabelProperty = DependencyProperty::Register(
          "Label", Platform::String::typeid, ImageWithLabelControl::typeid, nullptr);
    } 
}

Hinweis

Für den C++/CX-Code ist der Grund, warum Sie über ein privates Feld und eine öffentliche schreibgeschützte Eigenschaft verfügen, die die DependencyProperty-Eigenschaft anzeigt, sodass andere Aufrufer, die Ihre Abhängigkeitseigenschaft verwenden, auch Eigenschaftensystem-Hilfsprogramm-APIs verwenden können, für die der Bezeichner öffentlich sein muss. Wenn Sie den Bezeichner privat halten, können personen diese Hilfsprogramm-APIs nicht verwenden. Beispiele für solche API und Szenarien sind GetValue oder SetValue nach Wahl, ClearValue, GetAnimationBaseValue, SetBinding und Setter.Property. Sie können dafür kein öffentliches Feld verwenden, da Windows-Runtime Metadatenregeln keine öffentlichen Felder zulassen.

Namenskonventionen für Abhängigkeitseigenschaften

Es gibt Benennungskonventionen für Abhängigkeitseigenschaften; Folgen Sie ihnen unter allen außer außergewöhnlichen Umständen. Die Abhängigkeitseigenschaft selbst weist einen einfachen Namen ("Label" im vorherigen Beispiel) auf, der als erster Parameter von Register angegeben wird. Der Name muss innerhalb jedes Registrierungstyps eindeutig sein, und die Eindeutigkeitsanforderung gilt auch für alle geerbten Member. Abhängigkeitseigenschaften, die über Basistypen geerbt werden, gelten bereits als Teil des Registrierungstyps; Namen von geerbten Eigenschaften können nicht erneut registriert werden.

Warnung

Obwohl der hier angegebene Name ein beliebiger Zeichenfolgenbezeichner sein kann, der bei der Programmierung für die gewünschte Sprache gültig ist, möchten Sie in der Regel auch ihre Abhängigkeitseigenschaft in XAML festlegen können. Um in XAML festzulegen, muss der von Ihnen ausgewählte Eigenschaftsname ein gültiger XAML-Name sein. Weitere Informationen finden Sie in der XAML-Übersicht.

Wenn Sie die Bezeichnereigenschaft erstellen, kombinieren Sie den Namen der Eigenschaft bei der Registrierung mit dem Suffix "Property" ("LabelProperty", z. B. "LabelProperty"). Diese Eigenschaft ist Ihr Bezeichner für die Abhängigkeitseigenschaft und wird als Eingabe für die Aufrufe "SetValue" und "GetValue" verwendet, die Sie in Ihren eigenen Eigenschaftenwrappern ausführen. Sie wird auch vom Eigenschaftensystem und anderen XAML-Prozessoren wie {x:Bind} verwendet.

Implementieren des Wrappers

Ihr Eigenschaftenwrapper sollte GetValue in der Get-Implementierung und SetValue in der Setimplementierung aufrufen.

Warnung

Unter allen ausnahmebedingungen sollten Ihre Wrapperimplementierungen nur die GetValue- und SetValue-Vorgänge ausführen. Andernfalls erhalten Sie ein anderes Verhalten, wenn Ihre Eigenschaft über XAML festgelegt wird, wenn sie über Code festgelegt wird. Aus Effizienzgründen umgeht der XAML-Parser Wrapper beim Festlegen von Abhängigkeitseigenschaften; und spricht über SetValue mit dem Sicherungsspeicher.

public String Label
{
    get { return (String)GetValue(LabelProperty); }
    set { SetValue(LabelProperty, value); }
}
Public Property Label() As String
    Get
        Return DirectCast(GetValue(LabelProperty), String) 
    End Get 
    Set(ByVal value As String)
        SetValue(LabelProperty, value)
    End Set
End Property
// ImageWithLabelControl.h
...
winrt::hstring Label()
{
    return winrt::unbox_value<winrt::hstring>(GetValue(m_labelProperty));
}

void Label(winrt::hstring const& value)
{
    SetValue(m_labelProperty, winrt::box_value(value));
}
...
//using namespace Platform;
public:
...
  property String^ Label
  {
    String^ get() {
      return (String^)GetValue(LabelProperty);
    }
    void set(String^ value) {
      SetValue(LabelProperty, value);
    }
  }

Eigenschaftenmetadaten für eine benutzerdefinierte Abhängigkeitseigenschaft

Wenn Eigenschaftenmetadaten einer Abhängigkeitseigenschaft zugewiesen werden, werden dieselben Metadaten für jede Instanz des Eigenschaftsbesitzertyps oder deren Unterklassen auf diese Eigenschaft angewendet. In Eigenschaftenmetadaten können Sie zwei Verhaltensweisen angeben:

  • Ein Standardwert, den das Eigenschaftensystem allen Fällen der Eigenschaft zuweist.
  • Eine statische Rückrufmethode, die automatisch innerhalb des Eigenschaftensystems aufgerufen wird, wenn eine Änderung des Eigenschaftswerts erkannt wird.

Aufrufen des Registers mit Eigenschaftenmetadaten

In den vorherigen Beispielen für den Aufruf von DependencyProperty.Register haben wir einen NULL-Wert für den propertyMetadata-Parameter übergeben. Wenn Sie eine Abhängigkeitseigenschaft aktivieren möchten, um einen Standardwert bereitzustellen oder einen Rückruf mit geänderten Eigenschaften zu verwenden, müssen Sie eine PropertyMetadata-Instanz definieren, die eine oder beide Dieser Funktionen bereitstellt.

In der Regel stellen Sie eine PropertyMetadata als inline erstellte Instanz innerhalb der Parameter für DependencyProperty.Register bereit.

Hinweis

Wenn Sie eine CreateDefaultValueCallback-Implementierung definieren, müssen Sie die Hilfsmethode PropertyMetadata.Create anstelle eines PropertyMetadata-Konstruktors verwenden, um die PropertyMetadata-Instanz zu definieren.

In diesem nächsten Beispiel werden die zuvor gezeigten DependencyProperty.Register-Beispiele geändert, indem auf eine PropertyMetadata-Instanz mit einem PropertyChangedCallback-Wert verwiesen wird. Die Implementierung des Rückrufs "OnLabelChanged" wird weiter unten in diesem Abschnitt angezeigt.

public static readonly DependencyProperty LabelProperty = DependencyProperty.Register(
  nameof(Label),
  typeof(String),
  typeof(ImageWithLabelControl),
  new PropertyMetadata(null,new PropertyChangedCallback(OnLabelChanged))
);
Public Shared ReadOnly LabelProperty As DependencyProperty =
    DependencyProperty.Register("Label",
      GetType(String),
      GetType(ImageWithLabelControl),
      New PropertyMetadata(
        Nothing, new PropertyChangedCallback(AddressOf OnLabelChanged)))
// ImageWithLabelControl.cpp
...
Windows::UI::Xaml::DependencyProperty ImageWithLabelControl::m_labelProperty =
    Windows::UI::Xaml::DependencyProperty::Register(
        L"Label",
        winrt::xaml_typename<winrt::hstring>(),
        winrt::xaml_typename<ImageWithLabelControlApp::ImageWithLabelControl>(),
        Windows::UI::Xaml::PropertyMetadata{ nullptr, Windows::UI::Xaml::PropertyChangedCallback{ &ImageWithLabelControl::OnLabelChanged } }
);
...
DependencyProperty^ ImageWithLabelControl::_LabelProperty =
    DependencyProperty::Register("Label",
    Platform::String::typeid,
    ImageWithLabelControl::typeid,
    ref new PropertyMetadata(nullptr,
      ref new PropertyChangedCallback(&ImageWithLabelControl::OnLabelChanged))
    );

Standardwert

Sie können einen Standardwert für eine Abhängigkeitseigenschaft angeben, sodass die Eigenschaft immer einen bestimmten Standardwert zurückgibt, wenn sie nicht festgelegt ist. Dieser Wert kann sich vom inhärenten Standardwert für den Typ dieser Eigenschaft unterscheiden.

Wenn kein Standardwert angegeben wird, ist der Standardwert für eine Abhängigkeitseigenschaft null für einen Bezugstyp oder der Standardwert des Typs für einen Werttyp oder Einen Sprachgrundtyp (z. B. 0 für eine ganze Zahl oder eine leere Zeichenfolge für eine Zeichenfolge). Der Hauptgrund für die Einrichtung eines Standardwerts ist, dass dieser Wert beim Aufrufen von ClearValue für die Eigenschaft wiederhergestellt wird. Das Einrichten eines Standardwerts pro Eigenschaft ist möglicherweise praktischer als das Einrichten von Standardwerten in Konstruktoren, insbesondere für Werttypen. Stellen Sie jedoch für Referenztypen sicher, dass beim Einrichten eines Standardwerts kein unbeabsichtigtes Singletonmuster erstellt wird. Weitere Informationen finden Sie weiter unten in diesem Thema unter "Bewährte Methoden ".

// ImageWithLabelControl.cpp
...
Windows::UI::Xaml::DependencyProperty ImageWithLabelControl::m_labelProperty =
    Windows::UI::Xaml::DependencyProperty::Register(
        L"Label",
        winrt::xaml_typename<winrt::hstring>(),
        winrt::xaml_typename<ImageWithLabelControlApp::ImageWithLabelControl>(),
        Windows::UI::Xaml::PropertyMetadata{ winrt::box_value(L"default label"), Windows::UI::Xaml::PropertyChangedCallback{ &ImageWithLabelControl::OnLabelChanged } }
);
...

Hinweis

Registrieren Sie sich nicht mit einem Standardwert von UnsetValue. Wenn Sie dies tun, wird es die Verbraucher von Immobilien verwirren und unbeabsichtigte Folgen innerhalb des Eigenschaftensystems haben.

CreateDefaultValueCallback

In einigen Szenarien definieren Sie Abhängigkeitseigenschaften für Objekte, die in mehr als einem UI-Thread verwendet werden. Dies kann der Fall sein, wenn Sie ein Datenobjekt definieren, das von mehreren Apps verwendet wird, oder ein Steuerelement, das Sie in mehreren Apps verwenden. Sie können den Austausch des Objekts zwischen verschiedenen UI-Threads aktivieren, indem Sie eine CreateDefaultValueCallback-Implementierung anstelle einer Standardwertinstanz bereitstellen, die an den Thread gebunden ist, der die Eigenschaft registriert hat. Grundsätzlich definiert ein CreateDefaultValueCallback eine Factory für Standardwerte. Der von CreateDefaultValueCallback zurückgegebene Wert ist immer dem aktuellen UI CreateDefaultValueCallback-Thread zugeordnet, der das Objekt verwendet.

Um Metadaten zu definieren, die einen CreateDefaultValueCallback angeben, müssen Sie PropertyMetadata.Create aufrufen, um eine Metadateninstanz zurückzugeben. Die PropertyMetadata-Konstruktoren verfügen nicht über eine Signatur, die einen CreateDefaultValueCallback-Parameter enthält.

Das typische Implementierungsmuster für ein CreateDefaultValueCallback besteht darin, eine neue DependencyObject-Klasse zu erstellen, den spezifischen Eigenschaftswert jeder Eigenschaft des DependencyObject auf den beabsichtigten Standardwert festzulegen und dann die neue Klasse als Objektverweis über den Rückgabewert der CreateDefaultValueCallback-Methode zurückzugeben.

Rückrufmethode mit Eigenschaftsänderung

Sie können eine Eigenschaft geänderte Rückrufmethode definieren, um die Interaktionen Ihrer Eigenschaft mit anderen Abhängigkeitseigenschaften zu definieren oder eine interne Eigenschaft oder den Status Des Objekts zu aktualisieren, wenn sich die Eigenschaft ändert. Wenn Ihr Rückruf aufgerufen wird, hat das Eigenschaftensystem festgestellt, dass eine effektive Eigenschaftswertänderung vorliegt. Da die Rückrufmethode statisch ist, ist der d-Parameter des Rückrufs wichtig, da sie Ihnen mitteilt, welche Instanz der Klasse eine Änderung gemeldet hat. Eine typische Implementierung verwendet die NewValue-Eigenschaft der Ereignisdaten und verarbeitet diesen Wert in irgendeiner Weise, in der Regel durch Ausführen einer anderen Änderung des objekts, das als d übergeben wird. Zusätzliche Antworten auf eine Eigenschaftsänderung sind das Ablehnen des von NewValue gemeldeten Werts, das Wiederherstellen von OldValue oder das Festlegen des Werts auf eine programmgesteuerte Einschränkung, die auf den NewValue angewendet wird.

Dieses nächste Beispiel zeigt eine PropertyChangedCallback-Implementierung. Sie implementiert die Methode, auf die Sie in den vorherigen Registerbeispielen verwiesen haben, als Teil der Konstruktionsargumente für propertyMetadata. Das Von diesem Rückruf angesprochene Szenario ist, dass die Klasse auch eine berechnete schreibgeschützte Eigenschaft mit dem Namen "HasLabelValue" aufweist (Implementierung nicht dargestellt). Wenn die Eigenschaft "Label" erneut ausgewertet wird, wird diese Rückrufmethode aufgerufen, und der Rückruf ermöglicht es dem abhängigen berechneten Wert, mit Änderungen an der Abhängigkeitseigenschaft synchronisiert zu bleiben.

private static void OnLabelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
    ImageWithLabelControl iwlc = d as ImageWithLabelControl; //null checks omitted
    String s = e.NewValue as String; //null checks omitted
    if (s == String.Empty)
    {
        iwlc.HasLabelValue = false;
    } else {
        iwlc.HasLabelValue = true;
    }
}
    Private Shared Sub OnLabelChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
        Dim iwlc As ImageWithLabelControl = CType(d, ImageWithLabelControl) ' null checks omitted
        Dim s As String = CType(e.NewValue,String) ' null checks omitted
        If s Is String.Empty Then
            iwlc.HasLabelValue = False
        Else
            iwlc.HasLabelValue = True
        End If
    End Sub
void ImageWithLabelControl::OnLabelChanged(Windows::UI::Xaml::DependencyObject const& d, Windows::UI::Xaml::DependencyPropertyChangedEventArgs const& e)
{
    auto iwlc{ d.as<ImageWithLabelControlApp::ImageWithLabelControl>() };
    auto s{ winrt::unbox_value<winrt::hstring>(e.NewValue()) };
    iwlc.HasLabelValue(s.size() != 0);
}
static void OnLabelChanged(DependencyObject^ d, DependencyPropertyChangedEventArgs^ e)
{
    ImageWithLabelControl^ iwlc = (ImageWithLabelControl^)d;
    Platform::String^ s = (Platform::String^)(e->NewValue);
    if (s->IsEmpty()) {
        iwlc->HasLabelValue=false;
    }
}

Verhalten von Eigenschaftsänderung für Strukturen und Enumerationen

Wenn der Typ einer DependencyProperty eine Aufzählung oder eine Struktur ist, kann der Rückruf auch dann aufgerufen werden, wenn sich die internen Werte der Struktur oder des Enumerationswerts nicht geändert haben. Dies unterscheidet sich von einem Systemgrundtyp, z. B. einer Zeichenfolge, in der sie nur aufgerufen wird, wenn sich der Wert geändert hat. Dies ist ein Nebeneffekt von Box- und Unbox-Vorgängen auf diese Werte, die intern ausgeführt werden. Wenn Sie über eine PropertyChangedCallback-Methode für eine Eigenschaft verfügen, bei der ihr Wert eine Enumeration oder Struktur ist, müssen Sie den OldValue und NewValue vergleichen, indem Sie die Werte selbst umwandeln und die überladenen Vergleichsoperatoren verwenden, die für die jetzt umgewandelten Werte verfügbar sind. Oder wenn kein solcher Operator verfügbar ist (dies kann der Fall für eine benutzerdefinierte Struktur sein), müssen Sie möglicherweise die einzelnen Werte vergleichen. Normalerweise würden Sie nichts tun, wenn das Ergebnis darin besteht, dass sich die Werte nicht geändert haben.

private static void OnVisibilityValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
    if ((Visibility)e.NewValue != (Visibility)e.OldValue)
    {
        //value really changed, invoke your changed logic here
    } // else this was invoked because of boxing, do nothing
}
Private Shared Sub OnVisibilityValueChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
    If CType(e.NewValue,Visibility) != CType(e.OldValue,Visibility) Then
        '  value really changed, invoke your changed logic here
    End If
    '  else this was invoked because of boxing, do nothing
End Sub
static void OnVisibilityValueChanged(Windows::UI::Xaml::DependencyObject const& d, Windows::UI::Xaml::DependencyPropertyChangedEventArgs const& e)
{
    auto oldVisibility{ winrt::unbox_value<Windows::UI::Xaml::Visibility>(e.OldValue()) };
    auto newVisibility{ winrt::unbox_value<Windows::UI::Xaml::Visibility>(e.NewValue()) };

    if (newVisibility != oldVisibility)
    {
        // The value really changed; invoke your property-changed logic here.
    }
    // Otherwise, OnVisibilityValueChanged was invoked because of boxing; do nothing.
}
static void OnVisibilityValueChanged(DependencyObject^ d, DependencyPropertyChangedEventArgs^ e)
{
    if ((Visibility)e->NewValue != (Visibility)e->OldValue)
    {
        //value really changed, invoke your changed logic here
    } 
    // else this was invoked because of boxing, do nothing
    }
}

Bewährte Methoden

Beachten Sie die folgenden Überlegungen als bewährte Methoden, wenn Sie Ihre benutzerdefinierte Abhängigkeitseigenschaft definieren.

DependencyObject und Threading

Alle DependencyObject-Instanzen müssen im UI-Thread erstellt werden, der dem aktuellen Fenster zugeordnet ist, das von einer Windows-Runtime-App angezeigt wird. Obwohl jedes Abhängigkeitsobjekt im Haupt-UI-Thread erstellt werden muss, können auf die Objekte mithilfe eines Verteilerverweises aus anderen Threads zugegriffen werden, indem dispatcher aufgerufen wird.

Die Threadingaspekte von DependencyObject sind relevant, da es im Allgemeinen bedeutet, dass nur Code, der im UI-Thread ausgeführt wird, den Wert einer Abhängigkeitseigenschaft ändern oder sogar lesen kann. Threadingprobleme können in der Regel im typischen UI-Code vermieden werden, der die korrekte Verwendung von asynchronen Mustern und Hintergrundarbeitsthreads ermöglicht. Normalerweise treten nur Probleme mit DependencyObject-bezogenen Threading auf, wenn Sie Ihre eigenen DependencyObject-Typen definieren und versuchen, sie für Datenquellen oder andere Szenarien zu verwenden, in denen ein DependencyObject nicht notwendigerweise geeignet ist.

Vermeiden unbeabsichtigter Singletons

Ein unbeabsichtigter Singleton kann auftreten, wenn Sie eine Abhängigkeitseigenschaft deklarieren, die einen Verweistyp akzeptiert, und Sie einen Konstruktor für diesen Verweistyp als Teil des Codes aufrufen, der Ihre PropertyMetadata erstellt. Was passiert, ist, dass alle Verwendungen der Abhängigkeitseigenschaft nur eine Instanz von PropertyMetadata gemeinsam nutzen und daher versuchen, den erstellten einzelnen Verweistyp zu teilen. Alle Untereigenschaften dieses Werttyps, die Sie über Ihre Abhängigkeitseigenschaft festlegen, werden dann auf andere Objekte verteilt, die Sie möglicherweise nicht beabsichtigt haben.

Sie können Klassenkonstruktoren verwenden, um Anfangswerte für eine Abhängigkeitseigenschaft vom Typ "Bezugstyp" festzulegen, wenn Sie einen Wert ungleich Null verwenden möchten, beachten Sie jedoch, dass dies als lokaler Wert für die Übersicht über Abhängigkeitseigenschaften betrachtet wird. Es ist möglicherweise besser geeignet, eine Vorlage für diesen Zweck zu verwenden, wenn Ihre Klasse Vorlagen unterstützt. Eine weitere Möglichkeit, ein Singletonmuster zu vermeiden, aber dennoch einen nützlichen Standardwert bereitzustellen, besteht darin, eine statische Eigenschaft für den Referenztyp verfügbar zu machen, der einen geeigneten Standardwert für die Werte dieser Klasse bereitstellt.

Abhängigkeitseigenschaften des Sammlungstyps

Bei Abhängigkeitseigenschaften vom Auflistungstyp gibt es zusätzliche Implementierungsprobleme, die man berücksichtigen sollte.

Abhängigkeitseigenschaften vom Sammlungstyp sind in der Windows-Runtime-API relativ selten. In den meisten Fällen können Sie Sammlungen verwenden, bei denen es sich bei den Elementen um eine DependencyObject-Unterklasse handelt, die Sammlungseigenschaft selbst jedoch als herkömmliche CLR- oder C++-Eigenschaft implementiert wird. Dies liegt daran, dass Sammlungen nicht unbedingt einigen typischen Szenarien entsprechen, in denen Abhängigkeitseigenschaften beteiligt sind. Zum Beispiel:

  • Sie animieren in der Regel keine Sammlung.
  • Normalerweise füllen Sie die Elemente in einer Auflistung nicht mit Formatvorlagen oder einer Vorlage vor.
  • Obwohl die Bindung an Sammlungen ein hauptszenario ist, muss eine Auflistung keine Abhängigkeitseigenschaft sein, um eine Bindungsquelle zu sein. Für Bindungsziele ist es typischer, Unterklassen von ItemsControl oder DataTemplate zu verwenden, um Sammlungselemente zu unterstützen oder Ansichtsmodellmuster zu verwenden. Weitere Informationen zum Binden an und aus Sammlungen finden Sie unter "Datenbindung" im Detail.
  • Benachrichtigungen für Sammlungsänderungen werden besser über Schnittstellen wie INotifyPropertyChanged oder INotifyCollectionChanged oder durch Ableiten des Sammlungstyps von ObservableCollection<T> behoben.

Dennoch sind Szenarien für Abhängigkeitseigenschaften des Sammlungstyps vorhanden. Die nächsten drei Abschnitte enthalten einige Anleitungen zum Implementieren einer Abhängigkeitseigenschaft vom Typ "Sammlung".

Initialisieren der Auflistung

Wenn Sie eine Abhängigkeitseigenschaft erstellen, können Sie einen Standardwert mithilfe von Metadaten für Abhängigkeitseigenschaften einrichten. Achten Sie jedoch darauf, keine statische Singleton-Auflistung als Standardwert zu verwenden. Stattdessen müssen Sie den Sammlungswert bewusst auf eine eindeutige (Instanz)-Auflistung als Teil der Klassenkonstruktorlogik für die Besitzerklasse der Auflistungseigenschaft festlegen.

// WARNING - DO NOT DO THIS
public static readonly DependencyProperty ItemsProperty = DependencyProperty.Register(
  nameof(Items),
  typeof(IList<object>),
  typeof(ImageWithLabelControl),
  new PropertyMetadata(new List<object>())
);

// DO THIS Instead
public static readonly DependencyProperty ItemsProperty = DependencyProperty.Register(
  nameof(Items),
  typeof(IList<object>),
  typeof(ImageWithLabelControl),
  new PropertyMetadata(null)
);

public ImageWithLabelControl()
{
    // Need to initialize in constructor instead
    Items = new List<object>();
}

Ein DependencyProperty und der Standardwert von PropertyMetadata sind Teil der statischen Definition der DependencyProperty. Durch die Bereitstellung eines Standardauflistungswerts (oder eines anderen Instanzwerts) als Standardwert wird er für alle Instanzen Ihrer Klasse freigegeben, anstatt jede Klasse über eine eigene Auflistung zu verfügen, wie es in der Regel gewünscht wäre.

Änderungsbenachrichtigungen

Das Definieren der Auflistung als Abhängigkeitseigenschaft stellt nicht automatisch Änderungsbenachrichtigungen für die Elemente in der Auflistung bereit, indem das Eigenschaftensystem die Rückrufmethode "PropertyChanged" aufruft. Wenn Sie Benachrichtigungen für Sammlungen oder Sammlungselemente benötigen , z. B. für ein Datenbindungsszenario, implementieren Sie die INotifyPropertyChanged - oder INotifyCollectionChanged-Schnittstelle . Weitere Informationen finden Sie unter Datenbindung im Detail.

Überlegungen zur Sicherheit von Abhängigkeitseigenschaften

Deklarieren Sie Abhängigkeitseigenschaften als öffentliche Eigenschaften. Deklarieren Sie Abhängigkeitseigenschaftsbezeichner als öffentliche statische Readonly-Member . Selbst wenn Sie versuchen, andere Zugriffsstufen zu deklarieren, die von einer Sprache zugelassen werden (z . B. geschützt), kann immer über den Bezeichner in Kombination mit den Eigenschaftensystem-APIs auf eine Abhängigkeitseigenschaft zugegriffen werden. Das Deklarieren des Bezeichners der Abhängigkeitseigenschaft als intern oder privat funktioniert nicht, da das Eigenschaftensystem nicht ordnungsgemäß ausgeführt werden kann.

Wrappereigenschaften sind wirklich nur zur Vereinfachung geeignet, Sicherheitsmechanismen, die auf die Wrapper angewendet werden, können umgangen werden, indem stattdessen GetValue oder SetValue aufgerufen wird. Behalten Sie die Wrappereigenschaften also öffentlich; andernfalls machen Sie Ihre Eigenschaft nur schwieriger für legitime Anrufer zu verwenden, ohne wirklichen Sicherheitsvorteil zu bieten.

Die Windows-Runtime bietet keine Möglichkeit, eine benutzerdefinierte Abhängigkeitseigenschaft als schreibgeschützt zu registrieren.

Abhängigkeitseigenschaften und Klassenkonstruktoren

Es gibt ein allgemeines Prinzip, dass Klassenkonstruktoren keine virtuellen Methoden aufrufen sollten. Dies liegt daran, dass Konstruktoren aufgerufen werden können, um die Basisinitialisierung eines abgeleiteten Klassenkonstruktors durchzuführen und die virtuelle Methode über den Konstruktor einzugeben, wenn die erstellte Objektinstanz noch nicht vollständig initialisiert ist. Wenn Sie von einer Klasse abgeleitet werden, die bereits von DependencyObject abgeleitet ist, denken Sie daran, dass das Eigenschaftensystem selbst virtuelle Methoden intern als Teil seiner Dienste aufruft und verfügbar macht. Um potenzielle Probleme mit der Laufzeitinitialisierung zu vermeiden, legen Sie keine Abhängigkeitseigenschaftswerte innerhalb von Konstruktoren von Klassen fest.

Registrieren der Abhängigkeitseigenschaften für C++/CX-Apps

Die Implementierung für die Registrierung einer Eigenschaft in C++/CX ist schwieriger als C#, sowohl aufgrund der Trennung in Header- und Implementierungsdatei als auch aufgrund der Initialisierung im Stammbereich der Implementierungsdatei ist eine schlechte Methode. (Visual C++-Komponentenerweiterungen (C++/CX) fügt statischen Initialisierungscode aus dem Stammbereich direkt in den Stammbereich ein.DllMain, während C#-Compiler die statischen Initialisierer Klassen zuweisen und somit DllMain Ladesperrprobleme vermeiden.) Im Folgenden wird empfohlen, eine Hilfsfunktion zu deklarieren, die die gesamte Registrierung von Abhängigkeitseigenschaften für eine Klasse( eine Funktion pro Klasse) ausführt. Anschließend müssen Sie für jede benutzerdefinierte Klasse, die Ihre App nutzt, auf die Hilfsregistrierungsfunktion verweisen, die von jeder benutzerdefinierten Klasse verfügbar gemacht wird, die Sie verwenden möchten. Rufen Sie jede Hilfsregistrierungsfunktion einmal als Teil des Application-Konstruktors (App::App()) vor InitializeComponent. Dieser Konstruktor wird nur ausgeführt, wenn die App zum ersten Mal wirklich referenziert wird, wird sie nicht erneut ausgeführt, wenn beispielsweise eine angehaltene App fortgesetzt wird. Wie im vorherigen C++-Registrierungsbeispiel gezeigt, ist auch die Nullptr-Überprüfung jedes Registeraufrufs wichtig: Es ist eine Versicherung, dass kein Aufrufer der Funktion die Eigenschaft zweimal registrieren kann. Ein zweiter Registrierungsaufruf würde Ihre App wahrscheinlich ohne eine solche Überprüfung abstürzen, da der Eigenschaftenname ein Duplikat wäre. Sie können dieses Implementierungsmuster im BEISPIEL für XAML-Benutzer und benutzerdefinierte Steuerelemente sehen, wenn Sie sich den Code für die C++/CX-Version des Beispiels ansehen.