Propriétés jointes personnalisées
Une propriété jointe est un concept défini par XAML. Les propriétés jointes sont généralement définies comme une forme spécialisée de la propriété de dépendance. Ce sujet 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.
Prérequis
Nous partons du principe que vous savez ce que sont 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 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.
Scénarios pour les propriétés jointes
Vous pouvez créer une propriété jointe si un mécanisme de définition de jeu de propriétés doit être disponible pour les classes autres que la classe de définition. Les scénarios les plus courants ici sont la prise en charge du layout et des services. Les exemples de propriétés de layout existantes sont Canvas.ZIndex et Canvas.Top. Dans un scénario de layout, les éléments existant comme éléments enfants sur les éléments contrôlant le layout sont en mesure d’exprimer individuellement des spécifications de layout à leurs éléments parents et chacun d’eux définit une valeur de propriété configurée par le parent en tant que 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 XAML Windows Runtime est que vous ne pouvez pas animer votre propriété jointe personnalisée.
Inscription d’une propriété jointe personnalisée
Si vous définissez la propriété jointe strictement pour son utilisation sur d’autres types, la classe dans laquelle la propriété est enregistrée ne doit pas nécessairement dériver de DependencyObject. Toutefois, vous devez disposer du paramètre cible pour que les accesseurs utilisent DependencyObject si vous suivez le modèle typique d’avoir votre propriété jointe également comme propriété de dépendance, afin que vous puissiez utiliser le stockage de la bibliothèque de propriétés.
Définissez votre propriété jointe en tant que propriété de dépendance en déclarant une propriété statique publique en lecture seule de type DependencyProperty. Vous définissez cette propriété à l’aide de la valeur renvoyée 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 nom RegisterAttached, avec la chaîne « Property » ajoutée à la fin. Il s’agit de la convention établie pour nommer les identifiants des propriétés de dépendance par rapport aux propriétés qu’elles déclarent.
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ées, 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 sous-traitant 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 « *Propriété » pour nommer des identifiants lorsqu’ils rencontrent une propriété de dépendance personnalisée dans un assembly référencé.
Accesseurs
La signature pour l’accesseur GetPropertyName doit être ce qui suit.
public static
valueType GetPropertyName (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 renvoyée valueType peut être aussi 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 cohérence des types. L’utilisation de la saisie dans les signatures getter et setter est une technique de cohérence des types recommandée.
La signature pour l’accesseur SetPropertyName doit être ce qui suit.
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 As
valueType)
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 sous-traitant XAML quand il rencontre votre propriété jointe dans une balise. La conversion de type ou l’extension de balisage existante doit être prise en charge pour le type utilisé afin que le type approprié puisse être créé à partir de la valeur d’attribut (laquelle est en fin de compte une simple chaîne). Le type d’objet de base est acceptable, mais vous souhaiterez souvent une cohérence des types supplémentaire. Pour ce faire, placez l’application du type dans les accesseurs.
Remarque
Il est également possible de définir une propriété jointe où l’utilisation prévue est par le biais de 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 cet exemple, le nom de la propriété jointe est IsMovable
. Les accesseurs doivent donc ê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’IU propre ; son objectif est uniquement de fournir les services de propriétés jointes 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 factoriser l’en-tête et le fichier de code. En outre, vous devez exposer l’identifiant 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 les mots clés .NET en lecture seule et un stockage implicite de 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. Le lieu d’appel typique de vos fonctions d’assistance d’inscription de propriétés pour toutes les propriétés dépendantes ou jointes provient du constructeur de l’application / 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));
}
Paramétrage de votre propriété jointe personnalisée à partir de la balise 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 de l’application pour l’application en question.
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 propriété jointe GameService.IsMovable
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 avec le nom de la propriété jointe. Ceci 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" .../>
Remarque
Si vous écrivez une IU XAML avec C++/CX, vous devez alors 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 en-tête code-behind associé (.xaml.h). C’est là que vous devez inclure (à l’aide de #include) l’en-tête de la définition du type de propriétaire de la propriété jointe.
Définition impérative de votre propriété jointe 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 valeur d’une propriété jointe personnalisée
Le type utilisé comme type valeur d’une propriété jointe personnalisée affecte l’utilisation, la définition ou l’utilisation ainsi que la définition. Le type 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 la fonction appelante RegisterAttached.
Le type valeur le plus courant pour les propriétés jointes (personnalisées ou autres) est une chaîne simple. Ceci est dû au fait que les propriétés jointes sont généralement destinées à l’utilisation des attributs XAML, et 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 valeur, qui ne prennent pas en charge la conversion de chaîne native, comme valeur de propriété jointe. Toutefois, ceci implique de choisir entre 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 ne l’utilisez que 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 qu’est-ce que cela change-t-il dans la façon dont un Espace 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 typique 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 Espace est d’être un conteneur de disposition positionné de manière absolue dans l’IU. Les enfants d’un Espace sont stockés dans une propriété définie par la classe de base Children. Parmi tous les volets, l’espace est le seul qui utilise le positionnement absolu. Il aurait gonflé le modèle objet du type commun UIElementpour ajouter des propriétés susceptibles d’être uniquement préoccupantes pour l’espace et ces cas particuliers UIElement où ils sont des éléments enfants d’un UIElement. La définition des propriétés de contrôle du layout d’un espace, pour être des propriétés jointes que n’importe quel UIElement peut utiliser, conserve le modèle objet plus propre.
Pour être un volet pratique, l’espace a un comportement qui remplace les méthodes au niveau de l’infrastructure Measure et Arrange. C’est là que l’espace vérifie réellement les valeurs de la propriété jointe sur ses enfants. Une partie des modèles Measure et Arrange est une boucle qui itère sur n’importe quel contenu, et un volet a la propriété Children qui le rend explicite, ce qui est censé être considéré comme l’enfant d’un volet. Par conséquent, le comportement du layout de l’espace itère dans ces enfants et effectue des appels Canvas.GetLeft et Canvas.GetTop statiques sur chaque enfant pour voir si ces propriétés jointes contiennent une valeur non par défaut (la valeur par défaut est 0). Ces valeurs sont ensuite utilisées pour positionner absolument chaque enfant dans le layout disponible de l’espace en fonction des valeurs spécifiques fournies par chaque enfant et validées à l’aide d’Arrange.
Le code se présente comme 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
}
Remarque
Pour plus d’informations sur le fonctionnement des volets, consultez Vue d’ensemble des volets personnalisés XAML..