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 |
o | Links can have a direction, e.g., arrows |
o | Links can have a label |
o | Nodes 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: String; override;
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.