Turbo Vision Tutorial

Part 5 -- Collections




Welcome

Hi! Welcome back. Now, we will discuss on how any object can be converted so that it can make use stream. Streams, discussed previously, in Turbo Vision (TV) is very handy, right?

 

Streaming Requirements

When you look into previous TV streams tutorial, you may notice that the streamable objects must be of TObject descendant. Yes, it is, but can we encapsulate a data structure into an object, then make that object a descendant of TObject to make the stream work for us? Yes, you can. However, there are some extra steps involved.

First of all, you need to encapsulate your data structure into an object. Make that object a descendant of TObject. The next step is to provide a field that hold a data of type the data structure you want to stream. Then, you have to inherit constructor Load and method Store. The last step is to provide a registration record inside that object.

Hunh?? :-) See the code below for an illumination. Remember the TOrder and POrder we define in the previous chapter? Suppose we want to make it streamable. Here's how.

type
   POrderObj = ^TOrderObj;
   TOrderObj = object(TObject)  { Encapsulate, and make it descendant of TObject }
                 Data: TOrder;  { Provide a data field }
                 constructor Load(var S: TStream);  { Inherit Load and Store }
                 procedure Store(var S: TStream);
               end;
const ROrderObj: TStreamRec = ( { A registration record } ObjType: 15000; { <-- A unique word constant of 100 or above } VmtLink: Ofs(TypeOf(TOrderObj)^); Load: @TOrderObj.Load; Save: @TOrderObj.Save ); constructor TOrderObj.Load(var S: TStream); begin inherited init; S.Read(Data, SizeOf(Data)); end; procedure TOrderObj.Store(var S: TStream); begin S.Write(Data, SizeOf(Data)); end;

Hmm... that's pretty easy. Note that the contents of Load and Store must be that way. No change is necessary regardless of your data structure. The registration record name is, by convention, equal to the encapsulator class name, but use the prefix R instead of T. The definition must be as is. The only thing you should change is the ObjType. It is a word constant greater than or equal to 100. This word constant must be unique across all registration records.

In your Init constructor of your main class (i.e. TMyApp), you need to specify Register... commands right? Now, you have to do the same for each data types you need to stream. It is by doing RegisterType(ROrderObj);. Of course if you have more streamable data types, you need to RegisterType each of them.

Well, the way I describe here is the standard way of making data structures stream, which I think the easiest way. If you modify things (like in TStringCollection), you have to inherit TCollection and do some more effort (i.e. override some methods). For simplicity, I just refer to the "standard" way for this tutorial.

 

Saving and Loading Collection Data

Since you use it in all standard way, you need not to modify TCollection. To use it in your application, you must define a field inside your main application class (i.e. TMyApp), say Orders of type PCollection to hold the entire data. Then, loading the data is pretty much the same way:

procedure TMyApp.LoadOrders;
var  Buf: TBufStream;
begin
   Buf.Init('ORDERS.DAT', stOpenRead, 1024);
   Orders := PCollection(Buf.Get);
   Buf.Done;
end;

Hmm... easy. Don't forget to include those preambles that I mentioned in the previous stream tutorial. Saving the stream is also analoguous.

procedure TMyApp.SaveOrders;
var  Buf: TBufStream;
begin
   Buf.Init('ORDERS.DAT', stOpenWrite, 1024);
   Buf.Put(Orders);
   Buf.Done;
end;

After loading it to Orders, how can we extract the data out? Look at the following snippet:

var
   DataPos: word;
   MyOrder: TOrder;
begin
  :
  :
  MyOrder := POrderObj(Orders^.At(DataPos))^.Data;
  :
  :
end;

Ha! That's easy. How about storing them back? It's similar:

POrderObj(Orders^.At(DataPos))^.Data := MyOrder

That's easy!!

 

ForEach, FirstThat, and LastThat

A more versatile feature of collections is that you can call a procedure (or a function) to iterate through the collection object without explicitly construct a loop. That will simplify a lot of things! How can it be accomplished?

In TCollection we have a method called ForEach which takes a parameter. This parameter must be filled with an address of a procedure which takes one pointer element of the data structure contained within the collection. Confused? See the code below:

procedure PrintOrder(P: POrder); far; { A far procedure }
begin
  :   { Prints the contents of one P^ }
  :
end;

procedure TMyApp.PrintOrders;
begin
  Orders^.ForEach(@PrintOrder);
end;

This will print all orders. Easy right?

TCollection also has a method called FirstThat. FirstThat has a parameter of a boolean function. This boolean function also takes a parameter like above (i.e. having P: POrder). The boolean function's job is to check whether the parameter matched a specific criterion. Thus, FirstThat is great for finding things. The snippet is as follows:

function Match(P: POrder): boolean; far; { A far function }
begin
  match := P^.OrderNum = '111-2222';
end;

procedure TMyApp.FindOrders;
var Temp: POrder;
begin
  Temp := Orders^.FirstThat(@Match);
  if Temp = nil then
  begin
    writeln('User not found!');
  end else
  begin
    :
    :  { We've found user's data }
  end;
end;

Easy right? LastThat is just like FirstThat except that it start finding the item backwards, so the last matched is shown first.

 

Closing

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

 


Where to go

Chapter 12
News Page
Pascal Lesson 3 index
Contacting Me


Roby Joehanes © 2001