Board index » delphi » Saving the Desktop

Saving the Desktop

I am having some troubles saving the desktop in a program that I am
writing. Actually more to the point, the problem is with loading the
desktop. I have registered all views. The biggest problem is with the
objects derived from TWindow. The interior of these windows are
derived from TListViewer. The interior has a TCollection associated
with it. This is where I think the problem is. Because the collection
is populated by reading from a straight binary file I don't want to
store this collection on the stream used to store the desktop. I
simply want to store the windows, and thier locations. Since the
binary file that contains the data may change I would rather rebuild
the collection when I restore the desktop from the original file. Is
this possible? If anyone has an example of saving and loading the
desktop in a Turbo Pascal 7.0 app it would be appreciated.

        Mike....

 

Re:Saving the Desktop


Quote
mike_wal...@mindlink.bc.ca (Mike Walker) writes:

[...]

Quote
>derived from TListViewer. The interior has a TCollection associated
>with it. This is where I think the problem is. Because the collection
>is populated by reading from a straight binary file I don't want to
>store this collection on the stream used to store the desktop. I
>simply want to store the windows, and thier locations. Since the
>binary file that contains the data may change I would rather rebuild
>the collection when I restore the desktop from the original file. Is
>this possible? If anyone has an example of saving and loading the
>desktop in a Turbo Pascal 7.0 app it would be appreciated.
>    Mike....

I haven't tested, but I think, the following should work:

In tMyCollection (or what you call it) add:
{Example: with tMyCollection.init(aFileName:string);}

procedure tMyCollection.Store(var s:tStream);
 begin
  {do NOT call the inherited Store}
  {Write to the Stream all data needed for init}
  {f.ex.:}
  s.WriteStr(FileName^);
 end;

constructor tMyCollection.Load(var s:tStream);
 var
  {The Data you need for init}
  {f.ex.:}
  TheFileName:pString;
 begin
  {do NOT call the inherited Load}
  {Read the data written in Store to the local Vars}
  {f.ex.:}
  TheFileName:=s.ReadStr;
  {call your init method}
  tMyCollection.init({The data read, f.ex.:}TheFileName^);
  {Clean up any additional space}
  {f.ex.:}
  DisposeStr(TheFileName)
 end;

Hope it helps.

Ch. Eltschka
If you are missing information, just remember, that this article was moving
through the net with high relativistic speed, so any shortening comes from lorentz
contraction...

Re:Saving the Desktop


Quote
celts...@Physik.TU-Muenchen.DE (Christopher Eltschka) wrote:
>mike_wal...@mindlink.bc.ca (Mike Walker) writes:
>I haven't tested, but I think, the following should work:
>In tMyCollection (or what you call it) add:
>{Example: with tMyCollection.init(aFileName:string);}
>procedure tMyCollection.Store(var s:tStream);
> begin
>  {do NOT call the inherited Store}
>  {Write to the Stream all data needed for init}
>  {f.ex.:}
>  s.WriteStr(FileName^);
> end;
>constructor tMyCollection.Load(var s:tStream);
> var
>  {The Data you need for init}
>  {f.ex.:}
>  TheFileName:pString;
> begin
>  {do NOT call the inherited Load}
>  {Read the data written in Store to the local Vars}
>  {f.ex.:}
>  TheFileName:=s.ReadStr;
>  {call your init method}
>  tMyCollection.init({The data read, f.ex.:}TheFileName^);
>  {Clean up any additional space}
>  {f.ex.:}
>  DisposeStr(TheFileName)
> end;
>Hope it helps.

I may be missing something here. I'm pretty sure that I understand how
to read and write from the stream. Thats what I do with string lists,
menu bars, and the status line. What I think I'm missing here is the
details of how an object gets initialized when read off of a stream.
For example lets say that you put an object that does  require some
parameters passed to its init method to be initialized propery. How
when reading from a stream file do you pass these parameters to the
init method. Also if one of the data elements is a pointer to another
object how does this pointer get initialized or for that matter stored
in a stream file? As you can see what I'm really missing here are some
of the mechanics behind streams.

I tried the suggestion that you made above and I now get a system
reboot when restoring the desktop. I will be continuing to investigate
this. I'm thinking there may be a pointer gone astray somewhere. Time
to go over the code again with a fine tooth comb. <g>

        Mike....

Re:Saving the Desktop


Quote
mike_wal...@mindlink.bc.ca (Mike Walker) writes:
>celts...@Physik.TU-Muenchen.DE (Christopher Eltschka) wrote:
>>mike_wal...@mindlink.bc.ca (Mike Walker) writes:
>>I haven't tested, but I think, the following should work:
>>In tMyCollection (or what you call it) add:
>>{Example: with tMyCollection.init(aFileName:string);}
>>procedure tMyCollection.Store(var s:tStream);
>> begin
>>  {do NOT call the inherited Store}
>>  {Write to the Stream all data needed for init}
>>  {f.ex.:}
>>  s.WriteStr(FileName^);
>> end;
>>constructor tMyCollection.Load(var s:tStream);
>> var
>>  {The Data you need for init}
>>  {f.ex.:}
>>  TheFileName:pString;
>> begin
>>  {do NOT call the inherited Load}
>>  {Read the data written in Store to the local Vars}
>>  {f.ex.:}
>>  TheFileName:=s.ReadStr;
>>  {call your init method}
>>  tMyCollection.init({The data read, f.ex.:}TheFileName^);
>>  {Clean up any additional space}
>>  {f.ex.:}
>>  DisposeStr(TheFileName)
>> end;
>>Hope it helps.
>I may be missing something here. I'm pretty sure that I understand how
>to read and write from the stream. Thats what I do with string lists,
>menu bars, and the status line. What I think I'm missing here is the

You always do it *as I wrote above*?
That is not the standard way! Normally you *don't* call the init constructor
from the load constructor. At normal stream loading, the Read constructor
builds up the object itself, without referring to the init method. And
normally you *have* to call the inherited load/store methods.

Quote
>details of how an object gets initialized when read off of a stream.

The stream works like the following:
If you write s.put(MyObject), the stream s first determines the type of the
object (like TypeOf), determines the object's registration number, writes
this number to the file, and then calls the object's store method (which
it also gets from the registration record). The store method is responsible
to write all the data of the object (and the sub-objects) to the stream
by using the stream's methods.

If you do MyObject:=s.Get, The stream assumes, that at the current position
an object is starting. It reads the registration number and looks up the
registration number to find out, which object type should be created.
Then it calls the object's Read constructor, which is responsible for
creating an object, that is identical to the previous stored object,
and for reading all the data from the stream (even if it doesn't need all
the data - but then most probably the store method should be changed...).
So what the stream does, is nothing but (1) identifying the object and telling
it to store or create itself, and (2) doing all the file handling stuff.

Quote
>For example lets say that you put an object that does  require some
>parameters passed to its init method to be initialized propery. How
>when reading from a stream file do you pass these parameters to the
>init method. Also if one of the data elements is a pointer to another

Normally you don't. The Read constructor creates the object itself.
If you don't inherit from tObject, but of an descendant, the inherited
Load does most of the work for you, and you just have to store your
additional fields.

In my code above, it works different, because the situation is different.
The collection isn't really stored on the stream, but instead the data
used to create a NEW collection from the (possibly changed) file
is stored (possibly you even have to recalculate that data, if you don't
store it in your collection). For this reason I changed the following
from the standard method:

- I didn't call the inherited Store/Load methods, so the collection itself
  doesn't get stored/created.
- I didn't read to object fields, but to local variables in the read
  constructor.
- I called the object's init constructor to create a new collection out of
  the file.
  (The tMyCollection.init is because the read constructor already has
  reserved the memory and made the VMT link, so a "half-constructed"
  object already exists; just calling init would build a completely new
  object at a different place, whose pointer then would get lost, while
  the Read constructor would give the address of the half-constructed
  object to the stream, which then gives you back this object as result of
  the Put method.

Quote
>object how does this pointer get initialized or for that matter stored
>in a stream file? As you can see what I'm really missing here are some
>of the mechanics behind streams.
>I tried the suggestion that you made above and I now get a system
>reboot when restoring the desktop. I will be continuing to investigate
>this. I'm thinking there may be a pointer gone astray somewhere. Time
>to go over the code again with a fine tooth comb. <g>

Well, rebooting the system was not intended by the code... ;-)
As I told, the code was untested, so I can't guarantee, that it is really
correct, but I can't see the error now. Does storing/loading of the desktop
work, if you don't have a collection at all?

If you use TP/BP 7.0, there might also be an incompatibility - I'm using TP 6.0.

The system crash could also happen because of deleting the desktop inside
the event loop. Try to make a flag, and then delete/change the desktop
in the Idle procedure of your application. Deleting views during event
handling can sometimes cause trouble (don't know why, but I had already
troubles deleting windows as answer to an event, which disappeared by
moving the actual deletion to the application's Idle method).

A good way to test what the stream really writes, is to make a tTestStream,
which writes all the information about what is done by the stream to an log
file.

The methods should look like this:

procedure tTestStream.Put(p:pObject);
 begin
  {needs a function, that converts pointers to Hex strings,
   called PointerToHex}
  writeln(LogFile,'Put called with object ',PointerToHex(p));
  if p<>nil
   then
    writeln(LogFile,'  (Object Type: ',PointerToHex(TypeOf(p)),')');
  {$IFDEF VER70}
   inherited Put(p);
  {$ELSE}
   tXXXStream.Put(p);
  {$ENDIF}
   writeln(LogFile,'Put of object ',PointerToHex(p),' ended.')
           {There could be a put in the object's store method!}
 end;

and so on (don't forget the method Error!).
Then you can see on your log file exactly, what the stream does (esp. what
data it reads from/writes to disk, as all reading/writing [including the
registration number] goes through the methods Read/Write of the Stream.
Maybe you find the error this way.

Quote
>    Mike....

Ch. Eltschka
If you are missing information, just remember, that this article was moving
through the net with high relativistic speed, so any shortening comes from lorentz
contraction...

Other Threads