XL 2016 Collection d'objets personnalisés avec propriété tableau dans VBA excel

CyrilleB

XLDnaute Nouveau
Je tente de programmer une sorte de Jeu Tetris pour lequel je voudrais stocker les pièces dans une collection d'objet
J'ai créé une classe d'objet dans un module de classe appelé "Piece"
J'ai créé une collection dans un module appelé "Pieces", la collection comprendra l'ensemble des pièces
Le module standard instancie une collection ColP
Et ajoute un objet pièce à la collection ColP, la pièce est définie par sa Forme (tableau de booléen)

A l'execution, j'ai un message d'erreur sur la ligne .Forme = bForme de la fonction CreatePiece
Le message dit succinctement "Internal Error"

J'ai tenté de remplacer
VB:
CreatePiece(ByRef bForme() As Boolean)
par
VB:
CreatePiece(bForme() As Boolean)

Mais ça ne change rien

Voici le module standard
VB:
Sub NouvellePartie()

Dim colP As Pieces
Dim Forme() As Boolean

'Initialisation de forme à false
ReDim Forme(0 To 5, 0 To 5)
For i = 1 To 5
    For j = 1 To 5
        Forme(i, j) = False
    Next
Next

Set colP = New Pieces
With colP
   .Add .CreatePiece(Forme)
End With

End sub

Le module de classe appelé "Piece" (au sigulier)
VB:
'---------------------
' Module de Classe Pièce de Puzzle
'---------------------

'Forme de la pièce dans un tableau de booléens
Private mbForme() As Boolean

'Forme
'--------------------------
Property Get Forme() As Boolean()

  ' Propriété en lecture
   Forme = mbForme
End Property

Property Let Forme(ByRef MaForme() As Boolean)

    ' Propriété en écriture
    mbForme = MaForme
End Property

Le module de classe appelé "Pieces" (au pluriel), la collection comprendra l'ensemble des pièces
VB:
Private mcolPieces As Collection

Property Get Count() As Long
    Count = mcolPieces.Count
End Property

Public Sub Add(ByRef objPiece As Piece, _
                    Optional ByVal NumPiece As String = "")

'Si aucune clé key n'est fournit on en génère une automatiquement
    If Len(NumPiece) = 0 Then
        NumPiece = CStr(objPiece.ID)
    End If
    mcolPieces.Add objPiece, NumPiece
    Set objPiece = Nothing
End Sub


Public Sub Remove(ByVal Index As Variant)
    mcolPieces.Remove (Index)
End Function

Public Function Item(ByVal Index As Variant) As Piece
    Set Item = mcolPieces.Item(Index)
End Function

Public Function CreatePiece(ByRef bForme() As Boolean) As Piece
Dim objPiece  As Piece

    Set objPiece = New Piece
    With objPiece
             .Forme = bForme
    End With

    Set CreatePiece = objPiece

    If Not (objPiece Is Nothing) Then Set objPiece = Nothing
End Function


Ce qui est étonnant c'est que lorsque je crée et j'ajoute la pièce à la collection Colp directement dans le module standard je n'ai pas d'erreur:
VB:
Sub NouvellePartie()

Dim colP As Pieces
Dim objPiece  As Piece

ReDim Forme(0 To 5, 0 To 5)
For i = 1 To 5
    For j = 1 To 5
        Forme(i, j) = False
    Next
Next

Set colP = New Pieces
Set objPiece = New Piece

With objPiece
        .Forme = Forme
End With
colP.Add objPiece
If Not (objPiece Is Nothing) Then Set objPiece = Nothing

end sub

Merci d'avance pour votre aide précieuse.
 

Dranreb

XLDnaute Barbatruc
Bonjour.
Il vaudrait mieux joindre le classeur pour qu'on puisse analyser la situation.
D'habitude à une méthode Add d'une classe représentant une collection, je crée le membre dans celle ci, et il n'y a donc pas lieu de le spécifier. Un tableau de dimensions fixes de Boolean est partout à True par défaut à sa création.
 

Dranreb

XLDnaute Barbatruc
Non mais simplifiez tout ça. Supprimez la méthode CreatePiece et faites tout dans la Add, à laquelle vous ne préciserez pas d'objet Pièce.
Parce que là j'ai l'impression qu'à un moment donné vous en créez deux.
Écrivez la Add comme une Function renvoyant le nouvel objet Pièce ajouté, ça peut toujours servir.
Il vaudrait mieux que la propriété ID soit String et reprenne exactement la clé dans la collection.
Ça pourrait donner quelque chose comme ça :
VB:
Public Function Add(Optional ByVal NumPiece As String = "") As Piece
   Set Add = New Piece
   If NumPiece = "" Then NumPiece = "x" & mcolPieces.Count + 1
   Add.ID = NumPiece
   mcolPieces.Add Add, NumPiece
   End Function
 
Dernière édition:

Dranreb

XLDnaute Barbatruc
Autre chose: Un objet se transmet toujours ByVal à une procédure. Même dans le cas où ça se justifierait de transmettre ByRef une variable objet que la procédure serait charger d'initialiser par un Set, il vaudrait mieux chercher un autre moyen de réaliser cette initialisation, tel que d'écrire la procédure sous forme d'une Function qui le renvoie.
Là ce n'était même pas une variable que vous transmettiez mais une expression !
La récupérer ByRef ne fait qu'interdire au compilateur de transmettre l'adresse de l'objet, car vous exigez qu'il transmette l'adresse d'une zone contenant l'adresse de l'objet. C'est ce en quoi consiste une variable objet certes. Mais pour une expression c'est plus compliqué car il lui est interdit d'exposer la zone de base d'une expression à une possible modification par une procédure en lui transmettant directement son adresse. Il est donc contraint de déposer l'adresse de l'objet dans une zone de travail, construisant ainsi une variable objet temporaire, et c'est l'adresse de cette zone qu'il transmet. Il vaut quand même mieux qu'il transmettre directement l'adresse de l'objet, non ? Donc toujours ByVal. D'autant que même si procédure faisait un Set dessus, ce qui modifierait la zone temporaire, celle ci n'est de tout façon pas récupérable par la procédure appelante.
 
Dernière édition:

CyrilleB

XLDnaute Nouveau
Je ne comprends par pourquoi vous dites
Là ce n'était même pas une variable que vous transmettiez mais une expression !

Est-ce que votre remarque s'applique à ces lignes ?
VB:
  With colP
        .Add .CreatePiece(ID, Forme)
  End With

Si vous analysez bien le code vous verrez que c'est bien une variable qui est transmise puisque la fonction CreatePiece renvoie la Piece créée:
Public Function CreatePiece(iID As Integer, ByRef bForme() As Boolean) As Piece

J'ai utilisé le tutoriel de Michel Blavin et son exemple
https://sinarf.developpez.com/access/vbaclass/

Dans cet exemple, il transmet son object ByRef et ça fonctionne bien.

Il justifie l'existence de la fonction CreatePiece (qui dans son exemple s'appelait CreatePerson) par
Nous allons maintenant ajouter une méthode CreatePerson à la collection qui permet d'instancier un objet Person sans avoir à créer de pointeur vers un objet personne. Cela nous permet comme vous allez le voir dans le code qui suit d'instancier et d'insérer un objet dans la collection.

Ce qui génère le bug c'est le fait que j'ai ajouté le tableau Forme aux propriétés de l'objet Pièce
Dans son exemple l'objet Person ne contenait des variables simples et pas de tableau.

Quand je debugue le programme pas à pas, je vois bien que le plantage se situe sur cette ligne là précisément :
VB:
 .Forme = bForme

Je n'ai pas encore trouvé la raison précise.
Merci d'avance si vous y parvenez
 

Dranreb

XLDnaute Barbatruc
Bonjour.
Je n'ai pas trouvé la raison précise pour laquelle ça ne marchait pas. Mais avec une méthode Init comme je fais d'habitude ça a l'air de passer mieux :
Classe Piece :
VB:
Sub Init(ByVal iID As Integer, ByRef bForme() As Boolean)
   miID = iID
   mbForme = bForme
   End Sub
Classe Pieces :
VB:
Public Function CreatePiece(ByVal iID As Integer, ByRef bForme() As Boolean) As Piece
   Set CreatePiece = New Piece
   CreatePiece.Init iID, bForme
   End Function
J'insiste:
Public Sub Add(ByVal objPiece As Piece, Optional ByVal NumPiece As String = "")
Une expression n'est pas une variable dans la mesure où elle n'est pas connue par un nom. Il lui correspond certes une zone de mémoire pareillement structurée mais inaccessible autrement que par cette expression. De sorte que même si la procédure effectuait un Set sur cette variable, ce qui seul modifierait la zone, ça ne servirait donc à rien de la passer ByRef. De plus le compilateur n'a pas le droit d'exposer à la modification la zone d'une expression. Il est obligé d'en exposer une copie préalablement établie. C'est en tout cas déjà absolument sûr si l'expression est une constante: elle ne doit jamais être exposée à une modification par transmission ByRef de sa véritable adresse. Seule l'adresse d'une copie peut être transmise ainsi.
 
Dernière édition:

Dranreb

XLDnaute Barbatruc
Bonjour.
Il faut se rendre à l'évidence: dans une procédure, l'affectation d'un tableau lui ayant été transmis en paramètre à une propriété tableau d'un objet provoque une erreur interne.
Seule solution: le retransmettre plutôt à une méthode de l'objet prévue pour ça.
Ça a peut être plus ou moins un rapport avec le fait qu'un tableau ne peut être défini comme propriété qu'à l'aide de procédures Property et non en tant que simple variable Public (j'ai voulu essayer bêtement de voir si ça faisant pareil en la définissant ainsi, mais zut, on ne peut pas …)
On peut aussi y arriver en y affectant une copie du tableau :
VB:
Option Explicit
Private X As Classe1
Sub Test()
   Dim T()
   ReDim T(1 To 5)
   Set X = New Classe1
   Créer T
   End Sub
Sub Créer(T())
   'X.Tabl = T ' Erreur interne
   Dim TCopie()
   TCopie = T
   X.Tabl = TCopie ' Là c'est OK
   End Sub
mais je préfère de loin une méthode Init pour ça.
 

dysorthographie

XLDnaute Accro
Bonjour,
Le tableau est passé en byreff à une propriété puis attribué à une variable privé !

Qu'est censé faire ce genre d'opération ?
Une affectation de tableau par référence ?

Non ça n'existe pas ! On ne peut affecter que des objets par référence !

Set objet=MyObj!

Le plus simple, vu que tu veux travailler sur le même tableau, c'est d'ajouter un module standard et créer ta variable tableau en public. Plus besoin de passer ton tableau par un propriété!
 

Dranreb

XLDnaute Barbatruc
Il faut une variable Private dans la classe puisque Public n'y est pas permis pour un tableau.
Un tableau ne peut être transmis que ByRef à une procédure, ou à travers un Variant, qui lui peut être ByVal (hors de question d'encombrer la pile avec tout le contenu du tableau !).
Comprends pas ce que vous voulez controverser par cette affirmation.
Non, il veut que chaque exemplaire ait un petit tableau en propriété.
 

dysorthographie

XLDnaute Accro
Remettons les choses a plat !
Il est possible de définir une variable tableau en public dans un classe mais là n'est pas ce qui nous préoccupe !

Ce qui nous préoccupe c'est de pouvoir modifier une ou toutes les valeurs ne notre tableau dans une sous instance de la classe ce qui revient implicitement à faire Set tableau=MyTableau ce qui est illégal !

Deux possibilités créer ce tableau dans un module standard et y accéder directement dans la classe,

Soit déclarer le tableau en public dans un nouveau module de classe et créer une propriété public set mypropriété dans le module de classe existant !

VB:
Sub test()
Dim Forme As Object
Set Forme = CreateObject("Scripting.Dictionary")
For i = 1 To 5
    For j = 1 To 5
        Forme(i & "-" & j) = False
    Next
Next
End Sub
 
Dernière édition:

dysorthographie

XLDnaute Accro
module de classe Piece
VB:
Property Set Forme(MaForme As Object)

    ' Propriété en écriture
    mbForme = MaForme
End Property
Code:
Public Function CreatePiece(iID As Integer, ByRef bForme as Object) As Piece
Dim objPiece  As Piece

    Set objPiece = New Piece
    With objPiece
        .ID = iID
       set  .Forme = bForme
    End With

    Set CreatePiece = objPiece

    If Not (objPiece Is Nothing) Then Set objPiece = Nothing
End Function
 

Dranreb

XLDnaute Barbatruc
Il est possible de définir une variable tableau en public dans un classe mais là n'est pas ce qui nous préoccupe !
Non ! Essayez, vous verrez bien.
1597412712210.png

On est donc obligé de le déclarer Private et d'y accéder par une paire de Property Let et Get.
modifier une ou toutes les valeurs ne notre tableau dans une sous instance de la classe ce qui revient implicitement à faire Set tableau=MyTableau
Non ! Sans Set bien évidemment: simplement affecter le tableau à la propriété.
On a juste vu que ça se passait mal si ce tableau était récupéré d'un paramètre.
Par contre ça passe s'il est transmis à une méthode qui fait la même chose.
Un objet comme propriété ne pose pas ce problème. Mais ce n'est pas ce que voulait le demandeur, c'est un tableau.
@patricktoulon, oui c'est sûr, ne n'aurais pas non plus défini une classe juste pour un tableau définissant les caractéristiques de la pièce, j'aurais simplement rangé ceux ci dans une Collection ou un Dictionary, voire un simple tableau de Variant à une dimension, ça dépendrait des besoins d'accès ultérieurs à chaque élément, chose sur laquelle le demandeur ne s'est jamais exprimé ou n'a même jamais réfléchi.
 
Dernière édition:

Discussions similaires

Statistiques des forums

Discussions
312 160
Messages
2 085 839
Membres
103 001
dernier inscrit
vivinator