__property
Remonter à Extensions de mots clés C++
Cette section présente comment C++ Builder prend en charge le mot clé __property, un élément essentiel dans le modèle PME (property-method-event) utilisé pour la création d'interfaces utilisateur dans C++.
Une propriété apparaît comme un champ en termes de syntaxe lorsqu'on y accède dans le code, mais sa lecture ou son écriture peut être mise en oeuvre par un champ ou une méthode. En contrepartie, la lecture et l'écriture de propriétés ont des effets secondaires tels qu'une "évaluation paresseuse" lors de la lecture, et l'invalidation des données lors de l'écriture.
Voici un exemple simple de propriété. La lecture est effectuée depuis un champ appelé mX
, et l'écriture est effectuée par l'intermédiaire d'une méthode setX()
:
int mX;
void setX(int value);
__property int X = {read = mX, write = setX};
Lorsqu'elle est utilisée dans le code, sa syntaxe s'apparente à celle d'un champ :
int foo = myObject->X;
myObject->X = 5;
La première ligne (celle qui lit la propriété) effectue la lecture à partir du champ mX
. Comme dans cet exemple le spécificateur d'accès write utilise une méthode, la deuxième ligne assignant une valeur à la propriété invoque la méthode setX
qui transmet la valeur à la méthode. C'est à la méthode d'agir : si vous voulez que le champ mX
soit effectivement mis à jour lors de la définition (écriture) de la propriété, la méthode doit définir la valeur de mX
.
Les développeurs de composants utilisent généralement les propriétés. Les propriétés sont un moyen pratique de gérer les contrôles de l'interface utilisateur, et ce à deux niveaux :
- Au niveau du comportement : en affectant des valeurs (dans ce cas, un contrôle d'interface utilisateur doit exécuter une action définie via une valeur).
- Au niveau du streaming : enregistrement et restauration d'un ensemble de contrôles d'interface utilisateur et de leurs valeurs.
Alors qu'il est possible d'utiliser des propriétés avec n'importe quel code C++, il est probable que vous n'interagirez avec elles qu'en écrivant du code utilisant des interfaces utilisateur VCL ou FMX. Pour coder avec des composants VCL et FMX, il est important de comprendre le fonctionnement des propriétés et comment elles peuvent être utilisées efficacement en les traitant comme des accès de type champ, et en considérant que vous ne pouvez pas obtenir l'adresse ou la référence d'une propriété.
En matière de conception d'interface utilisateur, les propriétés sont essentielles. Les propriétés utilisant le spécificateur __published sont affichées dans l'inspecteur d'objets lors de la construction d'une interface utilisateur et peuvent être définies dans l'inspecteur d'objets en mode Conception ou dans le code à l'exécution.
Pour la conception d'interface utilisateur, les propriétés de type __closure (un pointeur de méthode orienté objet) sont utilisées pour contenir des événements.
Les propriétés de C++Builder offrent de nombreuses fonctionnalités comme l'accès indexé, les valeurs par défaut lors du streaming et bien d'autres encore tout aussi puissantes.
Sommaire
Syntaxe __property
La déclaration de __property spécifie un nom et un type, et contient au moins un spécificateur d'accès. La syntaxe d'une déclaration de propriété a la forme suivante :
__property type propertyName = { attributes };
ou dans le cas d'une propriété indexée,
__property type propertyName [index1Type index1 ][indexNType indexN ] = { attributes };
où :
type
est un type de donnée intégré ou préalablement déclaré.propertyName
est un identificateur valide.indexNType
est un type de donnée intégré ou préalablement déclaré.indexN
est le nom d'un paramètre index
- Remarque: Les paramètres indexN entre crochets sont facultatifs. S'ils sont présents, ils déclarent une propriété tableau.
attributes
est une séquence séparée par des virgules de spécificateursread
,write
,stored
,index
,default
(ounondefault
), ouimplements
.
- Remarque: Chaque déclaration de propriété doit comporter au moins un spécificateur read ou write.
Analysons les exemples ci-dessous à partir des champs et méthodes suivants dans une classe :
private:
int readX;
void writeX(int Value);
double GetZ();
float cellValue(int row, int col);
void setCellValue(int row, int col, float Value);
int GetCoordinate(int Index);
void SetCoordinate(int Index, int Value);
Les déclarations de propriété suivantes sont valides :
// Standard, common property declaration, reading from a field and writing via a method:
__property int X = {read = readX, write = writeX};
// A read-only property (note the absence of the write specifier.)
__property double Z = {read = GetZ};
// An indexed property - note two indices in use:
__property float Cells[int row][int col] = {read = cellValue,
write = setCellValue};
// Redeclaring a property declared in an ancestor class (used for redeclaring in a wider visibility scope, such as bringing an ancestor protected property to [[public]] or [[__published]] scope):
__property Foo;
// Wrapping an indexed method into a simple property:
__property int Left = { index = 0, read = GetCoordinate,
write = SetCoordinate };
// Another indexed method wrapped to a simple property, this time with specifiers used for streaming:
__property int Top = { read = GetCoordinate,
write = SetCoordinate, index = 1, stored = true, default = 5 };
Des restrictions existent concernant le mot-clé _property :
- Vous ne pouvez pas obtenir l'adresse de la propriété,
- Vous ne pouvez pas transmettre une propriété par référence,
car il s'agit d'une caractéristique syntaxique qui peut être soutenue par un champ ou une méthode.
Accès à ___property
Chaque déclaration __property doit comporter des spécificateurs read, write ou les deux. Ce sont des spécificateurs d'accès. Ils ont la forme suivante :
read = _ // member
write = _ // member
member
peut être un champ ou une méthode, privé ou protégé, localisable dans n'importe quelle visibilité. Lorsque vous utilisez le spécificateur read
avec une méthode, cette dernière renvoie le même type que la propriété sans les paramètres.
Lorsque vous utilisez le spécificateur write
avec une méthode, cette dernière renvoie void. Elle a un seul paramètre du même type plus des paramètres pour chacun des index de tableau.
Propriétés tableau
Les propriétés tableau sont des propriétés indexées. Elles peuvent représenter les éléments d'une liste, les contrôles enfant d'un contrôle ou les pixels d'un bitmap.
La déclaration d'une propriété tableau inclut une liste de paramètres spécifiant le nom et le type des index.
__property type PropertyName[IndexType IndexName]
Il est possible de faire se succéder des déclarations de tableau multiples dans le cas d'une propriété à indexation multiple :
__property type PropertyName[IndexTypeA IndexNameA][IndexTypeB IndexNameB]
A la différence des tableaux, les propriétés tableau autorisent les index de tout type. Pour les propriétés tableau, les spécificateurs d'accès doivent lister des méthodes plutôt que des champs.
- La méthode d'un spécificateur
read
doit être une fonction qui prend le nombre et le type des paramètres listés dans la liste de paramètres index de la propriété, dans le même ordre, et dont le type de résultat est identique au type de la propriété. - Lorsque vous utilisez le spécificateur
read
, le type de résultat de la méthode est identique au type de la propriété. - Lorsque vous utilisez le spécificateur
write
, vous devez ajouter une valeur supplémentaire ou un paramètre const du même type que la propriété.
Spécificateurs d'index
Les spécificateurs d'index permettent à plusieurs propriétés de partager la même méthode d'accès alors qu'elles représentent des valeurs différentes. Un spécificateur d'index est composé de l'index de la directive suivi de la constante entière comprise entre -2147483647 et 2147483647. Par exemple :
class TRectangle {
private:
float FCoordinates[4];
protected:
float GetCoordinate(int index){
return FCoordinates[index];
}
void SetCoordinate(int index, float value){
FCoordinates[index]=value;
}
public:
__property int Left = {read = GetCoordinate, write = SetCoordinate, index = 0};
__property int Top = {read = GetCoordinate, write = SetCoordinate, index = 1};
__property int Right = {read = GetCoordinate, write = SetCoordinate, index = 2};
__property int Bottom = {read = GetCoordinate, write = SetCoordinate, index = 3};
__property int Coordinates[int index] = {read = GetCoordinate, write = SetCoordinate};
};
Si une propriété a un spécificateur d'index, ses spécificateurs de propriété read
et write
doivent être des méthodes plutôt que des champs.
Une méthode d'accès d'une propriété ayant un spécificateur d'index doit prendre un paramètre valeur supplémentaire de type Integer.
- Dans le cas d'une fonction
read
, ce doit être le dernier paramètre ; - Dans le cas d'une procédure
write
, ce doit être l'avant-dernier paramètre (avant le paramètre spécifiant la valeur de la propriété).
Quand un programme accède à la propriété, la constante entière de la propriété est automatiquement transmise à la méthode d'accès.
Spécificateurs de stockage
Les directives facultatives stored
, default
et nodefault
, connues en tant que spécificateurs de stockage, n'ont pas d'effet sur le comportement du programme, mais contrôlent si les valeurs des propriétés publiées doivent ou non être enregistrées dans les fichiers fiche. Les contrôles VCL et FMX de streaming sont gérés par la RTL. Utilisez ces paramètres lorsque vous développez des composants VCL ou FMX.
Stored
Cette directive est utilisée par le système de streaming de la fiche. Par défaut, toutes les propriétés sont mises en flux dans un fichier .dfm
. Cela vous permet d'exclure une propriété (sous condition ou toujours) du streaming. Vous ne pouvez pas obliger le streaming d'une propriété en définissant la valeur de Stored à true
. Pour qu'elle ne soit pas mise en flux, il suffit de la définir sur false
.
La directive stored
doit être suivie par true
, false
, le nom d'un champ booléen, ou le nom d'une méthode sans paramètre qui renvoie une valeur booléenne.
stored = [Boolean]
Si la propriété n'a pas de directive stored, elle est traitée comme si elle était à True
par défaut.
Default et nodefault
La directive default
vous permet de spécifier la valeur par défaut d'une propriété. Elle est utilisée par l'inspecteur d'objets pour dessiner la propriété différemment lorsqu'elle n'a pas un comportement par défaut. Lorsque leur valeur correspond à la valeur par défaut, les propriétés ne sont pas mises en flux.
Il faut savoir que la spécification de la valeur par défaut ne provoque pas l'initialisation de la propriété. Vous devez initialiser les champs (pour les propriétés supportées par des champs) dans votre constructeur de classe. Si vous changez la valeur d'initialisation d'un champ qui supporte une propriété, vous devez aussi mettre à jour la valeur utilisée par le mot clé default. Le compilateur n'envoie pas d'avertissement pour vérifier la valeur de cette directive par rapport à un champ utilisé par la propriété.
La directive default
doit être suivie par une constante du même type que la propriété.
default = _ (value of the same type)
nodefault
La directive nodefault
est utilisée pour surcharger une valeur default
héritée sans spécifier de nouvelle valeur. Si vous ne spécifiez pas de valeur par défaut lors de la spécification d'une propriété, cette dernière n'aura pas de valeur par défaut. Cela est utilisé pour les redéclarations de propriété lorsque vous souhaitez redéfinir une valeur par défaut spécifiée par l'ancêtre.
Les directives default
et nodefault
ne prennent en charge que les types scalaires et les types ensemble, à condition que les bornes inférieure et supérieure de l'ensemble sont des valeurs scalaires comprises entre 0 et 31. Si une propriété n'est pas déclarée comme default
ou nodefault
, elle est traitée comme nodefault
. Les types à virgule flottante, les pointeurs et les chaînes ont respectivement une valeur par défaut implicite de 0, nullptr, et (chaîne vide).
nodefault
.Utilisation des spécificateurs stored, default/nodefault pour le streaming
Ces spécificateurs sont utilisés lors de la mise en flux (streaming) d'une classe. Si la valeur en cours d'une propriété est différente de sa valeur par défaut (default
) (ou bien s'il n'y a pas de valeur default
), du fait que default
n'ait pas été spécifié ou que nodefault
ait été spécifié), ou si le spécificateur stored vaut true, alors la valeur de la propriété est enregistrée lors du streaming. Sinon, elle n'est pas enregistrée (pas écrite).
Attributs Implements
C++Builder introduit l'attribut implements pour le mot clé __property
. L'attribut implements vous permet d'implémenter efficacement les interfaces, sans impliquer l'héritage multiple.
L'attribut implements
du mot clé C++ __property
vous permet d'implémenter une interface en la spécifiant en tant qu'attribut ou champ d'une classe. Cette implémentation est similaire à la façon dont Delphi implémente les directives en permettant à une classe d'implémenter une interface par délégation à une propriété.
Dans une instruction __property
, l'attribut implements est placé en dernier, de façon similaire au placement de l'attribut nodefault
.
Pour plus d’informations sur les attributs implements, cliquez ici.
Redéclarations et redéfinitions de propriétés
Une déclaration de propriété qui ne spécifie pas de type est appelée une redéfinition de propriété. La redéfinition de propriété permet de modifier les spécificateurs ou la visibilité hérités d'une propriété.
La redéfinition la plus simple est constituée uniquement du mot réservé __property
suivi de l'identificateur de propriété hérité ; cette forme est utilisée pour changer la visibilité d'une propriété.</br>
Si, par exemple, une classe ancêtre déclare une propriété comme protégée, une classe dérivée peut la redéclarer dans une section publique ou publiée de la classe. Les redéfinitions de propriétés peuvent inclure les directives read
, write
, stored
, default
et nodefault
; l'une quelconque de ces directives redéfinit la directive héritée correspondante.
Une redéfinition peut remplacer un spécificateur d'accès hérité, ajouter un spécificateur manquant ou augmenter la visibilité d'une propriété. Elle ne peut pas retirer un spécificateur d'accès ou réduire la visibilité d'une propriété. Une redéfinition peut comporter la directive implements qui s'ajoute à la liste des interfaces implémentées sans supprimer celles qui ont été héritées.
L'exemple suivant redéclare une propriété Left avec la visibilité __published
. Dans la classe ancêtre, elle doit avoir une visibilité publique ou protégée :
__published
__property Left;
The following example redeclares a Left property, adding the stored specifier:
__property Left = { stored = true };
Une redéclaration d'une propriété qui inclut un identificateur de type masque la propriété héritée au lieu de la redéfinir. Cela signifie qu'une nouvelle propriété est créée portant le même nom que celle héritée. Toute déclaration de propriété spécifiant un type doit être complète et doit donc comporter au moins un spécificateur d'accès.
Exemples d'utilisation de __property
L'exemple suivant illustre des déclarations de propriétés simples :
class PropertyExample {
private:
int Fx, Fy;
float Fcells[100][100];
protected:
int readX() {
return (Fx);
}
void writeX(int newFx) {
Fx = newFx;
}
double computeZ() {
// Do some computation and return a floating-point value...
return (0.0);
}
float cellValue(int row, int col) {
return (Fcells[row][col]);
}
public:
__property int X = {read = readX, write = writeX};
__property int Y = {read = Fy};
__property double Z = {read = computeZ};
__property float Cells[int row][int col] = {read = cellValue};
};
Cet exemple montre plusieurs déclarations de propriétés :
- La propriété X a un accès en lecture-écriture via respectivement les fonctions membres readX et writeX.
- La propriété Y correspond directement à la variable membre Fy, et elle est en lecture seule.
- La propriété Z est une valeur en lecture seule qui est calculée, elle n'est pas stockée dans un membre de données de la classe.
- La propriété Cells illustre une propriété tableau avec deux index.
L'exemple suivant illustre la manière d'accéder à ces propriétés dans votre code :
// Previous code goes here
int main(int argc, char * argv[]) {
PropertyExample myPropertyExample ;
myPropertyExample.X = 42; // Evaluates to: myPropertyExample.WriteX(42) ;
int myVal1 = myPropertyExample.Y; // Evaluates to: myVal1 = myPropertyExample.Fy ;
double myVal2 = myPropertyExample.Z; // Evaluates to: myVal2 = myPropertyExample.ComputeZ() ;
float cellVal = myPropertyExample.Cells[3][7]; // Evaluates to : cellVal = myPropertyExample.cellValue(3,7);
}
Prise en charge du compilateur
La prise en charge du mot clé __property
varie selon le compilateur C++ utilisé : clang (recommandé) ou compilateur classique. Plus particulièrement, l'implémentation __property
dans les compilateurs clang gère des fonctionnalités que le compilateur bcc32 classique ne gère pas.
La principale différence est illustrée par l'exemple suivant :
#include <string>
typedef std::string PROPTYPE;
class TTest {
PROPTYPE getProp();
void setProp(PROPTYPE);
public:
__property PROPTYPE Val = { read=getProp, write=setProp };
};
void test() {
TTest t;
t.Val += "hello"; // <<<<< **
}
PROPTYPE TTest::getProp() { printf("%s\n", __FUNCTION__); return PROPTYPE(); }
void TTest::setProp(PROPTYPE) { printf("%s\n", __FUNCTION__); }
int main() {
test();
}
Dans le code, alors que le compilateur bcc32 classique invoque le getter et opère sur le résultat temporaire renvoyé, les compilateurs clang fonctionnent de même tout en appelant également le setter avec ce résultat. Ce qui signifie que seul le compilateur clang générera du code ayant le comportement attendu par un développeur.