Propriétés de dépendance personnalisées

Ici, nous expliquons comment définir et implémenter vos propres propriétés de dépendance pour une application Windows Runtime à l’aide de C++, C# ou Visual Basic. Nous listons les raisons pour lesquelles les développeurs d’applications et les auteurs de composants peuvent vouloir créer des propriétés de dépendance personnalisées. Nous décrivons les étapes d’implémentation d’une propriété de dépendance personnalisée, ainsi que certaines bonnes pratiques qui peuvent améliorer les performances, la facilité d’utilisation ou la polyvalence de la propriété de dépendance.

Prérequis

Nous partons du principe que vous avez lu la vue d’ensemble des propriétés de dépendance et que vous comprenez les propriétés de dépendance du point de vue d’un consommateur de propriétés de dépendance existantes. Pour suivre les exemples de ce sujet, vous devez également comprendre XAML et savoir comment écrire une application Windows Runtime de base à l’aide de C++, C# ou Visual Basic.

Qu’est-ce qu’une propriété de dépendance ?

Pour prendre en charge le style, la liaison de données, les animations et les valeurs par défaut d’une propriété, elle doit être implémentée en tant que propriété de dépendance. Les valeurs de propriété de dépendance ne sont pas stockées en tant que champs de la classe, elles sont stockées par l’infrastructure xaml et sont référencées à l’aide d’une clé, qui est récupérée lorsque la propriété est inscrite auprès du système de propriétés Windows Runtime en appelant la méthode DependencyProperty.Register. Les propriétés de dépendance ne peuvent être utilisées que par les types dérivant de DependencyObject. Mais DependencyObject est assez élevé dans la hiérarchie de classes, de sorte que la majorité des classes destinées à la prise en charge de l’interface utilisateur et de la présentation peuvent prendre en charge les propriétés de dépendance. Pour plus d’informations sur les propriétés de dépendance et certaines des terminologies et conventions utilisées pour les décrire dans cette documentation, consultez la vue d’ensemble des propriétés de dépendance.

Voici quelques exemples de propriétés de dépendance dans Windows Runtime : Control.Background, FrameworkElement.Width et TextBox.Text, entre autres.

La convention est que chaque propriété de dépendance exposée par une classe a une propriété statique publique correspondante de type DependencyProperty qui est exposée sur cette même classe, qui fournit l’identificateur de la propriété de dépendance. Le nom de l’identificateur suit cette convention : nom de la propriété de dépendance, avec la chaîne « Property » ajoutée à la fin du nom. Par exemple, l’identificateur DependencyProperty correspondant pour la propriété Control.Background est Control.BackgroundProperty. L’identificateur stocke les informations sur la propriété de dépendance telle qu’elle a été inscrite et peut ensuite être utilisé pour d’autres opérations impliquant la propriété de dépendance, telles que l’appel de SetValue.

Wrappers de propriétés

Les propriétés de dépendance ont généralement une implémentation wrapper. Sans le wrapper, la seule façon d’obtenir ou de définir les propriétés consisterait à utiliser les méthodes de l’utilitaire de propriété de dépendance GetValue et SetValue et à transmettre l’identificateur à ces derniers en tant que paramètre. Il s’agit d’une utilisation plutôt non naturelle pour quelque chose qui est ostensiblement une propriété. Toutefois, avec le wrapper, votre code et tout autre code qui fait référence à la propriété de dépendance peut utiliser une syntaxe simple de propriété d’objet naturelle pour le langage que vous utilisez.

Si vous implémentez une propriété de dépendance personnalisée vous-même et souhaitez qu’elle soit publique et facile à appeler, définissez également les wrappers de propriétés. Les wrappers de propriétés sont également utiles pour signaler des informations de base sur la propriété de dépendance aux processus de réflexion ou d’analyse statique. Plus précisément, le wrapper est l’endroit où vous placez des attributs tels que ContentPropertyAttribute.

Quand implémenter une propriété en tant que propriété de dépendance

Chaque fois que vous implémentez une propriété en lecture/écriture publique sur une classe, tant que votre classe dérive de DependencyObject, vous avez la possibilité de faire fonctionner votre propriété en tant que propriété de dépendance. Parfois, la technique typique de sauvegarde de votre propriété avec un champ privé est suffisante. La définition de votre propriété personnalisée en tant que propriété de dépendance n’est pas toujours nécessaire ou appropriée. Le choix dépend des scénarios que vous avez l’intention de prendre en charge votre propriété.

Vous pouvez envisager d’implémenter votre propriété en tant que propriété de dépendance lorsque vous souhaitez qu’elle prend en charge une ou plusieurs de ces fonctionnalités de Windows Runtime ou d’applications Windows Runtime :

  • Définition de la propriété par le biais d’un style
  • Action en tant que propriété cible valide pour la liaison de données avec {Binding}
  • Prise en charge des valeurs animées par le biais d’un Storyboard
  • Création de rapports lorsque la valeur de la propriété a été modifiée par :
    • Actions effectuées par le système de propriétés lui-même
    • L’environnement
    • Actions utilisateur
    • Lecture et écriture de styles

Liste de contrôle pour la définition d’une propriété de dépendance

La définition d’une propriété de dépendance peut être considérée comme un ensemble de concepts. Ces concepts ne sont pas nécessairement des étapes procédurales, car plusieurs concepts peuvent être traités dans une seule ligne de code dans l’implémentation. Cette liste offre une vue d’ensemble rapide. Nous allons expliquer chaque concept plus en détail plus loin dans cette rubrique, et nous allons vous montrer un exemple de code dans plusieurs langages.

  • Inscrivez le nom de la propriété auprès du système de propriétés (appelez Register), en spécifiant un type de propriétaire et le type de la valeur de propriété.
    • Il existe un paramètre obligatoire pour Register qui attend les métadonnées de propriété. Spécifiez null pour cela, ou si vous souhaitez modifier le comportement des propriétés ou une valeur par défaut basée sur les métadonnées qui peut être restaurée en appelant ClearValue, spécifiez une instance de PropertyMetadata.
  • Définissez un identificateur DependencyProperty en tant que membre de propriété en lecture seule statique public sur le type de propriétaire.
  • Définissez une propriété wrapper, en suivant le modèle d’accesseur de propriété utilisé dans le langage que vous implémentez. Le nom de la propriété wrapper doit correspondre à la chaîne de nom que vous avez utilisée dans Register. Implémentez les accesseurs get et set pour connecter le wrapper à la propriété de dépendance qu’il encapsule, en appelant GetValue et SetValue et en passant l’identificateur de votre propre propriété en tant que paramètre.
  • (Facultatif) Placez des attributs tels que ContentPropertyAttribute sur le wrapper.

Remarque

Si vous définissez une propriété jointe personnalisée, vous omettez généralement le wrapper. Au lieu de cela, vous écrivez un autre style d’accesseur qu’un processeur XAML peut utiliser. Consultez les propriétés jointes personnalisées. 

Inscription de la propriété

Pour que votre propriété soit une propriété de dépendance, vous devez inscrire la propriété dans un magasin de propriétés géré par le système de propriétés Windows Runtime. Pour inscrire la propriété, vous appelez la méthode Register.

Pour les langages Microsoft .NET (C# et Microsoft Visual Basic), vous appelez Register dans le corps de votre classe (à l’intérieur de la classe, mais en dehors des définitions de membres). L’identificateur est fourni par l’appel de méthode Register , comme valeur de retour. L’appel Register est généralement effectué en tant que constructeur statique ou dans le cadre de l’initialisation d’une propriété en lecture seule publique de type DependencyProperty dans le cadre de votre classe. Cette propriété expose l’identificateur de votre propriété de dépendance. Voici quelques exemples de l’appel Register .

Remarque

L’inscription de la propriété de dépendance dans le cadre de la définition de propriété d’identificateur est l’implémentation classique, mais vous pouvez également inscrire une propriété de dépendance dans le constructeur statique de classe. Cette approche peut être logique si vous avez besoin de plusieurs lignes de code pour initialiser la propriété de dépendance.

Pour C++/CX, vous avez des options pour fractionner l’implémentation entre l’en-tête et le fichier de code. Le fractionnement classique consiste à déclarer l’identificateur lui-même en tant que propriété statique publique dans l’en-tête, avec une implémentation get, mais pas de jeu. L’implémentation get fait référence à un champ privé, qui est une instance DependencyProperty non initialisée. Vous pouvez également déclarer les wrappers et les implémentations get et set du wrapper. Dans ce cas, l’en-tête inclut une implémentation minimale. Si le wrapper a besoin d’une attribution Windows Runtime, attribut dans l’en-tête également. Placez l’appel Register dans le fichier de code, dans une fonction d’assistance qui ne s’exécute que lorsque l’application initialise la première fois. Utilisez la valeur de retour de Register pour remplir les identificateurs statiques mais non initialisés que vous avez déclarés dans l’en-tête, que vous définissez initialement sur nullptr au niveau de l’étendue racine du fichier d’implémentation.

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);
    } 
}

Remarque

Pour le code C++/CX, la raison pour laquelle vous avez un champ privé et une propriété en lecture seule publique qui expose DependencyProperty est de sorte que d’autres appelants qui utilisent votre propriété de dépendance puissent également utiliser des API utilitaires de système de propriétés qui nécessitent que l’identificateur soit public. Si vous conservez l’identificateur privé, les utilisateurs ne peuvent pas utiliser ces API utilitaires. Par exemple, ces API et scénarios incluent GetValue ou SetValue par choix, ClearValue, GetAnimationBaseValue, SetBinding et Setter.Property. Vous ne pouvez pas utiliser de champ public pour cela, car les règles de métadonnées Windows Runtime n’autorisent pas les champs publics.

Conventions de nom de propriété de dépendance

Il existe des conventions d’affectation de noms pour les propriétés de dépendance ; suivez-les dans toutes les circonstances, mais exceptionnelles. La propriété de dépendance elle-même a un nom de base (« Label » dans l’exemple précédent) qui est donné comme premier paramètre de Register. Le nom doit être unique dans chaque type d’inscription et l’exigence d’unicité s’applique également aux membres hérités. Les propriétés de dépendance héritées par le biais de types de base sont considérées comme faisant déjà partie du type d’inscription ; les noms des propriétés héritées ne peuvent pas être enregistrés à nouveau.

Avertissement

Bien que le nom que vous fournissez ici peut être n’importe quel identificateur de chaîne valide en programmation pour votre langage de choix, vous souhaitez généralement pouvoir définir votre propriété de dépendance en XAML également. Pour être défini en XAML, le nom de propriété que vous choisissez doit être un nom XAML valide. Pour plus d’informations, consultez la vue d’ensemble du code XAML.

Lorsque vous créez la propriété d’identificateur, combinez le nom de la propriété au fur et à mesure que vous l’avez inscrite avec le suffixe « Property » (« LabelProperty », par exemple). Cette propriété est votre identificateur pour la propriété de dépendance et elle est utilisée comme entrée pour les appels SetValue et GetValue que vous effectuez dans vos propres wrappers de propriétés. Il est également utilisé par le système de propriétés et d’autres processeurs XAML tels que {x :Bind}

Implémentation du wrapper

Votre wrapper de propriété doit appeler GetValue dans l’implémentation get et SetValue dans l’implémentation set.

Avertissement

Dans toutes les circonstances, mais exceptionnelles, vos implémentations de wrapper doivent effectuer uniquement les opérations GetValue et SetValue. Dans le cas contraire, vous obtiendrez un comportement différent lorsque votre propriété est définie via XAML ou lorsqu’elle est définie via du code. Pour plus d’efficacité, l’analyseur XAML contourne les wrappers lors de la définition des propriétés de dépendance ; et communique avec le magasin de stockage via SetValue.

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);
    }
  }

Métadonnées de propriété pour une propriété de dépendance personnalisée

Lorsque les métadonnées de propriété sont affectées à une propriété de dépendance, les mêmes métadonnées sont appliquées à cette propriété pour chaque instance du type propriétaire de propriété ou de ses sous-classes. Dans les métadonnées de propriété, vous pouvez spécifier deux comportements :

  • Valeur par défaut affectée par le système de propriétés à tous les cas de la propriété.
  • Méthode de rappel statique appelée automatiquement dans le système de propriétés chaque fois qu’une modification de valeur de propriété est détectée.

Appel de l’inscription avec les métadonnées de propriété

Dans les exemples précédents d’appel de DependencyProperty.Register, nous avons passé une valeur Null pour le paramètre propertyMetadata. Pour permettre à une propriété de dépendance de fournir une valeur par défaut ou d’utiliser un rappel modifié par propriété, vous devez définir une instance PropertyMetadata qui fournit une ou les deux fonctionnalités.

En règle générale, vous fournissez une propriétéMetadata en tant qu’instance créée en ligne, dans les paramètres de DependencyProperty.Register.

Remarque

Si vous définissez une implémentation CreateDefaultValueCallback, vous devez utiliser la méthode utilitaire PropertyMetadata.Create plutôt que d’appeler un constructeur PropertyMetadata pour définir l’instance PropertyMetadata.

Cet exemple montre comment modifier les exemples DependencyProperty.Register précédemment affichés en référençant une instance PropertyMetadata avec une valeur PropertyChangedCallback. L’implémentation du rappel « OnLabelChanged » sera présentée plus loin dans cette section.

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))
    );

Valeur par défaut

Vous pouvez spécifier une valeur par défaut pour une propriété de dépendance de telle sorte que la propriété retourne toujours une valeur par défaut particulière lorsqu’elle n’est pas définie. Cette valeur peut être différente de la valeur par défaut inhérente pour le type de cette propriété.

Si aucune valeur par défaut n’est spécifiée, la valeur par défaut d’une propriété de dépendance est null pour un type référence, ou la valeur par défaut du type pour un type valeur ou une primitive de langage (par exemple, 0 pour un entier ou une chaîne vide pour une chaîne). La principale raison de l’établissement d’une valeur par défaut est que cette valeur est restaurée lorsque vous appelez ClearValue sur la propriété. L’établissement d’une valeur par défaut par propriété peut être plus pratique que d’établir des valeurs par défaut dans les constructeurs, en particulier pour les types valeur. Toutefois, pour les types de référence, assurez-vous que l’établissement d’une valeur par défaut ne crée pas de modèle singleton involontaire. Pour plus d’informations, consultez Les meilleures pratiques plus loin dans cette rubrique

// 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 } }
);
...

Remarque

N’inscrivez pas avec la valeur par défaut UnsetValue. Si vous le faites, cela confondra les consommateurs de biens et aura des conséquences inattendues dans le système de propriété.

CreateDefaultValueCallback

Dans certains scénarios, vous définissez des propriétés de dépendance pour les objets utilisés sur plusieurs threads d’interface utilisateur. Cela peut être le cas si vous définissez un objet de données utilisé par plusieurs applications ou un contrôle que vous utilisez dans plusieurs applications. Vous pouvez activer l’échange de l’objet entre différents threads d’interface utilisateur en fournissant une implémentation CreateDefaultValueCallback plutôt qu’une instance de valeur par défaut, qui est liée au thread qui a inscrit la propriété. En fait, un CreateDefaultValueCallback définit une fabrique pour les valeurs par défaut. La valeur retournée par CreateDefaultValueCallback est toujours associée au thread CreateDefaultValueCallback actuel qui utilise l’objet.

Pour définir des métadonnées qui spécifient un CreateDefaultValueCallback, vous devez appeler PropertyMetadata.Create pour retourner une instance de métadonnées ; les constructeurs PropertyMetadata n’ont pas de signature qui inclut un paramètre CreateDefaultValueCallback.

Le modèle d’implémentation classique d’un CreateDefaultValueCallback consiste à créer une classe DependencyObject, à définir la valeur de propriété spécifique de chaque propriété de DependencyObject sur la valeur par défaut prévue, puis à renvoyer la nouvelle classe en tant que référence Object via la valeur de retour de la méthode CreateDefaultValueCallback.

Méthode de rappel modifiée par propriété

Vous pouvez définir une méthode de rappel modifiée par propriété pour définir les interactions de votre propriété avec d’autres propriétés de dépendance, ou pour mettre à jour une propriété interne ou un état de votre objet chaque fois que la propriété change. Si votre rappel est appelé, le système de propriétés a déterminé qu’il existe une modification effective de la valeur de propriété. Étant donné que la méthode de rappel est statique, le paramètre d du rappel est important, car il vous indique quelle instance de la classe a signalé une modification. Une implémentation classique utilise la propriété NewValue des données d’événement et traite cette valeur d’une certaine manière, généralement en effectuant une autre modification sur l’objet passé en tant que d. Des réponses supplémentaires à une modification de propriété sont de rejeter la valeur signalée par NewValue, de restaurer OldValue ou de définir la valeur sur une contrainte programmatique appliquée à NewValue.

Cet exemple suivant montre une implémentation PropertyChangedCallback. Il implémente la méthode que vous avez vue référencée dans les exemples Register précédents, dans le cadre des arguments de construction pour PropertyMetadata. Le scénario traité par ce rappel est que la classe a également une propriété en lecture seule calculée nommée « HasLabelValue » (implémentation non affichée). Chaque fois que la propriété « Label » est réévaluée, cette méthode de rappel est appelée et le rappel permet à la valeur calculée dépendante de rester synchronisée avec les modifications apportées à la propriété de dépendance.

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;
    }
}

Comportement modifié de propriété pour les structures et les énumérations

Si le type d’une DependencyProperty est une énumération ou une structure, le rappel peut être appelé même si les valeurs internes de la structure ou la valeur d’énumération n’ont pas changé. Ceci est différent d’une primitive système telle qu’une chaîne où elle est appelée uniquement si la valeur a changé. Il s’agit d’un effet secondaire des opérations box et unbox sur ces valeurs effectuées en interne. Si vous disposez d’une méthode PropertyChangedCallback pour une propriété où votre valeur est une énumération ou une structure, vous devez comparer OldValue et NewValue en castant les valeurs vous-même et en utilisant les opérateurs de comparaison surchargés disponibles pour les valeurs de cast. Ou, si aucun opérateur de ce type n’est disponible (ce qui peut être le cas pour une structure personnalisée), vous devrez peut-être comparer les valeurs individuelles. Vous choisissez généralement de ne rien faire si le résultat est que les valeurs n’ont pas changé.

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
    }
}

Bonnes pratiques

Gardez à l’esprit les considérations suivantes en tant que bonnes pratiques quand vous définissez votre propriété de dépendance personnalisée.

DependencyObject et threading

Toutes les instances DependencyObject doivent être créées sur le thread d’interface utilisateur associé à la fenêtre active affichée par une application Windows Runtime. Bien que chaque DependencyObject soit créé sur le thread d’interface utilisateur principal, les objets sont accessibles à l’aide d’une référence de répartiteur à partir d’autres threads, en appelant Dispatcher.

Les aspects threading de DependencyObject sont pertinents, car cela signifie généralement que seul le code qui s’exécute sur le thread d’interface utilisateur peut changer ou même lire la valeur d’une propriété de dépendance. Les problèmes de threading peuvent généralement être évités dans le code d’interface utilisateur classique qui utilise correctement les modèles asynchrones et les threads de travail en arrière-plan. En règle générale, vous rencontrez uniquement des problèmes de thread liés à DependencyObject si vous définissez vos propres types DependencyObject et que vous tentez de les utiliser pour des sources de données ou d’autres scénarios où un DependencyObject n’est pas nécessairement approprié.

Éviter les singletons involontaires

Un singleton involontaire peut se produire si vous déclarez une propriété de dépendance qui accepte un type de référence et que vous appelez un constructeur pour ce type de référence dans le cadre du code qui établit votre PropertyMetadata. Ce qui se passe est que toutes les utilisations de la propriété de dépendance partagent une seule instance de PropertyMetadata et essaient donc de partager le type de référence unique que vous avez construit. Toutes les sous-propriétés de ce type valeur que vous définissez par le biais de votre propriété de dépendance se propagent ensuite à d’autres objets de manière à ce que vous n’ayez peut-être pas prévu.

Vous pouvez utiliser des constructeurs de classe pour définir des valeurs initiales pour une propriété de dépendance de type référence si vous souhaitez une valeur non null, mais sachez que cela serait considéré comme une valeur locale à des fins de vue d’ensemble des propriétés de dépendance. Il peut être plus approprié d’utiliser un modèle à cet effet, si votre classe prend en charge les modèles. Une autre façon d’éviter un modèle singleton, mais toujours fournir une valeur par défaut utile, consiste à exposer une propriété statique sur le type de référence qui fournit une valeur par défaut appropriée pour les valeurs de cette classe.

Propriétés de dépendance de type collection

Pour les propriétés de dépendance de type collection, vous devrez prendre en compte certains autres aspects relatifs à l’implémentation.

Les propriétés de dépendance de type collection sont relativement rares dans l’API Windows Runtime. Dans la plupart des cas, vous pouvez utiliser des collections où les éléments sont une sous-classe DependencyObject , mais la propriété de collection elle-même est implémentée en tant que propriété CLR ou C++ conventionnelle. Cela est dû au fait que les collections ne conviennent pas nécessairement à certains scénarios typiques où les propriétés de dépendance sont impliquées. Par exemple :

  • En règle générale, vous n’animez pas une collection.
  • En règle générale, vous ne préremplisez pas les éléments d’une collection avec des styles ou un modèle.
  • Bien que la liaison à des collections soit un scénario majeur, une collection n’a pas besoin d’être une propriété de dépendance pour être une source de liaison. Pour les cibles de liaison, il est plus courant d’utiliser des sous-classes d’ItemsControl ou DataTemplate pour prendre en charge les éléments de collection, ou pour utiliser des modèles de modèle d’affichage. Pour plus d’informations sur la liaison vers et à partir de collections, consultez La liaison de données en profondeur.
  • Les notifications pour les modifications de collection sont mieux traitées par le biais d’interfaces telles que INotifyPropertyChanged ou INotifyCollectionChanged, ou en dérivant le type de collection à partir de ObservableCollection<T>.

Néanmoins, les scénarios de propriétés de dépendance de type collection existent. Les trois sections suivantes fournissent des conseils sur l’implémentation d’une propriété de dépendance de type collection.

Initialisation de la collection

Lorsque vous créez une propriété de dépendance, vous pouvez établir une valeur par défaut au moyen de métadonnées de propriété de dépendance. Toutefois, veillez à ne pas utiliser une collection statique singleton comme valeur par défaut. Au lieu de cela, vous devez définir délibérément la valeur de la collection sur une collection unique (instance) dans le cadre de la logique de constructeur de classe pour la classe propriétaire de la propriété de collection.

// 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>();
}

Une dependencyProperty et sa valeur par défaut PropertyMetadata font partie de la définition statique de DependencyProperty. En fournissant une valeur de collection par défaut (ou d’une autre instance) comme valeur par défaut, elle sera partagée entre toutes les instances de votre classe au lieu de chaque classe ayant sa propre collection, comme cela est généralement souhaité.

Notifications de modifications

La définition de la collection en tant que propriété de dépendance ne fournit pas automatiquement de notification de modification pour les éléments de la collection en vertu du système de propriétés appelant la méthode de rappel « PropertyChanged ». Si vous souhaitez des notifications pour des collections ou des éléments de collection, par exemple pour un scénario de liaison de données, implémentez l’interface INotifyPropertyChanged ou INotifyCollectionChanged . Pour plus d’informations, voir Présentation détaillée de la liaison de données.

Considérations relatives à la sécurité des propriétés de dépendance

Déclarez les propriétés de dépendance en tant que propriétés publiques. Déclarez les identificateurs de propriété de dépendance en tant que membres en lecture seule statique publique. Même si vous tentez de déclarer d’autres niveaux d’accès autorisés par une langue (par exemple, protégée), une propriété de dépendance est toujours accessible via l’identificateur en combinaison avec les API du système de propriétés. La déclaration de l’identificateur de propriété de dépendance comme interne ou privé ne fonctionnera pas, car le système de propriétés ne peut pas fonctionner correctement.

Les propriétés wrapper ne sont vraiment qu’à des fins pratiques, les mécanismes de sécurité appliqués aux wrappers peuvent être contournés en appelant GetValue ou SetValue à la place. Conservez donc les propriétés wrapper publiques ; sinon, vous rendez votre propriété plus difficile pour les appelants légitimes à utiliser sans fournir d’avantage de sécurité réel.

Windows Runtime ne permet pas d’inscrire une propriété de dépendance personnalisée en lecture seule.

Propriétés de dépendance et constructeurs de classes

Il existe un principe général selon lequel les constructeurs de classe ne doivent pas appeler de méthodes virtuelles. Cela est dû au fait que les constructeurs peuvent être appelés pour accomplir l’initialisation de base d’un constructeur de classe dérivée, et l’entrée de la méthode virtuelle par le biais du constructeur peut se produire lorsque l’instance d’objet en cours de construction n’est pas encore complètement initialisée. Lorsque vous dérivez de n’importe quelle classe qui dérive déjà de DependencyObject, n’oubliez pas que le système de propriétés lui-même appelle et expose des méthodes virtuelles en interne dans le cadre de ses services. Pour éviter les problèmes potentiels liés à l’initialisation au moment de l’exécution, ne définissez pas de valeurs de propriété de dépendance au sein des constructeurs de classes.

Inscription des propriétés de dépendance pour les applications C++/CX

L’implémentation pour l’inscription d’une propriété dans C++/CX est plus difficile que C#, à la fois en raison de la séparation dans l’en-tête et le fichier d’implémentation, et aussi parce que l’initialisation au niveau de l’étendue racine du fichier d’implémentation est une mauvaise pratique. (Les extensions de composant Visual C++ (C++/CX) placent le code d’initialiseur statique de l’étendue racine directement dans DllMain, alors que les compilateurs C# attribuent les initialiseurs statiques aux classes et évitent donc les problèmes de verrouillage de charge DllMain .). La meilleure pratique ici consiste à déclarer une fonction d’assistance qui effectue toutes vos inscriptions de propriétés de dépendance pour une classe, une fonction par classe. Ensuite, pour chaque classe personnalisée que votre application consomme, vous devez référencer la fonction d’inscription de l’assistance exposée par chaque classe personnalisée que vous souhaitez utiliser. Appelez chaque fonction d’inscription d’assistance une fois dans le cadre du constructeur d’application (App::App()), avant InitializeComponent. Ce constructeur s’exécute uniquement lorsque l’application est vraiment référencée pour la première fois, elle ne s’exécute plus si une application suspendue reprend, par exemple. En outre, comme indiqué dans l’exemple d’inscription C++ précédent, la vérification nullptr autour de chaque appel d’inscription est importante : il est d’assurance qu’aucun appelant de la fonction ne peut inscrire la propriété deux fois. Un deuxième appel d’inscription plantait probablement votre application sans vérification de ce type, car le nom de la propriété serait un doublon. Vous pouvez voir ce modèle d’implémentation dans l’exemple d’utilisateur XAML et de contrôles personnalisés si vous examinez le code de la version C++/CX de l’exemple.