pyscripter 695 Posted Tuesday at 12:15 PM (edited) The code of TReader.ReadCollection is as follows: procedure TReader.ReadCollection(const Collection: TCollection); var Item: TPersistent; begin Collection.BeginUpdate; try if not EndOfList then Collection.Clear; while not EndOfList do begin if NextValue in [vaInt8, vaInt16, vaInt32] then ReadInteger; Item := Collection.Add; ReadListBegin; while not EndOfList do ReadProperty(Item); ReadListEnd; end; ReadListEnd; finally Collection.EndUpdate; end; end; It has been like this forever. My question is about the statement: if not EndOfList then Collection.Clear; It means that if you read an empty collection, the collection you are reading is not cleared or otherwise changed. The practical implication of this is that if you have a component that initializes a collection property with some items, there is no way to really clear it from the collection property inspector. Next time the form is loaded, either at run time or at design time, the collection will revert to the initialized state. Can you think of any reason TReader.ReadCollection does this? I think that if you read an empty collection, the collection property you are loading should be cleared. Should I report this as an issue? Also is there any workaround? I want a collection property to have a default state with some items created by the component constructor, but the users to be able to clear it at design time. Edited Tuesday at 12:24 PM by pyscripter Share this post Link to post
Anders Melander 1820 Posted Tuesday at 02:03 PM 1 hour ago, pyscripter said: Can you think of any reason TReader.ReadCollection does this? Form inheritance would be my guess. Share this post Link to post
pyscripter 695 Posted Tuesday at 03:21 PM (edited) 1 hour ago, Anders Melander said: Form inheritance would be my guess. Could you please explain why not clearing the collection plays better with form inheritance? I would think the opposite. Say you inherit from a form with a component that has a collection property with some items. As it stands, there is no way for the inherited form to clear the inherited collection. Edited Tuesday at 03:21 PM by pyscripter Share this post Link to post
Anders Melander 1820 Posted Tuesday at 03:43 PM 10 minutes ago, pyscripter said: Could you please explain why not clearing the collection plays better with form inheritance? I didn't say it plays better. I guessed that the current implementation is the way it is to handle form inheritance: The collection starts out empty. The base form is loaded. If the base form has collection entries it populates the collection. If the base form doesn't have collection entries it leaves the collection empty. The inherited form is loaded. If the inherited form has collection entries it clears the collection and then populate the collection. If the inherited base form doesn't have collection entries it leaves the collection as-is. 11 minutes ago, pyscripter said: As it stands, there is no way for the inherited form to clear the inherited collection. Yes. It would seem that way. Share this post Link to post
Anders Melander 1820 Posted Tuesday at 08:22 PM 7 hours ago, pyscripter said: Should I report this as an issue? I wouldn't bother. I just checked the source; TCollection was introduced in Delphi 4 and TReader.ReadCollection hasn't been changed since then. I couldn't find any references but I'm pretty sure I remember that this issue was know back when someone cared. I also can't really see what they could do without breaking backward compatibility. 8 hours ago, pyscripter said: Also is there any workaround? Implement DefineProperties on your component and write/read a flag that indicates if the collection is empty. Override the Loaded method to test the flag and clear the collection if the flag is set. 1 Share this post Link to post
Uwe Raabe 2067 Posted yesterday at 09:30 AM 12 hours ago, Anders Melander said: I also can't really see what they could do without breaking backward compatibility. Indeed! Probably no one at Embarcadero is going to change a that old behavior, because no one can say for sure what the consequences would be for existing code. Asking here only reaches a small part of the existing Delphi developers, from which some may not even know whether their code relies on the current implementation or not. I second Anders view of better using a workaround. Besides his suggestion, you can also write a sentinel value as the only collection item, which is removed after reading. Candidates for interception can be the ReadState/WriteState methods of the component. In WriteState add the sentinel when the collection is empty before calling inherited and in ReadState remove the sentinel if found after calling inherited. 1 Share this post Link to post
pyscripter 695 Posted yesterday at 01:07 PM (edited) @Uwe Raabe@Anders MelanderThank you for your feedback. 15 hours ago, Uwe Raabe said: Probably no one at Embarcadero is going to change a that old behavior, because no one can say for sure what the consequences would be for existing code. Fully agree. The chances of Embarcadero changing this old code are close, very close, to zero. However let me elaborate on the problems with the current situation. There are two issues: A) Form Inheritance Consider the attached project, There are two forms, one inheriting from the other. The first contains a TCategoryButtons with two categories (Showing just the relevant part) object Form1: TForm1 object CategoryButtons: TCategoryButtons Categories = < item Caption = 'A' Color = 16771839 Collapsed = False Items = <> end item Caption = 'B' Color = 16771818 Collapsed = False Items = <> end> end end If the inherited form does not change the Categories collection then the dfm of the second form does not have any reference to the Categories collection. inherited Form2: TForm2 inherited CategoryButtons: TCategoryButtons Width = 505 end end If you now remove the two categories in Form2, the form is correctly saved as: inherited Form2: TForm2 inherited CategoryButtons: TCategoryButtons Width = 505 Categories = <> end end However, if you run the application though, Form2 still shows the two categories. Also if you view the form as text and then back as form, the Categories revert to the inherited value. The same happens if you close and reopen the form. This is counter intuitive and problematic. I do not see how clearing the selection on reading would result in any undesired behavior. It would work correctly whether the collection is changed or not, Instead, it would solve the above issue. B) Cannot clear a collection created with items at design time Whilst you both focused on inheritance, for me the most important issue is unrelated to form inheritance. If a custom component contains a Collection property that is initialized non-empty at component creation, there is no way to clear it at design time. As an example, Synedit, has a collection property ScrollbarAnnotations. At construction this is initialized with some default annotations. The user may want to remove all annotations, but this cannot be achieved at design time. If you clear all annotations, the form reverts back to the initial state, when you run the project, or just close and open again the form. I cannot think of any backward compatibility issues that result from clearing the collection on reading empty collections. Can you? FormInheritance.zip Edited 19 hours ago by pyscripter Share this post Link to post
pyscripter 695 Posted yesterday at 01:51 PM (edited) It turns out that the workaround for the second issue is quite simple: procedure TCustomSynEdit.ReadState(Reader: TReader); // If the component is inherited from another form, ReadState will be called // more than once. We only clear the collections if it is the first call // i.e. when reading the base form. begin if not FStateRead then begin FScrollbarAnnotations.Clear; FStateRead := True; end; inherited ReadState(Reader); end; Thanks to @Uwe Raabe for giving me the idea. Edited 19 hours ago by pyscripter 1 Share this post Link to post
Vincent Parrett 781 Posted 20 hours ago I have hit this issue before, but didn't delve into it too far - I just made a point of not adding items to collections in the base form. TBH, I had so many issues with form inheritance getting messed up by the IDE that I tend to avoid it - more trouble than it's worth. Share this post Link to post