• No results found

Reading to Write?

What actually happens is this: While the simple model described above does apply in most cases, things get weird when the property is a TPersistent descendent like a TBitmap or a TFont. For TPersistent descendents, the write method is called when you set the property at design time or change it at runtime—but not when a component is created and loaded from its form’s .DFM

Go!

Keyword

---Go!

stream. Instead, the runtime library calls the property’s read method to get a pointer to the property’s private field variable, then uses that pointer to call the property’s read-from-stream method. The write method is not called when the component is loaded!

Of course, in most cases this doesn’t matter—the property gets loaded, and it has the same value at runtime as it had at design time. However, there are a couple of ways this can affect you.

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 set the property directly at design time 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.

Reasonable Workarounds

Now, I think there’s a very good chance that Borland will conclude that read-to-write is a bug, and will change this behavior in future releases of Delphi. That is, you should avoid any solutions to the problems of GPF-on-load or partial-loading that will fail if the write method does get called at component load time.

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 shown in Listing 7.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.

Listing 7.1 PERSIST.SRC {interface}

type DemoComponent = class(TComponent) private

fGlyph: TBitmap;

fGlyphWritten: boolean;

procedure SetGlyph(Glyph: TBitmap); {not shown}

protected

constructor Create(Owner: TComponent); override;

procedure Loaded; override;

public published

property Glyph: TBitmap read fGlyph write SetGlyph;

end;

{implementation}

constructor DemoComponent.Create(Owner: TComponent);

begin

inherited Create(Owner);

fGlyph := TBitmap.Create;

{Be sure to fill in the field with an empty object}

end;

procedure DemoComponent.SetGlyph(Glyph: TBitmap);

begin

if fGlyph <> Glyph then { fGlyph = Glyph when SetGlyph is } begin { called by the Loaded procedure } fGlyph.Free; {Assign can fail if the target is not Empty:}

fGlyph := TBitmap.Create; {Free/Create/Assign is a lot safer}

fGlyph.Assign(Glyph);

end;

{Extract any necessary data ... set the PropertyWritten flag}

fGlyphWritten := True;

end;

procedure DemoComponent.Loaded;

begin

inherited Loaded; {Don't forget to do this!}

if (not fGlyphWritten) and (not fGlyph.Empty) then

SetGlyph(fGlyph); {Extract any necessary data from the bitmap}

end;

Partial-loading is a little 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 an fPropertyWritten boolean flag for every TPersistent property that might be stored. (See Listing 7.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 (using the override directive) your component’s Loaded method, and add a line like this

if not fPropertyWritten then SetProperty(fProperty);

Previous Table of Contents Next

Products | Contact Us | About Us | Privacy | Ad Info | Home

Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.

All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is prohibited. Read EarthWeb's privacy statement.

Brief Full Advanced Search

Search Tips

To access the contents, click the chapter and section titles.

Kick Ass Delphi Programming

(Publisher: The Coriolis Group)

Author(s): Don Taylor, Jim Mischel, John Penman, Terence Goggin ISBN: 1576100448

Publication Date: 09/01/96

Search this book:

Previous Table of Contents Next

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 that shown in Listing 7.2. 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.

Listing 7.2 PERSIST2.SRC

if fProperty <> NewPropertyValue then begin

fProperty.Free; {Assigning 'over' a } fProperty := TPropertyType.Create; {TPersistent can fail:}

fProperty.Assign(NewPropertyValue) {Free/Create/Assign's safer}

end;

{Extract any necessary data from NewPropertyValue}

fPropertyWritten := True;

A Little Perspective

My personal suspicion is that read-to-write is the result of a bit of overzealous optimization by the Delphi team. While it seems like it would be pretty hard to make a case for it’s not being a bug,

whenever I run into a bug or bit of poor design in Delphi, I like to ask myself how many apps I’ve ever used or written that are more stable than Delphi, or have a higher ratio of good to bad design decisions.

The answer is always “Few, if any.”

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 TPersistent objects and descendents is pretty easy to deal with.