Propriétés de dépendance personnalisées (WPF .NET)

Les développeurs d’applications windows Presentation Foundation (WPF) et les auteurs de composants peuvent créer des propriétés de dépendance personnalisées pour étendre les fonctionnalités de leurs propriétés. Contrairement à une propriété CLR (Common Language Runtime), une propriété de dépendance ajoute la prise en charge du style, de la liaison de données, de l’héritage, des animations et des valeurs par défaut. Background, Widthet Text sont des exemples de propriétés de dépendance existantes dans les classes WPF. Cet article explique comment implémenter des propriétés de dépendance personnalisées et présente des options permettant d’améliorer les performances, la facilité d’utilisation et la polyvalence.

Important

La documentation du Guide du bureau pour .NET 7 et .NET 6 est en cours de construction.

Prérequis

L’article suppose une connaissance de base des propriétés de dépendance et que vous avez lu la vue d’ensemble des propriétés de dépendance. Pour suivre les exemples de cet article, il vous aide à connaître le langage XAML (Extensible Application Markup Language) et à savoir comment écrire des applications WPF.

Identificateur de propriété de dépendance

Les propriétés de dépendance sont des propriétés inscrites auprès du système de propriétés WPF via Register ou RegisterReadOnly par appels. La Register méthode retourne une DependencyProperty instance qui contient le nom inscrit et les caractéristiques d’une propriété de dépendance. Vous allez affecter l’instance DependencyProperty à un champ en lecture seule statique, appelé identificateur de propriété de dépendance, qui par convention est nommé <property name>Property. Par exemple, le champ d’identificateur de la Background propriété est toujours BackgroundProperty.

L’identificateur de propriété de dépendance est utilisé comme champ de stockage pour obtenir ou définir des valeurs de propriété, plutôt que le modèle standard de sauvegarde d’une propriété avec un champ privé. Non seulement le système de propriétés utilise l’identificateur, les processeurs XAML peuvent l’utiliser, et votre code (et éventuellement le code externe) peut accéder aux propriétés de dépendance via leurs identificateurs.

Les propriétés de dépendance ne peuvent être appliquées qu’aux classes dérivées de DependencyObject types. La plupart des classes WPF prennent en charge les propriétés de dépendance, car DependencyObject elles sont proches de la racine de la hiérarchie de classes WPF. Pour plus d’informations sur les propriétés de dépendance et la terminologie et les conventions utilisées pour les décrire, consultez la vue d’ensemble des propriétés de dépendance.

Wrappers de propriété de dépendance

Les propriétés de dépendance WPF qui ne sont pas jointes sont exposées par un wrapper CLR qui implémente get et set accesseurs. En utilisant un wrapper de propriétés de propriété, les consommateurs de propriétés de dépendance peuvent obtenir ou définir des valeurs de propriété de dépendance, comme ils le feraient pour n’importe quelle autre propriété CLR. set Les get accesseurs interagissent avec le système de propriétés sous-jacent via DependencyObject.GetValue et DependencyObject.SetValue appellent, en passant l’identificateur de propriété de dépendance en tant que paramètre. Les consommateurs de propriétés de dépendance n’appellent généralement pas ou SetValue n’appellent GetValue pas directement, mais si vous implémentez une propriété de dépendance personnalisée, vous utiliserez ces méthodes dans le wrapper.

Quand implémenter une propriété de dépendance

Lorsque vous implémentez une propriété sur une classe qui dérive de DependencyObject, vous en faites une propriété de dépendance en enregistrant votre propriété avec un DependencyProperty identificateur. S’il est avantageux de créer une propriété de dépendance dépend de votre scénario. Bien que la sauvegarde de votre propriété avec un champ privé soit suffisante pour certains scénarios, envisagez d’implémenter une propriété de dépendance si vous souhaitez que votre propriété prend en charge une ou plusieurs des fonctionnalités WPF suivantes :

  • Propriétés qui sont définis dans un style. Pour plus d’informations, consultez Styles et modèles.

  • Propriétés qui prennent en charge la liaison de données. Pour plus d’informations sur les propriétés de dépendance de liaison de données, consultez Lier les propriétés de deux contrôles.

  • Propriétés qui sont paramétrables par le biais de références de ressources dynamiques. Pour plus d’informations, consultez les ressources XAML.

  • Propriétés qui héritent automatiquement de leur valeur d’un élément parent dans l’arborescence d’éléments. Pour cela, vous devez vous inscrire à l’aide RegisterAttached, même si vous créez également un wrapper de propriétés pour l’accès CLR. Pour plus d’informations, consultez Héritage des valeurs de propriété.

  • Propriétés animatables. Pour plus d’informations, consultez Vue d’ensemble de l’animation.

  • Notification par le système de propriétés WPF lorsqu’une valeur de propriété change. Les modifications peuvent être dues à des actions effectuées par le système de propriétés, l’environnement, l’utilisateur ou les styles. Votre propriété peut spécifier une méthode de rappel dans les métadonnées de propriété qui seront appelées chaque fois que le système de propriétés détermine que votre valeur de propriété a changé. Le forçage de valeur de propriété est un concept connexe. Pour plus d’informations, consultez Rappels et validation des propriétés de dépendance.

  • Accès aux métadonnées de propriété de dépendance, lues par les processus WPF. Par exemple, vous pouvez utiliser les métadonnées de propriété pour :

    • Spécifiez si une valeur de propriété de dépendance modifiée doit faire en sorte que le système de disposition recompile les visuels d’un élément.

    • Définissez la valeur par défaut d’une propriété de dépendance, en substituant les métadonnées sur les classes dérivées.

  • Le concepteur WPF Visual Studio prend en charge, par exemple la modification des propriétés d’un contrôle personnalisé dans la fenêtre Propriétés . Pour plus d’informations, consultez vue d’ensemble de la création de contrôles.

Pour certains scénarios, la substitution des métadonnées d’une propriété de dépendance existante est une meilleure option que l’implémentation d’une nouvelle propriété de dépendance. Si un remplacement de métadonnées est pratique dépend de votre scénario et de la manière dont ce scénario ressemble à l’implémentation des propriétés et classes de dépendance WPF existantes. Pour plus d’informations sur la substitution de métadonnées sur les propriétés de dépendance existantes, consultez métadonnées de propriété de dépendance.

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

Suivez ces étapes pour créer une propriété de dépendance. Certaines des étapes peuvent être combinées et implémentées dans une seule ligne de code.

  1. (Facultatif) Créez des métadonnées de propriété de dépendance.

  2. Inscrivez la propriété de dépendance auprès du système de propriétés, en spécifiant un nom de propriété, un type de propriétaire, le type de valeur de propriété et éventuellement les métadonnées de propriété.

  3. Définissez un DependencyProperty identificateur en tant que public static readonly champ sur le type de propriétaire. Le nom du champ d’identificateur est le nom de propriété avec le suffixe Property ajouté.

  4. Définissez une propriété wrapper CLR portant le même nom que le nom de propriété de dépendance. Dans le wrapper CLR, implémentez get et set accesseurs qui se connectent à la propriété de dépendance qui sauvegarde le wrapper.

Inscription de la propriété

Pour que votre propriété soit une propriété de dépendance, vous devez l’inscrire auprès du système de propriétés. Pour inscrire votre propriété, appelez la Register méthode à partir du corps de votre classe, mais en dehors de toutes les définitions de membre. La Register méthode retourne un identificateur de propriété de dépendance unique que vous utiliserez lors de l’appel de l’API du système de propriétés. La raison pour laquelle l’appel Register est effectué en dehors des définitions de membre est que vous affectez la valeur de retour à un public static readonly champ de type DependencyProperty. Ce champ, que vous allez créer dans votre classe, est l’identificateur de votre propriété de dépendance. Dans l’exemple suivant, le premier argument du nom de la propriété AquariumGraphicde Register dépendance .

// Register a dependency property with the specified property name,
// property type, owner type, and property metadata. Store the dependency
// property identifier as a public static readonly member of the class.
public static readonly DependencyProperty AquariumGraphicProperty =
    DependencyProperty.Register(
      name: "AquariumGraphic",
      propertyType: typeof(Uri),
      ownerType: typeof(Aquarium),
      typeMetadata: new FrameworkPropertyMetadata(
          defaultValue: new Uri("http://www.contoso.com/aquarium-graphic.jpg"),
          flags: FrameworkPropertyMetadataOptions.AffectsRender,
          propertyChangedCallback: new PropertyChangedCallback(OnUriChanged))
    );
' Register a dependency property with the specified property name,
' property type, owner type, and property metadata. Store the dependency
' property identifier as a public static readonly member of the class.
Public Shared ReadOnly AquariumGraphicProperty As DependencyProperty =
    DependencyProperty.Register(
        name:="AquariumGraphic",
        propertyType:=GetType(Uri),
        ownerType:=GetType(Aquarium),
        typeMetadata:=New FrameworkPropertyMetadata(
            defaultValue:=New Uri("http://www.contoso.com/aquarium-graphic.jpg"),
            flags:=FrameworkPropertyMetadataOptions.AffectsRender,
            propertyChangedCallback:=New PropertyChangedCallback(AddressOf OnUriChanged)))

Remarque

La définition de la propriété de dépendance dans le corps de classe est l’implémentation classique, mais il est également possible de définir une propriété de dépendance dans le constructeur statique de classe. Cette approche peut être plus logique si vous avez besoin de plus d’une ligne de code pour initialiser la propriété de dépendance.

Nommage des propriétés de dépendance

La convention d’affectation de noms établie pour les propriétés de dépendance est obligatoire pour le comportement normal du système de propriétés. Le nom du champ d’identificateur que vous créez doit être le nom inscrit de la propriété avec le suffixe Property.

Un nom de propriété de dépendance doit être unique dans la classe d’inscription. Les propriétés de dépendance héritées par le biais d’un type de base ont déjà été inscrites et ne peuvent pas être inscrites par un type dérivé. Toutefois, vous pouvez utiliser une propriété de dépendance inscrite par un type différent, même un type dont votre classe n’hérite pas, en ajoutant votre classe en tant que propriétaire de la propriété de dépendance. Pour plus d’informations sur l’ajout d’une classe en tant que propriétaire, consultez métadonnées de propriété de dépendance.

Implémentation d’un wrapper de propriétés

Par convention, le nom de la propriété wrapper doit être le même que le premier paramètre de l’appel Register , qui est le nom de propriété de dépendance. Votre implémentation de wrapper appelle GetValue l’accesseur get et SetValue dans l’accesseur set (pour les propriétés en lecture-écriture). L’exemple suivant montre un wrapper, suivant l’appel d’inscription et la déclaration de champ d’identificateur. Toutes les propriétés de dépendance publique sur les classes WPF utilisent un modèle wrapper similaire.

// Register a dependency property with the specified property name,
// property type, owner type, and property metadata. Store the dependency
// property identifier as a public static readonly member of the class.
public static readonly DependencyProperty AquariumGraphicProperty =
    DependencyProperty.Register(
      name: "AquariumGraphic",
      propertyType: typeof(Uri),
      ownerType: typeof(Aquarium),
      typeMetadata: new FrameworkPropertyMetadata(
          defaultValue: new Uri("http://www.contoso.com/aquarium-graphic.jpg"),
          flags: FrameworkPropertyMetadataOptions.AffectsRender,
          propertyChangedCallback: new PropertyChangedCallback(OnUriChanged))
    );

// Declare a read-write property wrapper.
public Uri AquariumGraphic
{
    get => (Uri)GetValue(AquariumGraphicProperty);
    set => SetValue(AquariumGraphicProperty, value);
}
' Register a dependency property with the specified property name,
' property type, owner type, and property metadata. Store the dependency
' property identifier as a public static readonly member of the class.
Public Shared ReadOnly AquariumGraphicProperty As DependencyProperty =
    DependencyProperty.Register(
        name:="AquariumGraphic",
        propertyType:=GetType(Uri),
        ownerType:=GetType(Aquarium),
        typeMetadata:=New FrameworkPropertyMetadata(
            defaultValue:=New Uri("http://www.contoso.com/aquarium-graphic.jpg"),
            flags:=FrameworkPropertyMetadataOptions.AffectsRender,
            propertyChangedCallback:=New PropertyChangedCallback(AddressOf OnUriChanged)))

' Declare a read-write property wrapper.
Public Property AquariumGraphic As Uri
    Get
        Return CType(GetValue(AquariumGraphicProperty), Uri)
    End Get
    Set
        SetValue(AquariumGraphicProperty, Value)
    End Set
End Property

Sauf dans de rares cas, votre implémentation de wrapper ne doit contenir GetValue que du code.SetValue Pour les raisons de cela, consultez Implications pour les propriétés de dépendance personnalisées.

Si votre propriété ne suit pas les conventions d’affectation de noms établies, vous pouvez rencontrer ces problèmes :

  • Certains aspects des styles et des modèles ne fonctionnent pas.

  • La plupart des outils et concepteurs s’appuient sur les conventions d’affectation de noms pour sérialiser correctement XAML et fournir une assistance d’environnement de concepteur au niveau de la propriété.

  • L’implémentation actuelle du chargeur XAML WPF contourne entièrement les wrappers et s’appuie sur la convention d’affectation de noms pour traiter les valeurs d’attribut. Pour plus d’informations, consultez les propriétés de chargement et de dépendance XAML.

Métadonnées de propriété de dépendance

Lorsque vous inscrivez une propriété de dépendance, le système de propriétés crée un objet de métadonnées pour stocker les caractéristiques de propriété. Les surcharges de la Register méthode vous permettent de spécifier les métadonnées de propriété pendant l’inscription, par exemple Register(String, Type, Type, PropertyMetadata). Une utilisation courante des métadonnées de propriété consiste à appliquer une valeur par défaut personnalisée pour les nouvelles instances qui utilisent une propriété de dépendance. Si vous ne fournissez pas de métadonnées de propriété, le système de propriétés affecte les valeurs par défaut à la plupart des caractéristiques de propriété de dépendance.

Si vous créez une propriété de dépendance sur une classe dérivée FrameworkElement, vous pouvez utiliser la classe FrameworkPropertyMetadata de métadonnées plus spécialisée plutôt que sa classe PropertyMetadatade base. Plusieurs FrameworkPropertyMetadata signatures de constructeur vous permettent de spécifier différentes combinaisons de caractéristiques de métadonnées. Si vous souhaitez simplement spécifier une valeur par défaut, utilisez FrameworkPropertyMetadata(Object) et transmettez la valeur par défaut au Object paramètre. Vérifiez que le type de valeur correspond à celui propertyType spécifié dans l’appel Register .

Certaines FrameworkPropertyMetadata surcharges vous permettent de spécifier des indicateurs d’option de métadonnées pour votre propriété. Le système de propriétés convertit ces indicateurs en propriétés discrètes et les valeurs d’indicateur sont utilisées par les processus WPF, tels que le moteur de disposition.

Définition des indicateurs de métadonnées

Tenez compte des éléments suivants lors de la définition d’indicateurs de métadonnées :

  • Si votre valeur de propriété (ou la modifie) affecte la façon dont le système de disposition restitue un élément d’interface utilisateur, définissez un ou plusieurs des indicateurs suivants :

    • AffectsMeasure, qui indique qu’une modification de la valeur de propriété nécessite une modification du rendu de l’interface utilisateur, en particulier l’espace occupé par un objet dans son parent. Par exemple, définissez cet indicateur de métadonnées pour une Width propriété.

    • AffectsArrange, qui indique qu’une modification de la valeur de propriété nécessite une modification dans le rendu de l’interface utilisateur, en particulier la position d’un objet au sein de son parent. En règle générale, l’objet ne change pas non plus la taille. Par exemple, définissez cet indicateur de métadonnées pour une Alignment propriété.

    • AffectsRender, qui indique qu’une modification s’est produite qui n’affecte pas la disposition et la mesure, mais nécessite toujours un autre rendu. Par exemple, définissez cet indicateur pour une Background propriété ou toute autre propriété qui affecte la couleur d’un élément.

    Vous pouvez également utiliser ces indicateurs comme entrées pour vos implémentations de remplacement des rappels de système de propriétés (ou disposition). Par exemple, vous pouvez utiliser un OnPropertyChanged rappel pour appeler InvalidateArrange lorsqu’une propriété de l’instance signale une modification de valeur et a AffectsArrange défini dans les métadonnées.

  • Certaines propriétés affectent les caractéristiques de rendu de leur élément parent d’une autre manière. Par exemple, les modifications apportées à la MinOrphanLines propriété peuvent modifier le rendu global d’un document de flux. Utilisez ou AffectsParentMeasure signalez AffectsParentArrange les actions parentes dans vos propres propriétés.

  • Par défaut, les propriétés de dépendance prennent en charge la liaison de données. Toutefois, vous pouvez utiliser IsDataBindingAllowed pour désactiver la liaison de données lorsqu’il n’existe aucun scénario réaliste pour celui-ci, ou lorsque les performances de liaison de données sont problématiques, telles que sur les objets volumineux.

  • Bien que le mode de liaison de données par défaut pour les propriétés de dépendance soit OneWay, vous pouvez modifier le mode de liaison d’une liaison spécifique en TwoWay. Pour plus d’informations, consultez La direction de liaison. En tant qu’auteur de propriété de dépendance, vous pouvez même choisir d’effectuer une liaison bidirectionnelle en mode par défaut. Exemple de propriété de dépendance existante qui utilise une liaison de données bidirectionnelle, MenuItem.IsSubmenuOpenqui a un état basé sur d’autres propriétés et appels de méthode. Le scénario est IsSubmenuOpen que sa logique de définition et la composition de MenuItem, interagissent avec le style de thème par défaut. TextBox.Text est une autre propriété de dépendance WPF qui utilise par défaut une liaison bidirectionnelle.

  • Vous pouvez activer l’héritage des propriétés pour votre propriété de dépendance en définissant l’indicateur Inherits . L’héritage de propriété est utile pour les scénarios dans lesquels les éléments parent et enfant ont une propriété en commun et il est judicieux que l’élément enfant hérite de la valeur parente de la propriété commune. Un exemple de propriété héritée est DataContext, qui prend en charge les opérations de liaison qui utilisent le scénario maître-détail pour la présentation de données. L’héritage des valeurs de propriété vous permet de spécifier un contexte de données à la page ou à la racine de l’application, ce qui permet de les spécifier pour les liaisons d’éléments enfants. Bien qu’une valeur de propriété héritée remplace la valeur par défaut, les valeurs de propriété peuvent être définies localement sur n’importe quel élément enfant. Utilisez l’héritage de la valeur de propriété avec parcimonie, car il a un coût de performances. Pour plus d’informations, consultez Héritage des valeurs de propriété.

  • Définissez l’indicateur Journal pour indiquer que votre propriété de dépendance doit être détectée ou utilisée par les services de journalisation de navigation. Par exemple, la propriété définit l’indicateur SelectedIndexJournal pour recommander aux applications de conserver l’historique de journalisation des éléments sélectionnés.

Propriétés de dépendance en lecture seule

Vous pouvez définir une propriété de dépendance en lecture seule. Un scénario classique est une propriété de dépendance qui stocke l’état interne. Par exemple, IsMouseOver est en lecture seule, car son état ne doit être déterminé que par l’entrée de la souris. Pour plus d’informations, consultez les propriétés de dépendance en lecture seule.

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

Les propriétés de dépendance de type collection présentent des problèmes d’implémentation supplémentaires, tels que la définition d’une valeur par défaut pour les types de référence et la prise en charge de la liaison de données pour les éléments de collection. Pour plus d’informations, consultez propriétés de dépendance de type Collection.

Sécurité de propriété de dépendance

En règle générale, vous allez déclarer des propriétés de dépendance en tant que propriétés publiques et DependencyProperty des champs d’identificateur en tant que public static readonly champs. Si vous spécifiez un niveau d’accès plus restrictif, par protectedexemple, une propriété de dépendance est toujours accessible via son identificateur en combinaison avec les API du système de propriétés. Même un champ d’identificateur protégé est potentiellement accessible par le biais des API de création de rapports de métadonnées WPF ou de détermination de valeur, comme LocalValueEnumerator. Pour plus d’informations, consultez La sécurité des propriétés de dépendance.

Pour les propriétés de dépendance en lecture seule, la valeur retournée RegisterReadOnly est DependencyPropertyKey, et en général, vous ne rendrez DependencyPropertyKey pas membre public de votre classe. Étant donné que le système de propriétés WPF ne propage pas l’extérieur DependencyPropertyKey de votre code, une propriété de dépendance en lecture seule offre une meilleure set sécurité qu’une propriété de dépendance en lecture-écriture.

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

Il existe un principe général dans la programmation de code managé, souvent appliqué par les outils d’analyse du code, que les constructeurs de classe ne doivent pas appeler de méthodes virtuelles. Cela est dû au fait que les constructeurs de base peuvent être appelés pendant l’initialisation d’un constructeur de classe dérivée, et une méthode virtuelle appelée par un constructeur de base peut s’exécuter avant l’initialisation complète de la classe dérivée. Lorsque vous dérivez d’une classe qui dérive déjà de DependencyObject, le système de propriétés lui-même appelle et expose les méthodes virtuelles en interne. Ces méthodes virtuelles font partie des services de système de propriétés WPF. La substitution des méthodes permet aux classes dérivées de participer à la détermination de valeur. Pour éviter les problèmes potentiels liés à l’initialisation du runtime, vous ne devez pas définir de valeurs de propriété de dépendance dans les constructeurs de classes, sauf si vous suivez un modèle de constructeur spécifique. Pour plus d’informations, consultez Coffre modèles de constructeur pour DependencyObjects.

Voir aussi