Solved

Drawing Line with arrows to connect objects

Posted on 1998-10-30
10
850 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
Highfive Gives IT Their Time Back

Highfive is so simple that setting up every meeting room takes just minutes and every employee will be able to start or join a call from any room with ease. Never be called into a meeting just to get it started again. This is how video conferencing should work!

 
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

Do You Know the 4 Main Threat Actor Types?

Do you know the main threat actor types? Most attackers fall into one of four categories, each with their own favored tactics, techniques, and procedures.

Join & Write a Comment

Objective: - This article will help user in how to convert their numeric value become words. How to use 1. You can copy this code in your Unit as function 2. than you can perform your function by type this code The Code   (CODE) The Im…
In my programming career I have only very rarely run into situations where operator overloading would be of any use in my work.  Normally those situations involved math with either overly large numbers (hundreds of thousands of digits or accuracy re…
This video discusses moving either the default database or any database to a new volume.
You have products, that come in variants and want to set different prices for them? Watch this micro tutorial that describes how to configure prices for Magento super attributes. Assigning simple products to configurable: We assigned simple products…

746 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

11 Experts available now in Live!

Get 1:1 Help Now