Solved

Drawing Line with arrows to connect objects

Posted on 1998-10-30
10
857 Views
Last Modified: 2010-04-06
I need a code, or component that I can use to draw lines between one or more controls on a form.  If I move the controls at runtime, the line should also move.  I would like the option of displaying an arrow at each, one, no ends. The arrow should correctly position itself upon move.  Very similar to a flowcharting package.  Drag a box, connected lines move with it.
0
Comment
Question by:blitz051697
  • 3
  • 3
  • 2
  • +2
10 Comments
 
LVL 8

Expert Comment

by:ZifNab
ID: 1345084
blitz,
 
 if you need it for flowcharting, then look at the excelent   component of TeeMach (TeeChart), namely TeeTree. It does all you need.  http://www.teemach.com/tee4/tree/TeeTree1.htm Isn't that expensive either.

Zif.
 


0
 
LVL 8

Expert Comment

by:ZifNab
ID: 1345085
0
 
LVL 8

Expert Comment

by:ZifNab
ID: 1345086
ok, I knew I once saw a freeware one. I found it very interesting then, but still haven't used it. Maybe looking at the code will help you something :

http://www.melander.dk/delphi/statemachine/index.html

Zif.

ps. I don't think you can make it with the standard components, you'll always need to make other components which have your specific feature.
0
 
LVL 5

Expert Comment

by:scrapdog
ID: 1345087
To give you a way you could possibly do this:

Create a data structure for each component on your form.  This structure could contain whether or not it contains a line, which kind of arrow, which components it points to, etc..

Instead of making a new class with this structure, you could dynamically allocate memory for each one, and point to it using the component's "tag" property.  The tag would contain a pointer.  Note that you could use either an object, or simply a dynamically allocated data structure.  For example, you could do it like this:

Button1DataStructure := TComponentLineStructure.Create;

or

New(Button1DataStructure);

and then set the tag in Button1:

Button1.Tag := LongInt(Button1DataStructure);



Then, in your form's OnPaint method, go through all components on the page, and test what kind of component it is, and the structure attributed to each of them, to determine what kind of line to draw.

Here is something to get you started:  (note that I in this code I am referring to pointers rather than objects in the tag property)

procedure TForm1.FormPaint(Sender: TObject);
var x1,y1,x2,y2,i :integer;
    comp :TComponent;
    p :^TComponentLineStruct;
begin
  for i := 0 to ComponentCount-1 do begin
    comp := Components[i];
    if comp is TButton then begin
      p := Pointer(comp.Tag);
      if p^.DrawALine then begin
        x1 := comp.left;
        y1 := comp.top;
        x2 := p^.OtherComponent.Left;
        y2 := p^.OtherComponent.Top;
        if y2 > y1 then y1 := comp.top + comp.height;
        if x2 > x1 then x1 := comp.left + comp.width;
        with Canvas do begin
          MoveTo(x1,y1);
          LineTo(x2,y2);
        end;
    end
    else if comp is TListBox  {etc ...}
      ...
      ...

end;


The line drawing is done right in OnPaint.  You could have separate line drawing methods for each component, or have some that covers more than one component (such as "if (comp is TEdit) or (comp is TMaskEdit) then...").

Again, this is just to give you an idea where you could start.  Add any details as necessary.

scrapdog
0
 
LVL 5

Accepted Solution

by:
inter earned 500 total points
ID: 1345088
Hi friends,
the following is the complete implementation of list of control that links. To test it
1 - Add a button and edit to your empty form
2 - paste the following code
3 - link Button1Click, FormCreate, FormDestroy and FormPaint events
4 - Run
Questions and suggestions are wellcome, c.u.
igor

//***************CODE BEGINS******************
interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;

type
  TForm1 = class(TForm)
    Edit1: TEdit;
    Button1: TButton;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormPaint(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}
uses
  Math;

type
  TArrowStyle = (asNone, asTip, asBoth);
  // this one defines each connection info
  PLinkDesc = ^TLinkDesc;
  TLinkDesc = packed record
    S,D : TControl;
    Style : TArrowStyle;
  end;
var
  Links : TList = nil; //this list stores all of your links
//************************************************************
// Link operations
procedure AddLink(S,D:TControl; Style : TArrowStyle);
var
  P : PLinkDesc;
begin
  if Links = nil then Links := TList.Create;
  P := New(PLinkDesc);
  P.S := S; P.D := D; P.Style := Style;
  Links.Add(P);
end;

procedure ClearLinks;
var
  i : integer;
begin
  for i := 0 to Links.Count -1 do
    Dispose(Links.Items[i]);
  Links.Free; Links := nil;
end;

//Draws an arrow give angles in degrees in to specified canvas
procedure DrawArrow(C: TCanvas;
  O, P: TPoint; //start and end of line
  ALen: integer; //length of each arrow tip line
  AAngle: integer); //angle between small lines with base line
var
  M_PI_DEG: real;
  function Rotate(P: TPoint; Angle: Integer): TPoint;
  begin
    Result.X := Round(P.X * Cos(M_PI_DEG * Angle) - P.Y * Sin(M_PI_DEG * Angle));
    Result.Y := Round(P.X * Sin(M_PI_DEG * Angle) + P.Y * Cos(M_PI_DEG * Angle));
  end;
  function PolarToCartesian(R: Integer; Angle: Integer): TPoint;
  begin
    Result.X := -Round(R * Cos(M_PI_DEG * Angle));
    Result.Y := Round(R * Sin(M_PI_DEG * Angle));
  end;
var
  BAngle: Integer; //angle between our line and X axis
  Arrow: TPoint; //storage for our small lines
begin
  M_PI_DEG := Pi / 180; //for degree to radian conversion
  try
        // determine base angle(note that screen Y is negative of
        // cartesian Y
    BAngle := Round(ArcTan2(-(P.Y - O.Y), (P.X - O.X)) / M_PI_DEG);
        // Draw base line
    C.MoveTo(O.X, O.Y);
    C.LineTo(P.X, P.Y);
        // Rotate tip line 1 to correct position and draw
    Arrow := Rotate(PolarToCartesian(ALen, BAngle), AAngle);
    C.MoveTo(P.X, P.Y);
    C.LineTo(P.X + Arrow.X, P.Y + Arrow.Y);
        // Rotate tip line 1 to correct position and draw
    Arrow := Rotate(PolarToCartesian(ALen, -BAngle), AAngle);
    C.MoveTo(P.X, P.Y);
    C.LineTo(P.X + Arrow.X, P.Y - Arrow.Y);
  except
  end;
end;

procedure ConnectControls(C: TCanvas; S, D: TControl; Style: TArrowStyle);
var
  O, P: TPoint;
  Sr, Dr: TRect; //source and destination rectangles
begin
  // find out the starting and ending points
  // to do this find the side on the source that should be connected
  // with the side on the destination
  Sr := S.BoundsRect; Dr := D.BoundsRect;
  if Sr.Right < Dr.Left then
  begin
    O := Point(Sr.Right, Sr.Top + S.Height div 2);
    P := Point(Dr.Left, Dr.Top + D.Height div 2);
  end else if Sr.Left > Dr.Right then
  begin
    O := Point(Sr.Left, Sr.Top + S.Height div 2);
    P := Point(Dr.Right, Dr.Top + D.Height div 2);
  end else if Sr.Bottom < Dr.Top then
  begin
    O := Point(Sr.Left + S.Width div 2, Sr.Bottom);
    P := Point(Dr.Left + D.Width div 2, Dr.Top);
  end else if Sr.Top > Dr.Bottom then
  begin
    O := Point(Sr.Left + S.Width div 2, Sr.Top);
    P := Point(Dr.Left + D.Width div 2, Dr.Bottom);
  end;
  // now draw the line
  if Style = asNone then
  begin
    C.MoveTo(O.X, O.Y); C.LineTo(P.X, P.Y);
  end else //draw at the tip
  begin
    DrawArrow(C,
      O, P, //start and end of line
      10, 20); //arrow length and seperation
  end;
  //if both add arrow to destination source Tip
  if Style = asBoth then
  begin
    DrawArrow(C,
      P, O, //start and end of line
      10, 20); //arrow length and seperation
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  //add a link definition -you can do this as many times as you want
  AddLink(Edit1,Button1,asBoth);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  ClearLinks; //delete links
end;

procedure TForm1.FormPaint(Sender: TObject);
var
  i : integer;
begin
  if Assigned(Links) then
    for i:= 0 to Links.Count-1 do
      with PLinkDesc(Links.Items[i])^ do
      ConnectControls(Canvas, S, D, Style);
end;
// YOU MUST CALL REFRESH of the FORM when a position of control changes
procedure TForm1.Button1Click(Sender: TObject);
begin
  Button1.Left := Button1.Left + 5;
  Refresh;
end;

end.

0
Is Your Active Directory as Secure as You Think?

More than 75% of all records are compromised because of the loss or theft of a privileged credential. Experts have been exploring Active Directory infrastructure to identify key threats and establish best practices for keeping data safe. Attend this month’s webinar to learn more.

 
LVL 5

Expert Comment

by:inter
ID: 1345089
To test just click on the button
0
 
LVL 10

Expert Comment

by:viktornet
ID: 1345090
Hello y'all

Hello Igor.

Looking at your code I just got a question... When do you use something = record and when do you use something = packed record  I've seen people use packed record pretty frequnetly, but I didn't know why???

btw- The rest of the code looks okay to me...

10x

Cheers,
Viktor
0
 
LVL 5

Expert Comment

by:inter
ID: 1345091
My friend,
Packed record tells delphi to pack the structure components into their original sizes. Lets explore it with an example,
 TRec = record
   x : byte;
   y : DWORD;
 end;
var
 T : TRec;
Normally this record should occupy 5 bytes in memory but due to 32 bit compiler it actually takes 8 bytes. Since system is tuned to 32 bit boundary delphi aligns each structure component to 4 byte boundaries -in 32 bit architecture such kind of memory access is native -> fast. However some routines expect that your data should appear as it declared. For example if you cast pointer to the above structure the following is illegal(points to wrong location)
  PDWord(PByteArray(@T)^[1])^ is not equal to T.y
when we declare it as PACKED the above expressions are equivalent. The PACKED stuff is necessary for most of the Win API calls and for dynamic record allocation. So as a rule of thumb I always use packed keyword for not to have trouble. These is also equivalent to {$A-} directive which means the ALIGNED RECORD FIELDS, I suppose I could make it clear, c.u. igor
0
 
LVL 10

Expert Comment

by:viktornet
ID: 1345092
10x man....That's a perfect explanation.... Tanks for your time :-)

Viktor
0
 

Author Comment

by:blitz051697
ID: 1345093
Thanks All.  I've checked out a couple of the recomendations--Very Interesting.  I like the TeeTree component.  It has many interesting properties.  I'm not sure its an exact fit because I don't neccessary need it to be a tree control.  But, I like the formatting it has.  
0

Featured Post

Is Your Active Directory as Secure as You Think?

More than 75% of all records are compromised because of the loss or theft of a privileged credential. Experts have been exploring Active Directory infrastructure to identify key threats and establish best practices for keeping data safe. Attend this month’s webinar to learn more.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Suggested Solutions

Title # Comments Views Activity
Delphi - replicating a form 8 67
find a node in VST 2 64
Reconfigure Delphi Install? 2 47
How to debug For loops? 3 46
Introduction The parallel port is a very commonly known port, it was widely used to connect a printer to the PC, if you look at the back of your computer, for those who don't have newer computers, there will be a port with 25 pins and a small print…
Creating an auto free TStringList The TStringList is a basic and frequently used object in Delphi. On many occasions, you may want to create a temporary list, process some items in the list and be done with the list. In such cases, you have to…
Along with being a a promotional video for my three-day Annielytics Dashboard Seminor, this Micro Tutorial is an intro to Google Analytics API data.
With the power of JIRA, there's an unlimited number of ways you can customize it, use it and benefit from it. With that in mind, there's bound to be things that I wasn't able to cover in this course. With this summary we'll look at some places to go…

863 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

19 Experts available now in Live!

Get 1:1 Help Now