Turbo Vision Tutorial

Part 3 -- Streams and Resources




Welcome

Hi! Turbo Vision (TV) provides a mean to save any TV objects through mechanism called stream. This chapter discusses on how to use that feature. Moreover, I will give a brief overture on resources. If you are already familiar with Windows programming, resources concept in TV is just way similar with the one in Windows.

 

Streams

You know that Pascal provides BlockRead and BlockWrite for reading and writing disk. Moreover we have other file statements to aid us. However, we find out that it would be more convenient if we can save the objects directly (or load them) without converting it into a certain format. TV has the answer: Use the stream, Luke!

Before we can use the stream, we have to register the objects we want to use the stream with. This can be done inside the Init constructor of our application. So, our Init will look like this:

constructor TMyApp.Init;
var R: TRect;
begin
  MaxHeapSize := 8192;
  EditorDialog := StdEditorDialog;
  StreamError := @MyStreamError;     { This is needed to trap stream error }
  RegisterObjects;                   { Registration is here }
  RegisterViews;
  RegisterMenus;
  RegisterDialogs;
  RegisterEditors;
  RegisterApp;
  inherited init;
  :
  :  { and so on, if you initialize the clipboard, place it here }

As you can quickly notice the Register... statements do the registration. That's easy. Then the assignment to StreamError is merely to trap the stream error. Of course we have to define the procedure MyStreamError to give the proper error message. So, that procedure looks like this:

procedure MyStreamError(var S: TStream); far;     { Declare it as far procedure }
var msg: String;
begin
  case S.Status of
    stError:      msg := 'Stream access error';
    stInitError:  msg := 'Cannot initialize stream';
    stReadError:  msg := 'Read beyond end of stream';
    stWriteError: msg := 'Cannot expand stream';
    stGetError:   msg := 'Unregistered type read from stream';
    stPutError:   msg := 'Unregistered type written to stream';
  end;
  clearscreen;
  PrintStr('Error : ' + msg);
  Halt(abs(S.Status));
end;

Hmm... that's simple. Notice that MyStreamError is non-OOP procedure and it is non elegant, but simply show you on how the error handler should be set up. If you want to ignore the error, simply set StreamError to nil.

Now, how can I save the object? Let's say that you want to save the whole desktop:

procedure TMyApp.SaveDesktop;
var Buf: TBufStream;
begin
  Buf.Init('DESKTOP.SAV', stCreate, 1024);   { rewrite DESKTOP.SAV using 1024 bytes buffer }
  Buf.Put(Desktop);
  Buf.Done;
end;

Woo... that's easy. The constant stCreate tells TV to rewrite the file DESKTOP.SAV. The Done destructor simply tells TV to flush the buffer and close the stream. Of course when saving the entire desktop, you want to exclude the clipboard, right? If so, before you Init, set aside the clipboard window by doing: Desktop^.Delete(ClipboardWin);. And then, after Done, restore it using InsertWindow(ClipboardWin);.

To restore the desktop, do this:

procedure TMyApp.LoadDesktop;
var Buf: TBufStream;
    R: TRect;
    Temp: PDesktop;
begin
  Buf.Init('DESKTOP.SAV', stOpenRead, 1024);   { Read the file using 1024 bytes buffer}
  Temp := PDesktop(Buf.Get);
  Buf.Done;      { Close the file }

  { Validify before replacing the desktop }
  if ValidView(Temp) <> nil then
  begin
    Desktop^.Delete(ClipboardWin);   { Throw out the clipboard window }
    Delete(Desktop);                 { Throw out the desktop }
    Dispose(Desktop, Done);
    Desktop := temp;                 { Replace it with the newly loaded }
    Insert(Desktop);                 { Insert it to our application }
    GetExtent(R);
    R.Grow(0,-1);                    { Fix the desktop dimension in case }
    Desktop^.Locate(R);              { user has changed screen resolution to 43 or 50 lines }
    InsertWindow(ClipboardWin);      { Restore clipboard }
  end;
end;

Hmm... that's easy. You can easily follow the comments for the explanation.

 

Resources

The resources concept are pretty much similar to the one in Windows. Still remember on how should we define the menu bar? It's cumbersome isn't it. Well, why couldn't we just build it once then save it to disk and then load it when we need them? Of course you can. The answer is by using resource file.

The first steps on using the resource files are just the same as the ones in using streams: Give those bunch of Register... statements at the same place as above. Then, you can use the resources nearly the same way. To save the resource, say the menu bar, first, you have to construct the menu bar in a separate file other than the one you use for your main application. The construction is just the same way. Then, use TResourceFile to save it. The sketch is as follows:

var
  Buf: TResourceFile;
  MyMenu: PMenuBar;
begin
  MyMenu :=  .... { construct the menu bar as usual }
  Buf.Init(New(PBufStream, Init('MENU.SAV', stCreate, 1024)));
  Buf.Put(MyMenu, 'MAINMENU');   { Put the menu bar and name the resource "MAINMENU" }
  Buf.Done;                   { Flush and close }
end;

Hmm... pretty similar. The program should be clear enough to read. Then, in the main program (i.e. the TMyApp), just register the things using Register... statements. Just before inherited init;, load the resources you'd like to use. So, if you have saved the menu bar previously, you can load it by using this line:

Buf.Init(New(PBufStream, Init('MENU.SAV', stOpenRead, 1024)));

Remember to declare Buf as a field of type TResourceFile. You can also load other resources the same way. There is one extra step: closing the resource file. How can we do that? We cannot close the resource file immediately after Buf.Init. If we do that, the resource is simply unusable. So, the solution is to inherit the destructor Done like this:

destructor TMyApp.Done;
begin
  Buf.Done;   { Close and flush it }
  :
  :           { Close and flush other resources as well }
  inherited Done; { call the super class Done destructor }
end;

Then, to load the menu bar: MyMenu := PMenuBar(Buf.Get('MAINMENU'));. That's pretty straight forward. Just like streams. By naming the resources, we can store several resources like menu bar, status line, dialog boxes, etc inside one file. Then, we can load it using the name tag (e.g. MAINMENU).

That's easy. Then, before setting it up to the desktop, you may adjust the position so that it matches the current screen resolution. To do so, you may want to use GetExtent and see the example in Stream section to get the insights.

 

Extra: DOS Shell in TV

To shell to DOS in TV, you first inherit the method WriteShellMsg. This method simply says some message before shelling out. It look like this:

procedure TMyApp.WriteShellMsg;
begin
  PrintStr('Leaving Turbo Vision to DOS, type EXIT to come back');
end;

Then, to really shell to DOS, invoke DosShell method. Easy, no?

 

Closing

OK, I think that's all for now. See you at the next chapter about dialog boxes.

 


Where to go

Chapter 10
News Page
Pascal Lesson 3 index
Contacting Me


Roby Joehanes © 2001