home *** CD-ROM | disk | FTP | other *** search
/ Programmer 7500 / MAX_PROGRAMMERS.iso / PASCAL / TOTDOC.ZIP / CHAPT19.TXT < prev    next >
Encoding:
Text File  |  1991-02-11  |  19.6 KB  |  456 lines

  1.                                                                      Customizing
  2.                                                                           Linked
  3.                                                                            Lists
  4.  
  5.  
  6.  
  7.  
  8.  
  9.          "As we acquire knowledge, things do not become more comprehensible, but
  10.          more mysterious."
  11.  
  12.                                                                      Will Durant
  13.  
  14.  
  15.  
  16.  
  17.  
  18.          Doubly-linked lists are ideal for managing large lists of data, they
  19.          are memory efficient and fast. The only problem is they are compli-
  20.          cated! Fortunately, the Toolkit provides a very easy to use doubly-
  21.          linked list object called DLLOBJ. DLLOBJ is an abstract object designed
  22.          specifically to simplify the task of extending the object. In this
  23.          chapter, techniques for developing a custom doubly-linked list for
  24.          managing records is discussed.
  25.  
  26.          You might want to consider re-reading the section on linked list theory
  27.          (page 9-1) before proceeding.
  28.  
  29.  
  30.  
  31. DLLOBJ
  32.  
  33.          Unlike many objects, you do not need to understand very much about
  34.          DLLOBJ to create descendant objects. All the following methods (which
  35.          were described in chapter 9) are unaffected by the type of data stored
  36.          in the list, and do not need to be modified in descendant objects:
  37.  
  38.          procedure   Advance(Amount:longint);
  39.          procedure   Retreat(Amount:longint);
  40.          function    NodePtr(NodeNumber:longint): DLLNodePtr;
  41.          procedure   Jump(NodeNumber:longint);
  42.          procedure   ShiftActiveNode(NewNode: DLLNodePtr; NodeNumber: longint);
  43.          procedure   DelNode(Node:DLLNodePtr);
  44.          procedure   DelAllStatus(BitPos:byte;On:boolean);
  45.          function    TotalNodes: longint;
  46.          function    ActiveNodeNumber: longint;
  47.          function    ActiveNodePtr: DLLNodePtr;
  48.          function    StartNodePtr: DLLNodePtr;
  49.          function    EndNodePtr: DLLNodePtr;
  50.          procedure   EmptyList;
  51.          procedure   Sort(SortID:shortint;Ascending:boolean);
  52.          procedure   SwapNodes(Node1,Node2:DLLNodePtr);
  53.  
  54.  
  55.          The DLLOBJ stores un-typed data in binary format. You can literally
  56.          stored any type of data in a DLLOBJ list. The following methods add and
  57.          modify data in a list:
  58.  
  59.  
  60. 19-2                                                       Extending the Toolkit
  61.  
  62. --------------------------------------------------------------------------------
  63.  
  64.          function Add(var TheData;Size:longint): integer;
  65.          function Change(Node:DLLNodePtr;var TheData;Size:longint): integer;
  66.          function InsertBefore(Node:DLLNodePtr;var TheData;Size:longint): inte-
  67.          ger;
  68.  
  69.  
  70.          Each of these three methods are passed an untyped parameter and a lon-
  71.          gint indicating the size of the data. In descendant objects, you will
  72.          call these methods to manipulate the data in the list. The following
  73.          methods return information about the data stored in the list:
  74.  
  75.          procedure   Get(var TheData);
  76.          procedure   GetNodeData(Node:DLLNodePtr;Var TheData);
  77.          function    GetNodeDataSize(Node:DLLNodePtr):longint;
  78.          function    GetMaxNodeSize: longint;
  79.  
  80.  
  81.          The most used method is GetNodeData, which will update a passed un-
  82.          typed parameter with the data stored in the list. It is the fundamental
  83.          way for a descendant object to get data from the list.
  84.  
  85.  
  86.  
  87. Extending DLLOBJ
  88.  
  89.          The main reason for extending DLLOBJ is to make the new object manage a
  90.          specific type of data. The Toolkit includes the descendant StrDLLOBJ,
  91.          which is specifically designed to store strings, and FileDLLOBJ, which
  92.          stores DOS file details.
  93.  
  94.          In this section, DLLOBJ will be extended and customized to store a
  95.          record. To illustrate the principles involved, we will create a new
  96.          object RecordDLLOBJ to store the following record data:
  97.  
  98.          RecordInfo = record
  99.             FirstName: string[15];
  100.             LastName: string[15];
  101.             Company: string[20];
  102.             Tel: string[10];
  103.             CumDollarsSpent: real;
  104.             LastOrder: longint;
  105.             Comments: string[40];
  106.          end;
  107.  
  108.  
  109.          The main methods that need to be customized are the data manipulation
  110.          methods, i.e. Add, Change and InsertBefore. If you want to display the
  111.          object in a Browse or List window you must also customize the GetStr
  112.          virtual method. GetStr is called by the browse and list objects, and is
  113.          simply a function which returns the data stored at a node in string
  114.          form. The fifth method which usually needs to be customized is Wron-
  115.  
  116.  
  117.  
  118. Customizing Linked Lists                                                    19-3
  119.  
  120. --------------------------------------------------------------------------------
  121.  
  122.          gOrder. This method provides the Sort method with the information
  123.          needed to sort the data, and is discussed in a later section. The new
  124.          object would therefore be declared as follows:
  125.  
  126.          RecordListOBJ = object (DLLOBJ)
  127.             constructor Init;
  128.             function    Add(Rec:RecordInfo): integer;
  129.             function    Change(Node:DLLNodePtr;Rec:RecordInfo): integer;
  130.             function    InsertBefore(Node:DLLNodePtr;Rec:RecordInfo): integer;
  131.             function    WrongOrder(Node1,Node2:DLLNodePtr;
  132.                                    Asc:boolean): boolean;        VIRTUAL;
  133.             function    GetStr(Node:DLLNodePtr;
  134.                                Start,Finish: longint):string;    VIRTUAL;
  135.             destructor  Done;                                    VIRTUAL;
  136.          end; {RecordListOBJ}
  137.  
  138.  
  139.          Notice that Add, Change and InsertBefore are each passed a variable of
  140.          type RecordInfo. All these methods need to do is call their correspond-
  141.          ing DLLOBJ method and pass the record as an untyped parameter together
  142.          with the record size. The three methods would be implemented as
  143.          follows:
  144.  
  145.          function RecordDLLOBJ.Add(Rec:RecordInfo): integer;
  146.          begin
  147.             Add := DLLOBJ.Add(Rec,sizeof(Rec));
  148.          end; {RecordDLLOBJ.Add}
  149.  
  150.          function RecordDLLOBJ.Change(Node:DLLNodePtr;
  151.                                        Rec:RecordInfo): integer;
  152.          begin
  153.             Change := DLLOBJ.Change(Node,Rec,sizeof(Rec));
  154.          end; {RecordDLLOBJ.Change}
  155.  
  156.          function RecordDLLOBJ.InsertBefore(Node:DLLNodePtr;
  157.                                              Rec:RecordInfo): integer;
  158.          {}
  159.          begin
  160.             InsertBefore := DLLOBJ.InsertBefore(Node,Rec,sizeof(Rec));
  161.          end; {RecordDLLOBJ.InsertBefore}
  162.  
  163.  
  164.          It's really as simple as that.
  165.  
  166.          The function method GetStr is passed three parameters; a node pointer
  167.          indicating which data to access, and the Start and Finish parameters of
  168.          type longint. Start and Finish identify the first and last character
  169.          positions of the sub-string to be returned by the function. The DLLOBJ
  170.          method GetNodeData can be used to retrieve the node data, and then the
  171.          data must be converted into string form. The requested portion of this
  172.          string can then be returned. GetStr could be implemented as follows:
  173.  
  174.  
  175.  
  176. 19-4                                                       Extending the Toolkit
  177.  
  178. --------------------------------------------------------------------------------
  179.  
  180.          function RecordDLLOBJ.GetStr(Node:DLLNodePtr;Start,Finish: lon-
  181.          gint):string;
  182.          {Returns string representation of record}
  183.          var
  184.            Temp: string;
  185.            Rec: RecordInfo;
  186.          begin
  187.             if Node = nil then
  188.                GetStr := 'Not found'
  189.             else
  190.             begin
  191.                GetNodeData(Node,Rec);  {inherited method}
  192.                with Rec do
  193.                begin
  194.                   Temp := inttostr(ActiveNodeNumber)+': '+
  195.                           FirstName+
  196.                           LastName+
  197.                           Company;
  198.                   if Finish > 53 then
  199.                      Temp := Temp + PicFormat(Tel,'(###) ###-####',' ')+' ';
  200.                   if Finish > 68 then
  201.                      Temp := Temp + JultoStr(LastOrder,MMDDYY)+' ';
  202.                   if Finish > 77 then
  203.                      Temp := Temp + FmtNumberTOT.FormattedReal
  204.                                     (CumDollarsSpent,2,10)+' ';
  205.                   if Finish > 88 then
  206.                      Temp := Temp + Comments;
  207.                 end;
  208.                 GetStr := copy(Temp,Start,succ(Finish-start));
  209.              end;
  210.          end; {RecordDLLOBJ.GetStr}
  211.  
  212.  
  213.          GetStr will never be called with Start and Finish parameters that are
  214.          more than 255 characters apart. In this case example, the entire record
  215.          can be represented by a string, so GetStr builds a string and returns
  216.          the requested sub-string. In cases where the record is larger than will
  217.          fit in a 255 string, the method should only convert the requested por-
  218.          tion of the record in string form. Note that the Browse object calls
  219.          GetStr many times during a browse session, and that GetStr needs to
  220.          respond quickly to avoid sluggishness. If you find that browsing is too
  221.          slow, try the program with range checking, stack checking etc. turned
  222.          off. These compiler directives slow down string-related activity con-
  223.          siderably.
  224.  
  225.  
  226.          The file EXTLINK.PAS contains the entire code for the customized
  227.          RecordDLLOBJ object, and is only about 100 lines long. Use this unit as
  228.          a template for your own specific record types.
  229.  
  230.  
  231.  
  232.  
  233. Customizing Linked Lists                                                    19-5
  234.  
  235. --------------------------------------------------------------------------------
  236.  
  237. Displaying RecordListOBJ Records
  238.  
  239.          The List and Browse objects automatically support any descendant of
  240.          DLLOBJ, and so the new object RecordDLLOBJ can be easily displayed in a
  241.          List or Browse window.
  242.  
  243.          The key method controlling what information is displayed in the window
  244.          is the GetStr method. Thanks to polymorphism, the List and Browse
  245.          objects don't need to know the specifics of GetStr. When they need a
  246.          node in string form, they simply call the DLLOBJ (or descendant) GetStr
  247.          method, and use whichever string is returned.
  248.  
  249.          Listed below is the demo program, EXTDEM5.PAS, which displays the list
  250.          contents in a browse window. Figure 19.1 shows the resultant output.
  251.  
  252.          Program ExtendedDemoFive;
  253.  
  254.          Uses DOS,CRT,
  255.               totFAST, totINPUT, totList, extLINK, totSTR;
  256.  
  257.          var
  258.            RecList: RecordDLLOBJ;
  259.            ListWin: BrowseLinkOBJ;
  260.  
  261.          procedure BuildTheList(Filename:string);
  262.          {loads in the data from disk - could also be from d/b}
  263.          var
  264.            F: file of RecordInfo;
  265.            Rec: RecordInfo;
  266.            Ecode: integer;
  267.          begin
  268.             assign(F,filename);
  269.             {$I-}
  270.             reset(F);
  271.             {$I+}
  272.             if ioresult <> 0 then
  273.             begin
  274.                writeln('The file ',filename,' cannot be located.');
  275.                writeln('Demo aborting');
  276.                halt(1);
  277.             end;
  278.             Ecode := 0;
  279.             RecList.Init;
  280.             while not eof(F) and (Ecode = 0) do
  281.             begin
  282.                Read(F,Rec);
  283.                with Rec do
  284.                begin
  285.                   FirstName:= padleft(FirstName,15,' ');
  286.                   LastName:= padleft(LastName,15,' ');
  287.                   Company:= padleft(Company,20,' ');
  288.                end;
  289.  
  290.  
  291.  
  292. 19-6                                                       Extending the Toolkit
  293.  
  294. --------------------------------------------------------------------------------
  295.  
  296.                Ecode := RecList.Add(Rec);
  297.             end;
  298.             close(F);
  299.          end; {BuildtheList}
  300.  
  301.          begin {Main program}
  302.             BuildTheList('EXTDEM5.DBF');
  303.             Screen.Clear(white,'░'); {paint the screen}
  304.             Key.SetFast;
  305.             with ListWin do
  306.             begin
  307.                Init;
  308.                AssignList(RecList);
  309.                Go;
  310.             end;
  311.          end.
  312.  
  313.  
  314. Figure 19.1                                                             [SCREEN]
  315. Browsing
  316. RecordDLLOBJ
  317.  
  318.  
  319.  
  320.          As it stands, the string returned by GetStr is not really suited for
  321.          displaying in a list. Each item in the list would be wider than the
  322.          list display! A quick solution is to build a descendant of RecordDLLStr
  323.          and replace the GetStr method with a method which only returns a short
  324.          string, e.g. last name. The on-disk example EXTDEM6.PAS illustrates
  325.          this technique. In this example, ListLinkOBJ is also extended and cus-
  326.          tomized to show the full record in the message box at the bottom of the
  327.          list. This technique was described on page 9-30. Figure 19.2 shows the
  328.          output generated by EXTDEM6.
  329.  
  330.  
  331.  
  332. Figure 19.2                                                             [SCREEN]
  333. Listing
  334. RecordDLLOBJ
  335.  
  336.  
  337.  
  338. Sorting
  339.  
  340.          It is very easy to make your custom linked lists sortable. To make a
  341.          list sortable, the inherited virtual method WrongOrder must be com-
  342.          pleted. Behind the scenes, the Toolkit sort routines repeatedly call
  343.          WrongOrder to determine whether two nodes are in the correct order.
  344.  
  345.          The WrongOrder method is declared as follows:
  346.  
  347.             function WrongOrder(Node1,Node2:DLLNodePtr;Asc:boolean): boolean;
  348.  
  349.  
  350.  
  351.  
  352. Customizing Linked Lists                                                    19-7
  353.  
  354. --------------------------------------------------------------------------------
  355.  
  356.          WrongOrder is a boolean function method which should return TRUE if the
  357.          data in the two nodes are in the wrong order. The method is passed
  358.          three parameters; two DLLNodePtr pointers to identify the nodes to be
  359.          compared, and a boolean to indicate whether the list is being sorted in
  360.          ascending (true) or descending (false) order.
  361.  
  362.          The WrongOrder method must therefore get the data from the two nodes,
  363.          and decide if they are in the appropriate order for the sort. All
  364.          DLLOBJ objects include a shortint vSortID. This variable can be used to
  365.          provide multiple sorting capabilities. When you call the Sort method,
  366.          you must pass two parameters; a Sort ID, and a boolean to indicate
  367.          whether the sort order is ascending or descending. The Toolkit automat-
  368.          ically updates vSortID with the parameter passed to Sort. WrongOrder
  369.          should therefore check vSortID to determine what data to use in
  370.          deciding whether the order is correct.
  371.  
  372.          In the RecordDLLOBJ example, the following codes might be appropriate:
  373.  
  374.                    1      sort on LastName
  375.                    2      sort on Company
  376.                    3      sort on Tel
  377.                    4      sort on CumDollarsSpent
  378.                    5      sort on LastOrder
  379.  
  380.          The actual codes you select are not important. You decide which fields
  381.          you want to be able to sort on, and which codes to use. Using the
  382.          listed codes, the WrongOrder method for the RecordDLLOBJ object would
  383.          be as follows:
  384.  
  385.          function RecordDLLOBJ.WrongOrder(Node1,Node2:DLLNodePtr;Asc:boolean):
  386.          boolean;
  387.          var
  388.            S1,S2: string;
  389.            Rec1,Rec2: RecordInfo;
  390.            R1,R2: real;
  391.            D1,D2: longint;
  392.          begin
  393.             GetNodeData(Node1,Rec1);
  394.             GetNodeData(Node2,Rec2);
  395.             if vSortID in [1,2,3] then
  396.             begin
  397.                case vSortID of
  398.                   1:begin      {LastName}
  399.                      S1 := Rec1.LastName;
  400.                      S2 := Rec2.LastName;
  401.                   end;
  402.                   2: begin     {Company}
  403.                      S1 := Rec1.Company;
  404.                      S2 := Rec2.Company;
  405.                   end;
  406.                   3: begin     {Tel}
  407.  
  408.  
  409.  
  410. 19-8                                                       Extending the Toolkit
  411.  
  412. --------------------------------------------------------------------------------
  413.  
  414.                      S1 := Rec1.Tel;
  415.                      S2 := Rec2.Tel;
  416.                   end;
  417.                end; {case}
  418.                if Asc then
  419.                   WrongOrder := (S1 > S2)
  420.                else
  421.                   WrongOrder := (S2 > S1);
  422.             end
  423.             else if vSortID = 4 then  {CumDollars}
  424.             begin
  425.                R1 := Rec1.CumDollarsSpent;
  426.                R2 := Rec2.CumDollarsSpent;
  427.                if Asc then
  428.                   WrongOrder := (R1 > R2)
  429.                else
  430.                   WrongOrder := (R2 > R1);
  431.             end
  432.             else                       {LastOrder}
  433.             begin
  434.                D1 := Rec1.LastOrder;
  435.                D2 := Rec2.LastOrder;
  436.                if Asc then
  437.                   WrongOrder := (D1 > D2)
  438.                else
  439.                   WrongOrder := (D2 > D1);
  440.             end;
  441.          end; {RecordDLLOBJ.WrongOrder}
  442.  
  443.  
  444.          To sort the list, call the method Sort, e.g. MyList.Sort(1,true). The
  445.          demo file EXTDEM6.PAS includes a sort statement.
  446.  
  447.  
  448.  
  449. Using Status Codes
  450.  
  451.          List status codes are used internally by the Toolkit to support the
  452.          displaying of lists in windows. In some circumstances, you may be able
  453.          to use this facility in your custom lists.
  454.  
  455.          All items stored in a DLLOBJ, or descendant object, include a Status
  456.          byte. Each of the eight bits in the Status byte can be used for a
  457.          different purpose. Chapter 9: Managing Lists (page 9-27), describes how
  458.          the first two bits are used by the List display object. You may recall
  459.          that bit 0 is used to identify whether the item is tagged, and bit 1
  460.          identifies which items to display in an alternate color. The remaining
  461.          six bits can be used for your own custom needs. For example, you might
  462.          use bit 2 to identify all customers with a delinquent account, and bit
  463.          3 might identify whether the customer is international or domestic, or
  464.  
  465.  
  466.  
  467.  
  468. Customizing Linked Lists                                                    19-9
  469.  
  470. --------------------------------------------------------------------------------
  471.  
  472.          you might extend ListOBJ to use a three-color list, and use bit 2 to
  473.          identify items to show in the third color. Whatever the reason, they
  474.          are there if you need them.
  475.  
  476.          To access the Status bits, you need to know how data is stored at each
  477.          node. Each node in a list actually points to a DLLNodeOBJ. This object
  478.          manages the storage of data at the node, as well as the node status
  479.          byte. The following four DLLNodeOBJ methods support the Status byte:
  480.  
  481.                   GetStatus(Bitpos:byte): boolean;
  482.                   SetStatus(BitPos:byte;On:boolean);
  483.                   GetStatusByte:byte;
  484.                   SetStatusByte(Val:byte);
  485.  
  486.          To call these methods use the syntax MyList.NodePtr(number)^.method.
  487.          Use the Set methods to change a status bit, and the get methods to
  488.          check the current status. For example, to set the fourth bit to true
  489.          for the 12th entry in the list MyList, you would use the following
  490.          statement:
  491.  
  492.                   MyList.NodePtr(12).SetStatus(3,true);
  493.