Show/Hide Toolbars

RiverSoftAVG Products Help

Last step in creating our new chart type, we defined our chart value classes.  Now that we have the TRSGraphChartValue and TRSGraphChartValues classes, we need to customize them and add our links.  For the links, we need to

 

Allow unlimited connections, or links, between nodes

oLinks can have a direction, e.g., arrows

oLinks can have a label

oNodes can have one or more links

 

It would also be nice to be able to edit the links at design-time.  Based on these requirements, we will make another collection which will be our links.  We will include this collection as a published property of nodes (TRSGraphChartValue).  In other words, a node will own the links it establishes to other nodes.  Our nodes will have to create the links collection, destroy them and publish them.  This is what our new graph value nodes will look like (to show what we are changing I took out the code we wrote before):

 

  TRSGraphChartValue = class(TRSShapeChartValue)
  { Purpose: To encapsulate one shape (node) in an Graph chart, with links }
  private
    { Private declarations }
    FLinks: TRSGraphChartLinks;
    procedure SetLinks(const Value: TRSGraphChartLinks);
  protected
    { Protected declarations }
  public
    { Public declarations }
    procedure Assign(Source: TPersistent); override;
    constructor Create(Collection: TCollection); override;
    destructor Destroy; override;
  published
    { Published declarations }
    property Links: TRSGraphChartLinks read FLinks write SetLinks;
  end{ TRSGraphChartValue }
 
procedure TRSGraphChartValue.Assign(Source: TPersistent);
begin
     if Source is TRSGraphChartValue then
        Links.Assign(TRSGraphChartValue(Source).Links);
     inherited Assign(Source);
end;
 
constructor TRSGraphChartValue.Create(Collection: TCollection);
begin
     inherited Create(Collection);
     FLinks := TRSGraphChartLinks.Create(Self);
end;
 
destructor TRSGraphChartValue.Destroy;
begin
     FreeAndNil(FLinks);
     inherited Destroy;
end;
 
procedure TRSGraphChartValue.SetLinks(const Value: TRSGraphChartLinks);
begin
  FLinks.Assign( Value );
end; 

 

Now, we need to create the links.  First, let's decide what the properties of a link will be.  The first and most important property of a link will be to hold a reference to the ode or value we are pointing to.  Also, we want to be able to assign a string label or Caption to the link.  By using a collection, we are satisfying our final requirement that our node must be able to create an unlimited amount of links.

 

Using the New Collection Wizard again, we can easily create the link item and the link collection.  This time, we will also use the wizard to add our properties.  Start the wizard, fill in the information as shown in the figure and click OK.

 

You may have noticed that instead of descending from TCollection or TOwnedCollection, we descended from a TGCollection class.  This class is in the RiverSoftAVG Common Classes Library.  It provides the useful ability to assign the collection to a TStrings and provides an IndexOf function, but the most important aspect of the class is that it adds the Notify method for Delphi 5 or less.  We could have chosen TOwnedCollection as well - we chose it to maximize code compatibility across all versions of Delphi.

 

More importantly, after we run our wizard, we need to change the TRSGraphChartLink declaration to descend from TGCollectionItem instead of TCollectionItem.  Why?  Because TGCollectionItem (again part of the Common Classes Library) provides us with automatic notification methods when refering to other TGCollectionItems.  This is similar to the TComponent Notification mechanism but for collection items.  Since we are referring to a node in the link, if that node was deleted we would otherwise be holding onto an invalid pointer and cause an access violation at some point.

 

This brings up another point that we cannot rely on Delphi's streaming mechanism to stream our node we are pointing to.  We need to set Stored False for the Value property and then find a way to stream the Value out.  This is out of the scope of this article so we encourage you to look at the source code in RSGraphCharts for yourself.  (To give you a hint, basically, we stream out a pseudo property that contains the Index of the Value and after the stream is loaded, we go back and fix the references.)

 

Our code for the TRSGraphChartLink is below (without all the extra stuff in the source file):

 
  TRSGraphChartLink = class(TCollectionItem)
  { Purpose: }
  private
    { Private declarations }
    FCaption: String;
    FValue: TRSGraphChartValue;
    function GetCollection: TRSGraphChartLinks;
    procedure SetCollection(const Value: TRSGraphChartLinks); reintroduce;
    procedure SetCaption(const Value: String);
    procedure SetValue(const Value: TRSGraphChartValue);
  protected
    { Protected declarations }
    function GetDisplayName: Stringoverride;
  public
    { Public declarations }
    procedure Assign(Source: TPersistent); override;
    property Collection: TRSGraphChartLinks read GetCollection write SetCollection;
  published
    { Published declarations }
    property Caption: String read FCaption write SetCaption;
    property Value: TRSGraphChartValue read FValue write SetValue stored False;
  end{ TRSGraphChartLink }
 
{ TRSGraphChartLink }
 
procedure TRSGraphChartLink.Assign(Source: TPersistent);
begin
     if Source is TRSGraphChartLink then
     begin
          // Copy properties here
          FCaption := TRSGraphChartLink(Source).Caption;
          Value := TRSGraphChartLink(Source).Value;
          Changed(False);
     end
     else
         inherited Assign(Source);
end;
 
function TRSGraphChartLink.GetCollection: TRSGraphChartLinks;
begin
     result := TRSGraphChartLinks(inherited Collection);
end;
 
procedure TRSGraphChartLink.SetCollection(const Value: TRSGraphChartLinks);
begin
     inherited Collection := Value;
end;
 
function TRSGraphChartLink.GetDisplayName: String;
begin
     result := Caption;
     if result = '' then
        result := inherited GetDisplayName;
end;
 
procedure TRSGraphChartLink.SetCaption(const Value: String);
begin
     if Value <> Caption then
     begin
          FCaption := Value;
          Changed(False);
     end;
end;
 
procedure TRSGraphChartLink.SetValue(const Value: TRSGraphChartValue);
begin
     if FValue <> Value then
     begin
          if FValue <> nil then
             FValue.FreeNotification(Self);
          FValue := Value;
          if FValue <> nil then
             FValue.RemoveFreeNotification(Self);
          Changed(False);
     end;
end; 

 

The next step in the tutorial is to Customize your TRSCustomChart descendant.

RiverSoftAVG Products Help © 1996-2016 Thomas G. Grubb