Click here to Skip to main content
15,881,938 members
Articles / Programming Languages / C#

Movable Elements: From Primitive to Complex Objects

Rate me:
Please Sign up or sign in to vote.
5.00/5 (5 votes)
23 Apr 2013CPOL44 min read 22.8K   550   17   11
An article that discusses movable elements right from primitive to complex objects

Introduction

When an application is designed in a standard way on the basis of fixed elements, then users can look at such program only through the eyes of its developer and go only in his steps. When all the screen elements are movable and resizable, then an application turns into a very powerful instrument. Users can do all the things that were coded by the developer but users can do a lot more and each user can do all these things in the way he personally wants them to be done. Users can work with such application differently from the developer’s ideas; users can deal with the same task differently from each other, and even the same user can change at any moment the view of an application and work with it in many different ways. Don’t forget that I am talking about already finished application which was distributed among the users. All these mentioned variations do not need any developer’s participation in the process of further changing; this is simply the main idea and the power of user-driven applications. To get all these possibilities, only one small change in the programs is needed: all the screen elements must be easily movable and resizable BY USER at any moment while an application is working! Making any object movable and resizable becomes the basis of design.

In the previous article “What can be simpler than graphical primitives?” I demonstrated the movability of the most popular screen elements; there were lines, polygons, circles, rings, and some others. I wanted to demonstrate the main principles of turning any element into movable / resizable and I deliberately demonstrated the set of elements which helped me to explain in the best way the standard methods of cover design. I also demonstrated some very useful techniques of dealing with the elements with curved borders and holes. (It would be useful at least to look through that previous article before reading the new one, but pay attention that the mentioned article was published on CodeProject in three separate parts.)

In order to concentrate the readers’ attention on the cover design and basic steps of organizing movability, all the examples of that article used only the primitive objects. In our applications, we often have to work with more complex objects consisting of different parts and these parts are involved in individual, related, and synchronous movements. The current article is about the basic things in organizing different movements of the complex objects.

Movability of the complex objects is described in the special chapter Complex objects in the book World of Movable Objects. Some examples in the current article are similar to the tasks from the text-book Easy Tasks; this text-book contains a lot of explanations. Both books together with the full projects of their accompanying applications and some other helpful documents can be downloaded from here.

Complex objects can consist of different parts, but while demonstrating the examples of such objects I often prefer to use the depending elements in the form of some movable texts. First, they are simple and it is easy to understand all their movements. Second, the same texts can be used not only as the parts of objects but their text can be used as a comment to actions, for example, they can show the values of the most interesting at the moment changeable parameters, like positioning coefficient, angle, or distance. In this way, there is no need in other auxiliary objects on the screen as the involved elements talk about themselves and show all the needed information. But before using such movable texts as parts of complex objects, I need to demonstrate the pure work of these texts. So the first example is going to deal only with such texts without their relations with any other objects and this is the only example of this article that does not use any complex object.

Movable Texts

File: Form_MoveableTexts.cs

Menu position: Movable texts

The MoveGraphLibrary.dll contains the TextMR class and several classes of comments derived from this base class. These specialized comments are used with rectangles, circles, and so on; the difference between these comments is only in the methods of their positioning in relation to the main object. All these comments have the same features that they inherit from their base TextMR class. In the current example, I use the MoveableText class which is simply the copy of the TextMR class.

C#
public class MoveableText : GraphicalObject
{
    Point ptMiddle;                 // MoveableText is the centered text
    string m_text;
    Font m_font;
    double m_angle;       // in radians
    Color m_color;
    bool bBackground;
    Color clrBack;
    bool bRotatable;
    int nW;
    int nH;

The main (and the only) part of any MoveableText object is its text (m_text). Regardless of the number of lines in which this string is shown at the screen, the MoveableText object is considered as a single rectangular area. The sizes of this rectangle (nW and nH) depend on the used font (m_font). By using these sizes and the angle of the text (m_angle), the corners of the rectangular area are calculated with the help of the Auxi_Geometry.TextCorners() method. The cover of any MoveableText object consists of a single node, but the behaviour of this node depends on whether the particular text is declared movable or not. It looks a bit strange at first to develop a whole theory of turning any screen object into movable / resizable and then to think about turning such movable objects into unmovable and to design a special addition for this purpose, but… I have written about this situation in the book while describing the design of user-driven applications. From time to time, there is a need for an easy switch of movable objects into unmovable and then back again. The main principle of the user-driven applications is that user has the total control of any application and decides at any moment about the features of any object. It is possible that at some moment, the user will prefer to turn otherwise movable objects into unmovable; for this case an object must be ready to change its behaviour.

The turn of any object into unmovable does not mean that it is simply excluded from the mover’s queue and becomes invisible for the mover. No, it still can be detected by mover, for example, to change its color, font, or do something else. All these possibilities are still open, but the text becomes unmovable. Regardless of whether the particular MoveableText object is movable or unmovable, its cover consists of a single node; the difference is only in the Behaviour field of this node.

  • For an unmovable text, I use the Behaviour.Frozen value for behaviour and Cursors.Default as the shape of the cursor over it.
  • For a movable text they get the default values; for polygonal nodes they are Behaviour.Moveable and Cursors.SizeAll.
C#
public override void DefineCover ()
{
    Point [] pts = Auxi_Geometry .TextCorners (nW, nH, m_angle, ptMiddle,
                                                   TextBasis .M);
    CoverNode [] nodes = new CoverNode [1];
    if (Movable)
    {
        nodes [0] = new CoverNode (0, pts);
    }
    else
    {
        nodes [0] = new CoverNode (0, pts, Behaviour.Frozen, Cursors.Default);
    }
    cover = new Cover (nodes);
}

Text Box:  Fig.1  Several MoveableText objects with their covers

A MoveableText object can be moved forward or rotated; the rotation goes around the middle point of an object. As there is only one node in the cover, then any movement can be started by any inner point (any point inside the calculated rectangle) and the type of movement is determined only by the pressed mouse button. All possibilities of movement and tuning are demonstrated with three different texts in the Form_MoveableTexts.cs (figure 1).

For the MoveableText objects in this form, there are possibilities to change only two parameters: font and color of the text. Both can be changed via the commands of the small context menu which can be called it any text.

A short remark about one more object in this form. The information at the bottom of figure 1 looks like any other text in the form but with an addition of the small cross in its corner. This is a ClosableInfo object derived from the TextM class. The TextM object is a text which is positioned by its top left corner and cannot be rotated. The ClosableInfo objects are used in several examples in the book World of Movable Objects, but this class is better described in the text-book Easy Tasks. As it is obvious from the name of the class ClosableInfo, such object can be closed (and then opened) at any moment and thus allows to save the screen space and to position some short Help on the screen only when it is really needed. By pressing the information area with the right button, you call an auxiliary form for tuning all its visualization parameters. I’ll use such information areas in all further examples.

Now we are familiar with the work of MoveableText objects as independent screen elements and can start with the simple examples of the complex objects in which movable texts play the role of the “children”. Further examples use several classes of comments from the MoveGraphLibrary.dll. I want to remind you again that all these classes are derived from the TextMR class of which the MoveableText class is the exact copy.

Comment to Point

File: Form_SpotsCommented.cs

Menu position: Objects with comments – Spots

The CommentToPoint class is maybe the simplest of all the comments derived from the TextMR class and included into the MoveGraphLibrary.dll. The idea of design of any comment to something class is the use of standard and easy to understand reaction of such “children” on the movement and resizing of the “parent”. For the CommentToPoint class, the “parent” is a point which is not an object with some parameters of visualization but an abstraction. There is also no resizing of the “parent” but only moving. To demonstrate all movements and related changes, I use a visible colored spot – a small circle – and a CommentToPoint object as a comment to the central point of such movable spot.

Let us start with a small colored spot that ignores anyone else and lives on its own; it is a funny little spot that likes to move around the world and to change its color according to the season, the time of day, or simply according to its mood. (Or maybe the user’s mood?)

C#
public class Spot : GraphicalObject
{
    PointF m_center;
    int m_radius = 5;
    Color m_color = Color .Red;

Any Spot object is determined by its central point, radius, and color. Radius of any spot is defined at the moment of initialization and is never changed after it. Thus, there is no resizing and the only required movement is the forward movement; for this purpose the cover consisting of a single circular node is just enough. To simplify the catch of the smallest spots, the minimum radius of the node is five pixels, so for the smallest spots the cover can be slightly bigger than the spot itself while for bigger spots the cover is equal to the area of the spot.

C#
public override void DefineCover ()
{
    cover = new Cover (new CoverNode (0, m_center, Math .Max (m_radius, 5)));
}

Such spot is a primitive element with the simplest MoveNode() method.

C#
public override bool MoveNode (int i, int dx, int dy, Point ptM,
                                   MouseButtons catcher)
{
    bool bRet = false;
    if (catcher == MouseButtons .Left)
    {
        Move (dx, dy);
        bRet = true;
    }
    return (bRet);
}

When the cover of an object consists of a single node used for movement, then the MoveNode() method automatically calls the Move() method. Position of the spot is defined by the location of the central point, so the movement of this point has to copy the mouse movement.

C#
public override void Move (int dx, int dy)
{
    m_center += new Size (dx, dy);
}

Now let us add a comment to such colored spot and see the work of the first class of complex objects – the SpotCommented class. There is a standard way of designing a class of complex objects: the “parent” is derived from the GraphicalObject, so it is movable by itself, and the “children” are included into the class as the fields. There is no limit on the number of “children” and in further examples you will see an arbitrary number of “children”. In the SpotCommented class, there is a strict rule “not more than one child” and there is simply nothing else except this “child”.

C#
public class SpotCommented : Spot
{
    CommentToPoint m_comment;

There are no new DefineCover() and MoveNode() methods in the SpotCommented class; instead the methods from the basic Spot class are used. However, there is the SpotCommented.Move() method, but I’ll return to it a bit later.

In any complex object, the “parent” and the “child” are both movable and each one has its own cover. These covers are never united; otherwise there will be a more sophisticated but not complex object. The main idea of the complex object is to provide the individual, related, and synchronous movements of the parts. “Child”, like our movable text, has the coordinates of its own, because usually there are variants when such “child” can be used without any “parent” at all and then such individual coordinates are used for its positioning. But when really used as a “child” of some object, then it has to have some additional parameters (coefficients) to describe its relative position to the “parent”.

These parameters of relative positioning can be stored either in the “parent” object or in the “child”; my experience shows that it is much better to store them in the “child” together with the description of the “parent” (it can be either the coordinates and sizes or the method to get them).

  • When a “child” is moved individually, then its absolute (independent) coordinates are changed and the parameters of relative positioning are recalculated by using these new absolute coordinates.
  • When the “parent” is moved or resized, then its new position and sizes are sent to the “child” and the new absolute coordinates of the “child” are calculated by using those stored and fixed parameters of relative positioning.

One of the new things that appear in any class of complex objects is its own method for registering in the mover’s queue. For any simple (non-complex) object, there is no need in any special method for registering. It can be a primitive or not so primitive object, but in any case there is a cover designed by the DefineCover() method and cover of any level of complexity can be registered by the Mover.Add() and Mover.Insert() methods. For a complex object, there is definitely more than one cover as each part of the complex object has its own cover. What is more, the number of the parts (and the number of covers) can change throughout the life of the complex object. Mover deals only with the covers and never deals with any other parameters of the objects, so there is no way for mover to find out the number of covers at any moment and to decide about their order in the mover’s queue. Only the developer knows the required order of covers, so each class of complex objects has to have its own IntoMover() method which defines the order of registering all those covers of all the parts. Here is the code of the SpotCommented.IntoMover() method.

C#
new public void IntoMover (Mover mover, int iPos)
{
    if (iPos < 0 || mover .Count < iPos)
    {
        return;
    }
    mover .Insert (iPos, this);
    if (m_comment .Visible)
    {
        mover .Insert (iPos, m_comment);
    }
}

As you can see from the above code, the comment for the spot can be invisible. The cover of the spot is always included into the mover’s queue while the cover of the comment is included only if the comment is visible. In the Form_SpotsCommented.cs, there is no way to declare the comments visible / invisible, but here is the standard way to organize such actions. Any “child” can be declared invisible through the command of its context menu. Invisible objects are never registered in the mover’s queue, so when an object is declared invisible, it is immediately excluded from this queue. Context menus are called after the mover’s analysis of the objects at the point of the right mouse click. Only the objects from the mover’s queue are analysed, so the invisible object cannot be detected by the mover and no menu can be called for such object. Thus, the command of turning an invisible “child” into visible must be included into another context menu and I often include it into the menu which is called on the “parent”. This process is demonstrated in the demo application which accompanies the book World of Movable Objects and it is described in the book with many details; for example, look at the classes ElasticGroup and Plot.

Text Box:  Fig.2  SpotCommented objects demonstrate the work of comments to points

The Form_SpotsCommented.cs (figure 2) demonstrates the work of comments to points. As I mentioned before, the points are represented by the colored spots and comments are united with these spots. Four pairs “spot + comment” are organized into four SpotCommented objects. Three of these objects use special methods to adjust the position of their comments. Only one of the objects, the one initially painted with magenta color (spotCoordinates), does not use any additional method of positioning. Let us start with this object and at the very end of our analysis, we will find the difference with other three objects.

At any moment, the comment to this spotCoordinates object shows the coordinates of the “parent” spot.

C#
public void DefaultView ()
{
    … …
    spotCoordinates = new SpotCommented (this, new PointF (400, 100), 6,
                                         Color .Magenta);
    spotCoordinates .SetComment (new CommentToPoint (this,
                         spotCoordinates .Center, 40, 50, CoordinatesToString, 
                                 fntCmnts, spotCoordinates .Color));
    … …
// -------------------------------------------------     CoordinatesToString
private string CoordinatesToString
{
    get { return (Auxi_Convert .PointToStr (spotCoordinates .Center,
                                            PointStr .IN_ROWS_WITH_LETTERS)); }
}

The individual movement of any comment is not mentioned anywhere in the methods of the Form_SpotsCommented.cs; such movement goes according to the CommentToPoint.MoveNode() method as was shown in the previous example. Interesting things might happen when the colored spot is moved because the comment is the “child” of such spot and must react to the movement of the “parent”.

C#
private void OnMouseMove (object sender, MouseEventArgs e)
{
    if (mover .Move (e .Location))
    {
        GraphicalObject grobj = mover .CaughtSource;
        if (grobj is SpotCommented)
        {
            if (grobj .ID == spotCoordinates .ID)
            {
                spotCoordinates .Comment .Text = CoordinatesToString;
                spotDistance .Comment .UpdateLocation ();
            }
            … …

The code of the Form_SpotsCommented.OnMouseMove() method shows that the movement of the spotCoordinates object affects the text of its comment and this is an expected result, but the change of the comment’s location is not seen from the above code, so it must happen somewhere behind the curtains or better to say, somewhere in the methods of the SpotCommented class. Let us try to detect the chain of the involved methods.

When any object is moved, its MoveNode() method must work. The SpotCommented class has no MoveNode() method of its own, but this class is derived from the Spot so the Spot.MoveNode() method is used. The code of this method was shown a bit earlier; it simply calls the Move() method. Because there exists the SpotCommented.Move() method, then this method is used.

C#
public override void Move (int dx, int dy)
{
    base .Move (dx, dy);
    InformComment ();
}

First it uses the Move() method of the base class and this moves the colored spot; then the SpotCommented.InformComment() method is called. This method informs the comment about the new position of the “parent” which is simply the new central point of the spot.

C#
protected void InformComment () 
{ 
     m_comment .ParentPoint = Center; 
}

Here is the code for the CommentToPoint.ParentPoint property.

C#
public PointF ParentPoint
{
    get { return (ptParent); }
    set
    {
        ptParent = value;
        Location = Point .Round (Auxi_Geometry .PointToPoint (ptParent,
                                 angleParentToCmnt, distParentToCmnt));
        UpdateLocation ();
    }
}

You can see from this code that three different things have to be done.

  • The new position of the “parent” (spot) is remembered.
    C#
    ptParent = value;
  • The new spot location and previously stored parameters of the relative positioning – the angle and distance from the point to the associated comment – are used to calculate the new location of comment.
    C#
    Location = Point .Round (Auxi_Geometry .PointToPoint 
    (ptParent, angleParentToCmnt, distParentToCmnt));
  • There is a call to the CommentToPoint.UpdateLocation() method, but the results of this call depend on some parameters of the particular CommentToPoint object.
    C#
    public void UpdateLocation ()
    {
        if (bUseCalcMethod && CalcLocation != null)
        {
            PointF ptNew = CalcLocation ();
            angleParentToCmnt = Auxi_Geometry .Line_Angle (ptParent, ptNew);
            distParentToCmnt =
                   Math .Min (Auxi_Geometry .Distance (ptParent, ptNew), distMax);
            Location = Point .Round (Auxi_Geometry .PointToPoint (ptParent,
                                           angleParentToCmnt, distParentToCmnt));
        }
    }
    

When the magenta spot with comment was initialized, then no method for automatic updating of its comment’s position was declared, so for this comment the UpdateLocation() method does not do anything at all. Other three comments were initialized with such special methods so for each of them, the particular CalcLocation() method is called. This method calculates the new comment’s location and after it, the new positioning coefficients are calculated. Methods for three objects in the Form_SpotsCommented.cs are different but all of them are simple; for example, letters A and B are always positioned opposite to the line which connects their spots.

The “parent” object in the next example has the same shape: it is also a circle but bigger and with the possibility of changing its radius. There is also a chance to have any number of “children”, but I decided to limit this thing in the particular example so we are going to look at one circle with two comments.

Circle with Comments

File: Form_CircleWithComments.cs

Menu position: Objects with comments – Circle

Text Box:  Fig.3  Circle with comments

The circle which you see in the Form_CircleWithComments.cs (figure 3) belongs to the CircleCommented class.

C#
public class CircleCommented : Circle_SimpleCover
{
    List<CommentToCircle> m_comments = new List<CommentToCircle> ();

This CircleCommented class is derived from the Circle_SimpleCover class and the only new part is the unlimited number of the “children” – the comments of the CommentToCircle class. You can read about the Circle_SimpleCover class in the article “What can be simpler than graphical primitives? Part 2” which was published on CodeProject in March 2013. The cover of such circles consists of two nodes; the forward movement and rotation can be started by any inner point; the resizing can be started by any border point.

The CommentToCircle class is included into the MoveGraphLibrary.dll. This is one of the classes derived from the TextMR class, so any such comment can be moved forward and rotated around its own central point without any mentioning in the code. The synchronous and related movements of the circle and its comments are described by several rules.

  1. When a circle is moved forward, all the comments to this circle move synchronously.
  2. If a circle is resized, then each comment moves along the radial line, but the exact movement depends on whether a comment was inside or outside the border of a circle when the resizing started. The position of any comment is determined by its center. If this point was outside the border of a circle at the beginning of resizing, then it has to stay outside and at exactly the same distance from the border throughout the whole process of resizing. If the comment was inside, then the ratio between its distance from the center of a circle and the radius of a circle is going to stay unchanged.
  3. Comments are not affected at all by the rotation of a circle.

Our CircleCommented class is the classical complex object because the forward movement of a circle initiates the synchronous movement of all the parts, while the resizing of a circle initiates the related movement of all the comments; in addition, there is an individual movement of each comment.

The individual movements of the comments go by default because they are organized in such a way for all the TextMR objects. For synchronous and related movements of the parts, we have to look on the MoveNode() and Move() methods of the CircleCommented class.

C#
public override void Move (int dx, int dy)
{
    base .Move (dx, dy);
    InformComments ();
}
// -------------------------------------------------        MoveNode
public override bool MoveNode (int i, int dx, int dy, Point ptM,
                               MouseButtons catcher)
{
    bool bRet = false;
    if (catcher == MouseButtons .Left)
    {
        if (i == 0)
        {
            Move (dx, dy);
        }
        else
        {
            base .MoveNode (i, dx, dy, ptM, catcher);
            InformComments ();
            bRet = true;
        }
    }
    else if (catcher == MouseButtons .Right)
    {
        base .MoveNode (i, dx, dy, ptM, catcher);
        bRet = true;
    }
    return (bRet);
}

In both cases, we can see the call to the InformComments() method. Through this method, all the comments get the values of the central point and radius of their “parent” – the circle. At any moment, the CommentToCircle object keeps the angle from the circle’s center and the positioning coefficient; by using these two values and the new location and radius of the circle, the new location of comment is calculated. You can see that the work of the CommentToPoint and CommentToCircle objects is similar and the only difference is in the way the relative position of each “child” is described.

You can see from the code of the CircleCommented.MoveNode() method that the rotation of a circle does not affect its comments. When the circle is rotated, no information is sent to the comments and they simply ignore such movement of their “parent”.

Two comments in the Form_CircleWithComments.cs show two different pieces of valuable information. One of them – with number one – shows the information about its parent. It is the radius of the circle; this information is changed only throughout the resizing of the circle.

C#
private void OnMouseMove (object sender, MouseEventArgs e)
{
    if (mover .Move (e .Location))
    {
        GraphicalObject grobj = mover .CaughtSource;
        if (e .Button == MouseButtons .Left)
        {
            if (grobj is CircleCommented && mover .CaughtNode == 1)
            {
                circle .Comments [1] .Text = RadiusToString;
            }
            … …

Another comment – with number zero – shows its own positioning coefficient, so it is changed only during the individual movement of this comment.

C#
private void OnMouseMove (object sender, MouseEventArgs e)
{
    … …
            else if (grobj .ID == circle .Comments [0] .ID)
            {
                circle .Comments [0] .Text = CoefToString;
            }
        }
        Invalidate ();
    }
}

The value of the positioning coefficient for any CommentToCircle object gives a clear understanding of whether this comment is inside or outside the circle. For the inside positioning, this coefficient belongs to the [0, 1] range and equals to the ratio between the distance from the center of comment to center of circle and the radius of circle. Any coefficient greater than one means the distance from the border to the comment placed somewhere outside the circle. For better demonstration of this rule, there is a small red spot painted in the middle of that comment in the Form_CircleWithComments.cs which shows the coefficient.

There is one more interesting feature of many complex objects demonstrated in this example. It is not a rare thing that the number of “children” in the complex object can be changed by user at any moment. (I’ll demonstrate this in one of further examples.) In some situations, users can add and delete “children”; in other cases it is preferable not to delete but only to hide “children” and then to unveil them later. Both operations of hiding and unveiling rely on the mover, but there is one small trick. When you need to hide or delete any “child” of the complex object, then you simply call the context menu on this “child” and select its Hide (or Delete) command. Mover knows the object on which the context menu was called, so there are no problems with the correct work of this command. However, the opposite command to unveil the hidden “child” cannot be used through the same context menu because the hidden object is removed from the mover’s queue and no menu can be called on the hidden object; mover simply does not see such object.

The only chance is to call another context menu on the “parent” which has to keep track of its visible and invisible “children”. In such way, all the hidden “children” can be unveiled by a single command or some instrument of looking through all the hidden “children” must be developed. I rarely organize the second possibility; more often I restore all the hidden “children”.

Objects of the CommentToCircle class react only to the change of the position or size of the circle but ignore its rotation. However, there can be a need in similar comments but associated not with the whole circle but with the sectors and such comments have to react on the rotation of their “parent”. In the MoveGraphLibrary.dll, there is the PieChart class which uses both types of comments associated with a circle; comments associated with sectors of a circle belong to the CommentToCircleSector class. You can read about this class in the book World of Movable Objects, look at the working examples and at the code of those examples.

The current example Form_CircleWithComments.cs and the examples from the book show that there are no limits on the number of “children” in the complex object and no limit on the number of types of those “children”. In one of the further examples, I am going to show that there is no limit on the complexity of the objects so the “children”, in their turn, can be the complex objects themselves. But this will be a bit later and now let us turn to the “parents” of the most often used shape on our screens – the rectangles.

Rectangles with Comments

File: Form_RectanglesCommented.cs

Menu position: Rectangles plus something – Rectangles with comments

Text Box:  Fig.4  Rectangles with comments

I hope that now you are well familiar with the comments accompanying different objects so without any information on the screen you know that any comment can be moved around the screen and rotated individually and at the same time all these comments are involved in synchronous or related movements initialized by the moving or resizing of their “parents”. Such movements are organized in the similar way regardless of the shape of the “parent” and the difference is only in using one or another positioning coefficient and in transformation of those coefficients into real positions. The new example Form_RectanglesCommented.cs (figure 4) is based on the “parents” of rectangular shape but there are also some additional new features.

  • The number of “parents” in this example can be changed by adding and deleting the rectangles. Certainly, when any rectangle is deleted, then it disappears with all its associated comments.
  • The number of comments to any rectangle can be also changed.
  • There are two ways of taking the comments from the screen: they can be either deleted or only hidden. As I mentioned before, hiding of comments is usually done on the personal basis (though there is also a command to hide all the comments of rectangle) but the opposite operation of restoring on the screen can be applied only to all the comments of the rectangle.

The comments in this example belongs to the CommentToRect class which is included into the MoveGraphLibrary.dll. (In the book World of Movable Objects, you can find a similar example of rectangles with comments belonging to the CommentToRectangle class; you can find all the codes of that class in the accompanying project. Both classes are identical and I did it in order to demonstrate the design of such comments.)

The most interesting thing for our analysis of rectangles with comments is the way the position of any comment is described in relation to the position of its “parent”. There are two independent coefficients along X and Y axes. Here are the rules for xCoef:

  • xCoef < 0 number of pixels to the left of rectangle;
  • 0 <= xCoef && xCoef <= 1 position between left border (xCoef = 0) and right border (xCoef = 1);
  • xCoef > 1 number of pixels to the right of rectangle.

The same rules are applied to yCoef and vertical coordinates of the rectangle.

Design of the RectangleCommented class is simple.

C#
public class RectangleCommented : GraphicalObject
{
    RectangleF m_rect;
    SolidBrush m_brush;
    List<CommentToRect> m_comments = new List<CommentToRect> ();
    static int minSide = 20;

Any rectangle is initialized with an empty List of comments. Later, the comments can be added and deleted. For adding the new comment, only its position and text are indispensable.

C#
public void AddComment (PointF pt, string txt)
{
    CommentToRect cmnt = new CommentToRect (form, m_rect, pt, txt);
    cmnt .ParentID = ID;
    m_comments .Insert (0, cmnt);
}

As usual, each comment gets and stores the ID of the rectangle with which it is associated; this helps in many situations when the link between the comment and rectangle is needed.

Rectangles of the RectangleCommented class have a standard cover which allows the resizing by any border point, so there are four small circular nodes in the corners, four strip nodes along the borders, and a big node covering the whole rectangle.

C#
public override void DefineCover ()
{
    cover = new Cover (m_rect, Resizing .Any);
}

Though the cover is a standard one (for rectangles) and has the standard order of nodes, it is the first time when I use the technique of adhered mouse for resizing of rectangles. This means that at the starting moment of resizing, the cursor can be slightly moved in order to position it exactly on the border line. This is done in the RectangleCommented.StartResizing() method and in the same method the limits for further movement of the mouse cursor are calculated.

C#
private void OnMouseDown (object sender, MouseEventArgs e)
{
    ptMouse_Down = e .Location;
    if (mover .Catch (e .Location, e .Button, bShowAngle))
    {
        GraphicalObject grobj = mover .CaughtSource;
        if (grobj is RectangleCommented)
        {
            RectangleCommented rect = grobj as RectangleCommented;
            if (e .Button == MouseButtons .Left)
            {
                rect .StartResizing (e .Location, mover .CaughtNode);
            }
        }
    }
    ContextMenuStrip = null;
}

The limits for further movement of the mouse cursor depend on the part of the border which is initially pressed. When any corner is pressed, then two sizes of the rectangle can be changed simultaneously and the movement of the cursor is allowed inside the big rectangular area; when any other point of the border is pressed (not a corner!), then only one size of the rectangle can be changed and in this case the cursor can move only along the straight line. The code below shows part of the RectangleCommented.StartResizing() method for the top left corner (node number zero) and the right border (node number five) of rectangle.

C#
public void StartResizing (Point ptMouse, int iNode)
{
    float cx, cy;
    switch (iNode)
    {
        case 0:         // top left corner
            cxLimit_R = m_rect .Right - minSide;
            cxLimit_L = cxLimit_R - 4000;
            cyLimit_B = m_rect .Bottom - minSide;
            cyLimit_T = cyLimit_B - 4000;
            Cursor .Position =
                      form .PointToScreen (Point .Round (m_rect .Location));
            break;
        … …
        case 5:         // right border
            cy = ptMouse .Y;
            ptInner = new PointF (m_rect .Left + minSide, cy);
            ptOuter = new PointF (ptInner .X + 4000, cy);
            Cursor .Position = form .PointToScreen (Point .Round (
                                           new PointF (m_rect .Right, cy)));
            break;
        … …

Throughout the movement of the caught node, the mouse can be moved only inside the previously calculated limits and the new mouse location is considered as the new position of the border point. The next piece of code from the RectangleCommented.MoveNode() method demonstrates the cases of the same two nodes: the top left corner (node number zero) and the right border (node number five).

C#
public override bool MoveNode (int i, int dx, int dy, Point ptM,
                               MouseButtons catcher)
{
    bool bRet = true;
    if (catcher == MouseButtons .Left)
    {
        PointF pt;
        switch (i)
        {
            case 0:         // top left corner
                if (ptM .X <= cxLimit_R)
                {
                    SetBorder_Left (ptM .X);
                }
                if (ptM .Y <= cyLimit_B)
                {
                    SetBorder_Top (ptM .Y);
                }
                Cursor .Position =
                      form .PointToScreen (Point .Round (m_rect .Location));
                break;
            … …
            case 5:         // right border
                pt = Auxi_Geometry .NearestPointOnSegment (ptM, ptOuter,
                                                                ptInner);
                SetBorder_Right (pt .X);
                Cursor .Position = form .PointToScreen (Point .Round (pt));
                break;
            … …

For the case of a simple rectangle, it would be enough but we have a complex object with the rectangle in the role of the “parent”, so all the “children” must be informed about the change of rectangle.

C#
public override bool MoveNode (int i, int dx, int dy, Point ptM,
                               MouseButtons catcher)
{
    … …
    if (bRet == true)
    {
        InformRelatedElements ();
    }
    return (bRet);
}

Informing of all the comments about the new position and sizes of the rectangle is simple: each comment gets the new value of rectangle.

C#
private void InformRelatedElements ()
{
    Rectangle rc = Rectangle .Round (m_rect);
    foreach (CommentToRect cmnt in m_comments)
    {
        cmnt .ParentRect = rc;
    }
}

Each comment of the CommentToRect class keeps the value of its “parent” rectangle and the two positioning coefficients which describe the position of the comment in relation to rectangle. When the value of rectangle is changed, then two unchanged coefficients help to calculate the new position of comment.

C#
public Rectangle ParentRect
{
    get { return (rcParent); }
    set
    {
        rcParent = value;
        Location = Auxi_Geometry.LocationByCoefficients (rcParent, xCoef, yCoef);
    }
}

When any comment is moved individually, then the CommentToRect.Move() method works. Throughout such movement, the value of rectangle is not changed, but the new position of comment allows to calculate those two positioning coefficients. The rules of such calculation were described a bit earlier.

C#
public override void Move (int dx, int dy)
{
    base .Move (dx, dy);
    Auxi_Geometry .CoefficientsByLocation (rcParent, Location,
                                           out xCoef, out yCoef);
}

The next example continues to demonstrate the relations between different movable screen objects but there are some nuances. The “children” in the next complex object are not represented by the simple comments derived from the TextMR class. There are such comments in the next example but they are not the main “children” and play only the secondary role. Those comments are the “grandchildren” of the main object, so this is the first demonstration of the mentioned fact that the discussed complexity can include more than two levels of objects.

Track Bars for Rectangles

File: Form_Trackbars.cs

Menu position: Rectangles plus something – Area with track bars

Text Box:  Fig.5  In this example the Plot object is associated with four track bars

The track bars in the Form_Trackbars.cs (figure 5) behave like the “children” of an ordinary rectangle but in reality this “parent” is much more interesting. It is an object of the Plot class on which very complicated applications are designed. Some of these applications are described in the book World of Movable Objects and several examples with the Plot class are included into the demonstration program accompanying this book. Any Plot object has horizontal and vertical scales (at least one scale of each type) and an arbitrary number of comments associated with the main area and scales. Any Plot object is really a complex one, but I am not going to discuss it in this article. On the contrary, I did whatever I could to simplify the Plot object in this example; it has no comments and even the existing scales are hidden.

C#
plot = new Plot (this, new Rectangle (100, 100, ClientSize .Width / 2,
                                      ClientSize .Height / 2));
plot .HorScales [0] .Visible = false;
plot .VerScales [0] .Visible = false;

The united work of the Plot object and the track bars is not a classical case of the complex object. In all the demonstrated examples, “children” were declared as fields of the “parent” object and those “children” were informed about any change of their “parent” through its own method; in the previous examples, it was called InformComments() or InformRelatedElements(). While working on the Plot class, I thought about including into it the track bars, but then dropped this idea. In the majority of my applications, the plots are used without the track bars; whenever they are needed, they can be added as stand alone elements and the changing parameters can be sent from one object to another in different ways. For example, like it is done in the Form_Trackbars.cs.

C#
public partial class Form_Trackbars : Form
{
    Plot plot;
    Trackbar trackbar_Btm, trackbar_Left, trackbar_Right, trackbar_Top;

A Trackbar object can be used as a stand alone element without any “parent” but if it is initially associated with some rectangle and later is informed about each change of this rectangle, then the pair works as a normal complex object. All four track bars in the current example work this way.

C#
trackbar_Right = new Trackbar (this, rc, 10, Side .W, 0.1,
      SliderShape .Sexangle, 8, 10, 8, 4, true, 2, 5, 11, Draw_TrackbarR);
trackbar_Right .Slider .InnerColor = Color .Green;
trackbar_Right .AddComment (6, 0, "0", Font, 0, ForeColor, false);
trackbar_Right .AddComment (6, 1, "1", Font, 0, ForeColor, false);
trackbar_Right .AddComment (8, 0.5, "Movable", Font, 90, ForeColor, true);
trackbar_Top = new Trackbar (this, rc, -5, Side .S, 0.5,
                 SliderShape .Pentagon, 8, 8, 0, 6, false, 0, 0, 0, null);
trackbar_Top .Slider .InnerColor = Color .Magenta;
trackbar_Top .ShowAreaBorder = false;
trackbar_Top .Movable = false;
trackbar_Top .AddComment (trackbar_Top .Slider .PositionCoefficient, -8,
               trackbar_Top .Slider .PositionCoefficient .ToString ("F2"),
                          Font, 0, Color .Blue, false);

The information about the change of area of the “parent” is sent to such “external children” not through the method of the “parent” (this is impossible because it does not know the number of such “children”) but through the method of each “child”.

C#
private void OnMouseMove (object sender, MouseEventArgs e)
{
        … …
        else if (grobj is Plot || grobj is RectCorners)
        {
            Rectangle rc = plot .PlottingArea;
            trackbar_Btm .ParentRect = rc;
            trackbar_Left .ParentRect = rc;
            trackbar_Right .ParentRect = rc;
            trackbar_Top .ParentRect = rc;
        }
        … …

The track bars are used as “children” of the plot but at the same time the Trackbar class represents a normal complex object in which the “children” are included into the “parent”. (The Trackbar class is included into the MoveGraphLibrary.dll but you can find the exact copy of it as the TrackbarC class in the project accompanying the book.)

C#
public class Trackbar : GraphicalObject
{
    Rectangle rcSlider; // area, in which the slider's axis can move
    Rectangle rcTicks;  // exists, if the ticks are shown
    Rectangle rcArea;   // includes rcSlider and rcTicks (if shown)
    bool bShowTicks;
    Rectangle rcParent = new Rectangle (100, 100, 100, 100);
    TrackbarSlider m_slider;
    List<CommentToRect> m_comments = new List<CommentToRect> (); 
                                            // comments are related to rcArea
    Delegate_Draw DrawTrackbar = null;     
             // it can include, for example, drawing of the bar and the border

For the first time in this article, I demonstrate a complex object with the “children” belonging to different classes: there is a slider of the TrackbarSlider class and an arbitrary number of the comments of already familiar CommentToRect class. For correct registering in the mover’s queue, any complex object must use its own IntoMover() method so the Trackbar class has such method.

C#
new public void IntoMover (Mover mover, int iPos)
{
    if (iPos < 0 || mover .Count < iPos)
    {
        return;
    }
    mover .Insert (iPos, this);
    mover .Insert (iPos, m_slider);
    for (int i = 0; i < m_comments .Count; i++)
    {
        mover .Insert (iPos, m_comments [i]);
    }
}

There are some very interesting details in using the track bars along the plot areas, but I don’t think that I need to include their explanation into this article. I wrote about all those things in the book and you can read much more detailed description of similar example with the track bars in the chapter Complex objects of the mentioned book World of Movable Objects.

Plot Analogue

File: Form_PlotAnalogue.cs

Menu position: Rectangles plus something – Plot analogue

Text Box:  Fig.6	Colored rectangles represent the plots; frames with an additional line in the middle represent the scales; comments are shown as normal comments.

This example demonstrates the use of complex objects with the “children” belonging to different classes; at the same time some of these “children” are the complex objects themselves. It is not an artificial example to demonstrate the work with complex objects; it was designed to demonstrate, but in a simplified version, the real situation in the scientific programs where the plots have scales, all of them may have different comments and all those screen elements are involved in related movements. There are many important details in very complicated scientific and engineering applications, but here we are interested only in analysis of the individual, related, and synchronous movements, so all the involved elements are represented in this example by the rectangular areas (figure 6).

  • Any Plot object is substituted by the PlotAnalogue and is shown as a colored rectangle which can be moved by any inner point and resized by any border point.
  • Scales, depending on their orientation, are substituted by the HorScaleAnalogue or VerScaleAnalogue and are shown by a thin rectangular frame with an additional line in the middle. Each scale can be moved by any inner point and they can be also slightly resized by two borders painted with wider pen. Any scale can be moved and resized only in the direction orthogonal to its middle line. (By pressing on any scale and trying to move or resize any scale, you will understand these rules in lesser time than reading this paragraph.)
  • Comments are used in many examples and there is no sense in using any analogue instead of the CommentToRect elements. As usual, all these comments can be moved and rotated.

Any comment is associated either with a plotting area or a scale. Comment does not know about the class of its “parent”, and there is no need for such knowledge. In any way, the “parent” has a rectangular area; the knowledge of this area in combination with two positioning coefficients is enough for correct involvement of any comment in individual movements and related movements with its “parent”. The use of rcParent and two coefficients was explained in one of the previous examples (see Rectangles with comments).

No engineer or scientist ever asked me about the rotation of the plots in the real applications. It’s not a problem to add such possibility but there is no need for it. Without any rotation of the plotting areas, each scale goes either along X or Y axis. The position of the scale is determined by the position of its central line in relation to the plotting area. Because each scale can be moved only along one axis, then it is enough to have only one positional coefficient; this coefficient – posCoef – is calculated in exactly the same way as the positioning coefficients of comments in relation to rectangle. The scale is lined with two opposite sides of the plotting area; the end points of the central line of the scale are stored as pt_LT and pt_RB. The width of the scale can be slightly changed and the value of the wHalf field is only half of this width.

C#
public class HorScaleAnalogue : GraphicalObject
{
    Rectangle rcParent;     // main plotting area
    Point pt_LT, pt_RB;     // end points of the central line
    int hHalf;              // half of the rectangle's height
    double posCoef;         // positioning coefficient to the plot
    Pen penThin, penThick;
    Font fntComments;
    List<CommentToRect> m_comments = new List<CommentToRect> ();

Any complex object is registered in the mover’s queue by its IntoMover() method; here is the code for the PlotAnalogue.IntoMover() method.

C#
new public void IntoMover (Mover mover, int iPos)
{
    if (iPos < 0 || mover .Count < iPos)
    {
        return;
    }
    mover .Insert (iPos, this);
    for (int i = comments .Count - 1; i >= 0; i--)
    {
        mover .Insert (iPos, comments [i]);
    }
    for (int i = horScales .Count - 1; i >= 0; i--)
    {
        horScales [i] .IntoMover (mover, iPos);
    }
    for (int i = verScales .Count - 1; i >= 0; i--)
    {
        verScales [i] .IntoMover (mover, iPos);
    }
}

Because the scales are also complex objects, then they are registered by their IntoMover() methods.

C#
new public void IntoMover (Mover mover, int iPos)
{
    if (iPos < 0 || mover .Count < iPos)
    {
        return;
    }
    mover .Insert (iPos, this);
    for (int i = m_comments .Count - 1; i >= 0; i--)
    {
        mover .Insert (iPos, m_comments [i]);
    }
}

When a plot is moved or resized, then the PlotAnalogue.InformRelatedElements() method is called.

C#
private void InformRelatedElements ()
{
    foreach (CommentToRect comment in comments)
    {
        comment .ParentRect = rc;
    }
    foreach (HorScaleAnalogue scale in horScales)
    {
        scale .ParentRect = rc;
    }
    foreach (VerScaleAnalogue scale in verScales)
    {
        scale .ParentRect = rc;
    }
}

We have already seen the reaction of comment on moving or resizing of its “parent”. The scales react in similar way, but the chain of the involved methods is longer; here is the code of the HorScaleAnalogue.ParentRect property.

C#
public Rectangle ParentRect
{
    get { return (rcParent); }
    set
    {
        rcParent = value;
        int cy = Convert .ToInt32 (Auxi_Geometry .CoorByCoefficient (
                                        rcParent .Top, rcParent .Bottom, posCoef));
        pt_LT = new Point (rcParent .Left, cy);
        pt_RB = new Point (rcParent .Right, cy);
        CommentsNotification ();
        DefineCover ();
    }
}

By using the new value of the parent rectangle and previously stored positioning coefficient, the new coordinate of the central line (cy) and the end points of this line (pt_LT and pt_RB) are calculated. All the comments of the scale are notified by the CommentsNotification() method and the new positions of all the comments are calculated.

C#
private void CommentsNotification ()
{
    Rectangle rect = Rectangle;
    foreach (CommentToRect comment in m_comments)
    {
        comment .ParentRect = rect;
    }
}

When initially the plot is moved or resized, then the notification and the order for recalculation is sent to all the direct comments of this plot and to all the scales of this plot; through these scales the notification is sent to all their comments. When initially some scale is moved or resized, then the notification is sent only to the comments related to this particular scale.

The Form_PlotAnalogue.cs example implements many commands that are very useful in the real scientific applications. These commands can be started through several context menus which can be called on plots, scales, comments, and at any empty spot. I don’t think that I need to write about these possibilities in this article, but you can find much more detailed description of the same example in the book.

Groups as Complex Objects

File: Form_RegularPolygonCommented.cs

Menu position: Familiar and new

The last example of this article demonstrates not one but two different complex objects and maybe the most valuable thing in this example is their comparison side by side. Menu position to open this example is called “Familiar and new” where the word familiar corresponds to the regular polygon with comments (figure 7). I didn’t use regular polygons in the previous examples of this article though similar elements but without comments were demonstrated in the third part of the previous article “What can be simpler than graphical primitives?” I would say that the word familiar in this context speaks more not about the shape of an object but about the whole organization of the co-work of the main element and its “children” – the comments. The RegularPolygonCommented class is very similar to the CircleCommented class. For some time, I even wanted to use in the new class the comments of the same CommentToCircle class but then decided to use different comments.

Text Box:  Fig.7	Regular polygon with comments is similar in behaviour to the rectangles and circles with comments.  The ElasticGroup object is also a complex object but the design is different and very interesting.
C#
public class RegularPolygonCommented : GraphicalObject
{
    PointF m_center;
    float radiusVertices;
    int nVertices;
    double m_angle;
    SolidBrush m_brush;
    bool bSynchronicCommentsRotation = false;
    List<CommentToRegPoly> m_comments = new List<CommentToRegPoly> ();

The cover of such polygons is similar to the cover of the circles from one of the previous examples. We need to move a rectangle by any inner point and resize it by any border point, so the theoretical minimum of the number of nodes is two and here we have a cover consisting of two nodes. Both nodes copy the shape of the object; the first node is slightly smaller than the object; the second node is slightly bigger.

C#
public override void DefineCover ()
{
    double delta = minDelta / Math .Cos (Math .PI / nVertices);
    PointF [] ptsInside = Auxi_Geometry .RegularPolygon (m_center,
                                      radiusVertices - delta, nVertices, m_angle);
    PointF [] ptsOutside = Auxi_Geometry .RegularPolygon (m_center,
                                      radiusVertices + delta, nVertices, m_angle);
    CoverNode [] nodes = new CoverNode [] { new CoverNode (0, ptsInside), 
                                   new CoverNode (1, ptsOutside, Cursors .Hand) };
    cover = new Cover (nodes);
}

The first node covers nearly the whole object and by pressing any point inside this node, the forward movement of the whole polygon is started. The greater part of the second node is covered and thus blocked by the first node so only a narrow strip of the second node along the border is visible to the mover. By pressing anywhere inside this strip, the resizing is started.

C#
public override bool MoveNode (int i, int dx, int dy, Point ptM,
                               MouseButtons catcher)
{
    bool bRet = false;
    if (catcher == MouseButtons .Left)
    {
        if (i == 0)
        {
            Move (dx, dy);
        }
        else
        {
            PointF ptBase, ptNearest;
            PointOfSegment typeOfNearest;
            Auxi_Geometry .Distance_PointSegment (ptM, ptInnerLimit,
                  ptOuterLimit, out ptBase, out typeOfNearest, out ptNearest);
            double dist = Auxi_Geometry .Distance (m_center, ptNearest);
            Radius = Convert .ToSingle (dist * scaling);
            bRet = true;
            Cursor .Position = form .PointToScreen (Point .Round (ptNearest));
        }
    }
    else if (catcher == MouseButtons .Right)
    {
        double angleMouse =
                    -Math .Atan2 (ptM .Y - m_center .Y, ptM .X - m_center .X);
        m_angle = angleMouse - compensation;
        if (bSynchronicCommentsRotation)
        {
            InformRelatedElements ();
        }
        else
        {
            InformElementsAboutAngle ();
        }
        bRet = true;
    }
    return (bRet);
}

There is a small but interesting change in the process of rotation. When the circle from one of the previous examples is rotated, then its comments stay still at their places. For polygons, there is a possibility for user to decide whether to organize the rotation in exactly the same way or to have the synchronous rotation of the polygon and all its comments.

Comments of the CommentToRegPoly class are derived from the TextMR class. Because the position of these comments is determined by the radius of the vertices, then these comments can be substituted by the CommentToCircle elements.

C#
public class CommentToRegPoly : TextMR
{
    PointF ptCenterPoly;
    float radiusVertices;
    double anglePoly;
    double coef;             // special coefficient to the circle of vertices
    double angleFromPolyStart;

Comments have two types of positioning: they inherit the absolute positioning from their base TextMR class while the new fields of the CommentToRegPoly class describe the position in relation to the polygon. When the polygon is moved and resized, the new values of its parameters are sent to the “children” and the calculations of their new location use the unchanged parameters of relative positioning. When any comment is moved individually, then it is a simple move of the TextMR element and the new coordinates are used to calculate the parameters of relative position. This is a classical design of the complex object and the whole idea is absolutely the same for circles, rectangles, or regular polygons with comments. The idea of the ElasticGroup class is different and it is also orthogonal to the idea of the groups that are used in numerous applications throughout the last decades.

For several decades, all the designers have only two possibilities to unite several screen elements: it is either a GroupBox with a frame around the included elements or a Panel on which those objects are placed. In the book, World of Movable Objects I demonstrated the way to transform these standard groups into movable but this is only a small change in the standard design. Step by step, I drifted from those ideas of group design; there were different interesting results on the way, so the chapter Groups in the mentioned book is one of the biggest. At the end, I came to the idea of the ElasticGroup class which simply turns the initial idea of a group upside down. This class deserves a separate article and here I want only to introduce it, so I demonstrate only a very primitive object of this class.

In the ElasticGroup class, inner elements play the main role in rearranging the group while their unit is a secondary thing but this thing turns the whole union into a complex object.

The group at figure 7 looks like an ordinary group consisting of a set of controls, a frame around them, and a title included into the frame. For the purposes of design, each inner element is wrapped and transformed into an ElasticGroupElement object. The text of the title must be painted with some font and color, so there are obvious fields in the ElasticGroup class.

C#
public class ElasticGroup : GraphicalObject
{
    Form formParent;
    List<ElasticGroupElement> m_elements = new List<ElasticGroupElement> ();
    bool bShowFrame;
    Pen penFrame;
    string m_title;
    double coefTitleAlignment;          // [0, 1]   0 - Left, 1 - Right
    int m_titlesidespace;
    Font fontTitle;
    Color clrTitle;

What is not seen from figure 7 but what you can find in an instant in the working application is the movability of the title which can be moved between the left and the right sides of the frame. If you press the left button anywhere inside the group but not close to any inner element, then you can move the whole group around the screen. To provide these two movements (of the whole group and individually for a title), the cover consisting of two nodes is needed; both nodes are simple rectangles; the area under the title is called rcGap. In the user-driven applications, users have absolute control over all the features of the screen elements so it is possible at any moment (for example, through the context menu) to declare the title of the group either movable or fixed. In the second variant the node under the title is diminished to zero; this is one of the ways to make the title unmovable without changing the number of nodes.

C#
public override void DefineCover ()
{
    CoverNode [] nodes = new CoverNode [2];
    if (!string .IsNullOrEmpty (m_title) && bTitleMovable)
    {
        … …
        nodes [0] = new CoverNode (0, rcGap, Cursors .SizeWE);
    }
    else
    {
        nodes [0] = new CoverNode (0, rcFrame .Location, 0.0f);
    }
    Rectangle rc = Rectangle .FromLTRB (rcFrame .Left - 3, rcFrame .Top - 3,
                                    rcFrame .Right + 3, rcFrame .Bottom + 3);
    nodes [1] = new CoverNode (0, rc, Cursors .Hand);
    cover = new Cover (nodes);
    … …
    {
}

When the group is moved for (dx, dy) pixels, then each of its inner elements is shifted in exactly the same way and this is a standard synchronous movement of the complex object.

C#
public override void Move (int dx, int dy)
{
    rcFrame .X += dx;
    rcFrame .Y += dy;
    if (!string .IsNullOrEmpty (m_title))
    {
        rcGap .X += dx;
        rcGap .Y += dy;
    }
    foreach (ElasticGroupElement elem in m_elements)
    {
        elem .Move (dx, dy);
    }
}

There is no direct resizing of such group. Though there is a frame which looks like the border of the group, there are no additional nodes on this border, so there is no possibility for a user to move this border. It would be easy to organize the resizing in the same way as was done for regular polygons, circles, and rectangles with comments: two very close nodes with a small difference between them; the resulting narrow strip along the border is used for resizing.

Such direct resizing is not implemented in the ElasticGroup class because it would be against the main idea of this class. Even more: it would be against the main idea of user-driven applications. Suppose that I have added the resizing of the group by its border and we are looking at the placement of elements as seen at figure 7. What do you expect as a result if you press the line at the bottom of the group and push it down? Without thinking for even a second, I can announce several possibilities:

  • Only the height of the TextBox changes.
  • Heights of the inner elements change with the same coefficient.
  • Heights of the inner elements do not change but the space between them does.
  • Heights of the elements and the space between them changes with different coefficients.

There are no chances that all the readers will pick out one of the possibilities and reject all others. There is a high probability that some readers will prefer something else which is not listed above. There is a miserable chance that a designer of such program would code the mentioned variants and produce an instrument to select among them. There are no chances that all possible variants would be coded because the community of users can think out much more variants than any developer, even a genius one.

I mentioned a tiny chance that the developer would code several variants of resizing and give users a chance to select. It was possible several years ago when the variety of users’ requests was still considered. Throughout the last years, the dominant idea of adaptive interface squeezed to one general line – the dynamic layout. With the very high probability, you will see now only one decision based on the dynamic layout. Everyone has to like whatever the developer prefers. This is the main and absolutely wrong trend in design of the programs. I write all the time against the use of the dynamic layout. It is the easiest way for designers, but why the easiness of development became the main goal of any application? I don’t think that marching is the best way to explore the wonderful world around us. That is why I advocate for user-driven applications which give users the chance to do the things in the way each of them personally wants.

Text Box:  Fig.8	There is an infinitive number of variants to change the group; this is only one of them.

The ElasticGroup class perfectly implements and demonstrates the best ideas of user-driven applications. User wants to change the view of the group, so he can move and resize the inner elements. (In the current example, two small buttons are not resizable; this is very easy to change.) You can resize the inner elements and put them in any positions you want; there is an infinitive number of variants; figure 8 shows one of them. When you change anything inside the group, the frame is automatically adjusted to all these changes. You don’t need the standard resizing of the group; the resizing of the frame is the aftermath of the main and really requested changes of the inner elements.

I want to underline the differences in interaction between the “parent” and “children” in all the previously demonstrated complex objects (I can call them classical complex objects) and this group.

For the classical complex objects:

  • Forward movement of a “parent” causes the synchronous movement of its “children”.
  • Resizing of a “parent” usually causes the related “movement of its “children”.
  • Movement of the “children” does not affect the “parent” at all.

In the case of the ElasticGroup object.

  • Forward movement of a “parent” causes the synchronous movement of its “children”.
  • There is no direct resizing of the “parent”.
  • Movement of the “children” may affect the “parent”.

There are very significant differences, but as any other complex object, the ElasticGroup has to be registered in the mover’s queue by its IntoMover() method.

C#
new public void IntoMover (Mover mover, int iPos)
{
    if (Visible && VisibleAsMember)
    {
        if (iPos < 0 || mover .Count < iPos)
        {
            return;
        }
        mover .Insert (iPos, this);
        for (int i = m_elements .Count - 1; i >= 0; i--)
        {
            if (m_elements [i] .Visible  &&  m_elements [i] .VisibleAsMember)
            {
                m_elements [i] .IntoMover (mover, iPos);
            }
        }
    }
}

The ElasticGroup class allows to use recursion in design and any inner element can be the group itself. In this way, the objects can be very complex without the overwhelming complexity of the code because all the interactions between the group and its inner elements are automated. I want to underline that such automation is possible only for the controls and the controls with an additional text (graphical). If any inner element must be represented by a graphical object, then different technique of organizing the group with similar behaviour must be used and the resulting group belongs to the ArbitraryGroup class.

In this article, I only briefly introduce the ElasticGroup class and write about this class only as another representative of complex objects. This class plays a very important role in design of applications and in the book, I wrote about many interesting features of this class.

Conclusion

This is one more of my articles about the movability of the screen objects. The previous one (in three parts) was about the graphical primitives and useful techniques of turning very different simple objects into movable. In general, we deal more not with the independent elements but with different objects which may affect each other. The movability of each part is organized in the way which was described earlier but the interaction between the parts must be taken into consideration when different movements are organized. For the classical complex objects, the main ideas are the same regardless of the shape of the “parent” and only the parameters of relative positioning vary from one example to another. Groups of the new type belonging to the ElasticGroup class are also the complex objects but there are different rules for interaction of their parts and this causes the difference in the code. There are other very interesting features in the ElasticGroup class, but it is better to discuss them in another article.

License

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


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionMoveGraphLibrary Source Pin
kjward2-Feb-17 3:22
kjward2-Feb-17 3:22 
QuestionWorld of Moveable Objects Pin
RichV7011-Nov-15 2:46
RichV7011-Nov-15 2:46 
AnswerRe: World of Moveable Objects Pin
SergeyAndreyev11-Nov-15 5:09
SergeyAndreyev11-Nov-15 5:09 
QuestionRECT INFORMATION Pin
carlozito703-Mar-14 23:05
carlozito703-Mar-14 23:05 
AnswerRe: RECT INFORMATION Pin
carlozito704-Mar-14 22:50
carlozito704-Mar-14 22:50 
GeneralRe: RECT INFORMATION Pin
SergeyAndreyev5-Mar-14 18:55
SergeyAndreyev5-Mar-14 18:55 
GeneralRe: RECT INFORMATION Pin
carlozito705-Mar-14 23:31
carlozito705-Mar-14 23:31 
GeneralRe: RECT INFORMATION Pin
SergeyAndreyev6-Mar-14 7:12
SergeyAndreyev6-Mar-14 7:12 
Hi, Carlo!
A program to place furniture in some room is one of those applications where the movement of all objects is very useful. I was thinking from time to time about adding such example into my book but for this I need to look on other programs of such type and see the common features which are used in other programs of such type. I prefer to work on the instrument and don't want to rewrite every program in the world.
While you move your rectangles (tables) around the screen, the program can easily calculate minimal distance between rectangles. There is Auxi_Geometry .CornersOfRectangle() which returns an array of corners (four points) and there is Auxi_Geometry .DistancePolylines() which returns minimal distance between two polylines (each polyline is a broken line determined by an array of points). In this way you can see minimal distance between rectangles (tables). Certainly, you need to use some recalculation of pixels into real distance, but you have to do it also when you use rectangles instead of tables.
One remark to the code above.
You don't need to write Text = aa.ToString();
aa is already a string, so you can write textBox1.Text = aa;
Regards,
Sergey Andreyev
GeneralRe: RECT INFORMATION Pin
carlozito7013-Mar-14 0:26
carlozito7013-Mar-14 0:26 
GeneralRe: RECT INFORMATION Pin
SergeyAndreyev13-Mar-14 18:03
SergeyAndreyev13-Mar-14 18:03 
GeneralRe: RECT INFORMATION Pin
carlozito7013-Mar-14 21:21
carlozito7013-Mar-14 21:21 

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

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