Bonjour,
Je suis rôdé au développement de composants non visuels et d'éditeurs de composants pour des cas courants.
Désormais j'ai besoin de créer un composant qui contient des sous-composants, qui ne sont pas tous du même type (ce qui exclut l'utilisation de TOwnedCollection).
Ce dont j'ai besoin est très similaire à TActionList avec les différents types dérivés de TAction.
J'ai donc pris modèle sur TContainedAction et TContainedActionList, avec une nuance : dans mon cas j'ai une liste arborescente (chaque sous-composant peut donc contenir d'autres sous-composants).
Chaque objet pouvant être à la fois conteneur et contenu, j'ai donc utilisé le même type de base pour le conteneur et le contenu, en "fusionnant" le code de TContainedAction et TContainedActionList.
Le code de mon composant (la partie qui concerne cette structure) se trouve au bas du message.
Avec ce code, les sous-composants sont correctement enregistrés dans le dfm, et correctement relus.
Mais après rechargement, ils apparaissent sur la fiche ! Tous empilés en haut à gauche tant que je ne les bouge pas, puis ils s'enregistrent avec leur position si je les bouge. Mon objectif était, comme pour le TActionList, qu'ils n'apparaissent pas directement sur la fiche mais seulement dans mon éditeur de composant et dans la vue structure de la fiche.
Autre différence notable : avec le TActionList, à chaque objet action correspond une déclaration de champ dans la classe de la fiche, qui donne un accès direct à l'action. Moi je ne l'ai pas, mais ce point là m'arrange (je n'ai pas besoin d'encombrer le code avec ces déclarations).
Enfin, j'ai été obligé de modifier légèrement la procédure getChildren, sans quoi les sous-composants ne sont pas enregistrés dans le dfm après un premier rechargement :
En effet : même si je prends la précaution de mettre le composant parent comme propriétaire de ses enfants avec mon éditeur dans l'EDI, le owner n'est pas indiqué dans le dfm, et après rechargement c'est systématiquement la fiche qui devient le owner de tous les sous-composants. J'obtiens pourtant bien la structure suivante dans le dfm (les types réels sont dérivés de TMVLogicNode) :
Je pensais que le fait que les objets soient imbriqués dans le dfm aurait une influence sur le owner imposé à la relecture, mais visiblement non.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10 object MVLogicView1: TMVLogicView Left = 529 Top = 144 object TMVDataLogicItem nodeExpanded = False object TMVDataLogicItem nodeExpanded = False end end end
Avec ce code qui me semble similaire à celui de TContainedAction et TContainedActionList, j'obtiens donc un comportement légèrement différent.
Je me demande si ces aspects ne seraient pas à régler dans le code de l'éditeur du composant (l'EDI n'interroge-t-il pas l'éditeur du composant pour savoir qui doit être le owner au moment du chargement ?), mais je ne trouve pas le code source de l'éditeur de TActionList pour avoir un exemple.
Quelqu'un saurait-il m'expliquer les subtilités ? Le plus important pour moi serait de ne pas polluer le visuel de la fiche avec les sous-composants.
Merci d'avance pour vos réponses.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199 TMVLogicNode = class(TComponent) private FContainer: TMVLogicNode; FItems:TList<TMVLogicNode>; //Fonctions de l'objet vu comme enfant procedure SetContainer(AContainer: TMVLogicNode); function GetIndex: Integer; procedure SetIndex(Value: Integer); //Fonctions de l'objet vu comme parent function GetLogicItem(Index: Integer): TMVLogicNode; // procedure SetLogicItem(Index: Integer; Value: TMVLogicNode); function GetLogicItemCount: Integer; protected procedure ReadState(Reader: TReader); override; procedure AddLogicItem(const item: TMVLogicNode); procedure RemoveLogicItem(const item: TMVLogicNode); procedure Notification(AComponent: TComponent; Operation: TOperation); override; procedure SetChildOrder(Component: TComponent; Order: Integer); override; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; //Fonctions et propriétés de l'objet vu comme parent procedure GetChildren(Proc: TGetChildProc; Root: TComponent); override; property LogicItems[Index: Integer]: TMVLogicNode read GetLogicItem {write SetLogicItem}; default; property LogicItemCount: Integer read GetLogicItemCount; function GetEnumerator:TList<TMVLogicNode>.TEnumerator; //Fonctions et propriétés de l'objet vu comme enfant function GetParentComponent: TComponent; override; function HasParent: Boolean; override; procedure SetParentComponent(AParent: TComponent); override; property Container: TMVLogicNode read FContainer write SetContainer; property Index: Integer read GetIndex write SetIndex stored False; end; { TMVLogicNode } procedure TMVLogicNode.AddLogicItem(const item: TMVLogicNode); begin if item = nil then raise Exception.CreateFMT(SParamIsNil, ['item']); FItems.Add(item); item.FContainer := Self; item.FreeNotification(Self); end; constructor TMVLogicNode.Create(AOwner: TComponent); begin inherited; FItems:=TList<TMVLogicNode>.Create; end; destructor TMVLogicNode.Destroy; begin if FItems <> nil then while FItems.Count > 0 do {$IF not Defined(AUTOREFCOUNT)} FItems.Last.Free; {$ELSE} FItems.Last.Container := nil; {$ENDIF} FreeAndNil(FItems); inherited; end; procedure TMVLogicNode.GetChildren(Proc: TGetChildProc; Root: TComponent); var I: Integer; LogicItem: TMVLogicNode; begin if @Proc = nil then raise Exception.CreateFMT(SParamIsNil, ['Proc']); for I := 0 to FItems.Count - 1 do begin LogicItem := FItems.List[I]; // if LogicItem.Owner = Root then //modif par rapport à TContainedActionList, car en pratique après rechargement le owner est la fiche et le root est le premier TMVLogicNode Proc(LogicItem); end; end; function TMVLogicNode.GetEnumerator:TList<TMVLogicNode>.TEnumerator; begin result:=FItems.GetEnumerator; end; function TMVLogicNode.GetIndex: Integer; begin if (FContainer <> nil) and (FContainer.FItems <> nil) then Result := FContainer.FItems.IndexOf(Self) else Result := -1; end; function TMVLogicNode.GetLogicItem(Index: Integer): TMVLogicNode; begin Result := FItems[Index]; end; function TMVLogicNode.GetLogicItemCount: Integer; begin if FItems <> nil then Result := FItems.Count else Result := 0; end; function TMVLogicNode.GetParentComponent: TComponent; begin if FContainer <> nil then Result := FContainer else Result := inherited GetParentComponent; end; function TMVLogicNode.HasParent: Boolean; begin if FContainer <> nil then Result := True else Result := inherited HasParent; end; procedure TMVLogicNode.Notification(AComponent: TComponent; Operation: TOperation); begin inherited Notification(AComponent, Operation); if Operation = opRemove then begin if (AComponent is TMVLogicNode) then RemoveLogicItem(TMVLogicNode(AComponent)); end; end; procedure TMVLogicNode.ReadState(Reader: TReader); begin inherited ReadState(Reader); if Reader.Parent is TMVLogicNode then Container := TMVLogicNode(Reader.Parent); end; procedure TMVLogicNode.RemoveLogicItem(const item: TMVLogicNode); begin if (FItems <> nil) and (FItems.Remove(item) >= 0) then begin item.RemoveFreeNotification(Self); item.FContainer := nil; end; end; procedure TMVLogicNode.SetChildOrder(Component: TComponent; Order: Integer); begin if (FItems <> nil) and (FItems.IndexOf(TMVLogicNode(Component)) >= 0) then (Component as TMVLogicNode).Index := Order; end; procedure TMVLogicNode.SetContainer(AContainer: TMVLogicNode); begin if AContainer <> FContainer then begin if FContainer <> nil then FContainer.RemoveLogicItem(Self); if AContainer <> nil then AContainer.AddLogicItem(Self); end; end; procedure TMVLogicNode.SetIndex(Value: Integer); var CurIndex, Count: Integer; begin CurIndex := GetIndex; if CurIndex >= 0 then begin Count := FContainer.FItems.Count; if Value < 0 then Value := 0; if Value >= Count then Value := Count - 1; if Value <> CurIndex then begin FContainer.FItems.Delete(CurIndex); FContainer.FItems.Insert(Value, Self); end; end; end; {procedure TMVLogicNode.SetLogicItem(Index: Integer; Value: TMVLogicNode); begin FItems[Index].Assign(Value); end;} procedure TMVLogicNode.SetParentComponent(AParent: TComponent); begin if not(csLoading in ComponentState) and (AParent is TMVLogicNode) then Container := TMVLogicNode(AParent); end;
Partager