__property

De RAD Studio
Aller à : navigation, rechercher

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.

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écificateurs read, write, stored, index, default (ou nondefault), ou implements.
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
Remarque: 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.

Avertissement: Les propriétés d'index par défaut de Delphi sont en conflit avec l'opérateur d'index de C++. En conséquence, toutes les propriétés d'index par défaut de Delphi sont exposées sous la forme d'un opérateur de membre [](int index) qui est transmis à la propriété sous-jacente

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

Remarque: Vous ne pouvez pas utiliser la valeur scalaire -2147483648 comme valeur par défaut. Cette valeur est utilisée en interne pour représenter 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).

Avertissement: Les spécificateurs de stockage ne sont pas supportés pour les propriétés tableau. La directive default a une signification différente quand elle est utilisée dans une déclaration de propriété tableau. Voir Propriétés tableau, ci-dessus.

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.

Voir aussi