Partager via


Propriétés jointes personnalisées

Une propriété jointe est un concept XAML. Les propriétés jointes sont généralement définies comme une forme spécialisée de propriété de dépendance. Cette rubrique explique comment implémenter une propriété jointe en tant que propriété de dépendance et comment définir la convention d’accesseur nécessaire pour que votre propriété jointe soit utilisable en XAML.

Prerequisites

Nous partons du principe 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 et que vous avez lu la vue d’ensemble des propriétés de dépendance. Vous devez également avoir lu la vue d’ensemble des propriétés jointes. Pour suivre les exemples de cette rubrique, vous devez également comprendre XAML et savoir comment écrire une application Windows Runtime de base à l’aide de C++, C# ou Visual Basic.

Scénarios pour les propriétés jointes

Vous pouvez créer une propriété jointe lorsqu’il existe une raison de disposer d’un mécanisme de paramètre de propriété disponible pour les classes autres que la classe de définition. Les scénarios les plus courants sont la prise en charge de la disposition et des services. Les propriétés de disposition existantes sont Canvas.ZIndex et Canvas.Top. Dans un scénario de disposition, les éléments qui existent en tant qu’éléments enfants pour les éléments de contrôle de disposition peuvent exprimer les exigences de disposition à leurs éléments parents individuellement, chacun définissant une valeur de propriété que le parent définit comme une propriété jointe. Un exemple de scénario de prise en charge des services dans l’API Windows Runtime est défini sur les propriétés jointes de ScrollViewer, telles que ScrollViewer.IsZoomChainingEnabled.

Avertissement

Une limitation existante de l’implémentation de Windows Runtime XAML est que vous ne pouvez pas animer votre propriété attachée personnalisée.

Enregistrement d'une propriété attachée personnalisée

Si vous définissez la propriété jointe strictement à utiliser sur d’autres types, la classe dans laquelle la propriété est inscrite n’a pas besoin de dériver de DependencyObject. Toutefois, vous devez avoir le paramètre cible pour que les accesseurs utilisent DependencyObject si vous suivez le modèle typique où votre propriété jointe est également une propriété de dépendance, afin que vous puissiez utiliser le magasin de propriétés de stockage.

Définissez votre propriété jointe en tant que propriété de dépendance en déclarant une propriété publique statiqueen lecture seule de type DependencyProperty. Vous définissez cette propriété à l’aide de la valeur de retour de la méthode RegisterAttached . Le nom de la propriété doit correspondre au nom de la propriété jointe que vous spécifiez comme paramètre de nomRegisterAttached, avec la chaîne «Property » ajoutée à la fin. Il s’agit de la convention établie pour nommer les identificateurs des propriétés de dépendance par rapport aux propriétés qu’elles représentent.

La zone principale où la définition d’une propriété jointe personnalisée diffère d’une propriété de dépendance personnalisée est la façon dont vous définissez les accesseurs ou les wrappers. Au lieu d’utiliser la technique wrapper décrite dans les propriétés de dépendance personnalisée, vous devez également fournir des méthodes GetPropertyName et SetPropertyName statiques en tant qu’accesseurs pour la propriété jointe. Les accesseurs sont utilisés principalement par l’analyseur XAML, bien que tout autre appelant puisse également les utiliser pour définir des valeurs dans des scénarios autres que XAML.

Important

Si vous ne définissez pas correctement les accesseurs, le processeur XAML ne peut pas accéder à votre propriété jointe et toute personne qui tente de l’utiliser obtiendra probablement une erreur d’analyseur XAML. En outre, les outils de conception et de codage s’appuient souvent sur les conventions « *Property » pour les identificateurs d’affectation de noms lorsqu’ils rencontrent une propriété de dépendance personnalisée dans un assembly référencé.

Accessors

La signature de l’accesseur GetPropertyName doit être celle-ci.

public static valueTypeGetPropertyName(DependencyObject target)

Pour Microsoft Visual Basic, il s’agit de ceci.

Public Shared Function Get PropertyName(ByVal target As DependencyObject) As valueType)

L’objet cible peut être d’un type plus spécifique dans votre implémentation, mais doit dériver de DependencyObject. La valeur de retour ValueType peut également être d’un type plus spécifique dans votre implémentation. Le type d’objet de base est acceptable, mais vous souhaiterez souvent que votre propriété jointe applique la sécurité du type. L'utilisation du typage dans les signatures getter et setter est une technique recommandée de sécurité de type.

La signature de l’accesseur SetPropertyName doit être celle-ci.

public static void Set PropertyName(DependencyObject target ,valueType value)

Pour Visual Basic, il s’agit de ceci.

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

L’objet cible peut être d’un type plus spécifique dans votre implémentation, mais doit dériver de DependencyObject. L’objet value et son valueType peuvent être d’un type plus spécifique dans votre implémentation. N’oubliez pas que la valeur de cette méthode est l’entrée provenant du processeur XAML lorsqu’elle rencontre votre propriété jointe dans le balisage. Il doit y avoir une conversion de type ou une prise en charge de l’extension de balisage existante pour le type que vous utilisez, afin que le type approprié puisse être créé à partir d’une valeur d’attribut (qui est finalement simplement une chaîne). Le type d’objet de base est acceptable, mais vous souhaiterez souvent une sécurité de type supplémentaire. Pour ce faire, appliquez le contrôle de type dans les accesseurs.

Note

Il est également possible de définir une propriété jointe où l’utilisation prévue est via la syntaxe d’élément de propriété. Dans ce cas, vous n’avez pas besoin de conversion de type pour les valeurs, mais vous devez vous assurer que les valeurs que vous envisagez peuvent être construites en XAML. VisualStateManager.VisualStateGroups est un exemple de propriété jointe existante qui prend uniquement en charge l’utilisation des éléments de propriété.

Exemple de code

Cet exemple montre l’inscription de propriétés de dépendance (à l’aide de la méthode RegisterAttached ), ainsi que les accesseurs Get et Set , pour une propriété jointe personnalisée. Dans l’exemple, le nom de la propriété jointe est IsMovable. Par conséquent, les accesseurs doivent être nommés GetIsMovable et SetIsMovable. Le propriétaire de la propriété jointe est une classe de service nommée GameService qui n’a pas d’interface utilisateur propre ; son objectif est uniquement de fournir les services de propriétés attachés lorsque la propriété jointe GameService.IsMovable est utilisée.

La définition de la propriété jointe en C++/CX est un peu plus complexe. Vous devez décider comment prendre en compte l’en-tête et le fichier de code. En outre, vous devez exposer l’identificateur en tant que propriété avec uniquement un accesseur get , pour des raisons décrites dans les propriétés de dépendance personnalisées. Dans C++/CX, vous devez définir explicitement cette relation de champ de propriété plutôt que de compter sur le mot-clé readonly de .NET et le soutien implicite des propriétés simples. Vous devez également effectuer l’inscription de la propriété jointe au sein d’une fonction d’assistance qui ne s’exécute qu’une seule fois, lorsque l’application démarre d’abord, mais avant que toutes les pages XAML nécessitant la propriété jointe soient chargées. L’emplacement typique pour appeler vos fonctions d’assistance d’inscription de propriétés pour toutes les propriétés dépendantes ou jointes est depuis le constructeur de App / Application dans le code de votre fichier app.xaml.

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

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

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

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

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

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

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

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

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

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

using namespace UserAndCustomControls;

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

GameService::GameService() {};

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

Définition de votre propriété attachée personnalisée à partir du code XAML

Une fois que vous avez défini votre propriété jointe et inclus ses membres de support dans le cadre d’un type personnalisé, vous devez ensuite rendre les définitions disponibles pour l’utilisation XAML. Pour ce faire, vous devez mapper un espace de noms XAML qui référence l’espace de noms de code qui contient la classe appropriée. Dans les cas où vous avez défini la propriété jointe dans le cadre d’une bibliothèque, vous devez inclure cette bibliothèque dans le cadre du package d’application pour l’application.

Un mappage d’espace de noms XML pour XAML est généralement placé dans l’élément racine d’une page XAML. Par exemple, pour la classe nommée GameService dans l’espace de noms UserAndCustomControls qui contient les définitions de propriétés jointes affichées dans les extraits de code précédents, le mappage peut ressembler à ceci.

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

À l’aide du mappage, vous pouvez définir votre GameService.IsMovable propriété jointe sur n’importe quel élément qui correspond à votre définition cible, y compris un type existant défini par Windows Runtime.

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

Si vous définissez la propriété sur un élément qui se trouve également dans le même espace de noms XML mappé, vous devez toujours inclure le préfixe sur le nom de la propriété jointe. Cela est dû au fait que le préfixe qualifie le type de propriétaire. L’attribut de la propriété jointe ne peut pas être supposé se trouver dans le même espace de noms XML que l’élément dans lequel l’attribut est inclus, même si, par des règles XML normales, les attributs peuvent hériter de l’espace de noms à partir d’éléments. Par exemple, si vous définissez GameService.IsMovable sur un type personnalisé de ImageWithLabelControl (définition non affichée), et même si les deux étaient définis dans le même espace de noms de code mappé au même préfixe, le code XAML serait toujours le suivant.

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

Note

Si vous écrivez une interface utilisateur XAML avec C++/CX, vous devez inclure l’en-tête du type personnalisé qui définit la propriété jointe, chaque fois qu’une page XAML utilise ce type. Chaque page XAML a un fichier d'en-tête code-behind associé (.xaml.h). C’est ici que vous devriez inclure (en utilisant #include) l’en-tête pour la définition du type de propriétaire de la propriété attachée.

Définition impérative de votre propriété attachée personnalisée

Vous pouvez également accéder à une propriété jointe personnalisée à partir du code impératif. Le code ci-dessous montre comment procéder.

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

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

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

Type de valeur d’une propriété attachée personnalisée

Le type utilisé comme type valeur d’une propriété jointe personnalisée affecte l’utilisation, la définition ou l’utilisation et la définition. Le type de valeur de la propriété jointe est déclaré à plusieurs endroits : dans les signatures des méthodes d’accesseur Get et Set , ainsi que comme paramètre propertyType de l’appel RegisterAttached .

Le type de valeur le plus courant pour les propriétés jointes (personnalisées ou autres) est une chaîne simple. Cela est dû au fait que les propriétés jointes sont généralement destinées à l’utilisation des attributs XAML et que l’utilisation d’une chaîne comme type valeur conserve les propriétés légères. D’autres primitives qui ont une conversion native en méthodes de chaîne, telles que l’entier, le double ou une valeur d’énumération, sont également courantes en tant que types valeur pour les propriétés jointes. Vous pouvez utiliser d'autres types de valeur, qui ne prennent pas en charge la conversion de chaîne native, comme valeur pour une propriété jointe. Toutefois, cela implique de choisir l’utilisation ou l’implémentation :

  • Vous pouvez laisser la propriété jointe telle quelle, mais la propriété jointe peut prendre en charge l’utilisation uniquement lorsque la propriété jointe est un élément de propriété et que la valeur est déclarée en tant qu’élément d’objet. Dans ce cas, le type de propriété doit prendre en charge l’utilisation XAML en tant qu’élément d’objet. Pour les classes de référence Windows Runtime existantes, vérifiez la syntaxe XAML pour vous assurer que le type prend en charge l’utilisation des éléments d’objet XAML.
  • Vous pouvez laisser la propriété jointe telle qu’elle est, mais l’utiliser uniquement dans une utilisation d’attribut par le biais d’une technique de référence XAML telle qu’une liaison ou StaticResource qui peut être exprimée sous forme de chaîne.

En savoir plus sur l’exemple Canvas.Left

Dans les exemples précédents d’utilisations de propriétés jointes, nous avons montré différentes façons de définir la propriété jointe Canvas.Left . Mais que change-t-il sur la façon dont un canevas interagit avec votre objet, et quand cela se produit-il ? Nous allons examiner cet exemple particulier plus loin, car si vous implémentez une propriété jointe, il est intéressant de voir ce qu’une classe de propriétaire de propriété jointe classique a l’intention de faire avec ses valeurs de propriété jointes si elle les trouve sur d’autres objets.

La fonction principale d’un canevas est d’être un conteneur de disposition positionné de manière absolue dans l’interface utilisateur. Les enfants d’un canevas sont stockés dans une propriété définie par la classe de base Children. Parmi tous les panneaux , le canevas est le seul qui utilise le positionnement absolu. Il aurait gonflé le modèle objet du type UIElement commun pour ajouter des propriétés susceptibles d’être uniquement préoccupantes pour Canvas et ces cas uiElement particuliers où ils sont des éléments enfants d’un UIElement. La définition des propriétés de contrôle de disposition d’un Canvas en tant que propriétés attachées, que n’importe quel UIElement peut utiliser, permet de garder le modèle objet plus propre.

Pour être un panneau pratique, Canvas a un comportement qui remplace les méthodes Measure et Arrange au niveau de l’infrastructure. C’est là que Canvas vérifie réellement les valeurs de propriété jointes sur ses enfants. Une partie des modèles Measure et Arrange est une boucle qui itère sur tout contenu, et un panneau a la propriété Children qui rend explicite ce qui est censé être considéré comme un enfant d'un panneau. Le comportement de disposition Canvas parcourt ces enfants et procède à des appels statiques Canvas.GetLeft et Canvas.GetTop sur chaque enfant afin de vérifier si ces propriétés jointes contiennent une valeur différente de la valeur par défaut (la valeur par défaut est 0). Ces valeurs sont ensuite utilisées pour positionner absolument chaque enfant dans l’espace de disposition disponible du canevas en fonction des valeurs spécifiques fournies par chaque enfant et validées à l’aide de Arrange.

Le code ressemble à ce pseudocode.

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

Note

Pour plus d’informations sur le fonctionnement des panneaux, consultez la vue d’ensemble des panneaux personnalisés XAML.