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?
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.
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!!
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.
OK, I think that's all for now. See you at the next chapter about extending components.
Chapter 12
News Page
Pascal Lesson 3 index
Contacting Me
Roby Joehanes © 2001