Right?
Wrong.
First, the read method should never return Nil. While it may seem sensible to use a strategy of not actually creating a private field variable until the write method supplies an actual value to copy, Delphi's component loading code is not quite smart enough to notice that it has no TPersistent to tell to load. If your read method returns Nil, you will get GPF's when the component is loaded. (For what it's worth, this behavior is what alerted me to the problem, though I'll confess that it took me a while to find out what was going on.)
Second, don't rely on your write method to extract information from the field variable and save it in runtime only fields elsewhere in your component. The write method will be called when you directly set the property at designtime or at runtime, but not when you indirectly set the property at component load time. If you expect your write method to update your component's internal state, your component will not load properly.
It's easy to prevent GPF-on-load in a 'forward-compatible' way. We now know that if a TPersistent property is stored, Delphi will call its read method when the object is loaded from a stream. So, as in Figure 1, the object's Create constructor must Create an object of the appropriate type, and set the property's private field variable. This is a bit wasteful if the property is not always set or stored, but a few hundred bytes of storage or a few hundred instructions of Create code are just plain insignificant to a sixteen or thirty-two megabyte Pentium.
Partial-loading is a bit more complex. Fortunately, Delphi components have a Loaded method which you can override to perform any necessary post-processing. You can overcome partial- loading by taking advantage of the Loaded method and by making a few small changes to your code.
The first thing to do is to add a 'fPropertyWritten' boolean flag for every TPersistent property that might be stored. (See Figure 1.) The flag will be set to False when the object is created, and should be set to True only by the write method.
Second, you must override your component's Loaded method, and add a line like
if not fPropertyWritten then SetProperty(fProperty)so that Loaded calls your write method if (and only if) the component loading code didn't call it.
Third, you generally don't want to Assign a TPersistent to itself, and you certainly don't want to Free it, Create a new instance, then Assign the old (freed) instance to the new instance. It's best to use code like
if fProperty <> NewPropertyValue then begin fProperty.Free; {Assigning 'over' a } fProperty := TPropertyType.Create; {TPersistent can fail:} fProperty.Assign(NewPropertyValue) {Free/Create/Assign is safer} end; {Extract any necessary data from NewPropertyValue} fPropertyWritten := True;This way, you only reset the private field variable if the new value does not equal the existing field. Adding this test ensures that SetProperty(fProperty) will not cause a GPF now, and won't constitute much overhead if read-to-write does go away.
It's also worth bearing in mind that the write method is called at load time for simple types like integers, enums, and strings - and that read-to-write for TPersistents is pretty easy to deal with.
This article originally appeared in PC Techniques.
Copyright © 1995, Jon Shemitz - jon@midnightbeach.com - HTML markup Feb-10-96