Click here to Skip to main content
Click here to Skip to main content

Family Tree

, 21 Jan 2013
Rate this:
Please Sign up or sign in to vote.
The development of a Family Tree application.

Introduction

This is only one example (or a set of preliminary examples showing the way to one real application) from the book World of Movable Objects. The idea of the main example is the development of a Family Tree application through exactly the same ideas and classes as can be used in the design of different block diagrams with different purposes and visual presentation of elements. If you are not interested in a Family Tree application this is the right moment to stop wasting time on this article.

The book together with its huge demo project and several other helpful things can be downloaded from http://sourceforge.net/projects/movegraph/files/?source=directory.

If you are not sure whether you are interested at looking into the development of a Family Tree application or not, you can try looking at the working application. To start the application, its EXE file must be accompanied by MoveGraphLibrary.dll. There is also a short description for users of this program; the text below is for developers.

To get all the code for the files mentioned below, download WorldOfMoveableObjects.zip. This is the whole project which, among many other examples, includes all the needed files.

The text below is only some part from Chapter 20 of the mentioned book. In order not to add any mistakes during renumbering, all figures below have the same numbers as in the original text of the book. It is a bit strange to see the pictures in an article starting from 20.30, but I think it is a minor inconvenience in comparison with the wrong text.

Of straight lines and simple comments only

Fig.20.30 A simple family tree for three generations

Closer to the end of this book the examples become more and more complicated. They are not any more the artificial examples to demonstrate one or another new feature but mostly they are the real applications which we use in our everyday work. The main example in this section is also a real application which I use myself. I have a feeling that a lot of other people will like to use it, so I also put this particular application as a stand alone one among other files available at www.SourceForge.net. But before we come to the final program in this section, there is a whole set of simpler examples which check all the needed features and demonstrate the development step by step. While writing the code for this subset of examples, I remembered not once the Hogben family described by Henry Kuttner (“Exit the Professor” by Henry Kuttner and C.L.Moore).  Members of that family could, among other things, construct an ultrasonic device on the basis of an old battery and several wires. Here I use only straight lines and the simplest comments which were explained long ago somewhere at the beginning of the book. At the end of this section you will see yourself what can be developed on such a primitive basis.

For some time I was planning to demonstrate several preliminary examples and only after it explain the main goal of the whole set; later I dropped this idea so I’ll start with a short description of the main goal for the whole series of further examples. I want to design a program for a family tree construction. It is easy to take a sheet of paper and start drawing your own family tree. Years ago I did it under the supervision of my grandmother and I still keep those old sheets of paper. A lot of people in many families are doing the same thing at one moment or another and nearly everyone has an idea of how such a family tree must look like. Family trees can be found in many books on history and those professionally prepared trees differ from the simple drawings in pen or pencil only by some tiny details. Any sketch of a family tree looks like figure 20.30. Only this particular sketch shows a very small and simple family tree representing three generations; to simplify the sketch and discussion, I took out the information about birth and death of each person though it is usually shown and you will see it further on.

Fig.20.31 Couple with children

This very simple sketch can say a lot about the rules to be implemented on developing such an application. Each person is represented by a small rectangle and a lot of information is shown by the set of connections between the rectangles. Usually a couple is shown as a pair of rectangles placed close to each other with a short straight line (bus) between them. If they have children, then there is another bus going down from that intermediate line (figure 20.31), but the view of the next generation depends on the number of children.

Fig.20.32 Couple with one child

If a couple has only one child, then this line going down can go straight to the rectangle representing this person (figure 20.32).

In case of several children, there is an intermediate bus between two generations and this bus has connections down to each of the children and up to the line between their parents. For example, a couple at figure 20.33 has three children.

Fig.20.33 Couple with three children

These are the most standard cases to be shown and when you draw a family tree containing only the closest relatives, usually you know all the needed information and there are no problems at all. When you try to draw some peripheral parts of the family tree, there can be some lack of information and you have to either draw some empty rectangles without any information or stay closer to your knowledge though it is against the simple rules of biology (figure 20.34). In any case, while thinking about the development of such a program for design of family trees, you have to think about several commands that can simplify the addition of some standard parts.

Fig.20.34 The case is not correct from the point of biology

I have such a version of the program in which by a single command of menu you can add a couple, one child or several siblings and so on. As a developer, you can think about the often needed variants, but there are always chances that you miss some situation and then it would be impossible for some users to add the type of relations they need to draw. When you have a genealogy tree that covers a significant period of time (for example, look at the genealogy tree in some serious book about monarchs), you have to draw some lines of very strange relations between people and all these variants cannot be covered by any set of predetermined commands. This is a classical situation where user-driven applications are much better than any applications with the restricted system of commands and this is one of the examples where user-driven application can be the only one really good solution.

Let us look at the figure 20.30 once more. There are rectangles and buses. Some buses connect the rectangles, some buses connect other buses, and there are buses between buses and rectangles. The flexible buses allow to draw whatever you need, so let us start our work on a Family Tree application with development of some flexible buses.

public class Bus : GraphicalObject
{
    Form form;
    Mover supervisor;
    Pen m_pen;
    bool bMarkJoints = true;
    bool bMarkEnds = true;
    Color clrEnds = Color .Red;
    Color clrJoints = Color .White;
    bool bRegisteredWithMover = true;
    bool bHeadConnected = false;
    bool bTailConnected = false;
    long idHeadBus = 0L;
    long idTailBus = 0L;
    bool bUseWarningColor = true;
    Pen penWarn = new Pen (Color .Magenta, 2);
    List<PointF> pts = new List<PointF> ();
    List<BusConnection> headsConnected = new List<BusConnection> ();
    List<BusConnection> tailsConnected = new List<BusConnection> ();

Any bus consists of a set of straight segments connected into a chain one after another; there is at least one segment in each bus and there is no upper limit on the number of segments. Each segment is described by two points and the end point of the previous segment is the starting point of the next one. Thus, the minimal number of points for a bus is two and the number of segments is always one less than the number of points. A bus can be initialized either by the List of points or by a couple of points; in the last case the bus will consist of a single segment.

public Bus (Form frm, Mover mvr, List<pointf> points, Pen pn) 
{
    form = frm;
    supervisor = mvr;
    m_pen = pn;
    pts = points;
    Movable = false;
}</pointf>

The cover for a bus consists of the small circular nodes on all the points from the List starting from one end point to another and of another set of strip nodes covering the segments.

public override void DefineCover ()
{
    int rad = Math .Max (4, Convert .ToInt32 (Width / 2));
    int nCircles = pts .Count;
    int nStrips = pts .Count - 1;
    CoverNode [] nodes = new CoverNode [nCircles + nStrips];
    for (int i = 0; i < nCircles; i++)
    {
        nodes [i] = new CoverNode (i, pts [i], rad);
    }
    for (int i = 0; i < nStrips; i++)
    {
        nodes [nCircles + i] = new CoverNode (nCircles + i, pts [i],
                                        pts [i + 1], rad, Behaviour .Frozen);
    }
    cover = new Cover (nodes);
    if (Movable)
    {
        cover .SetBehaviour (NodeShape .Circle, Behaviour .Frozen);
        cover .SetBehaviour (NodeShape .Strip, Behaviour .Moveable);
    }
}
File: Form_FreeBuses.cs

Menu position: Miscellaneous – Step by step to Family Tree – Free buses

By moving the circular nodes, the length and angle of the segments can be changed; in this way the configuration of the whole bus is changed. The Form_FreeBuses.cs demonstrates two buses; one of them consists of a single segment while another has four segments (figure 20.35).

Fig.20.35 Free buses with different number of segments
public Form_FreeBuses ()
{
    InitializeComponent ();
    mover = new Mover (this);
    bus_2 = new Bus (this, mover, new PointF (100, 100), new PointF (300, 200),
                     new Pen (Color .Green, 3));
    bus_5 = new Bus (this, mover,
          Auxi_Geometry .RandomPointsInsideRectangle (ClientRectangle, 20, 5),
                     new Pen (Color .Blue, 3));
    bus_2 .IntoMover (mover, 0);
    bus_5 .IntoMover (mover, 0);
}

Bus is a very simple object and has only three main parameters of visualization: color, width, and the type of line (DashStyle). Two additional colors are used to show the end points and the joints between the connected segments; two Boolean parameters are used to switch the drawing of these special points ON and OFF. In the Form_FreeBuses.cs neither the visualization parameters nor the number of segments in the buses can be changed but all these things are going to be used in further examples.

The use of those two additional colors to mark all the movable points makes the changing of bus configuration much easier, but there are situations when such changes are not needed at all; in such case a bus can be shown without all additional marks. When user is already familiar with the way of changing the buses, these additional marks maybe not needed; in the FamilyTree application there is an easy way to switch them ON or OFF and user can do it at any moment.

public void Draw (Graphics grfx)
{
    … …
    grfx .DrawLines (m_pen, pts .ToArray ());
    if (bMarkJoints)
    {
        for (int i = 1; i < pts .Count - 1; i++)
        {
            Auxi_Drawing .FillEllipse (grfx, pts [i], 3, clrJoints);
        }
    }
    if (bMarkEnds)
    {
        Auxi_Drawing .FillEllipse (grfx, pts [0], 3, clrEnds);
        Auxi_Drawing .FillEllipse (grfx, pts [pts .Count - 1], 3, clrEnds);
    }
}
File: Form_FreeBuses_AddingJoints.cs

Menu position: Miscellaneous – Step by step to Family Tree – Free buses; adding joints

Two new things are added in the Form_FreeBuses_AddingJoints.cs and both of them can be seen in the code of the OnMouseDown() method.

private void OnMouseDown (object sender, MouseEventArgs e)
{
    ptMouse_Down = e .Location;
     if (mover .Catch (e .Location, e .Button))
    {
        GraphicalObject grobj = mover .CaughtSource;
        if (e .Button == MouseButtons .Left)
        {
            if (grobj is Bus)
            {
                Bus bus = grobj as Bus;
                if (mover .CaughtNodeShape == NodeShape .Circle)
                {
                    bus .StartMoving (mover .CaughtNode);
                }
                else
                {
                    if (!bus .Movable)
                    {
                        int iSegment = mover .CaughtNode - bus .Points .Count;
                        PointF pt = bus .NearestPointOnSegment (e .Location,
                                                                iSegment);
                        mover .Release ();
                        bus .InsertPoint (iSegment + 1, pt);
                        Invalidate ();
                        mover .Catch (e .Location, e .Button);
                    }
                }
            }
        }
    }
}

The first change is the adjustment of the cursor position when the circular node is caught for movement.

if (mover .CaughtNodeShape == NodeShape .Circle)
{
    bus .StartMoving (mover .CaughtNode);
}

Inside the Bus.StartMoving() method the link between the mover and the caught bus is temporarily cut, the cursor is moved to the central point of the circular node, and then the link is restored.

public void StartMoving (int iCircle)
{
    supervisor .MouseTraced = false;
    Cursor .Position = form .PointToScreen (Point .Round (pts [iCircle]));
        supervisor .MouseTraced = true;
}

From the moment of such initial correction and until the moment of the mouse release, the coordinates of the mouse cursor can be used as the new location for the moved point (it can be one of the end points or one of the joints). This can be seen in the Bus.MoveNode() method. I remind that the cover of a bus starts with the circular nodes and for the first half of the nodes the number of the node is also the number of the point in the List<PointF>pts. The use of the Bus.InformConnections() method inside the Bus.MoveNode() method will be explained later when we start working with connected buses.

public override bool MoveNode (int i, int dx, int dy, Point ptM,
                                   MouseButtons catcher)
{
    bool bRet = false;
    if (catcher == MouseButtons .Left)
    {
        if (Movable)
        {
            Move (dx, dy);
            InformConnections (PositionUpdate .Soft, 0, pts .Count - 1);
        }
        else
        {
            if (i < pts .Count)
            {
                pts [i] = ptM;
                InformConnections (PositionUpdate .Soft, i - 1, i);
                bRet = true;
            }
        }
    }
    return (bRet);
}

The second interesting thing happens when some segment (not an end point and not a joint!) is pressed with a mouse.

private void OnMouseDown (object sender, MouseEventArgs e)
{
    … …
    if (!bus .Movable)
    {
        int iSegment = mover .CaughtNode - bus .Points .Count;
        PointF pt = bus .NearestPointOnSegment (e .Location, iSegment);
        mover .Release ();
        bus .InsertPoint (iSegment + 1, pt);
        Invalidate ();
        mover .Catch (e .Location, e .Button);
    }

By the number of the pressed node the number of segment is determined (iSegment); then the nearest point on this segment (pt) is calculated.  After it the mover is temporarily disconnected from the caught bus, the new joint is included into the bus at the calculated point, and the bus is caught again but now by the circular node on this new joint. There was no joint before, but now the bus is caught by the joint; any joint is movable, so the reconfiguring of the bus can go on. As you can see from the above code, such adding of the new joints is allowed only for the non-movable buses; the next example demonstrates the difference in behaviour of movable and non-movable buses.

File: Form_FreeBuses_ChangingMovability.cs

Menu position: Miscellaneous – Step by step to Family Tree – Free buses; changing movability

Look again at figure 20.33; even in such a simple tree of only several people there are buses with different types of connections.

  • There are buses to give information about a couple and everyone understands that such bus cannot be moved freely to any other location because it has to show the link between two people and its ends must be always connected to the rectangles representing these people.  If it is needed, this straight bus can be turned into a broken line with several joints, but at any moment it must show a link between those two people.
  • There are short buses to connect one long bus with siblings. The rectangles for the siblings can be arbitrary placed on the screen and the bus to any of them can be much longer and may need to be turned into a broken line, but two ends of each bus are connected to some objects (a bus and a rectangle), so such bus cannot be moved freely around the screen.
  • But there is one bus which ends are not fixed on any other objects. Other buses are connected to this bus and if you move this bus then all those connections must move also, but the ends of the bus itself are not fixed so this bus can be moved around without changing its view. This is the bus to which the siblings are connected. At figure 20.33 this bus is placed at the same distance from the rectangles showing two generations (between parents and their children). If anybody would prefer to move this bus closer to the rectangles of one generation or another he can easily do it without destroying the family tree.  Well, this would be a good feature of design, but then there is a question of the way to do it because up till now the mouse press on the bus allowed only to add the new joint and to move this joint.

In all the examples of this book the forward movement of a whole object or any part of an object is done by the left button and I am not going to break this rule. In the previous example Form_FreeBuses_AddingJoints.cs the left button press at any inner point of a segment adds a joint at the pressed point and initiates the process of moving this new joint. Yet, we have a situation when, depending on our wish, such left button press must start either the adding and moving of the new joint or the move of the whole bus without any change in its configuration. The computer cannot screen user’s mind to determine what type of action this user plans to do at one moment or another, so some preliminary user’s action must determine it. This preliminary action is the change of movability of the bus.

Four pages back in the code of the bus constructor you can find such a line

Movable = false;

This flag – the movability feature of the bus – is used in the Bus.DefineCover() method to change some parameters of the bus cover and then the same feature is used in the OnMouseDown() method of the form. The movability (or non-movability) of the bus means whether the bus can be moved as the whole object without any changes or not.

The cover of a bus consists of two sets of nodes:

  • Circular nodes on joints and end points.
  • Strip nodes along the segments.
public override void DefineCover ()
{
    … …
    for (int i = 0; i < nCircles; i++)
    {
        nodes [i] = new CoverNode (i, pts [i], rad);
    }
    for (int i = 0; i < nStrips; i++)
    {
        nodes [nCircles + i] = new CoverNode (nCircles + i, pts [i],
                                     pts [i + 1], rad, Behaviour .Frozen);
    }
    … …
}

By default any bus is non-movable; the behaviour of the circular nodes is not specified and it means that they get the default value Behaviour.Moveable; the behaviour of the strip nodes is set to Behaviour.Frozen. Together it means that:

  • You can press any circular node (any joint or end point) and move it around the screen.
  • You can press any strip node as the mover feels all the frozen nodes but you cannot move it. Instead, the new joint is organized at the pressed point and, as any other joint, it is movable; this was already explained in the previous example. This adding of the new joint is done inside the OnMouseDown() method but only if the pressed bus is non-movable (look at the code two pages back).

In the Form_FreeBuses_ChangingMovability.cs you can call the small context menu on any segment of a bus; this menu contains only one command which allows to change the movability of the pressed bus.

private void Click_miMovable (object sender, EventArgs e)
{
    busPressed .Movable = !busPressed .Movable;
    busPressed .MarkEnds = !busPressed .Movable;
    busPressed .MarkJoints = !busPressed .Movable;
    Invalidate ();
}

When the movability of the bus is changed, its cover is redefined; the number and order of nodes is not changed, but their behaviour changes.

public override void DefineCover ()
{
    … …
    if (Movable)
    {
        cover .SetBehaviour (NodeShape .Circle, Behaviour .Frozen);
        cover .SetBehaviour (NodeShape .Strip, Behaviour .Moveable);
    }
}

In the movable bus, all the circular nodes are frozen while the strip nodes are movable. This means that there is no more way to add new joints because it is allowed in the OnMouseDown() method only for non-movable buses. At the same time the Bus.MoveNode() method works as usual.

public override bool MoveNode (int i, int dx, int dy, Point ptM,
                                       MouseButtons catcher)
{
    bool bRet = false;
    if (catcher == MouseButtons .Left)
    {
        if (Movable)
        {
            Move (dx, dy);
            InformConnections (PositionUpdate .Soft, 0, pts .Count - 1);
        }
        … …

For a movable bus the MoveNode() method calls the Move() method in which all the points are moved synchronously; as a result, the whole bus is moved without changing its configuration.

public override void Move (int dx, int dy)
{
    Size size = new Size (dx, dy);
    for (int i = 0; i < pts .Count; i++)
    {
        pts [i] += size;
    }
}

The reaction on the left button press is absolutely different for movable and non-movable buses and it would be a real confusion for users if the movable and non-movable buses would look identically. To avoid such confusion, the view of the bus slightly changes depending on its movability.  For non-movable buses the joints and end points are marked by special colors; this makes the catching and moving of such special points easier. For movable buses these special colors disappear; the whole bus is painted by one color; there are no visible special points and this emphasizes that the whole bus is an object to be moved without changing of configuration.

In the real Family Tree application more changes in view and configuration of buses can be needed; these possible changes are tested in the next example.

File: Form_FreeBuses_AllPossibleChanges.cs

Menu position: Miscellaneous – Step by step to Family Tree – Free buses; all possible changes

Parameters of the buses in the Form_FreeBuses_AllPossibleChanges.cs can be changed via the commands of context menus. Two context menus are used in this form: one can be called on the joints of the buses, another – on segments. As usual, the menu to be called is determined in the OnMouseUp() method of the form and the decision is based on the shape of the pressed node. At the same moment the number of the pressed joint or segment is determined by the number of the pressed node.

private void OnMouseUp (object sender, MouseEventArgs e)
{
    int iWasObject, iWasNode;
    NodeShape shapeNode;
    ptMouse_Up = e .Location;
    if (mover .Release (out iWasObject, out iWasNode, out shapeNode))
    {
        if (e .Button == MouseButtons .Right && mover.WasCaughtSource is Bus &&
            Auxi_Geometry .Distance (ptMouse_Down, ptMouse_Up) <= 3)
        {
            busPressed = mover .WasCaughtSource as Bus;
            if (shapeNode == NodeShape .Strip)
            {
                iPressedSegment = iWasNode - busPressed .Points .Count;
                ContextMenuStrip = menuOnSegment;
            }
            else
            {
                iPressedPoint = iWasNode;
                ContextMenuStrip = menuOnJoint;
            }
        }
    }
}

Menu on joints (menuOnJoint) contains a single command to delete the pressed joint; when executed it allows to straighten the bus without moving the joints manually. In this Form_FreeBuses_AllPossibleChanges.cs the removal of the joints is allowed both for movable and non-movable buses, but there are some doubts about the correctness of applying this command to the movable buses. When the bus is declared movable, then it can be moved around the screen without any changes in view and the possibility of adding new joints is also eliminated. Two features of a bus – the movability and the possibility of its reconfiguring – to some extent associate with each other and there can be different views on the rigidity of their correlation.

  • If you look at the change of the bus movability only as the chance to move the whole object, then the possibility to erase the joints of such bus is correct.
  • If you think that for movable buses all the possibilities of changing their configuration must be blocked, then this command for movable buses must be disabled.

Anyway, if you prefer the second point of view, then you can add a couple of code lines into the OnMouseUp() method and allow to call the menuOnJoint only for the non-movable buses.

Menu which is called on segments has several commands (figure 20.36).

Fig.20.36 Menu on segments

Movable (bus) allows to switch the movability of the pressed bus between ON and OFF. The change of movability slightly changes the view of the bus as was explained in the previous example.

Tuning (bus) calls an additional tuning form to modify the view of the pressed bus; the Form_Tuning_Bus.cs is shown at figure 20.37. In this form you can change the color, width, and type (DashStyle) of the line; you can also change the additional colors for end points and joints.

Fig.20.37 Form_Tuning_Bus.cs allows to change the visualizing parameters of a bus
  • Remove all joints (inside bus) erases all the joints and leaves the bus consisting of a single segment between the end points.
  • Horizontal (segment) changes the angle of the pressed segment and turns it into horizontal line.
  • Vertical (segment) changes the angle of the pressed segment and turns it into vertical line.

The two last commands work in the similar way and have similar limitations of their use.

  • Both ends of a bus look similar and it is impossible to determine visually from which of them the order of points starts, but all the points are placed in the List<PointF>pts in some order and on using one of these commands the next of two points is moved.  This is correct for any segment except the last one for which the previous point is moved.
  • If the pressed segment is nearly horizontal and you order to turn it into a vertical line, then two of its end points get the same X coordinate and the new segment will be very short. If you apply the same command to the horizontal segment, it will turn into a single point and disappear from view.  To avoid such situations; the command is allowed only for segments which will turn into a new segment with the length not less than 10 pixels. Similar limitation works for another command of the pair.

Up till now we were looking at the free buses which were not connected to anything. In the real family tree we do not have absolutely free buses; any bus in the family tree is connected to one or several objects. Figure 20.30 shows that buses are either connected to each other or can be used as the links between the rectangles associated with people. We will deal with the second variant a bit later and now let us work on the problem of connected buses.

Buses are not connected to each other by the arbitrary points. On the contrary, there is only one way to organize any bus connection and there are strict rules on how this connection works after it. The only way to organize a connection between two buses is to move and end point of one bus and to fix it in some way on another bus.  From this moment the end point of the first bus can move only along the second bus using it as a rail. It does not matter whether the second bus is a straight or a broken line; the connected end of the first bus can move only along the second bus between its end points.

An additional class BusConnection is used to organize a connection between two buses.

public class BusConnection : GraphicalObject
{
    Form form;
    Mover supervisor;
    PointF pt;
    int m_radius;
    Bus m_busOfEnd;
     EndType end_type;
    Bus m_busRail;
    int iSegment;
    double coefOnSegment;

An object of the BusConnection class is organized at the end point of the bus which is going to be connected to another bus.  To organize a new connection, some information about both buses is needed. This information includes the point of connection (pt), the radius of the sensitive area of connection (m_radius), the bus which is connected by its end point (m_busOfEnd), the value which specifies the exact end of this bus (end_type), and another bus to which the first one is connected (m_busRail).

public BusConnection (Form frm, Mover mvr, PointF point, int rad, Bus bus_End,
                          EndType endType, Bus bus_Rail) 
{
    form = frm;
    supervisor = mvr;
    m_radius = Math .Max (Math .Abs (rad), 3);
    m_busOfEnd = bus_End;
    end_type = endType;
    busRail = bus_Rail;
    pt = Auxi_Geometry .NearestPointOnPolyline (point, busRail .Points,
                                   out dist, out iSegment, out coefOnSegment);
}

The preliminary calculated point of connection, passed as a parameter to constructor, can be not very accurate; you can pass even an arbitrary point as a parameter. In any case the real point of connection – the real point on the bus (pt), which is the closest to the one passed as a parameter, is calculated in the constructor.  The same method returns the segment of connection (iSegment) and the positioning coefficient for the point of connection on this segment (coefOnSegment). The cover of this new object consists of a single circular node.

public override void DefineCover ()
{
    cover = new Cover (new CoverNode (0, pt, m_radius));
}

The next example shows the design and use of the connection between two buses.  

File: Form_ConnectedBuses_Two.cs

Menu position: Miscellaneous – Step by step to Family Tree – Two connected buses

Fig.20.38 Two connected buses

Even the name of the example informs that there are two connected buses in the Form_ConnectedBuses_Two.cs (figure 20.38). First the green bus is organized, then the blue one.

public Form_ConnectedBuses_Two ()
{
    … …
    busGreen = new Bus (this, mover, points, new Pen (Color .Green, 3));
    … …
    busBlue = new Bus (this, mover, points, new Pen (Color .Blue, 3));

For both of them the constructor uses only the color and points, so they do not know anything about each other. If nothing else is done, these two buses behave like absolutely independent objects; we already have such an example.

Next steps are:

  • To construct a BusConnection object at the end of the blue bus.
  • con = new BusConnection (this, mover, pt, busBlue .NodeRadius, busBlue, EndType .Head, busGreen);
  • To pass to the green bus the information that the blue bus is now connected to it.
  • busGreen .AddConnection (con);
  • To include all movable objects into the mover’s queue in correct order: the connection must precede both buses.
  • busGreen .IntoMover (mover, 0);
    busBlue .IntoMover (mover, 0);
    con .IntoMover (mover, 0);

Now let us look in details how this connection works.

Three objects are included into the mover’s queue: two buses and one BusConnection object. This object is at the head of the queue and it covers the starting point of the blue bus. Thus, when you press with the left button on the point of connection not the end point of the blue bus is caught but this BusConnection object.

private void OnMouseDown (object sender, MouseEventArgs e)
{
    if (mover .Catch (e .Location, e .Button))
    {
        GraphicalObject grobj = mover .CaughtSource;
        if (e .Button == MouseButtons .Left)
        {
            … …
            else if (grobj is BusConnection)
            {
                (grobj as BusConnection) .StartMoving ();
            }
        … …

When this BusConnection object is caught, the BusConnection.StartMoving() method is called and the same sequence of steps is done as when any circular node of a bus is caught: the link between the mover and the caught object is cut, the mouse cursor is moved to the central point of the node, and then the link is restored.

public void StartMoving () 
{
    supervisor .MouseTraced = false;
    Cursor .Position = form .PointToScreen (Point .Round (pt));
    supervisor .MouseTraced = true;
}

The possible movements of the caught connection are described by the BusConnection.MoveNode() method.

public override bool MoveNode (int i, int dx, int dy, Point ptM,
                                       MouseButtons catcher)
{
    bool bRet = false;
    if (catcher == MouseButtons .Left)
    {
        Location = ptM;
        supervisor .MouseTraced = false;
        Cursor .Position = form .PointToScreen (Point .Round (pt));
        supervisor .MouseTraced = true;
    }
    return (bRet);
}

Visually it looks as if from the moment when the connection point is pressed and until the mouse is released it can move only along the bus on which the connection point is placed and at any moment the connection point goes with the mouse cursor.  In reality it works vice versa and the code of the BusConnection class determines the possible movements of the mouse cursor.

Mouse is moved to some position and with very high probability it is not on the bus but somewhere aside; then the BusConnection.Location property adjusts the position of the connection to the nearest point on the bus along which the connection point has to move, and the connected end of the bus is moved to the same point. Thus, regardless of any movement, the BusConnection object covers this end of the bus.

public PointF Location
{
    get { return (pt); }
    set
    {
        pt = Auxi_Geometry .NearestPointOnPolyline (value, busRail .Points,
                                out dist, out iSegment, out coefOnSegment);
        DefineCover ();
        if (end_type == EndType .Head)
        {
            m_busOfEnd .HeadPoint = pt; 
        }
        else
        {
            m_busOfEnd .TailPoint = pt; 
        }
    }
}

After it the mouse cursor is moved to the same point; for this cursor movement there is again the temporary cut of the link between the mover and the caught object (look at the BusConnection.MoveNode() method on the previous page).

In this way the point of connection is moved only along the green bus and the connected end of the blue bus moves with this point. But what happens when the configuration of the green bus is changed?  How the end of the blue bus keeps its connection to the green bus throughout its movement?

I have mentioned on the previous page that when the connection between two buses was organized the information about the connected bus (the blue one) was passed to the bus with which it was connected (the green one).

busGreen.AddConnection (con);

Any bus keeps the track of all its “side” connections. There are two separate lists for connections of heads and tails; the BusConnection.AddConnection() method adds a new element to the appropriate list.

public void AddConnection (BusConnection cnct)
{
    if (cnct .ConnectionType == EndType .Head)
    {
        headsConnected .Add (cnct);
    }
    else
    {
        tailsConnected .Add (cnct);
    }
}

Green bus at figure 20.38 consists of three segments; there are four points with numbers 0, 1, 2, and 3 going from left to right; the blue bus is connected at segment one between points 1 and 2. Now let us press and move the point number 1 – the second point of green bus from the left. When the joint of any bus is pressed and moved then the Bus.MoveNode() method is called. The code of this method was already shown several pages back; at the moment we are interested only in that part of the method which deals with moving of the joints.

public override bool MoveNode (int i, int dx, int dy, Point ptM,
                                   MouseButtons catcher)
{
    … …
    if (i < pts .Count)
    {
        pts [i] = ptM;
        InformConnections (PositionUpdate .Soft, i - 1, i);
        bRet = true;
    }

When any bus is moved or reconfigured, some or all of the connections on this bus must adjust their positions. First, there are two types of adjustment; this is regulated by the enumeration PositionUpdate. Imagine the situation (it is real, but we did not look at such case yet) when the bus is moved without any change of its configuration and all the connections must retain their relative positions. This means that each connection is still positioned on the same segment and with the same positioning coefficient along the segment.  Thus, only the absolute screen position of the connection is changed because the bus has moved; such change of position is described as PositionUpdate.Soft. Another situation happens when all the joints of a bus are deleted, the bus, which could be a broken line, turns into straight line, and all the connections have to be positioned on this new bus.  Certainly, there are no questions of gaining the number of segment and the positioning coefficient; both values must be recalculated; this situation is described as PositionUpdate.Hard.

When the whole bus is moved, then all its connections must be relocated. When any joint is moved, only the segments on both sides of this joint are changed and only the connections on these segments must be relocated; all other connections behind the neighboring joints are not disturbed at all. Thus, it would be enough to inform about the movement of any joint only the connections on the neighboring segments, but another logic is used in the Bus.MoveNode() method. As seen from the code, all the connections are informed, so it is for each connection to decide whether it has to react on the movement of a particular joint or not.

public void InformConnections (PositionUpdate updateType, int iSeg_0, int iSeg_1)
{
  foreach (BusConnection connection in headsConnected)
  {
      connection .RailChanged (updateType, iSeg_0, iSeg_1);
  }
  foreach (BusConnection connection in tailsConnected)
  {
      connection .RailChanged (updateType, iSeg_0, iSeg_1);
  }
}

Parameters that are passed to the Bus.InformConnections() method include the type of adjustment (updateType) and the range of the involved segments (from iSeg_0 to iSeg_1). The same set of parameters is passed to each of the connections to the method BusConnection.RailChanged(). This method has to calculate the new position of connection in reaction to the change of the bus on which it is positioned; any calculation is needed only if the particular connection is situated on a segment inside the specified range. If the connection has to be moved, then its new position depends on the type of the adjustment. If it is the PositionUpdate.Soft adjustment, then the number of the segment and the positioning coefficient inside the segment retain their values and the new point is calculated using these unchanged values. For the case of the PositionUpdate.Hard adjustment, the new position is calculated using only the points of the bus, while the new segment number and the positioning coefficient along this segment are the additional results of this calculation.

public void RailChanged (PositionUpdate updateType, int iSeg_0, int iSeg_1)
{
    if (iSeg_0 <= iSegment && iSegment <= iSeg_1)
    {
        PointF ptNew;
        if (updateType == PositionUpdate .Soft)
        {
            ptNew = Auxi_Geometry .PointOnLine (busRail .Points [iSegment],
                               busRail .Points [iSegment + 1], coefOnSegment);
        }
        else
        {
            ptNew = Auxi_Geometry .NearestPointOnPolyline (pt, busRail .Points,
                                      out dist, out iSegment, out coefOnSegment);
        }
        Location = ptNew;
    }
}

The calculated position is passed to the BusConnection.Location property where two things happen: the connection is relocated to this new position and then this value is sent to one or another property of the connected bus; the exact property depends on whether that bus is connected by the first or by the last of its points.

public PointF Location
{
    get { return (pt); }
    set
    {
        pt = Auxi_Geometry .NearestPointOnPolyline (value, busRail .Points,
                                    out dist, out iSegment, out coefOnSegment);
        DefineCover ();
        if (end_type == EndType .Head)
        {
            m_busOfEnd .HeadPoint = pt; 
        }
        else
        {
            m_busOfEnd .TailPoint = pt; 
        }
    }
}

If the head of another bus is connected at this point, then the Bus.HeadPoint property changes the first point of that bus.

public PointF HeadPoint
{
    get { return (pts [0]); }
    set { ChangePoint (0, value); }
}

If there are some connections on that second bus, then the Bus.ChangePoint() method not only relocates the first point of the bus but also disturbs all the connections; eventually this disturbance will die somewhere, but on the way all the involved connections and bus ends will take their new positions.

public void ChangePoint (int iPoint, PointF pt)
{
    if (0 <= iPoint && iPoint < pts .Count)
    {
        pts .RemoveAt (iPoint);
        pts .Insert (iPoint, pt);
        DefineCover ();
        InformConnections (PositionUpdate .Soft, iPoint - 1, iPoint);
    }
}

Now we have the full picture of what happens when one or another point of the green bus is caught and moved. In situation from figure 20.38 there are going to be different results when the second from the left or the right point of the green bus are moved. Let us look at the details of both cases.

If the second from the left point of the green bus is caught and the mouse is moved, then:

  1. The mouse is moved somewhere (ptM). By the Bus.MoveNode() method the caught joint ( i == 1 ) is moved to the same point as the mouse and all the connections of the green bus are informed that two segments of the bus has changed.
  2. pts [1] = ptM;
    InformConnections (PositionUpdate .Soft, 0, 1);
  3. The green bus has only one connection and for it the BusConnection.RailChanged() method is called with the same parameters.
  4. connection.RailChanged (PositionUpdate .Soft, 0, 1);
  5. Our connection happens to be inside the specified range of segments (iSegment == 1), so the new position for connection is calculated as a soft adjustment and then this new position is passed to the BusConnection.Location property.
  6. ptNew = Auxi_Geometry .PointOnLine (busRail .Points [1], busRail .Points [2], coefOnSegment);
    Location = ptNew;
  7. The BusConnection.Location property changes the real location of the connection point and sends this new location to the blue bus to be used in the Bus.HeadPoint property.
  8. pt = Auxi_Geometry .NearestPointOnPolyline (value, busRail .Points, out dist, out iSegment, out coefOnSegment);
    m_busOfEnd .HeadPoint = pt; 
  9. The Bus.HeadPoint property changes the position of the first point of the blue bus and then the disturbance is over because there are no connections on the blue bus.

When the right point of the green bus is caught and the mouse is moved, then the chain of events is shorter:

  1. The mouse is moved somewhere (ptM). By the Bus.MoveNode() method the caught joint (i == 3) is moved to the same point as the mouse and all the connections of the green bus are informed that two segments of the bus has changed.
  2. pts [3] = ptM;
    InformConnections (PositionUpdate .Soft, 2, 3);
  3. The green bus has only one connection and for it the BusConnection.RailChanged() method is called with the same parameters.
  4. connection.RailChanged (PositionUpdate .Soft, 2, 3);
  5. The connection on the green bus is outside the specified range (iSegment == 1), so nothing has to be done. In situation from figure 20.38 the movement of the right point of the green bus has no effect on connection.

We already saw that whenever an end point or any joint of a bus is moved, the Bus.InformConnections() method must be called.  Next examples will demonstrate several situations when this method must be called and there is a very simple logic to determine the set of needed parameters for such call.

  • If an end point or a joint is moved by a mouse, then the relative position of any connection on that bus is not changed; this means the use of the PositionUpdate.Soft parameter. As a result of the mentioned movement not more than two consecutive segments are changing, so two consecutive numbers represent the range of changing segments. For an end point only one segment is changed, but for the simplicity of the code the range of segments is still represented by two consecutive numbers. One of the mentioned segments does not exist, but this is not a problem at all as no connection can be positioned on a non-existing segment, so no action will be done for this non-existing segment.
  • If the number of joints is changed, then it is always the PositionUpdate.Hard parameter and all the segments must be included into the range.

One of the cases that fall into second variant is the addition of the new joint when any segment is pressed with the left button; in this case the call to the Bus.InformConnections() method is included into the OnMouseDown() method.

File: Form_ConnectedBuses_Three.cs

Menu position: Miscellaneous – Step by step to Family Tree – Three connected buses

Fig.20.39 Three connected buses

In the next example – the Form_ConnectedBuses_Three.cs – we have three buses and one of them is connected to others on both ends (figure 20.39). This example allows to check the correctness of work for connections on both ends of a bus while everything else is nearly the same as in the previous example.

Beginning from this example and further on until the development of the real Family Tree application there are going to be a lot of bus connections. In some cases only one end of a bus is connected to something; in other cases a bus is connected by its both ends. For all these situations the ConnectedBus() method can be very useful; this method constructs the new bus and all the needed connections.  The same method could be used in the previous example, but I decided to postpone its use until this Form_ConnectedBuses_Three.cs.

public Bus ConnectedBus (Form frm, Mover mvr, Pen pn, Bus busHeadConnected,
         Bus busTailConnected, List<PointF> points, out List<BusConnection> cons)
{
    List<BusConnection> newcons = new List<BusConnection> ();
    int iLast = points .Count - 1;
    if (busHeadConnected != null)
    {
        points [0] = Auxi_Geometry .NearestPointOnPolyline (points [0],
                                                     busHeadConnected .Points);
    }
    if (busTailConnected != null)
    {
        points [iLast] = Auxi_Geometry .NearestPointOnPolyline (points [iLast],
                                                     busTailConnected .Points);
    }
    Bus bs = new Bus (frm, mvr, pn, points);
    if (busHeadConnected != null)
     {
        BusConnection conHead = new BusConnection (frm, mvr, points [0],
                    Math .Max (busHeadConnected .NodeRadius, bs .NodeRadius),
                                         bs, EndType .Head, busHeadConnected);
        busHeadConnected .AddConnection (conHead);
        newcons .Add (conHead);
    }
    if (busTailConnected != null)
    {
        BusConnection conTail = new BusConnection (frm, mvr, points [iLast],
                    Math .Max (busTailConnected .NodeRadius, bs .NodeRadius),
                                         bs, EndType .Tail, busTailConnected);
        busTailConnected .AddConnection (conTail);
        newcons .Add (conTail);
    }
    cons = newcons;
    return (bs);
}

You can check one more interesting effect with the buses. Change one bus, for example, the green one in such a way that it will turn into a closed loop. This is easy to do as any number of joints can be added to the bus by simple left button press on the bus and the length of segments can be changed by moving the joints and end points. At the end of all changes move one end point of a bus on top of another. If you turn the green bus into a closed loop, then the connected end point of the brown bus can move without problems around such loop.  You can even put two end points of the green bus somewhere close to each other and the existence of the small gap will be not a problem. If the green bus is reconfigured in such a way as to cross itself, then the end point of the brown bus will jump from one segment to another. For the Family Tree application there is no need in adding any special checking to prevent such jumps from one segment of the bus to another.

I cannot imagine a situation when anyone would need to change the bus in such a way that it will cross itself, but the case of a bus in a form of a closed loop is different.  The bus in a form of a closed loop is not only a funny exercise. A bit later you will see that it is a very useful element.

File: Form_ConnectedBuses_ChangingMovability.cs

Menu position: Miscellaneous – Step by step to Family Tree – Connected buses; changing movability

In the new example, there are the same three buses connected in the same way, but I added a small menu containing one command to change the movability of the pressed bus. Well, this command can be applied not to every bus, so it is allowed for the green and blue buses but not for the brown bus.  Certainly, this exclusion is not based on the bus color. I have already explained before that movability can be changed for the buses with the free ends while the connected buses cannot be movable.

By default all three buses are non-movable and when you press with the left button any segment of the green bus, then the new joint of this green bus appears under the cursor.  You continue to move this new joint and the connected point of the brown bus moves only if it happens to be in the segment next to this joint.

Now call the context menu on the green bus (as usual, the right button click will do it) and turn the green bus into movable. The view of the green bus will change a bit to indicate the movability of the bus; this disappearance of the special colored marks for the end points and joints (figure 20.40) was already mentioned before.

Fig.20.40 Two buses (blue and brown) are unmovable while the green bus is movable

When you press with the left button this movable green bus, no new joint appears under the cursor, but instead the whole bus starts moving around the screen and the connected point of the brown bus moves with it regardless of the segment to which it is connected and of the point where you grabbed the green bus. What chain of methods works in this situation?

First, the Bus.MoveNode() method for the pressed bus is called. As you can see from the code, for the movable bus regardless of the pressed node this method calls the Bus.Move() method and thus the whole bus is moved. After it all the connections on the bus are informed about the change of points throughout the InformConnections() method and in this case the range of changed segments includes all the segments. This means that all the connections will adjust their positions and with them the end points of all the connected buses.

public override bool MoveNode (int i, int dx, int dy, Point ptM,
                                       MouseButtons catcher)
{
    bool bRet = false;
    if (catcher == MouseButtons .Left)
    {
        if (Movable)
        {
            Move (dx, dy);
            InformConnections (PositionUpdate .Soft, 0, pts .Count - 1);
        }
        … …

At the same time the type of adjustment is PositionUpdate.Soft. The bus is moved around the screen without any change of its configuration, so all points of connection retain their numbers of segments to which they are connected and positioning coefficients inside those segments.

File: Form_ConnectedBuses_AllPossibleChanges.cs

Menu position: Miscellaneous – Step by step to Family Tree – Connected buses; all possible changes

This example demonstrates all possible changes that can be applied to connected buses. The connected buses in this example are similar to what was used in the previous example; the changes allowed for the buses are the same that were demonstrated for free buses in the Form_FreeBuses_AllPossibleChanges.cs example. To start all the needed changes, the same two context menus are used. Menu to be called on any joint consists of a single command line to delete the pressed joint.

Menu on segments contains the same five commands which were explained nine pages back (figure 20.36). While writing about the commands to turn the pressed segment into vertical or horizontal, I mentioned some limitations but I forgot to mention one thing. These two commands – to turn a segment into vertical or horizontal – can move, if other limitations allow, one of the joints of a bus but they never move end points. Because of this limitation, the logic of these commands for the last segment of a bus slightly differs from the logic of applying these commands to any other segment. This restriction on moving any end point by these two commands also do not allow to apply these commands to any bus consisting of a single segment.

I think that the buses to be used in the Family Tree are now developed and tested and it is time to look at another important element of any family tree – at the rectangle representing a person.

Any person in the family tree is represented by an object of the Person class. On the screen such object is shown as a rectangular area with some additional information. The rectangle is movable and resizable (did you expect anything else?); the information is organized in the form of three CommentToRect objects associated with this rectangle.

public class Person : GraphicalObject
{
    Form form;
    RectangleF rc;
    SolidBrush m_brush;
    Pen penBorder = new Pen (Color .DarkGray);
    CommentToRect cmntName = null;
    CommentToRect cmntBirth = null;
    CommentToRect cmntDeath = null;
    Bus busOnBorder;

One comment shows the name of the person; this comment – cmntName – always exists.  Even if you do not know the name of the person, you can call him Unknown or anything else, but you cannot organize a Person object without this comment. Two other comments are not mandatory. For the living person the information about his death is rarely known, so in this case the cmntDeath is not needed.  It is not a rare case when for a relative several generations back the DOB information is not known. It is possible to put some date (year) with a question or simply omit this cmntBirth.

File: Form_Person.cs

Menu position: Miscellaneous – Step by step to Family Tree – Person

Fig.20.41 A standard view of the Person object

The class Person has several constructors; the Form_Person.cs demonstrates maybe the most often used of them which produces an object with all three comments in view (figure 20.41).

public Form_Person ()
{
    InitializeComponent ();
    mover = new Mover (this);
    person = new Person (this, new RectangleF (150, 150, 210, 60), Color .Cyan,
                         "James", "3-Oct-1916", "23-Feb-1995");
    person .IntoMover (mover, 0);
}

The standard feature of any user-driven application is that the choice of information to be shown and its view is the prerogative of the user. If there is an object on  the screen, then there must be an easy way to change its view; in the Form_Person.cs you can call the context menu on the Person object and via its only command open an additional tuning form to change the view of the pressed object (figure 20.42).

Fig.20.42 The tuning form to modify a Person object

The Form_Tuning_Person.cs allows to change the background color of the rectangle and to modify all three comments. For each of these comments the text color and the font can be set individually. There are no strict rules on how the birth and death information must be shown; these are only the strings and you can type anything there. If you clear the information from the text boxes for birth and death then those comments will not appear in the Person object. If you try to do the same with the text box for a name then the word Unknown will appear instead of the name.

The graphical object inside the Form_Tuning_Person.cs is also a Person object, so the comments of this object can be moved around the screen, but the position of these comments in the tuning form will not change the positions of the comments of the modified object. This was purposely done for tuning of a single Person object because in the same easy way the comments of the modified object can be moved. Further on, in the real Family Tree application, you will see a similar tuning of the Person object that is used as a sample for all others and for that case the positioning of comments in the tuning form will determine their positions in the new Person objects.

Any Person object is a complex object and its parts can be involved in individual, related, and synchronous movements.

  • Any comment can be moved individually.
  • When the main colored rectangle is moved then all its associated comments move synchronously.
  • When the colored rectangle is resized then the associated comments retain their relative positions.

As any other complex object, the Person object has to be registered with the mover not by Mover.Add() or Mover.Insert() methods but by its Person.IntoMover() method which registers in the correct order the main rectangle and all its associated comments.

new public void IntoMover (Mover mover, int iPos)
{
    if (iPos < 0 || mover .Count < iPos)
    {
        return;
    }
    mover .Insert (iPos, this);
    mover .Insert (iPos, cmntName);
    if (cmntBirth != null)
    {
        mover .Insert (iPos, cmntBirth);
    }
    if (cmntDeath != null)
    {
        mover .Insert (iPos, cmntDeath);
    }
}

Among the fields of the Person class (two pages back) you can see one field which was not mentioned up till now:

Bus busOnBorder;

When any Person object is initialized, its busOnBorder field is also initialized; this is done inside the Person.GeneralInitialization_WithoutName() method.

private void GeneralInitialization_WithoutName (Form frm, RectangleF rect,
                                                    Color clr)
{
    form = frm;
    rc = new RectangleF (rect .Left, rect .Top,
                         Math .Max (rect .Width, minSide),
                         Math .Max (rect .Height, minSide));
    busOnBorder = new Bus (form, null, Auxi_Geometry .BorderOfRectangle (rc),
                           penBorder);
    busOnBorder .MarkEnds = false;
    busOnBorder .MarkJoints = false;
    busOnBorder .RegisteredWithMover = false;
    m_brush = new SolidBrush (clr);
}

The points of the new bus – busOnBorder – are prepared by the Auxi_Geometry.BorderOfRectangle() method which returns the List<PointF>. This List contains the coordinates of the corners of rectangle, but there are not four but five elements in this List; the first and the last elements are the same.  Thus, the busOnBorder is organized as a closed loop.

The Person object is a complex object which has one main element and several other elements associated with this one. When the main element – a rectangle – is resized or moved, all the associated elements must be informed about the change of the main element; this is done by the Person.InformRelatedElements() method.  All the associated comments (from one to three, depending on the particular situation) adjust their positions by using the standard CommentToRect.ParentRect property; the busOnBorder gets the new points by calling again the Auxi_Geometry.BorderOfRectangle() method.  Thus, the busOnBorder is always a closed loop regardless of any changes of the rectangular area.

private void InformRelatedElements ()
{
    Rectangle rect = Rectangle .Round (rc);
    cmntName .ParentRect = rect;
    if (cmntBirth != null)
    {
        cmntBirth .ParentRect = rect;
    }
    if (cmntDeath != null)
    {
        cmntDeath .ParentRect = rect;
    }
    busOnBorder .Points = Auxi_Geometry .BorderOfRectangle (rc);
}

Neither joints nor the end points of the busOnBorder are marked by special colors:

busOnBorder.MarkEnds = false;
busOnBorder .MarkJoints = false;

When any Person object is shown on the screen, its bus along the border is not shown; the busOnBorder field is not even mentioned in the Person.Draw() method.

public void Draw (Graphics grfx)
{
    grfx .FillRectangle (m_brush, rc);
    grfx .DrawRectangle (penBorder, Rectangle .Round (rc));
    cmntName .Draw (grfx);
    if (cmntBirth != null)
    {
        cmntBirth .Draw (grfx);
    }
    if (cmntDeath != null)
    {
        cmntDeath .Draw (grfx);
    }
}

In the previous examples we did a lot of things with the Bus objects and we saw how they could be moved and changed. With the help of the BusConnection objects, buses can be organize into a system of connected buses which we are going to use in our Family Tree application. But as you can see from the code of the Person.IntoMover() method, this field busOnBorder is not even mentioned in the method, so mover does not know anything about the existence of this field. Yet, this is the field which allows to design the whole family tree. To understand the role and the importance of the busOnBorder field let us look at the next very simple example in which there is a single Person object and only one additional bus connected to it.

File: Form_PersonPlusBus.cs

Menu position: Miscellaneous – Step by step to Family Tree – Person with connected bus

Fig.20.43 A Person object with connected bus

There are only one person and one visible bus in the Form_PersonPlusBus.cs (figure 20.43). This bus has one of its end points on the border of the Person object; if you press this end point with the left button and try to move it anywhere, you will immediately find out that it will move only along the border and nowhere else. Does it look familiar to you? Does it look like moving the end of the bus along another bus with which it is connected?

Yes, this is exactly what happens when a bus is connected to a Person object: in reality a bus is connected to a busOnBorder – an invisible bus that goes along the border. It does not matter at all that one of two involved buses is invisible; the connection between a bus and a Person object is organized in the same way as between the buses, for example, in the Form_ConnectedBuses_Two.cs.

There are only one person and one shown bus in the Form_PersonPlusBus.cs (figure 20.43), but this is the first example in which we have all three types of elements on which the real Family Tree application will be designed. Elements of three classes are organized into three separate lists and in the next several more complex examples you will see exactly the same organization of all the elements.

public partial class Form_PersonPlusBus : Form
{
    Mover mover;
    List<Person> people = new List<Person> ();
    List<Bus> buses = new List<Bus> ();
    List<BusConnection> connections = new List<BusConnection> ();

When the Form_PersonPlusBus.cs starts, at first the Person object is designed.

public Form_PersonPlusBus ()
{
    InitializeComponent ();
    mover = new Mover (this);
    Person person = new Person (this, new RectangleF (100, 100, 140, 60),
                                Color .Cyan, "Unknown");
    AddPerson (person);

When any new object of the Person, Bus, or BusConnection class is organized, it is added to the end of the corresponding List. It is a bit more complicated with the construction of new Person object because it also contains the new bus; thus, the AddPerson() method calls the AddBus() method and eventually increases not one but two Lists.

private void AddPerson (Person prsn)
{
    … …
    people .Add (prsn);
    AddBus (prsn .Bus);
}
  private void AddBus (Bus busNew)
{
    … …
    buses .Add (busNew);
}

When the bus is organized in the Form_PersonPlusBus.cs, it gets, among other parameters, the list of points. This is not a free bus, but it must be connected to the bus on border of the Person object. Thus, with very high probability one of its points must be adjusted and then the BusConnection object must be organized at this corrected point. All these things are done in the BusFromPerson() method.

public Form_PersonPlusBus ()
{
    … …
    List<PointF> points = new List<PointF> ();
    points .Add (new PointF (220, 160));
    points .Add (new PointF (270, 210));
    points .Add (new PointF (410, 120));
    BusConnection con;
    Bus bus = BusFromPerson (this, mover, new Pen (Color .Blue, 3), person,
                             points, out con);
    AddBus (bus);
    AddConnection (con);
    RenewMover ();
}
private Bus BusFromPerson (Form frm, Mover mvr, Pen pn, Person person,
                           List<PointF> points, out BusConnection con)
{
    Bus busFrom = person .Bus;
    points [0] = Auxi_Geometry .NearestPointOnPolyline (points [0],
                                                        busFrom .Points);
    Bus bs = new Bus (frm, mvr, points, pn);
    con = new BusConnection (frm, mvr, points [0], bs .NodeRadius, bs,
                             EndType .Head, busFrom);
    busFrom .AddConnection (con);
    return (bs);
}

After all elements are organized and included into the corresponding lists, we have such filling of those lists:

  • List<Person> people contains one element.
  • List<Bus> buses contains two elements; one of them is seen at figure 20.43 while another is invisible and goes along the border of the Person object.
  • List<BusConnection> connections contains one element.

Mover can work with elements of all three mentioned classes, so all of them must be registered with the mover but registered in the correct order: BusConnection elements must precede all the Bus elements and they must precede all the Person elements.

private void RenewMover ()
{
    mover .Clear ();
    foreach (Person person in people)
    {
        person .IntoMover (mover, 0);
    }
    foreach (Bus bus in buses)
    {
        bus .IntoMover (mover, 0);
    }
    foreach (BusConnection con in connections)
    {
        con .IntoMover (mover, 0);
    }
}

We have two buses in the Form_PersonPlusBus.cs: one is perfectly visible at figure 20.43 and another one is invisible and goes along the border of the Person object. Both buses are included into the List of buses and the code of the RenewMover() method shows that for each bus from the List the Bus.IntoMover() method is called.  At the same time I already mentioned that in no case this busOnBorder can be registered with the mover. How is it done?

When any new Person object is initialized, one parameter of its busOnBorder gets a special value:

busOnBorder.RegisteredWithMover = false;

When the Bus.IntoMover() method is called for any bus, it checks this value and registers only the bus with the appropriate value of this field.

new public void IntoMover (Mover mover, int iPos)
{
    if (iPos < 0 || mover .Count < iPos || !bRegisteredWithMover)
    {
        return;
    }
    mover .Insert (iPos, this);
}

Thus, though we have two buses in the List of buses in our Form_PersonPlusBus.cs, only one of them – the visible one – is registered with the mover. Because the invisible bus on the border of the Person object is not registered, it is impossible to add any joint into such bus or move it anywhere outside of its place on the border of the associated rectangle.

One more remark about the Form_PersonPlusBus.cs. The connected bus is initialized to be the blue one, but it appears as magenta (figure 20.43) and there is no line of code in the Form_PersonPlusBus.cs to change the color. Who is responsible for such change of color?

The bus at figure 20.43 is connected to a person on one end while its other end is empty. For the real family tree it is not normal situation; you cannot imagine a bus that connects something with nothing; it is absurd. If this happens in the real Family Tree application, the user must be informed about this unusual situation. The best way to attract user’s attention to something unusual is to change the color of an element; it will be a warning that some action is needed from the user.  For this reason there is a special penWarn field in the Bus object. By default, this pen for a bus in unusual situation has the magenta color.

Pen penWarn = new Pen (Color .Magenta, 2);

There are situations when the bus, instead of its real color, is painted with this special pen.

public void Draw (Graphics grfx)
{
    if (((bHeadConnected != bTailConnected) ||
         (bHeadConnected == false && bTailConnected == false &&
          headsConnected .Count == 0 && tailsConnected .Count == 0 &&
          bRegisteredWithMover)) && bUseWarningColor)
   {
       grfx .DrawLines (penWarn, pts .ToArray ());
   }
   else
   {
       grfx .DrawLines (m_pen, pts .ToArray ());
   }
   … …

If you do not want to see this special color in any situation, it is enough to change the warning color and make it the same as the normal color of the bus. This was already done in one of the previous examples – in the Form_ConnectedBuses_Two.cs.  In that example the blue bus is connected only on one end and thus has to appear as magenta but its warning color is changed artificially. As the result, the bus which is declared as blue is seen as blue and not as magenta (figure 20.38).

public Form_ConnectedBuses_Two ()
{
    … …
    busBlue = new Bus (this, mover, points, new Pen (Color .Blue, 3));
    busBlue .ColorWarning = busBlue .Color;
    … …

There is also another way to regulate the use of warning color; it can be switched ON / OFF by the Bus.UseWarningColor property.  I will show the use of this property further on.

File: Form_Spouses.cs

Menu position: Miscellaneous – Step by step to Family Tree – Spouses

Fig.20.44 A primitive family tree containing only three people

The next example takes us closer to our goal of family tree and represents a very simple tree consisting of three people (figure 20.44). A direct connection between two Person objects is used very often in design of family trees, so I wrote special method LinkPeople() which can be very useful in such cases. Do not forget that there is not only a short bus between two Person objects but there are also the new BusConnection elements on both ends of the new bus.

public Form_Spouses ()
{
    InitializeComponent ();
    mover = new Mover (this);
    Person prsnWife_1 = new Person (this, new RectangleF (80, 100, 180, 60),
                                    Color .Cyan, "Sura-Leya", "1865", "1894");
    Person prsnHusband = new Person (this, new RectangleF (320, 100, 180, 60),
                                     Color .Yellow, "Iosif", "1862", "1939");
    Person prsnWife_2 = new Person (this, new RectangleF (600, 140, 180, 60),
                                    Color .Cyan, "Gesya", "1881", "1957");
    AddPerson (prsnWife_1);
    AddPerson (prsnHusband);
    AddPerson (prsnWife_2);
    Bus bus = LinkPeople (this, mover, new Pen (Color .Blue, 3),
                       prsnWife_1, Side .E, 0.4, prsnHusband, Side .W, 0.4);  
    AddBus (bus);
    bus = LinkPeople (this, mover, new Pen (Color .Blue, 3),
                       prsnHusband, Side .E, 0.5, prsnWife_2, Side .W, 0.5); 
    AddBus (bus);
    RenewMover ();
}

The initial position of the point where the bus is connected with the border of rectangle is determined by the side of rectangle and by the positioning coefficient along this side. The coefficient takes a value from the [0, 1] range.

private Bus LinkPeople (Form frm, Mover mvr, Pen pn,
                            Person personFrom, Side side_From, double coef_From,
                            Person personTo, Side side_To, double coef_To)
{
    Bus busFrom = personFrom .Bus;
    PointF ptA = Auxi_Geometry .PointOnRectangleBorder (personFrom .Area,
                                                        side_From, coef_From);
    ptA = Auxi_Geometry .NearestPointOnPolyline (ptA, busFrom .Points); 
    Bus busTo = personTo .Bus;
    PointF ptB = Auxi_Geometry .PointOnRectangleBorder (personTo .Area,
                                                        side_To, coef_To);
    ptB = Auxi_Geometry .NearestPointOnPolyline (ptB, busTo .Points);
    Bus bs = new Bus (frm, mvr, ptA, ptB, pn);
    List<BusConnection> conecs = new List<BusConnection> ();
    conecs .Add (new BusConnection (frm, mvr, ptA, bs .NodeRadius, bs,
                                    EndType .Head, busFrom));
    conecs .Add (new BusConnection (frm, mvr, ptB, bs .NodeRadius, bs,
                                    EndType .Tail, busTo));
    busFrom .AddConnection (conecs [0]);
    busTo .AddConnection (conecs [1]);
    connections .AddRange (conecs);
    return (bs);
}

File: Form_TwoGenerations.cs

Menu position: Miscellaneous – Step by step to Family Tree – Two generations

This is going to be the last preliminary example before design of the real Family Tree application. In reality, it is not an example to demonstrate one or another feature but the real, though a very small one, family tree with only one limitation: after this small family tree is constructed, there are no commands to add new people into it. Figure 20.45 shows that there are only four people in this family tree and they represent two generations.

Though this family tree is very small, it represents all possible types of buses. This classification of buses is based on whether their ends are connected to any objects and if they are, then to what types of objects they are connected.

  • Bus without connections at the ends; at figure 20.45 it is a brown one. At the figure the end of the brown bus and the end of the green bus are marked with the same small red spots and are positioned at the same place, so only the code can show the order of their design and the organization of their connection. But if you press this red spot with the left button, you will immediately see that this spot (connection) can move only along the brown bus taking the end of the green bus with the mouse. At the same time you will see that the end of the brown bus is free and is not connected to anything.
  • Bus between two Person objects; at figure 20.45 it is a blue one. In reality this bus is connected to the hidden buses on borders of those objects, but visually it is a bus between two people.
  • Bus between two buses; at figure 20.45 it is a gray one.
  • Bus between a bus and Person object; at figure 20.45 there are two green buses of this type. In reality each of them is between two buses, but one of them is hidden and is placed along the border of a Person object, so it looks like a link between bus and person.

Fig.20.45 A small family tree for two generations

For better explanation I use different colors for different types of buses. Any family tree, even such simple one, can be constructed in different ways and the type of some buses (in the mentioned classification) can depend on the order of construction.

I start my design from two Person objects representing parents and the bus between them; this bus is constructed with the help of the LinkPeople() method which was used in the previous example.

private void DefaultView ()
{
    Person father = new Person (this, new RectangleF (140, 100, 150, 60),
                                Color .Cyan, "James", "1916", "1995");
    AddPerson (father);
    Person mother = new Person (this, new RectangleF (380, 100, 150, 60),
                                Color .LightPink, "Joan", "1919", "1999");
    AddPerson (mother);
    Bus busParents = LinkPeople (this, mover, new Pen (Color .Blue, 3),
                                 father, Side .E, 0.5, mother, Side .W, 0.5);
    AddBus (busParents);
    … …

Next is the design of two more Person objects which represent children and the bus above them (the brown one) to which these two objects will be linked a bit later.

private void DefaultView ()
{
    … …
    float cyChildren = father .Area .Bottom + 140;
    Person son = new Person (this,
             new RectangleF (father .Area .Left - 80, cyChildren, 160, 60),
                             Color .Cyan, "James", "Feb-1943", "");
    Person daughter = new Person (this,
             new RectangleF (mother .Area .Left + 80, son .Area .Top, 150, 60),
                             Color .LightPink, "Rosemary", "May-1947", "");
    AddPerson (son);
    AddPerson (daughter);
    float cyAbove = (father .Area .Bottom + son .Area .Top) / 2;
    float cxL = Auxi_Geometry .Middle (son .Area) .X;
    float cxR = Auxi_Geometry .Middle (daughter .Area) .X;
    Bus busAbove = new Bus (this, mover, new PointF (cxL, cyAbove),
                           new PointF (cxR, cyAbove), new Pen (Color .Brown, 3));
    AddBus (busAbove);
    … …

Now it is time to connect “children” with the bus above them (the brown bus).

private void DefaultView ()
{
    … …
    AddBus (LinkBusPerson (this, mover, new Pen (Color .Green, 3), busAbove,
                           busAbove .HeadPoint, son, Side .N, 0.5));
    AddBus (LinkBusPerson (this, mover, new Pen (Color .Green, 3), busAbove,
                           busAbove .TailPoint, daughter, Side .N, 0.5));
    … …

These two green buses are organized with the LinkBusPerson() method. This method organizes a BusConnection on each end of the new bus and the exact location of the connection is calculated inside this method. The point of connection on another bus is passed as a parameter, but this is only a preferable point; the exact calculation is done inside the LinkBusPerson() method. The connection point on the border of the Person object is described by the side and the positioning coefficient along this side; this is done in exactly the same way as in the LinkPeople() method.

private Bus LinkBusPerson (Form frm, Mover mvr, Pen pn, Bus busFrom,
                     PointF ptFrom, Person personTo, Side side_To, double coef_To)
{
    ptFrom = Auxi_Geometry .NearestPointOnPolyline (ptFrom, busFrom .Points);
    Bus busTo = personTo .Bus;
    PointF ptTo = Auxi_Geometry .PointOnRectangleBorder (personTo .Area,
                                                         side_To, coef_To);
    ptTo = Auxi_Geometry .NearestPointOnPolyline (ptTo, busTo .Points);
    Bus bs = new Bus (frm, mvr, ptFrom, ptTo, pn);
    List<BusConnection> conecs = new List<BusConnection> ();
    conecs .Add (new BusConnection (frm, mvr, ptFrom, bs .NodeRadius, 
                                    bs, EndType .Head, busFrom));
    conecs .Add (new BusConnection (frm, mvr, ptTo, bs .NodeRadius,
                                    bs, EndType .Tail, busTo));
    busFrom .AddConnection (conecs [0]);
    busTo .AddConnection (conecs [1]);
    connections .AddRange (conecs);
    return (bs);
}

The last step in design of this small family tree is the construction of the link between two generations – the gray bus.

private void DefaultView ()
{
    … …
    PointF pt =
        Auxi_Geometry .Middle (busParents .HeadPoint, busParents .TailPoint);
    AddBus (LinkBusBus (this, mover, new Pen (Color .DarkGray, 3), busParents,
                   pt, busAbove, new PointF (pt .X, busAbove .TailPoint .Y)));
}

The link between two buses is constructed by the LinkBusBus() method. Two points passed as parameters to this method are again the preferable locations while the real points of connections are calculated inside the method.

private Bus LinkBusBus (Form frm, Mover mvr, Pen pn,
                            Bus busFrom, PointF ptFrom, Bus busTo, PointF ptTo)
{
    ptFrom = Auxi_Geometry .NearestPointOnPolyline (ptFrom, busFrom .Points);
    ptTo = Auxi_Geometry .NearestPointOnPolyline (ptTo, busTo .Points);
    Bus bs = new Bus (frm, mvr, ptFrom, ptTo, pn);
    List<BusConnection> conecs = new List<BusConnection> ();
    conecs .Add (new BusConnection (frm, mvr, ptFrom, bs .NodeRadius,
                                    bs, EndType .Head, busFrom));
    conecs .Add (new BusConnection (frm, mvr, ptTo, bs .NodeRadius,
                                    bs, EndType .Tail, busTo));
    busFrom .AddConnection (conecs [0]);
    busTo .AddConnection (conecs [1]);
    connections .AddRange (conecs);
    return (bs);
}

When the design of this small family tree is over, there are such collections of elements.

  • List<Person> people contains four elements.
  • List<Bus> buses contains nine elements. Five of them are shown at figure 20.45 in different colors while four other buses are invisible and go along the borders of the Person objects.
  • List<BusConnection> connections contains eight elements. All of them are shown at figure 20.45 as small red spots.

Once again about the order of elements in the mover’s queue which is defined by the RenewMover() method.

private void RenewMover ()
{
    mover .Clear ();
    foreach (Person person in people)
    {
        person .IntoMover (mover, 0);
    }
    foreach (Bus bus in buses)
    {
        bus .IntoMover (mover, 0);
    }
    foreach (BusConnection con in connections)
    {
        con .IntoMover (mover, 0);
    }
}

As seen from the above code, first go all the BusConnection elements, then all Bus elements, and at the end all the Person elements. The BusConnection elements must be at the head of the queue; otherwise something else can close the connection points from mover and it will be impossible to move this connection point. The order of Bus and Person objects is not so important and can be changed.  The order of elements in the mover’s queue correlates with the order of their drawing, so all depends on what you prefer to see in the situation when a bus goes across the rectangle of a Person object. If you prefer to see a bus over any Person object and have an opportunity to press the bus and move it aside, then the coded order of elements is correct. If you prefer buses to go beneath the Person objects, then you have to change both the RenewMover() method and the OnPaint() method.

In the next example – the real Family Tree application – you will see all the tuning possibilities; in this Form_TwoGenerations.cs, in order to simplify the code, some of the tunings are not implemented. For example, here any bus can be modified and the colors for end points and joints can be changed, but there is no command to switch OFF the painting of those small circles at the ends of the buses and over the joints. I also did not include here the command for tuning of the Person objects.

The brown bus from figure 20.45 is the only bus in this family tree which can be declared movable (via the command of its context menu) because it is the only bus with both ends free. If you declare the brown bus movable, then you can move it, for example, closer to the Person objects above or below and throughout this movement all the points of connection on this bus (there are three of them) will retain their relative positions.

The brown bus is different from all other buses in one more feature. The end points of any bus with the ends connected to something can be moved only as the result of moving those objects at the ends. For example, you can move two Person objects of a couple closer to each other or farther away from each other and in any case the end points will stay with the connected objects and thus change the length of the bus. The length of the brown bus can be changed only by the move of its end points; unfortunately, not in the situation shown at figure 20.45.

There are small red points at the ends of the brown bus, but there are also identical red points at the ends of the green buses which connect the brown bus with the Person objects below. When you press the red point at the end of the brown bus, you press not the end point of any bus, but the BusConnection element positioned there. Move this point of connection slightly aside from the end of the brown bus, then you can release it; after it press the free end of the brown bus which is now not blocked by anything and, by moving it, change the length of the brown bus.

For the first time among all the preliminary examples before the real Family Tree two new features appear in the Form_TwoGenerations.cs. They are not too much needed in this small tree but they will be very helpful in the real big family tree and I decided to check and demonstrate them here.

The first feature is the saving of the family tree in a binary file and restoration of the tree from the file. There are two commands (via the File menu) to save and restore later the view of a family tree. In this small example it will only give you a chance to organize different views of the same tree and to save all of them for comparison.  In the real Family Tree application the same pair of commands allows to organize different trees and then work with the needed one.

A family tree consists of the Person, Bus, and BusConnection elements and for correct restoration of any tree we need to save all these elements. Well, it is correct for the Person and BusConnection elements, while not all the buses need to be saved. For the correct restoration of any Person object, it is enough to save the ID number of its invisible bus on border while all other parameters of this bus can be skipped. The buses on borders of the Person objects are not registered with the mover; thus, only the buses registered with the mover need to be saved.

private void SaveFamilyTree (string file)
{
    … …
    bw .Write (people .Count);                      // 3
    bw .Write (buses .Count - people .Count);       // 4
    bw .Write (connections .Count);                 // 5
    for (int i = 0; i < people .Count; i++)
    {
        people [i] .IntoFile (bw);
    }
    for (int i = 0; i < buses .Count; i++)
    {
        if (buses [i] .RegisteredWithMover)
        {
            buses [i] .IntoFile (bw);
        }
    }
    for (int i = 0; i < connections .Count; i++)
    {
        connections [i] .IntoFile (bw);
    }
    … …

The second new feature in the Form_TwoGenerations.cs is the possibility to move the whole family tree around the screen. This feature is not needed at all for the tree consisting of four people but in the real big family tree it is very useful. First, there is no rule that regulates the order of construction of your real tree. You can start from the farthest known ancestor and go down from generation to generation, or you can put yourself in the middle of the screen and start adding relatives and the links to them in all the directions. In any case chances are high that even the biggest screen will be not enough and you will need to scroll it. This is done by a simple press of the left button at any empty place and moving it in the direction you need. The instrument is very simple.

There is a Boolean field to inform whether the whole family tree is currently under move or not..

bool bMoveAll = false;

When you press the left button at any empty spot the value of this field is changed and the current position of the mouse is remembered.

private void OnMouseDown (object sender, MouseEventArgs e)
{
    ptMouse_Down = e .Location;
    if (mover .Catch (e .Location, e .Button)))
    {
        … …
    }
    else
    {
        if (e .Button == MouseButtons .Left)
        {
            bMoveAll = true;
            ptMouse_prev = e .Location;
        }
    }
    ContextMenuStrip = null;
}

When no object is moved but this Boolean parameter signals that everything is under move, then the distance between the current mouse position and the previous one is calculated; this value is used for synchronous move of all the elements.

private void OnMouseMove (object sender, MouseEventArgs e)
{
    if (mover .Move (e .Location))
    {
        Invalidate ();
    }
    else
    {
        if (bMoveAll)
        {
            if (ptMouse_prev != e .Location)
            {
                int dx = e .X - ptMouse_prev .X;
                int dy = e .Y - ptMouse_prev .Y;
                foreach (Person person in people)
                {
                    person .Move (dx, dy);
                }
                foreach (Bus bus in buses)
                {
                    bus .Move (dx, dy);
                }
                ptMouse_prev = e .Location;
                Invalidate ();
            }
        }
    }
}

When the mouse is released, the value of this bMoveAll field is changed back to false, and there is no more movement of the whole tree.

private void OnMouseUp (object sender, MouseEventArgs e)
{
    … …
    if (mover .Release (out iWasObject, out iWasNode, out shapeNode))
    {
        … …
    }
    else
    {
        bMoveAll = false;
        … …
    }
}

Now is the time to put everything together, to add some commands in order to simplify the design of real family trees, and finally to develop a Family Tree application.

File: Form_FamilyTree.cs

Menu position: Miscellaneous – Step by step to Family Tree – Family Tree

While starting any of the previous preliminary examples you immediately see something.  There can be a bus or several buses, a Person object or several objects with some connections, but there is always something. When you start the Family Tree application, there is nothing in view; the form is empty.  It is exactly like starting the Word application: there is an empty area and a lot of possibilities to fill it. Nobody knows beforehand what kind of family tree the particular user is going to construct and nobody, except this user, knows from what part of his family tree he wants to start.

There are several possibilities to start the design of a new family tree and all of them are available through the commands of the context menu that can be called at any empty place (figure 20.46).

Fig.20.46 The context menu which can be called at any empty place

There are three groups of commands in this menu; commands of the first group allow to put on the screen the most often used combinations of elements that can be seen anywhere in a family tree. The view of any element in a family tree can be changed at any moment by calling special tuning forms (figures 20.37 and 20.42), but user can simplify his work by setting the preferable visualizing parameters and the new elements will be constructed according to these settings. I’ll write about these settings a bit later; now let us look at the reaction on the commands from the shown menu.

New person A new Person object appears on the screen. User needs to define at least the name of this person and has to decide whether some additional information about the birth and death has to appear or not. For this reason, an additional Form_Tuning_Person.cs is automatically called for this new Person object (figure 20.47).

Fig.20.47 A new Person object and a tuning form to set the needed information
private void Click_miNewPerson (object sender, EventArgs e)
{
    Person person = new Person (this, ptMouse_Up, personStandard);
    AddPerson (person);
    RenewMover ();
    Invalidate ();
    ModifyPerson (Auxi_Geometry .LocationByCoefficients (person .Area, 0.5,
                                                         0.95), person);
}

New couple: A couple is represented by a pair of Person objects positioned not far from each other and connected by a short straight bus (figure 20.48). It is up to the user to decide about the first of these objects to be modified, so there is no automatic call of the tuning form. The needed tuning form can be called through the menu on one or another object.

Fig.20.48 A new couple on the screen
private void Click_miNewCouple (object sender, EventArgs e)
{
    NewCouple (ptMouse_Up);
    SetMarks ();
    RenewMover ();
    Invalidate ();
}

This is not the only command through which a user can order the appearance of such elements on the screen. There are other commands which add the same elements on the screen and link them to other already existing elements. Because such link is organized from the short bus between two new Person elements, then the NewCouple() method returns this bus.

private Bus NewCouple (PointF ptLT)
{
    Person personL = new Person (this, ptLT, personStandard);
    AddPerson (personL);
    Person personR = new Person (this, new PointF (ptLT .X +
                personL .Area .Width + nDistSpouses, ptLT .Y), personStandard);
    AddPerson (personR);
    Bus link = LinkPersonPerson (this, mover, busStandard, personL,
                                 Side .E, 0.5, personR, Side .W, 0.5);
    return (link);
}

To organize a short bus between two new Person elements, the NewCouple() method uses the LinkPersonPerson() method, but the real link is always between the two buses (in this case there are two invisible buses along the borders of Person objects), so the LinkPersonPerson() method is only a wrapper around the LinkBusBus() method.

private Bus LinkPersonPerson (Form frm, Mover mvr, Bus busSample,
                             Person personFrom, Side side_From, double coef_From,
                             Person personTo, Side side_To, double coef_To)
{
    Bus busFrom = personFrom .Bus;
    PointF ptFrom = Auxi_Geometry .PointOnRectangleBorder (personFrom .Area,
                                                        side_From, coef_From);
    Bus busTo = personTo .Bus;
    PointF ptTo = Auxi_Geometry .PointOnRectangleBorder (personTo .Area,
                                                         side_To, coef_To);
    return (LinkBusBus (frm, mvr, busSample, busFrom, ptFrom, busTo, ptTo));
}
private Bus LinkBusBus (Form frm, Mover mvr, Bus busSample,
                        Bus busFrom, PointF ptFrom, Bus busTo, PointF ptTo)
{
    ptFrom = Auxi_Geometry .NearestPointOnPolyline (ptFrom, busFrom .Points);
    ptTo = Auxi_Geometry .NearestPointOnPolyline (ptTo, busTo .Points);
    Bus bs = new Bus (frm, mvr, ptFrom, ptTo, busSample);
    BusConnection conFrom = new BusConnection (frm, mvr, ptFrom,
                                  bs .NodeRadius, bs, EndType .Head, busFrom);
    BusConnection conTo = new BusConnection (frm, mvr, ptTo, bs .NodeRadius,
                                             bs, EndType .Tail, busTo);
    busFrom .AddConnection (conFrom);
    busTo .AddConnection (conTo);
    connections .Add (conFrom);
    connections .Add (conTo);
    AddBus (bs);
    return (bs);
}

In other situations when a link between a bus and a Person object is needed, similar wrappers around the LinkBusBus() method are used, for example, LinkBusPerson() and LinkPersonBus() methods.

New siblings: There is a submenu to put on the screen between two and six siblings; figure 20.49 shows the variant of three siblings. If there must be more than six siblings, then the best way is to use this command to put six siblings on the screen and add more by the command from another menu; I’ll write about it further on.

Fig.20.49 Three siblings
private Bus NewSiblings (PointF ptL_busAbove, int nSiblings)
{
    nSiblings = Math .Min (Math .Max (2, nSiblings), 6);
    float cyLineAbove = ptL_busAbove .Y;
    float cySiblings = cyLineAbove + nDistGenerations / 2;
    float lenLineAbove =
               (nSiblings - 1) * (personStandard .Area .Width + nDistSiblings);
    Bus busAbove = new Bus (this, mover, ptL_busAbove,
        new PointF (ptL_busAbove .X + lenLineAbove, cyLineAbove), busStandard); 
    AddBus (busAbove);
    for (int i = 1; i <= nSiblings; i++)
    {
        PointF ptOnBus =
           busAbove .NearestPointByCoefficient ((i - 1) * 1.0 / (nSiblings-1));
        Person child = new Person (this,
                    new PointF (ptOnBus .X - personStandard .Area .Width / 2,
                                cySiblings), personStandard);
        child .Name_Comment .Text = "Sibling-" + i .ToString ();
        AddPerson (child);
        LinkBusPerson (this, mover, busStandard, busAbove, ptOnBus, child,
                       Side .N, 0.5); 
    }
    SetMarks ();
    RenewMover ();
    Invalidate ();
    return (busAbove);
}

New bus: This is a simple but interesting command which produces such a small object on the screen that no figure is needed to illustrate it. This new object is a short bus with two free ends.

private void Click_miNewBus (object sender, EventArgs e)
{
    Bus bus = new Bus (this, mover, ptMouse_Up,
                       AnotherEndOfNewBus (ptMouse_Up), busStandard); 
    bus .UseWarningColor = bUseWarningColor;
    bus .WarningColor = busStandard .WarningColor;
    AddBus (bus);
    SetMarks ();
    RenewMover ();
    Invalidate ();
}

This new bus is very short and is not connected to anything but this bus allows to organize any needed link in the family tree; this can be a link between two buses, or two Person objects, or between bus and a person.  To do such a thing, you catch the end point of the bus, move it on top of the object with which you want to connect it, and release there. Because the last action is the release of an object, then for the result we have to look on the code of the OnMouseUp() method. The released end of the bus can be connected either to the visible bus or to the invisible bus on the border of a Person object in which case it looks like a connection to the person. The code for these two cases differs only in some details, so it is enough to look at the case of a bus released over another bus.

private void OnMouseUp (object sender, MouseEventArgs e)
{
    ptMouse_Up = e .Location;
    double dist = Auxi_Geometry .Distance (ptMouse_Down, ptMouse_Up);
    int iWasObject, iWasNode;
    NodeShape shapeNode;
    if (mover .Release (out iWasObject, out iWasNode, out shapeNode))
    {
        GraphicalObject grobj = mover .WasCaughtSource;
        if (e .Button == MouseButtons .Left)
        {
            if (grobj is Bus && shapeNode == NodeShape .Circle && !grobj .Movable)
            {
                Bus busReleased = grobj as Bus;
                if (iWasNode == 0 || iWasNode == busReleased .Points .Count - 1)
                {
                    EndType end_type = (iWasNode == 0) ? EndType .Head
                                                       : EndType .Tail;
                    List<MoverPointInfo> infoAll = mover.PointInfoAll (ptMouse_Up);
                    for (int j = infoAll .Count - 1; j >= 0; j--)
                    {
                        MoverPointInfo info = infoAll [j];
                        GraphicalObject objFound = mover [info .ObjectNum] .Source;
                        // bus cannot be connected to itself
                        if (busReleased .ID != objFound .ID) 
                        {
                            if (objFound is Bus)
                            {
                                Bus busFound = objFound as Bus;
                                if ((iWasNode == 0 && busReleased .TailConnected &&
                                     busReleased .ID_OnTail == busFound .ID) ||
                                    (iWasNode == busReleased .Points .Count - 1 &&
                                     busReleased .HeadConnected &&
                                     busReleased .ID_OnHead == busFound .ID))
                                {
                                    // two ends cannot be connected to the same bus
                                }
                                else
                                {
                                    PointF ptOnBus =
                                         Auxi_Geometry .NearestPointOnPolyline (
                                                    ptMouse_Up, busFound .Points);
                                    if (end_type == EndType .Head)
                                    {
                                        busReleased .HeadPoint = ptOnBus;
                                    }
                                    else
                                    {
                                        busReleased .TailPoint = ptOnBus;
                                    }
                                    BusConnection con = new BusConnection (this,
                                           mover, ptOnBus, busReleased .NodeRadius,
                                           busReleased, end_type, busFound);
                                    busFound .AddConnection (con);
                                    connections .Add (con);
                                    RenewMover ();
                                    Invalidate ();
                                }
                                break;
                            }
                            else if (objFound is Person)
                            … …

The code of the method is a bit long to put it here. There are a few and obvious limitations on organizing the new connection: no bus can be connected by its both ends to the same bus or the same person, so these checks are included into the code. Also a bus cannot be connected to itself. (I do not think that anyone would really need to organize such connections, but there are always people who like to check any application for unthinkable situations and there is also a possibility of accidental release of an object in the wrong place.)  If a new link is allowed, then it is organized and works in a normal way.

The Family Tree application provides different commands to design trees. These commands are named in such a way as to make them easier to understand by users (New couple or New siblings), but in reality there is no information inside whether the new Person elements were organized by one command or another. Thus, there is no information about the relations between the people represented by the screen elements; there are only the Bus, Person, and BusConnection elements connected in different ways. The same tree can be constructed in many different ways and the order of elements in three lists (people, buses, and connections) can be different.  It does not matter at all whether you prefer to use the commands that put on the screen groups of connected elements or to use other commands that add one bus or one person at a time.

For example the above mentioned command New couple puts on the screen two Person objects with a connection between them (figure 20.48). Exactly the same thing can be organized by a set of several commands and actions:

  • Use the New person command at one point and then the same command somewhere slightly aside; two new Person objects will appear on the screen.
  • By the New bus command put a new bus on the screen; on initialization this bus is not connected to anything.
  • Move one end of the new bus and drop it somewhere inside one Person object; the bus will be connected to this object (visually to the border of this object but in reality to the invisible bus that goes along the border of this object).
  • Move another end of the bus and drop it inside the second Person object; this end of the bus will be connected to this object (again to the border of this object).

Certainly, this sequence of commands and actions is much longer than a single click on the New couple command, but the result is the same.

If you try to organize a forbidden link (for example, drop both ends of the bus on the same Person object) the second link will be not organized and that is all.

What to do if you need to disconnect a bus?  Call a context menu on the point of connection and select the only command of this menu – Delete connection. The bus will be disconnected and in most cases its end point will be moved aside to organize a visual gap between two elements.  The end point is not moved aside and the gap is not organized only if the decrease of the bus length will make it too small. Regardless of whether the gap is organized or not the bus will be disconnected.

Fig.20.50 Menu on any segment of a bus

When you already have some family tree and want to enlarge it by adding new elements, then you will need some commands to do it. Certainly, it can be done by adding one element after another, but there are two menus with the commands which make these tasks easier. One menu can be called on any bus; another – on any Person object; let us start with the menu on buses.

This menu (figure 20.50) can be called on any bus and it does not matter at all whether this bus is connected to anything or absolutely free. Only two things matter: the selected command and the point at which the menu is called because the positioning of new elements is based on this point. The new elements will be connected to the pressed bus at the point of the right button click.

Add parents: A pair of Person objects with a bus between them represents a couple; the bus between the parents is linked with the pressed bus (figure 20.51, the pressed bus is shown in blue). The pair of new Person elements is identical to the pair at figure 20.48; the only difference in result is the link between this pair and the pressed bus.

Fig.20.51 The result of Add parents command

Add single parent: There has to be two parents (no social ideas or craziness can change the laws of biology), but if there is information only about one of them or user wants to include into the family tree only one person of a couple, then there is this command. Because only one new Person object appears on the screen (figure 20.52) and this new object needs some tuning, then an additional Form_Tuning_Person.cs is automatically called on this object.

Fig.20.52 Adding a single parent

Add child: This command is similar to the previous one, but the new Person object appears below the pressed bus (figure 20.53). There are two standard cases for using this command: first, it is the case of a single child of some couple and second is the case of more than six siblings. The screen elements for six siblings can be organized with a single command; other siblings can be added by using this command on the bus above siblings.

Fig.20.53 Add child command

Add children: There is a submenu to put on the screen between two and six siblings; figure 20.54 shows the variant of two siblings. If there must be more than six siblings, then you can put six of them by one command and then apply to the bus above the siblings the Add child command as many times as you need.

Fig.20.54 The result of the Add children – Two siblings command

Connect new bus: The new short bus is connected to the pressed bus; the second end of the new bus is free. You can reconfigure the new bus, move its free end, and connect it to any object (bus or person) you need.

Movable (bus): The command allows to change the movability of the pressed bus but this command can be applied only to a bus with both free ends.  You can organize any combination of bus connections, but usually only a bus above the siblings has both ends free.  Such bus can be declared movable and then moved closer to Person objects of the previous or next generation.

Tuning (bus): This command opens the additional Form_Tuning_Bus.cs (figure 20.37) which allows to change the view of the pressed bus.

Use this bus as sample for all: Suppose that you have spent some time on construction of the family tree and then at one moment you decide to change the view of all the buses. You can change the view of any bus individually by using the previous command, but it would be too long to repeat this procedure for every bus in a big tree. Instead, you can change the view of one bus to whatever you prefer and then use this command; all the buses will change their view according to this sample.

A family tree can be constructed by linking a group of new elements not only to a bus but also to any Person object; this is done through the commands of another menu which can be called on such object (figure 20.55). As you can see from the list of the available commands for adding new elements on the screen, they are similar to commands from menu for empty places (figure 20.46) and commands of menu associated with the buses (figure 20.50).

Fig.20.55 Menu on any Person object

One remark about the Connect new bus command. The new bus will be connected to the point on the border nearest to the point of calling the menu; later the point of connection can be easily moved around the border.

The Modify command opens the standard auxiliary form for tuning the Person object (figure 20.42). There are also several commands to change the view of the pressed object according to standard, but I did not explain yet where this standard view is organized; let us return to this question.

In the main menu of the Form_FamilyTree.cs there is a position Settings; if you click this menu command the Form_FamilyTreeSettings.cs is opened (figure 20.56). Not surprisingly at all that some parts of this form may look familiar to you. For example, the Bus group is used to set the parameters of all new buses, so the elements of this Bus group are mostly the same as can be seen in the tuning form for buses (figure 20.37).

Fig.20.56 A special Form_FamilyTreeSettings.cs to set the standard parameters of visualization which are used when the new elements are added to a family tree

Usually buses in a family tree have to connect some elements and the existence of a bus connected only at one end is often a mistake in design or the result of some interruption in the work on a tree. There is a possibility to organize a well visible warning about such unusual situation by changing the color of such buses. Two controls in the Bus group allow to define a color for such special buses and to switch ON / OFF the use of this special color. The regulation of this special case color warning can be also done through the command of menu at any empty spot in the Form_FamilyTree.cs (figure 20.46, the last command in menu).

Elements of the Person group have to define the view of any new Person object which will appear in the family tree; some elements are the same as in the Form_Tuning_Person.cs (figure 20.42) but there are also new elements to give more information and possibilities.

  • By changing the sizes of the sample you can define the sizes of the Person objects which will appear later; the sizes (in pixels) are shown in the special boxes.
  • By moving the name comment you can change the positioning of name in all new Person objects; two positioning coefficients are shown in the special box.
  • Appearance or absence of the comments with information about the dates of birth and death are regulated by two check boxes inside the Birth and Death groups. If these comments are shown, then they can be moved around the screen and their positioning coefficients in relation to the “parent” Person object are shown in special boxes.

There is also one more group with the title Distance between; controls of this group allow to define the standard distances between several Person objects that are added to the family tree by the commands from different context menus.

There are two useful commands in the main menu of the Form_FamilyTree.cs. The File – SaveAs command allows to save the tree in the binary file while the File – Open command allows to restore a family tree from a file.

A real family tree can be a big one and chances are high that you will need more space for it than you have on the screen. The whole tree can be easily scrolled in any way you want; simply press the left button at any empty spot and start moving it. This scrolling of the whole family tree was already explained in the previous example.

Do not be afraid to do anything and to make any mistakes in design because two main rules are implemented in this Family Tree application:

  • No user’s action is fatal for the design of a family tree.  If you have placed a wrong element, it can be deleted by the command of context menu; if you placed a wrong combination of elements, you will have to use the Delete command for each of these elements and that is all. If the bus is connected to the wrong element, simply disconnect it (via the command of context menu) and go on with your design.
  • There are no rules (except one or two purely programmatic) to prevent you from organizing the family tree in the way you want.  You cannot connect a bus to itself and you cannot connect both ends of a bus to the same object, but these are the only restrictions. Otherwise you can do everything. For example, if you want to highlight the inter-couple link by two buses, you can do it. Nobody is going to catch you on such an attempt and not allow you to organize two parallel buses between the same two Person objects.

This is a standard user-driven application. It is your application, you are the boss, and you organize the view exactly as you want.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

SergeyAndreyev

United States United States
No Biography provided

Comments and Discussions

 
QuestionThis is exactly what I want !!! Pinmemberamir aslan haghrah17-Jun-14 20:42 
AnswerRe: This is exactly what I want !!! PinpremiumSergeyAndreyev18-Jun-14 19:23 
GeneralMy vote of 5 PinmvpAbhinav S15-Mar-14 7:06 
GeneralMy vote of 5 PinmemberCHill6028-Jan-13 0:22 
GeneralRe: My vote of 5 PinmemberSergeyAndreyev28-Jan-13 20:51 
QuestionNice PinmvpSacha Barber21-Jan-13 23:46 
AnswerRe: Nice PinmemberSergeyAndreyev22-Jan-13 1:44 
GeneralI'm interested PinmemberTarek Elqusi21-Jan-13 18:38 
Questionhow Pinmemberahmed youness1121-Jan-13 11:23 
AnswerRe: how PinmemberSergeyAndreyev21-Jan-13 20:20 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140721.1 | Last Updated 21 Jan 2013
Article Copyright 2013 by SergeyAndreyev
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid