Click here to Skip to main content
15,860,943 members
Articles / Programming Languages / C#
Article

The theory of moveable objects

Rate me:
Please Sign up or sign in to vote.
5.00/5 (126 votes)
24 Jan 2010CPOL97 min read 117.9K   3.2K   230   51
This article describes an algorithm by which an object of an arbitrary shape can be made moveable and resizable.

Abstract

User-driven applications belong to the new type of programs in which users get full control of what, when, and how must appear on the screen. Such programs can exist only if the screen view is organized not according to a predetermined scenario written by developers, but if any screen object can be moved, resized, and reconfigured by any user at any moment. This article describes an algorithm by which an object of an arbitrary shape can be made moveable and resizable. It also explains some rules of such design, and a technique which can be useful in many cases. Both the individual movements of objects and their synchronous movements are analysed. After discussing the individually moveable controls, different types of groups are analysed and the arbitrary grouping of controls is considered.

Contents

Introduction

Moving of screen objects is an extremely important thing, so the efforts in this direction were already made years ago. Readers of this article might be able to remember the programs in which they saw moveable objects. But the expression moving of screen objects can mean absolutely different things, so let's make the purpose of my research and the results, which are described here, absolutely clear.

When a screen object is moved regardless of the user's actions or as a result of his pressing on the keyboard, if the reaction on each click is totally predetermined at the stage of program development, then it is an animation. The moving which is absolutely predictable, and fixed in the code of the program by the developer, is not the purpose of this work.

There are also movements which are not predefined, but are not real movements, though they can look like movements as a result of a simple programmers' tricks (or knowledge, if you look at it from a slightly different angle). For example, in several programs (the well known Paint is one of them), you can select a rectangular area by pressing the mouse at one point and dragging the mouse across the screen without releasing; while the mouse is moving, the dashed line moves across the screen. Even with much slower computers than we have now, this trick is easy to implement by using the XOR operation for drawing; it is not the actual moving of an object.

By moving of screen objects, I mean the process when the user selects an object and moves it across the screen as he wishes. The main difference from the previously mentioned cases is that such moving can't be predicted by the developer, because it is decided by the user only at the time when the application is running. The real moving of objects was introduced in some of the older programs, but in each case, it was especially done for one or another particular class of objects. My opinion is that for a specific class of objects, everything can be done; it's only the question of time and efforts (brains are also among the needed ingredients). But the existence of those rare samples of really moveable objects strongly underlines a couple of very important things:

  1. We really need the ability to move screen objects, because it is the most natural thing which we do with all objects around us in our normal (not virtual) life.
  2. The problem of moving screen objects is not trivial; that's why it was solved so rarely and only for a limited set of objects.

Even the programs which introduced such classes of moveable objects did it for some special tasks. There was not a single program in which everything was moveable and resizable. I have work on the development of very sophisticated applications for many years, and I have never heard of a single application in which everything would be moveable. I especially boldened not only the important, but the mandatory (absolutely required) feature of user-driven applications.

This and other features of such applications will be the subject of the second article (strongly related to this one), but the crucial point in development of such applications is the algorithm that allows to turn any screen object into moveable / resizable.

What are the basic requirements of such an algorithm?

First, it must be easy. I don't know anything about the complexity of algorithms used by other people before, but I know that when I did similar things (moving some objects around the screen) years ago, those algorithms were not simple at all. Those algorithms were understandable by the author (me!), but I wasn't even thinking about spreading them around. I assume that the algorithms on which other samples were based have the same level of complexity. The best procedure of turning objects into moveable was once described by Charles Perrault (Cendrillon, 1697); I hope to design an algorithm which is not far behind that one. With that famous example, we know that it looked simple, when it was already in use, but there is no information on how many years of preliminary work it required. The same thing with my algorithm: you'll see further on that it is extremely easy in use, but it took years of thinking and work to design it to such a level.

Second, the algorithm must be applicable to all shapes of objects. One of the things that people immediately begin to think about when they need to move some objects across the screen is the use of some bitmap operations. Those operations are applicable to rectangular areas, and it's not an algorithm, but a trick with serious limitations. A good algorithm must work with an arbitrary shape of objects. While showing the samples (there will be a lot of them) further on in the article, I'll demonstrate some cases which are simple in design; others will need a bit of extra thoughts, but there are no restrictions on the form of the moveable objects. An object can even consist of separate parts; still the algorithm works without any problems.

Third, the algorithm should not require any visual changes to the objects. Screen objects are designed according to the ideas of their developers; the algorithm must not interfere with this process. It must be enough for users to know that the objects are moveable / resizable. Though, if the designers want, they can add some indication of these features. The algorithm must work regardless of these visual indications.

Fourth, it should not be such that only the designers would be able to apply these features (moveable and resizable) to the objects. It should be possible for users to change them according to the purpose of the application. For example, if the application is used for assembling complex objects of some parts, then the user should be able to easily move and change all these parts, but when he decides that the result is satisfactory, then he should be able to declare the object an unchangeable (but, for example, still moveable) in order to prevent any accidental changes.

Fifth, there should not be any restrictions in the way the algorithm can be used.

I also have a few suggestions. I have tried to make all of them reasonable, but none of them are mandatory. Here are these suggestions:

  • From my point of view, the most natural way of moving / resizing screen objects is to use the mouse. It's the most accurate way of object selection, placing, and reconfiguring regardless of shape, size, or position.
  • To move an object, you simply press it at any point and drag to another place on the screen.
  • To resize an object, you press it on the border and either resize it or reconfigure. It's a challenge for the designer to make the resizing absolutely natural and according to the users' expectations. But this is one of the things where the designer's experience and understanding of the situation means a lot.
  • In reality this is a difference between a good design and a bad one. Though resizing by the border is absolutely natural, there can be nuances even in simple cases. For example, resizing of a circle means a symmetrical change in all the directions, but what about a rectangle? Is there going to be a symmetrical change on the opposite side, or should the rectangle be resized only in the direction of the moved border? This can depend on the application and on the objects, but this sample shows that even the simplest cases can be not so obvious as they might look.

  • In all my applications, the left button is used to start the forward movement or resizing, the right button is used to start the rotation. This is not fixed in any of the proposed classes or anywhere else. It's for anyone to decide, though whenever I see the implementation of the forward movement (drag-and-drop) in any other application, it is always done by the left button. Windows on the upper level is the best and most obvious example of using the left mouse for moving and resizing. If the left button is used for forward movements, resizing, and reconfiguring, then the use of the right button for rotation is the most natural thing. (There is only one class in my samples – a class of rotatable texts, for which the rotation with the right button was put into code. This was done only to exclude the duplication of the same several lines of code in all the places, where I use this class. Anyway, the class is primitive and can be easily reproduced without such a limitation.)
  • The exclusive use of mouse without any additions is enough for all the purposes even in the most complex classes. Because of this, all my samples now use only the MouseDown, MouseMove, and MouseUp events. It's again not a requirement that is to be strictly coded somewhere inside, but only my suggestions.

The samples which I'll use for explanation are made as simple as possible. A majority of the samples for this article will be using a set of simple geometrical figures, but this is done only for the purpose of better explanation. Don't think that the use of the proposed algorithm is limited to this set of figures. The inspiration for this work came from a huge demand for such moveable / resizable graphics for the area of complicated scientific applications, and this is where the results are used now all the time. The more complicated the task is, the more extraordinary the results are of using this algorithm. It can be the area of the engineering / scientific applications, or it can be for some financial analysis. Figure 1 shows the view from one of such demo applications.

Image 1

Fig. 1: Different types of moveable / resizable plotting

In this form, any number of plots of different types can be added to the screen by the user at any moment. All of them can be moved, resized, or rotated. Each piece of textual information can be moved and rotated individually. All the visualization parameters of the objects can be tuned individually, or spread on the group of "siblings". I don't want to go into all the details and features of such applications just now; I'll write about it in the second article, but the main point is that exactly the same algorithm that will be described on the simple samples is applicable to the most complicated classes of objects. And as you'll see, even the complexity of using the algorithm doesn't go up in stepping from the primitive figures into the world of scientific plotting. I would say more: the simplicity is exactly the same.

Throughout this article, I'll be using the samples only from the TheoryOfMoveableObjects program (figure 1 is an exception). This application was especially designed to accompany this article. The application, in the form of a ZIP file, is available at www.SourceForge.net in the MoveableGraphics project (names of the projects are case sensitive there!). The ZIP file contains the whole application's project, written in C# and ready to be used in Visual Studio. If you only want to run this application to see all the things I am going to write about, then you need only two files from that ZIP file: TheoryOfMoveableObjects.exe and MoveGraphLibrary.dll.

A short, but important remark about this program: there is not a single unmoveable object in this application.

I constantly work with the C# language, so all the programs I am going to mention here are written in this language; the code samples will be in C#, and I'll also use some terms from this language. But the algorithm and designed classes are not linked to C# – they can be easily developed with other languages, I simply prefer to use C#.

This paper consists of several parts:

  • Part 1 introduces the terms and the algorithm.
  • Part 2 describes the moving and resizing of simple figures, introduces the technique of resizing objects with straight and curved borders, and looks into the world of rotation.
  • Part 3 explores more complicated objects in which the parts can move individually, but all of them can be involved in synchronous movements.
  • Part 4 deals with controls and groups of controls, which are used as the blocks in forms design; also demonstrates the sample of an arbitrary controls' grouping, which is decided by the users.
  • Conclusion contains the summary, but also works as a link to the second article.

Algorithm

My idea of making graphical objects moveable and resizable is based on covering an object with a set of sensitive areas which are used either to start the moving of the whole object or some part of it. These areas are called nodes, and belong to the CoverNode class. The nodes are usually invisible, but their movements are transferred into real movement of the screen object with which they are associated. Four different types of movements are considered.

  • If the size of an object is not changed and an object is moved without any change of relative positions of its parts and without rotation, then it is a forward movement.
  • If all the parts of an object are increased or decreased with the same coefficient, but the general shape is not changed, then it is a resizing.
  • If a movement of any part changes the relative positions of the parts and the general shape, then it is reconfiguring.
  • If the general shape is not changed, but the whole object is turned from the original position, then it is a rotation.

The first three movements (forward movement of the whole object, resizing, or reconfiguring) are started with the left button press; the choice between these movements is decided by the starting point and the node to which this point belongs. There are special areas to start reconfiguring (it depends on the object) and resizing (usually, it is the border); the forward movement of a whole object is usually started at any inner point of an object. The start of rotation is distinguished from the other three movements by using a different button (the right one). The rotation of an object usually starts at any point.

There are no limits on the size of the nodes, but their possible shapes are described by this enumeration.

C#
enum NodeShape { Circle, Polygon, Strip };

Any circular node is defined by its central point and radius.

A polygon node is described by an array of points representing its vertices. Polygon must be convex!

A strip node is defined by two points and radius. The strip node can be also looked at as a rectangle with two additional semicircles on the opposite sides. Those two points are the middles of those sides and the centers of the semicircles; the diameter of the semicircles is equal to the width of the strip.

In addition to the location, size, and shape, each node has a parameter which determines the shape of the mouse cursor when the mouse is moved across this node. Usually, this is the only prompt for the users that they can grab an object under the mouse and move it one way or another, so it's better to make the cursor's shape informative and not confusing. Throughout all the further samples, you'll find that if a rectangular object can be resized in the horizontal or vertical direction, then Cursors.SizeWE and Cursors.SizeNS are used; the resizing possibilities for objects of an arbitrary form are signaled by Cursors.Hand; the possible movement of the whole object is signaled by Cursors.SizeAll.

Any node has one more parameter; originally, it was used for declaring the possibility of the node's individual movement. In reality, these movements are defined by the code of the MoveNode() method, which is described a bit later. But this parameter turned out to be extremely valuable in the cases of objects with a nontrivial shape, and in situations when the user wants to change the object's behaviour. Some of these possibilities will be described further on in this article; others will be mentioned in the second article, while talking about very complicated objects.

An array of nodes covering an object is called a cover (class Cover). A cover must include at least one node. There are no other restrictions on the number of nodes, their types, or sizes. The relative positions of the nodes are not regulated at all. If you want an object to be moveable by any point, then the whole object's area must be covered with the nodes; any gaps in such situations will result in the appearance of places by which an object can't be moved. Overlapping of nodes is not a problem at all, but their order can be important, if they are used for different movements, as the decision is made by analyzing the nodes according to their order in the cover. Usually, the number of nodes is small even for very complicated objects, but there are cases when a significant number of nodes is needed. This often happens for objects with curved borders; covers of such type are called N-node covers.

Technically, I saw two ways to add moveable / resizable features to objects: either to use an interface or an abstract class. After trying both ways, I decided upon an abstract class. Any graphical object that you want to turn into moveable and resizable must be derived from the abstract class GraphicalObject, and three crucial methods must be overridden:

C#
public abstract class GraphicalObject
{
    public abstract void DefineCover ();
    public abstract void Move (int dx, int dy);
    public abstract bool MoveNode (int i, int dx, int dy, Point ptMouse, 
                                   MouseButtons catcher);

For graphical objects, the cover is organized in the DefineCover() method. Controls are wrapped by a graphical object of the FramedControl class; for controls, none of these methods are needed, as everything is automated.

Each node in the cover has its personal identification number (from the range between 0 and the number of nodes in the cover); this number is often used for node identification, while the decision about movement is made in the MoveNode() method.

Note: Though the whole idea of moving and resizing objects is based on cover, the DefineCover() method is usually the only place where you have to think about the cover! The rule is: organize the cover and forget about it. There is one exception to this rule, which is mentioned several lines below in the explanation of the MoveNode() method.

Move (dx, dy) is the method for forward movement of the whole object for a number of pixels, passed as the parameters.

The drawing of any graphical object with any level of complexity is usually based on one or a few very simple elements (Point and Rectangle) and some additional parameters (sizes). While moving the whole object, the sizes are not changed, so only the positions of these basic elements have to be changed in this method.

MoveNode (i, dx, dy, ptMouse, catcher) is the method for individual moving of nodes. The method returns a boolean value indicating whether the required movement is allowed; in the case of forward movement, the true value must be returned if any of the proposed movements along the X or Y axes is allowed. If the movement of one node results in relocation of other nodes, it is natural to call DefineCover() from inside this method, and then it doesn't matter what value is returned from MoveNode(). The call for DefineCover() from inside the MoveNode() method may happen even for forward movement, when the movement of one node affects the relocation of other nodes, and it usually happens with rotation, when all the nodes must be relocated. This is the exception to the previously mentioned rule, that cover is not even thought about anywhere outside the method of its definition.

Though the call to the DefineCover() method from inside the MoveNode() method looks like a very good idea, it has some limitations of its own. In some cases, the movement of a node starts the resizing of an object, which in turn demands the change of the number of nodes in the cover (this depends on the Cover design); in such situations, the call to the DefineCover() method from inside the MoveNode() method may cause a problem, because in the new cover, the node with the same number (the one, which was originally pressed and caught) may belong to the node with different rules for moving. To avoid these problems, in such cases, the cover is not changed throughout the resizing, but only when an object is released; the explanation of such a case with more details is in the section N-node covers.

The MoveNode() method has five parameters:

  • int i: Identification number of the node – the same number that was used in the DefineCover() method on the design of this node.
  • int dx: Movement (in pixels) along the horizontal scale; positive number for moving from left to right; use this parameter if you write code for forward movement.
  • int dy: Movement (in pixels) along the vertical scale; positive number for moving from top to bottom; use this parameter if you write code for forward movement.
  • Point ptMouse: The mouse cursor position. For calculation of forward movement, I simply ignore this parameter and use the previous pair; for calculations of rotation, I ignore the previous pair and use only this mouse position; I found it much more reliable for organizing rotations.
  • MouseButtons catcher: Informs you which mouse button was used to grab the object. If, by the logic of an application, an object can be grabbed by any mouse button, then ignore this parameter; if the move is allowed by one button only, then use this parameter; in case the node can be involved in both types of movement (forward movement and rotation), this is a very useful parameter to distinguish between them.

The MoveNode() method can be the longest of all three, because it must include the code for moving each node, but it is always uncomplicated, as more often than not, the code for different nodes is partly the same. There are interesting situations when the MoveNode() method is very short, though the number of nodes is big. For example, the N-node covers often consist of a significant number of nodes. For such covers, the MoveNode() method is usually very short, because the behaviour of all those nodes is identical.

Even if an object is derived from the GraphicalObject class, thus receiving an ability to become moveable / resizable, it can be really moved and resized only if it is registered with the mover (of the Mover class). Mover supervises the whole moving / resizing process and, in addition, can provide a lot of information associated with it. Regardless of the number of different objects involved in moving / resizing, usually there is a single mover per form (dialog). However, there are situations when it is better (easier and more reliable) to have several movers in the form; for example, when you have moveable objects on different panels.

Only three mouse events - MouseDown, MouseMove, and MouseUp - are used for the whole moving / resizing process, and these are the methods where the mover works. To organize the moving / resizing of objects in the form, several steps must be made.

Declare and initialize a Mover object:

C#
Mover mover;
mover = new Mover (this);

Register with the mover all the objects that you want to move and / or resize.

C#
mover.Add (…);
mover.Insert (…);

Add the code for the three mouse events:

C#
private void OnMouseDown (object sender, MouseEventArgs e)
{
    mover.Catch (e.Location, e.Button);
}
private void OnMouseUp (object sender, MouseEventArgs e)
{
    mover.Release ();
}
private void OnMouseMove (object sender, MouseEventArgs e)
{
    if (mover.Move (e .Location))
    {
        Invalidate ();
    }
}

This is not a simplification; this is the code which you can see in nearly every form of the demo application, regardless of the number or complexity of the objects involved in moving / resizing. The three calls to the three mover methods (one call per each mouse event) are the only needed lines of code! Further on, you'll see some additional lines of code inside these methods, but they are used only for some auxiliary things, like changing the order of objects on the screen, or calling different context menus on different objects.

Those three mouse events – MouseDown, MouseMove, and MouseUp – are the standard and, often, the only places where the mover is used.

There are two other events – MouseDoubleClick and Paint - where the mover can be mentioned and used, but this happens only on special occasions.

I often use the MouseDoubleClick event for opening the tuning forms of complex objects, for example, scales and different plotting areas (as seen in figure 1). Without the mover at hand (before implementing the moving / resizing of objects), I had to decide about the clicked object by a comparison of the mouse location and the boundaries of the objects. The mover can do this job much better, as it informs not only about the occurrence of any catch, but also about the class of the object which was caught. And, as any object derived from GraphicalObject gets a unique identification number, with this, I get an easy access to the object itself.

C#
private void OnMouseDoubleClick (object sender, MouseEventArgs e)
{
    if (mover.Catch (e.Location, MouseButtons.Left)) {
        … …
       if (mover [iInMover].Source is MSPlot)
       {

Inside the Paint event, the mover can be mentioned on those rare occasions when covers have to be visualized, but it's really a rare thing, as good samples of design are those which do not require such visualization.

C#
private void OnPaint (object sender, PaintEventArgs e)
{
    mover.DrawCovers(e .Graphics);

Safe and unsafe moving and resizing

The design of applications on the basis of moveable elements opens an opportunity for accidental disappearance of elements from the view. This never happens in ordinary applications with immoveable elements, but now, users can simply move any element out of view across the border, release it there, and then what? If in the resizable form, an object is moved across the right or bottom border, then it's not a problem, as the form can be enlarged and the object returned back into play. Such temporary relocations of currently unneeded objects is often used in user-driven applications. But if an object is moved anywhere across the upper or left border of the form and dropped there, then there is no way to return it back into view by resizing the form. The mover can take care of this situation and prevent such disappearance of objects, but only if you want it to overlook this process. For this purpose, the mover has to be initialized with an additional parameter – the form itself. (If the mover works on a panel, then this panel is used as the parameter.)

C#
mover = new Mover (this);

You can find throughout the code of the TheoryOfMoveableObjects application that such type of mover initialization is used in all the forms. In such a case, when the mover grabs any element for moving or resizing, the clipping will be organized inside the borders of the client area. The level of clipping can be changed with one of the Mover's properties; three levels of clipping are implemented:

C#
public enum Clipping { Visual, Safe, Unsafe };
  • Visual – elements can be moved only inside the client area.
  • Safe – elements can be moved from view only across the right and bottom borders of the form.
  • Unsafe – elements can be moved from view across any border.

By default, the Visual level is used, but can be changed at any moment. No clipping is used if the mover is initialized without that additional parameter.

Unlimited shrinking of an object can lead to its total disappearance. To avoid this, minimum sizes must be declared for any class of resizable object; these restrictions are used in the MoveNode() method.

Technical note. To avoid screen flicker, don't forget to switch on double-buffering in any form where you use the moving / resizing algorithm. It has nothing to do with the described technique, but it is a nice feature from Visual Studio.

The simplest case – single node covers

Covers may consist of an arbitrary number of nodes. The minimum number of nodes in a cover is one, so let's look at the simplest case of objects, where the cover consists of a single node. As there are three possibilities of node shapes, we'll have a representative of each type (figure 2). In addition to the three colored figures, Form_Nodes.cs (menu position Nodes and covers – Node shapes) includes two more objects. The button Image 2 can be moved by its border. I'll write about the moveability of the individual controls and the groups of controls further on in a special chapter. By clicking this button, it is possible to switch on/off the visualization of covers. The text which you can see in this form belongs to the TextM class; such objects can be moved by any inner point. The cover of such objects also consists of a single node. All the informative text in this application is organized this way.

Image 3

Fig. 2: Three shapes of nodes

Each node of any cover can be used either to start the moving of the whole object, or resizing (reconfiguring). When a cover consists of a set of nodes (all further samples in this article will be of such a type), then some of the nodes are used for resizing, others – for moving. Because the cover of each colored figure in this form consists of exactly one node, and these objects are moveable, those single nodes are used to start the moving. Thus, all three objects are not resizable. Further on, I'll demonstrate resizable figures of the same shapes, but each of their covers will include a set of nodes. As the colored figures in Form_Nodes.cs can be only moved and nothing else, I included into the names of their classes the word primitive.

The sample with these primitive objects, which can be only moved, demonstrates a lot of things that are important and used for all the types of moving / resizing even for much more complicated objects. Let's look at some important details.

Default and non-default parameters

The cover for any object is organized as a set of nodes. Nodes can be of three different shapes, so there are three big groups of CoverNode constructors. The parameters which must be always declared on the node's construction include the identification number, position, and sizes. Other parameters often get the default values, but can be declared either during the construction or later. Of the three types of nodes, polygons are often used to move the objects around the screen, so their default cursor is Cursors.SizeAll; nodes of the two other forms are usually used for resizing or reconfiguring, and their default cursor is Cursors.Hand. Because all three types of nodes are used here for moving the objects, I decided to keep their cursors consistent, and changed the default parameters in two classes.

C#
public override void DefineCover ()
{
    CoverNode node = new CoverNode (0, center, radius, Cursors .SizeAll);
    cover = new Cover (new CoverNode [] { node });

Deciding on the buttons to move an object

The MoveNode() method allows to specify by which button each node can be moved. For two of the three primitive classes, the possibility of moving only with the left button is declared.

C#
public override bool MoveNode (int i, int dx, int dy, …, MouseButtons catcher)
{
    bool bRet = false;

    if (catcher == MouseButtons .Left)
    {
        Move (dx, dy);

For the PrimitiveStrip class, this limitation is not mentioned; objects of this class can be moved by any button.

C#
public override bool MoveNode (int i, int dx, int dy, …, MouseButtons catcher)
{
    Move (dx, dy);

The order of objects

In order to become moveable, all objects must be registered in the mover's queue. When objects are moved around the screen, they can overlap. When two or more objects share the same part of the screen, and you want to pickup some element at the point of overlapping, your expectation would be that the object on top has to be caught. The mover doesn't know anything about the drawing of objects and their appearance at the screen; the mover makes the decision about the object to catch only according to their order in its queue, so it's the developer's responsibility to draw the objects and to place them into the mover's queue in the appropriate order. The elements, viewed on top of others, must precede them in the mover's queue.

There is a strict rule enforced by the system: all the controls are always shown atop all the graphical objects. Thus, we receive rule 1.

Rule 1. All the controls must precede all graphical objects in the mover's queue.

There are four graphical objects and one control in the form; this control must be placed at the head of the queue. Here is the sequence of calls to register the objects in Form_Nodes.cs.

C#
mover.Add (text);
mover.Add (circle);
mover.Add (rect);
mover.Add (strip);
mover.Insert (0, btnCovers);

The second rule links the order of graphical objects in the mover's queue with the order of their drawing, and is based on the fact that graphical objects are painted from the bottom layer to the top.

Rule 2. The order of drawing objects must be opposite to their order in the mover's queue.

In other words, the drawing of the graphical objects must go from the tail of the mover's queue to the head. In such a way, the graphical object shown on top of others will always precede them in the mover's queue, and always the top one will be caught by the mover. Exactly what is expected.

C#
private void OnPaint (object sender, PaintEventArgs e)
{
    … …

    strip .Draw (grfx);
    rect .Draw (grfx);
    circle .Draw (grfx);
    text .Draw (grfx);

Into the realm of covers

The previous sample with the primitive figures demonstrated only the moving of those figures and nothing else. Two obvious questions arise after that demonstration:

  1. How to resize the objects?
  2. How to rotate the objects?

The resizing of an object is usually done by moving its border. Either an object can be resized in any direction, or the resizing is limited, depends on the purpose of using this object in an application. In any way, the needed part(s) of the border must be covered with a node or several nodes that are responsible for resizing. As a rule, these nodes are relatively narrow; so the resizing can be started "by the border". There is no requirement for any node to be positioned only and strictly inside the object's area. On the contrary, users expect that they can start the resizing in the vicinity of the visual border (not more than several pixels aside) on any side of the border line, so the border nodes often represent the strip (invisible!), which has a real (visual) border line of an object as its median.

A lot of objects have a border consisting of a set of straight lines; the standard technique is to cover each segment of such a border either with a narrow rectangular node or a strip node. Let's begin the exploration of resizing with a simple rectangle.

Resizing of rectangles

On opening Form_Rectangles.cs (menu position Nodes and covers – Rectangles), you see four colored rectangles which demonstrate four different types of resizing. Visually, the differences become obvious only on switching on the cover visualization (figure 3); without those covers in view, the rectangles do not give you any visual tip on how they can be changed. Though, there is another prompt – the changing of the mouse cursor, when the mouse crosses any area of possible resizing or moving an object.

Image 4

Fig. 3: Resizing of rectangles

The rectangular shape is so popular among the screen objects that a cover for such an object can be constructed by using one of the standard Cover constructors.

C#
public override void DefineCover ()
{
   cover = new Cover (rc, resize, radius, halfstrip);
}

The resize parameter in this constructor belongs to the Resizing enumeration:

C#
enum Resizing { None, NS, WE, Any };

This parameter totally defines the number of nodes in the cover, their types, and order.

  • For a non-resizable rectangle (the Blue one on the picture), there is a single node covering the whole rectangle.
  • If a rectangle is resizable only in one direction (Yellow and Green rectangles from the picture), then the two appropriate opposite sides are covered with the two narrow rectangles, and then comes the same big node.
  • For a fully resizable rectangle (the Cyan one), there are four small circular nodes in the corners, then the rectangular nodes on the borders, and then the big one, covering the whole area.

The nodes overlap with each other. At the point of their overlapping, the resizing to be started is decided by the first of those nodes in the cover's array. It's more difficult to find the small node with the mouse than the big one (remember that usually the covers are not shown), so the smaller nodes are usually placed first in this array. That's why, for the fully resizable rectangle, I decided about such an order of nodes: circular nodes from the corners, then the covering of the borders, and then the big node for the whole area.

There are two controls in this form which allow to change the sizes of the nodes and see how the easiness of the resizing depends on those values. These controls, with the comments, can be easily moved around the screen by grabbing them anywhere near those controls or at any point of their comments. This is an object of the LinkedRectangles class – one of the classes widely used for the design of user-driven applications. There will be much more on this and other similar classes further on.

Seven moveable objects in Form_Rectangles.cs are initially included into the mover's queue in such an order:

  1. The button to switch the visualization of covers on/off.
  2. The LinkedRectangles object to change the parameters of the nodes. Both controls with their comments can move only synchronously and never change their relative positions, so it is a single object for the mover.
  3. Information (a TextM object).
  4. Four rectangles (objects of the RectangleStandard class) in such an order: blue, yellow, green, and cyan.

The painting of the graphical objects is organized in the opposite order to their positions in the mover's queue; this is according with the rules declared in the previous chapter. But there is no direct mentioning of those four rectangles inside the OnPaint() method of this form. Instead, I decided to demonstrate how the object to be painted is obtained with one of the mover's properties.

C#
void OnPaint(object sender, PaintEventArgs e)
{
    GraphicalObject grobj;

    for (int i = mover.Count - 1; i >= 0; i--)
    {
        grobj = mover[i].Source;

        if (grobj is RectangleStandard)
        {
            (grobj as RectangleStandard).Draw(grfx);
        }

This was done because the order of rectangles can be changed by the left click on any of them.

C#
void OnMouseUp(object sender, MouseEventArgs e)
{

    if (mover.Release ())
    {
        if (e.Button == MouseButtons.Left  &&
            Auxi_Geometry.Distance(ptMouse_Down, e.Location) <= 3  &&
            mover[mover.WasCaughtObject].Source is RectangleStandard)
        {
            PopupRectangle ();

        }

In applications consisting of moveable/resizable objects, those objects often overlap, so at any moment, the needed object from underneath is brought to the top by a simple click. When you click an object with the left button, there is always a question of your intention: was it an ordinary move of the object, or did you decide to bring it on top? In all my applications, the decision between these two cases is based on the distance between the two points where the mouse was pressed and released. If the distance is really small, then it is considered not a move of an object, but the intention to bring this object on top. On clicking the right button, a similar decision is made between the rotation of an object and calling the context menu.

To bring the clicked rectangle on top, the order of rectangles in the mover's queue is changed, and they are repainted according to the new order.

C#
private void PopupRectangle()
{
    int jCur = mover.WasCaughtObject;

    while (jCur > 0  &&  mover[jCur - 1].Source is RectangleStandard)
    {
        mover.Reverse (jCur - 1, 2);
        jCur--;
    }
    Invalidate();

Resizing of polygons and a bit of reconfiguring

The resizing of rectangles doesn't look like a very complicated problem. Let's make one more step, now into the county of polygons, and see what is different with those figures that can be much more interesting in view. Form_Polygons.cs (menu position Nodes and covers – Polygons) allows to analyse the behaviour of four different classes. All these classes include in their names the abbreviation RsRt, which means Resize-and-Rotate. As their names indicate, all these objects can be involved in rotation, but the time for it will come later; no rotation is demonstrated in this form.

  • The first class – RectangleRsRt – still represents the rectangles. This class is similar to what was already shown in the previous sample with a fully resizable rectangle.
  • The second class – RegularPolygonRsRT – represents the regular polygons.
  • The third class – PerforatedPolygonRsRt – is also a regular polygon, but each object has a hole inside; the hole's border has the same form as the outer edge of the polygon.
  • The ChatoyantPolygonRsRt class represents polygons that can be not only resized, but reconfigured also. Such an object has a central point and a set of apexes; apexes are linked into an infinite loop. An object is composed as a set of triangles; each triangle is based on the central point and two consecutive apexes. All apexes and the central point can be moved individually; reconfiguring of the polygon is organized in such a way. Moving of any connection between two consecutive apexes starts the resizing of the whole object. If you try to move any apex or side of such a polygon, you'll understand all these things in a shorter time than it takes for reading these several lines. I often initialize an object of the ChatoyantPolygonRsRt class in the form of a regular polygon, because the calculation of the apexes in such a case is easy; then, such a polygon can be transformed with the mouse into an unpredictable shape. These objects are also associated with an array of colors, while the objects of the three previous classes are of a uniform colour.

Figure 4 can give you some idea of what can be done with the polygons in this form. As always, there are no immoveable elements in this form, so the two individual buttons, information text, or those three groups that can be seen at the left, all of them can be moved around the screen.

Image 5

Fig. 4: Form_Polygons.cs

A single button is enough to add the new RectangleRsRt object to the collection in the form, but the other three classes need some additional parameters, so the objects of these classes are added with the help of those three groups. The groups belong to the Group class – another class for synchronous moving of several objects; this class will be discussed a bit later.

We only made the second step into the realm of covers, but we have already received a nontrivial collection of moveable objects. Their main features are:

  • Objects of different classes are involved in movements. It is obvious that this number of classes is not limited; any other class can be added.
  • There are no restrictions on the number of moveable objects in the form. This number is not predetermined by the developer of the program, but can be changed at any moment by the user. Objects are added and deleted at any moment.
  • Sizes and locations of the objects are not predetermined. These things are decided by the users only.
  • The whole system of objects can be considered as a multilevel space, with each object occupying its own level. The size and position of any object can be changed without any influence on other inhabitants of this world. (There is a technique to prevent the overlapping of objects, if such a limitation is needed. I don't want to explain this mechanism in this paper, but it is explained in Moveable_Resizable_Objects.doc, which is available at www.sourceforge.net.) Any object can be easily (one click) brought atop all others; with the context menu, the level of any object can be changed up or down, thus changing the appearance of objects on the screen.

Though we are only dealing here with samples of purely geometrical objects, the mentioned set of features is essential to all user-driven applications. Further on, you'll see that exactly the same features are observed in all the samples from now on; the second article will demonstrate the same features in more complicated scientific and financial applications. If you combine these features together, then you'll get the main design principle of all user-driven programs.

A user-driven application does not determine the rules and limitations of dealing with designed screen objects. This application is an instrument with which the user can bring up to the screen any number of needed objects and deal with them in any way he decides. The application must provide an easy way to add, delete, or modify all those objects in order to provide the most efficient way of dealing with them for everyone.

Now, let's look at a way of implementing these features in the case of polygons.

Standard rules of cover design

The cover of any object must be designed in such a way that an object can be moved by any inner point and resized by any border point. The inner area of the objects from this form is always covered by a set of polygon nodes. The number of apexes in those polygons can vary, but all the polygons must be convex at any moment. In the PerforatedPolygonRsRt class, the division of any object into several polygons is shown by drawing the edges between them (see figure 4). The ChatoyantPolygonRsRt objects look like regular polygons only at the time of design; later on, they can be quickly turned into non-convex polygons, so from the beginning, their cover includes a set of triangles, which are later transformed, but always into other triangles, so each of these pieces continues to be a convex polygon.

The border of any object is covered by a set of strips connecting the neighbouring apexes. This technique is used for all types of objects, except those that have curved borders. For them, the special N-node covers are used; these covers are discussed a bit later.

The strips on the borders are used for resizing (scaling) of objects. If an object needs reconfiguring, then it is always done by changing the relative positions of its parts. To achieve this, the parts that can change their positions in relation to others are covered by the individually moveable nodes. If reconfiguring has to be started by some obvious point of an object (for example, an apex), then this point is covered by a small node. The small circular nodes are often used for such purposes, but the nodes for reconfiguring can be of any shape and any size. Of the mentioned four classes, only the objects of the ChatoyantPolygonRsRt class can be reconfigured; all their apexes and the central point are covered with such circular nodes.

Here is the implementation of these rules in the design of a cover for the RegularPolygonRsRT class.

C#
public override void DefineCover ()
{
    CoverNode [] nodes;

    if (bResize)
    {
        // first the strips along the sides;
        // the last one is the convex polygon
        nodes = new CoverNode [nApexes + 1];

        PointF [] pts = Apexes;

        for (int i = 0; i < nApexes; i++)
        {
            nodes [i] = new CoverNode (i, pts [i], 
                                       pts [(i + 1) % nApexes], 5);
        }

        nodes [nApexes] = new CoverNode (nApexes, Apexes);
    }
    else
    {
        nodes = new CoverNode [] { new CoverNode (0, Apexes) };
    }
    cover = new Cover (nodes);

Pay attention to two things:

  • The same object can be turned from resizable to non-resizable and vice versa at any moment, while an application is running. For example, when a user needs to change the size of the object and then fix it in order to prevent further accidental change. This can be done, for example, via the context menu. (I am using this technique in different programs.) On such a change, only the cover of the object is changed, and even its appearance on the screen is not, so the application continues its run without problems. The cover for an identical, but not resizable, object is much simpler, as the whole set of nodes to cover the border is not needed.
  • There is no indication of possible rotation in this piece of code. The cover does not depend on the object that is going to be rotated. All the objects in this form are not involved in rotation, but will be rotated in further samples; this will not demand any changes in their covers. The possibility of rotation is mentioned in the MoveNode() method of the class, because the change in rotation status can be decided by the user exactly in the same easy way, as the possibility of resizing.

Identification of objects

The moving, resizing, and modifying of objects or changing their order depends on objects identification. At any moment, when an object is selected for any of these operations, there must be an absolutely reliable identification of what really is supposed to move, change, or disappear. The first level of identification is provided with the unique ID that each moveable object receives at the moment of construction. So, the first code line in the constructor for any of these objects looks identical, and provides the new object with a unique ID.

C#
public RegularPolygonRsRt (PointF ptC, …)
{
  id = Auxi_Common.UniqueID;

In complex objects, their parts can often be involved both in synchronous movements with other parts (the movement of the whole object) and in individual movements. This means that not only is the "parent" object is registered in the mover's queue, but all these parts receive their personal places in the same queue. To decipher the whole chain of relations between the linked objects, even if some smaller part is clicked, those parts have to contain the ID of their parent; one level up is enough, as in the same way you can track the relations from level to level.

For example, an object of the BarChart class, which can be seen at figure 1, consists of the main plotting area with a couple of scales; each scale can be linked with an arbitrary number of comments. Each comment, which can be moved and rotated individually, receives its personal identification number. When a new comment is added either to a scale or to a main area, it receives also the "parent" ID (either from the scale or from the plotting area). Each scale has its personal ID, but also keeps the ID of the plotting area with which it is associated.

But this is one form of identification. Another type of information is provided by the mover; there is an overwhelming amount of information. Whenever an object is caught by the mover, you can get the information about the type of this object and, for example, start the needed resizing.

C#
private void OnMouseDown (object sender, MouseEventArgs e)
{
    ptMouse_Down = e .Location;

    if (mover .Catch (e .Location, e .Button))
    {
        if (e .Button == MouseButtons .Left) 
        {
            StartResizing (e .Location);

The mover's information is used for the moving of objects:

C#
private void OnMouseMove (object sender, MouseEventArgs e)
{
  if (mover .Move (e .Location))
  {
    Invalidate ();
    ControlsCausedUpdate ();

The mover's information is very valuable even after an object is released, because by analyzing it, the next action is decided.

C#
private void OnMouseUp (object sender, MouseEventArgs e)
{
    int iWasCaught;
    if (mover .Release (out iWasCaught))
    {
        GraphicalObject grobj = mover [iWasCaught] .Source;
        double dist = Auxi_Geometry .Distance (ptMouse_Down, e .Location);
        if (dist <= 3 && grobj is ElementRsRt)
        {
            if (e .Button == MouseButtons .Left)
            {
                PopupFigure (grobj .ID);
            }

The mover can give out not only the position of the object in its queue, but also the number of node by which it was caught, and the shape of this node. In some cases, it's easier to write the code based on this information than only based on the order of an element. For example, all three types of polygons in Form_Polygons.cs can be resized, but to calculate the correct coefficient of scaling at the moment, when the resizing is started, each class of polygons receives different information. You can see the difference inside the StartResizing() method, from which the scaling of the caught object is called with different parameters.

  • For RegularPolygonRsRT, it's enough to have only the point where the resizing started.
  • For PerforatedPolygonRsRT, in addition to the point, the number of caught nodes is needed; based on this number, the decision is made to change the inner or outer border of the object.
  • For ChatoyantPolygonRsRT, the form of the caught node is provided; if it is a strip node, then the resizing of the polygon is started.

But the mover can not only provide the information about an object that is caught, moved, or just released, it sniffs everything which is below the mouse, when the mouse is idly moved across the screen without any button pressed. You can get an object below with mover.SensedObject, and then get all the information about this object. (In this situation, a mover is like a shark, which is not hungry at the moment and lazy to attack, but watches carefully and keeps track of everything that is going around.) The mover can also provide the same type of information (object, node, node's shape, and so on) not only for the mouse position, but for any point occupied by any moveable object.

The order of objects

The order of objects on the screen is regulated by the law, which was declared once and forever by Microsoft and several other barons (any questions about the tyranny?). We are in the kingdom (liberals are banned from here), so controls first (aristocrats!), then all those graphical objects (peasants…). The rules of placing the moveable objects in correct order into the mover's queue and drawing them on the screen, those rules that were explained earlier, are only the consequences of using this law. In Form_Polygons.cs, the implementation of those rules means such an order of objects in the mover's queue:

  1. The small button to switch the covers' visualization on/off.
  2. The button to add new rectangles.
  3. Three groups of controls for adding chatoyant, perforated, and regular polygons (in the mentioned order).
  4. Information text.
  5. All the colored figures.

The first four groups are never changed, but the fifth group can be reorganized at any moment by adding a new figure (it goes to the head of the group), deleting any figure, or moving any existing figure to any position inside this group. The positions of elements in the mover's queue can be changed in an ordinary way by calling the appropriate methods, but I always prefer to call the same RenewMover() method, which will set the correct mover's queue at any moment.

C#
void RenewMover ()
{
    mover .Clear ();
    mover .Insert (0, groupRegular);
    mover .Insert (0, groupPerforated);
    mover .Insert (0, groupChatoyant);
    mover .Insert (0, btnAddRectangle);
    mover .Insert (0, btnCovers);
    mover .Add (info);
    for (int i = 0; i < elements .Count; i++)
    {
        mover .Add (elements [i]);
    }
}

According with rule 2, the objects drawing is organized in the opposite order to their placement in the mover's queue.

N-node covers

Not all objects that need resizing have a border consisting of straight lines. Some of them have curved borders, but they still need to be resized by any border point. What is the solution? I call it the N-node covers.

In this type of covers, when some part of the border can't be covered with a thin prolonged node (polygon or strip), then it is covered with a set of small nodes. Usually, the number of such nodes can be big enough, but it's not a problem, as they are identical in behaviour, so the MoveNode() method of such an object doesn't require a huge amount of code. For some time, I was only using small circular nodes in such cases. To organize a sensitive strip on the border line, consisting of small circular nodes, the neighbouring circles have to be positioned not side by side, but with a small overlapping. A set of small overlapping circular nodes is only one of the possible solutions. There are no regulations on the number of such nodes, their shapes, or the possibility of overlapping. Everything depends on the developer's ideas. The only thing that is required is that this set of small nodes covers the border without any gaps and provides the needed resizing of an object.

Let's look at some samples which use N-node covers. I am deliberately going to demonstrate the use of different small nodes for these samples. The two things which can change when you switch from one type of small nodes to another, are their number and the use of overlapping; everything else is exactly the same.

Figure 5 shows Form_NnodeCovers.cs in which objects of different classes can be moved and resized. The covers of these objects are shown in the picture, so the explanation will be easier.

Fig.5  Different classes with the N-node covers

Fig. 5 Different classes with N-node covers

The cover for the circles (CircleRsRt class) consists of one big circle covering nearly the whole area, and a set of small circular nodes overlapping each other along the border.

The cover for the rings (RingRsRt class) consists of three sets of nodes: first, the small polygon nodes covering the outer border, then another set of small polygons covering the inner border, and then a set of long narrow polygons covering the area of the ring. All these polygons have the shape of a trapeze, so there are no gaps between them.

The cover for the strips (StripRsRt class) consists of three different parts:

  1. Two strips cover the long border lines; these nodes are used to change the width of the strip.
  2. Two sets of small circular overlapping nodes cover the curved parts of the border; these nodes are used to change the length of the strip.
  3. Big strip node covering the whole body; the node is used to move the strip.

The cover for the polygons with the circular hole inside (CircleInsidePolyRsRt class) consists of four parts: a set of small circular nodes on the inner border, a set of strips on the outer border, a circular node to cover the hole, and a big regular polygon covering the whole area. This cover has an interesting feature, but we'll return to it a bit later.

Is there anything common in all these covers and at the same time really new, which differentiates the N-node covers from others? Yes, the changing number of nodes in the cover, when an object is resized. This happens nearly all the time, when the length of the curved border is changed. When the border of an object consists of straight lines and is covered by strips or rectangles, the resizing of such an object doesn't change the number of nodes, but only the size (length) of some of them. When the border is covered by a set of non-changeable nodes, then the change in the border's length requires another number of such nodes to cover it without gaps. There is a very interesting and important consequence of this changing number of nodes.

The rule of N-node covers

The DefineCover() method cannot be called from inside the MoveNode() method, and though the cover has to be changed because the size of an object has changed, the call to the DefineCover() method must be postponed until the release of the object. And before this call, the new number of nodes must be calculated.

For all the previously demonstrated objects with the possibility of resizing, there were no restrictions on calling the DefineCover() method on any change of size. It is done, for example, in all the classes for different polygons. When the number of nodes in the cover is fixed for the whole class, and does not depend on the size of the particular object, then the DefineCover() method can be used at any moment.

When an object is caught by the mover, it is caught by one or another node of the cover. The translation of the node's movement into one or another type of the object's movement is done by the MoveNode() method in which the exact type of movement (forward movement of the whole object, resizing, or reconfiguring) is usually determined according to the identification number of the caught node. If the number of nodes changes during the period of time when an object is grabbed by the mover, then this identification number can move into the range of nodes that react differently. The result can be absolutely different from what you expect.

For example, I have already mentioned the order of nodes in the RingRsRt class: first, the nodes on the outer border, then the nodes on the inner border, and then the nodes for the whole area. Suppose that you pressed the inner border and began to squeeze the hole. With the diminishing hole, less and less nodes are needed to cover the inner border. If you would be constantly changing the cover according to the changing size, then at some moment, the same number of caught nodes will move into the set of nodes that cover the whole ring's area, and instead of squeezing the hole, you'll see the movement of the ring. It is definitely not the expected thing. The rule of N-node covers prevents you from running into such a situation.

The redesign of cover for a previously caught object is done when this object is released. At this moment, the new number of needed nodes is calculated according to the new sizes, and the DefineCover() method is called.

C#
private void OnMouseUp (object sender, MouseEventArgs e)
{
    int iWasCaught;
    if (mover .Release (out iWasCaught))
    {
        GraphicalObject grobj = mover [iWasCaught] .Source;
        if (e .Button == MouseButtons .Left)
        {
            if (grobj is ElementRsRt)
            {
                RedefineCover (iWasCaught);
            }

Transparent nodes

I have mentioned before that the cover for the polygons with the circular hole inside (CircleInsidePolyRsRt class) has some very interesting feature; now, it's time to look at it more carefully.

The comparison of a ring and a regular polygon with a hole shows some similarity in the general shape as both have a circular hole, but their covers demonstrate a strange difference in their design. The ring's area is covered by a significant number of polygons, while the area of the regular polygon doesn't show such nodes; yet, both objects can be moved by any inner point. Certainly, I could easily design both covers in a similar way, but I deliberately made them different to show two ways of design.

The covering of the whole area with a set of nodes is a standard way to make an object moveable by any inner point; this technique is used, for example, in the ChatoyantPolygonRsRt class. When you have some nontrivial area (usually, a nonrectangular area, or with some holes inside), then you'll need to write more code for the calculations of all those nodes. While introducing the idea of nodes at the beginning of this article, I mentioned that each of them has a parameter, characterizing its ability for individual movement. The value of this parameter is a member of the MovementFreedom enumeration.

The Transparent member of this enumeration can be very helpful in some situations. When the mover sees the node with such a parameter, it not only ignores this node, but also skips all further nodes of the same object. Beginning from this node, an object becomes transparent for the mover and the mover looks for something farther on in its queue to grab and move. Two facts contribute to an extraordinary importance and, at the first glance, a strange use of transparent nodes: the invisibility of nodes, and the nonequivalence of the object's area and its cover's area.

For the majority of objects, these two areas can be equivalent or nearly the same. To make an object moveable by any inner point, its whole area must be covered by some set of nodes. If an object is moveable, but not resizable, then the sensitive area is limited by the object's area, so the cover's area can be equivalent to the object's area. When an object is resizable, then the resizing is usually done by the border points, in which case, the border is covered by the sensitive strip, so the cover's area becomes slightly wider than the object itself. All this is used in simple and the most obvious cases; for complicated cases of nontrivial areas, only the transparent nodes can help.

While making the decision about the possibility of catching any object, the mover checks the objects from its queue; the cover of each object is analysed node after node, according to their order in the cover. When the first node containing the mouse point is found, there are several possible reactions depending on the MovementFreedom parameter of this node:

  • If it is MovementFreedom.None, then the mover can't do anything with it. At the same time, the object blocks all other objects which might lie underneath. The analysis is over, try another point.
  • If it is MovementFreedom.Freeze, then the object under the mouse can't be moved by this point, but it is recognized by the mover as any other object, so, for example, the context menu can be easily called for it.
  • If it is MovementFreedom.All, .NS, or .WE, then the real possibility of movement is decided by the MoveNode() method of this object, according to the node's number (or shape) and the movement restrictions, if there are any.
  • If it is MovementFreedom.Transparent, then mover skips this and all other nodes of the same object, and continues the analysis of the situation from the next object in its queue.

In some situations, the use of transparent nodes makes it really simple to change covers with a lot of nodes (and a lot of their calculations). For example, look at the possible transformation of the ring's cover. All the small nodes for resizing, which are placed on both borders, will not be changed, but instead of a set of polygons covering the ring's area, two circular nodes can be used.

C#
nodes [k] = new CoverNode (k, center, rInner, MovementFreedom .Transparent);
nodes [k + 1] = new CoverNode (k + 1, center, rOuter, Cursors .SizeAll);

The case of a ring is really simple, but there are objects for which the use of a standard technique with a lot of nodes covering the object's area is either extremely difficult or nearly impossible. When I first designed the next sample, I called it SwissCheese, but because no Swiss cheese has holes in the form of polygons, I changed the name to its current variant. Figure 6 demonstrates the view of Form_AreaWithHoles.cs (menu position Nodes and covers – Transparent nodes). Each rectangular area initially has a number of holes of different shapes (circles and polygons); those holes can be closed by the moveable / resizable / rotatable figures of the appropriate shape, in which case, the hole disappears and the area's cover is changed. Such a nontrivial area has a very simple cover, consisting of a single transparent node for each hole, and a single node for the whole area. Regardless of the number and form of the holes, the number of nodes is always (nHoles + 1).

C#
public override void DefineCover ()
{
    CoverNode [] nodes = new CoverNode [holes.Count + 1];
    for (int i = 0; i < holes.Count; i++)
    {
        if (holes [i] .ApexesNumber == 0)
        {
            nodes [i] = new CoverNode(i, holes[i].Center, 
                Convert.ToInt32 (holes[i].Radius),
            MovementFreedom .Transparent, Cursors.Default);
        }
        else
        {
            nodes [i] = new CoverNode (i, holes [i].Apexes,
              MovementFreedom.Transparent, Cursors.Default);
        }
    }
    nodes [holes.Count] = new CoverNode(holes.Count, rc);
    cover = new Cover(nodes);
}

Pay attention that in the case of transparent nodes, their order in the cover is extremely important. No node can affect any other node which is positioned ahead of it in the cover. For all nontransparent nodes, it also doesn't matter what is placed behind it in the cover, because the reaction (possibility of movement) is determined only by the first node on which the mouse is pressed. Transparent nodes are the only exceptions to this simple mechanism: when such a node is pressed, it doesn't allow to make any decision on the possible movement, but orders the mover to skip the remaining part of the cover from this object and look for another one. Ridiculous behaviour, but very interesting, and in some cases, very helpful. Certainly, there is no limitation on the number of objects that can be skipped, if the mouse was pressed on their transparent nodes, so in the shown form, it is possible to move the lower area, if it was grabbed through the holes of all the upper areas.

Fig.6 Areas with holes; the holes are covered by the transparent nodes

Fig. 6 Areas with holes; the holes are covered by transparent nodes

This sample of area with holes still represents the case when the object's area is nearly the same as the cover's area, but the transparent nodes often organize a situation when these two areas become absolutely different. Consider the case of a rectangle with slightly concave sides: the cover is very simple (a rectangle plus four big circles), but the cover's area is much-much bigger than the object's area, as circles with big radiuses are used to organize the concave sides.

There is one more very interesting object in Form_AreaWithHoles.cs: the group of buttons that are responsible for adding the new figures to the form. Each button works in a standard way: click the button – receive the new figure. It's the behaviour of the group, which is not normal. Certainly, the whole group can be moved around the screen and placed anywhere you want: simply press at the frame or anywhere inside, and move the group. This is not an extraordinary thing, as there is not a single immoveable object in this demo application. But each button inside the group can be resized and moved individually; the frame will adjust itself so, as to be around the whole set of inner elements regardless of their sizes and positions. This is an object of the ElasticGroup class – another class, which is used to design the user-driven applications. The time to discuss this class and the consequences of using it for the design of applications will come further on in this article, and especially in the second article.

We looked at forward movement, resizing, and reconfiguring of objects. Now, it's time to look at their rotation.

Rotation

All the previously described classes with the abbreviation Rt in the names can be involved in rotation, so they don't need any changes at all. This is a common rule not only for these classes, but for all moveable objects: the design of their covers does not depend on whether those objects are going to be rotated or not. The covers are designed to provide the needed movements; the start of the forward movement and rotation are distinguished not by the touched place of an object, but by the used mouse button. (Certainly, it can be organized in a different way, when the places to start the forward movement or rotation would be different, but, from my point of view, it would be a wrong design, because then users would have to know and remember the difference between those areas. It's much better, when any object can be moved and rotated by any inner point. This will not demand from the users any extra knowledge about the screen objects with which they deal in this program or others.)

Any object which can be involved in rotation has to have a basic angle among its parameters. Usually, one angle is enough, because during the rotation, the relative angles between the parts are not changed, so the positions of all the basic points are calculated according to this angle and the sizes.

During rotation, all the object's points (with the exception of the rotation center, if it is inside the object) change their positions, so the positions of all the points on which the drawing of an object is based must be recalculated. The obvious place to do it is inside the MoveNode() method in the part which is associated with rotation. For example, here is this part of code for the StripRsRt class:

C#
override bool MoveNode (int i, int dx, int dy, Point ptM, MouseButtons catcher)
{
    … …
    else if (catcher == MouseButtons .Right && bRotate)
    {
        double angleMouse = Auxi_Geometry .Line_Angle (center, ptM);
        angle = angleMouse - compensation;
        ptC0 = Auxi_Geometry.PointToPoint (center, angle + Math.PI, length / 2);
        ptC1 = Auxi_Geometry .PointToPoint (ptC0, angle, length);
        DefineCover ();
        bRet = true;
    }

Several things which are important for rotation can be seen here:

  1. The number of the caught node is not mentioned in this part of code, because it doesn't matter at all; the rotation of an object can be started by any node.
  2. The parameters of linear movement of the mouse (dx and dy) are not mentioned either; I found it much more reliable to base the rotation on the exact mouse position ptM.
  3. You might be surprised to see the call to the DefineCover() method here, as it is against the rule which was declared for objects with N-node covers. That rule is important only for the process of resizing and reconfiguring, when the number of nodes can be changed. During rotation, the shape of an object is fixed, so the number of nodes cannot change, and the rule is not important. Certainly, the call to the DefineCover() method can be taken out of here, but then the cover must be redefined at the moment when an object is released after rotation. DefineCover() is never a time consuming method, so it's much easier to do it on any rotation than to add the special parts of code.

The really interesting thing here is the use of the compensation parameter, which brings us to the whole process of organizing the rotation.

Figure 7 shows the view of Form_Rotation.cs, in which any number of objects from different classes can be moved, resized, and rotated. In this figure, the objects are shown together with their covers, so you see the exact situation which the mover has to analyse while deciding to start any kind of movement. When any object is pressed with the mouse, then the decision on either to start the rotation or any other kind of movement is based on the pressed button.

C#
private void OnMouseDown (object sender, MouseEventArgs e)
{
    ptMouse_Down = e .Location;
    if (mover .Catch (e .Location, e .Button))
    {
        if (e .Button == MouseButtons .Left)
        {
            StartResizing (e .Location);
        }
        else if (e .Button == MouseButtons .Right)
        {
            StartRotation (e .Location);
        }

The start of resizing was discussed previously; for some classes, it would require additional information either in the form of the node's number or its shape. For rotation, no other information is needed, except the point where an object is pressed. But each class of objects has its own method, which is called at the start of rotation. The purpose of this method is to calculate this compensation and, maybe, some additional parameters (sizes), which are not going to change during the whole period of rotation. Here is this method for the StripRsRt class.

C#
public override void StartRotation (Point ptMouse)
{
    center = Center ();
    double angleMouse = Auxi_Geometry .Line_Angle (center, ptMouse);
    compensation = Auxi_Common .LimitedRadian (angleMouse - angle);
    length = Auxi_Geometry .Distance (ptC0, ptC1);
}

Each object is characterized by its angle on which the whole drawing of this object is based. The rotation can be started by pressing an object at any point, so compensation is the difference between the mouse angle and this basic angle at the start of rotation. During rotation, this difference is not changed, the basic angle is calculated from the changing mouse position by using this fixed compensation, and the whole object turns as the mouse goes. These calculations are made in that part of the MoveNode() method which is related to rotation; that is where the compensation parameter is used (see the code in the previous section).

Fig.7  Rotation of different objects

Fig.7 Rotation of different objects

One interesting aspect of moving graphical objects is not seen in figure 7, though it works in Form_Rotation.cs. The objects in all the previous samples moved individually. If there are a lot of objects on the screen, then in addition to individual movements, it would be nice to have an easy way of moving groups of objects. And not only the sets of objects that were unified into some kind of group at design time, but any arbitrary set of objects which the user would like to relocate synchronously. In this form, you can select any part of the screen by pressing the left button at any empty place and moving the mouse across the screen. If more than one figure is caught inside this rectangle, then the special frame (of the SimpleFrame class) appears on the screen. By moving the frame, all the figures inside are also moved. Selection of an empty area or an area with a single element inside automatically deletes the frame.

The area of this colored frame can not be grabbed for moving by any inner point, but only by the frame itself (the red line); there are also several visible nodes to change its size, which allows to include and exclude figures from this synchronous movement. The frame will not include the controls, which happen to be inside, into the synchronous movement, but this is only because we didn't start looking at the controls. In reality, there is a commented piece of code inside the OnMouseDown() method. Turn this comment into working code, and the controls will also move, but the discussion for this will come further on.

Separately and together

All the previously explored objects were moved forward, resized, reconfigured, and rotated individually. Though reconfiguring of the polygons from the ChatoyantPolygon class is the result of changing the relative positions of their parts, these parts can't be looked at as individual objects: an apex or central point of a polygon can't live by itself.

It's a common enough situation when individual objects are composed into something more complex, in which case, those smaller parts can live (move, rotate) by themselves, but at the same time, the proposed union is also treated as a single object in which everything can move and rotate synchronously. Thus, we receive a combination of individual and synchronous movements, where the parts have to inform each other about the changes in their positions and sizes. Let's look into the problems of such movements.

Fig.8  Objects, moving individually and synchronously

Fig. 8: Objects, moving individually and synchronously

Form_SeparatelyAndTogether.cs demonstrates the colored rectangles (class RectangleWithComments) and the comments (class CmntToRectangle) which are associated with them (figure 8). As usual, there are no limitations on anything the user would like to do with these objects: add, delete, remove, change the order, or change the parameters at any moment. There is a single button to add new rectangles; everything else is done with a mouse, or via a couple of context menus (one for rectangles, another for comments). The individual and synchronous movements of these objects are organized in such a way:.

  • When any rectangle is moved, then all its comments move synchronously with the rectangle.
  • When a rectangle is resized, then each of its comments is relocated, but the new position for each of them depends on whether the particular comment was originally inside or outside the "parent" rectangle. (Comment positions are described by their central point.) When a comment is outside the rectangle, then its distance from the rectangle is kept constant regardless of the rectangle's change. If a comment is inside, then its relative position inside the rectangle is kept unchanged.
  • Any comment can be moved and rotated freely; such movements have no effect on anyone else.

It looks like a very simple set of rules and a very simple sample, but exactly the same rules are applied to objects on which the plotting in very complicated scientific / engineering applications is based. At figure 1, several classes of financial plotting are demonstrated; all those classes use the same technique to organize synchronous and individual movements of their parts.

The first and the most important thing in organizing such movements is the correct registering of these complex objects in the mover's queue. Here are several statements that must be considered:

  • A rectangle might have an arbitrary number of comments.
  • These comments can move individually, so each comment must be registered in the mover's queue individually.
  • The comments can be placed anywhere, but they are always shown atop their "parent" rectangle. To be shown above the rectangle, the comments must be painted after this rectangle; then according to the previously declared rule, they must be registered before their parent in the mover's queue.
  • The comments can be added and deleted at an arbitrary moment; the request for such actions can come from different places in the code.

Combine these statements together, and it will be obvious that it is unreliable to change the queue manually on any changes in the comments situation. Such complex objects with individually moveable "children" always have to use a single method which will guarantee the correct registering, regardless of the components number. Here is such an IntoMover() method for the RectangleWithComments class; a rectangle will be correctly registered with all its comments regardless of their number.

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

The change in the number of moveable objects in the form might happen in different cases: add a new rectangle, delete a rectangle with all its comments, and add or delete any comment. In each case, the RenewMover() method is called.

C#
void RenewMover ()
{
    mover.Clear ();
    for (int i = rects.Count - 1; i >= 0; i -- )
    {
        rects[i].IntoMover(mover, 0);
    }
    mover.Insert (0, info);
    mover.Insert (0, btnAddRectangle);

These two methods work in pair:

  • The IntoMover() method guarantees that any rectangle is registered correctly regardless of the number of its comments.
  • The RenewMover() method guarantees that all the form's objects are registered fully and correctly. Pay attention that the comments are not even mentioned in this method, because their correct registering is hidden inside the IntoMover() method.

The RenewMover() method is developed for each form with the changing number of moveable / resizable objects. The IntoMover() method is designed for each complex class with individually and synchronously moveable parts.

The mover doesn't know anything about real objects, but deals only with their covers (in the form of a nodes array). If any node is caught for moving, this is translated into the MoveNode() method of the corresponding object. The mover doesn't know anything about whether it is going to be an individual movement or a synchronous one; only the correct method of the caught object is invoked, so the request for synchronous movements of all the comments must be somewhere inside the rectangle's Move() and MoveNode() methods. It is really there: on any movement of the rectangle, all its comments are informed about the new rectangle area via this method.

C#
private void InformRelatedElements ()
{
    foreach (CmntToRectangle comment in comments)
    {
        comment .ParentRect = rc;
    }
}

Any comment has two instruments to set its position in relation to the parent: the parent's area (rectangle) and the coefficient (in reality, two coefficients) to describe the position in relation to this rectangle. On any movement of any object, only one of these parameters is changed; then the second one is used to recalculate the position.

  • If the rectangle is moved or resized, then the fixed coefficients are used to calculate the position according to the changed area.
  • If a comment is moved, then its new point is used to calculate the new coefficients in relation to the unchanged rectangle.

This is the procedure for rectangles with comments, but exactly the same technique is used for other types of related objects. For example, circular types in financial graphics, which can be seen in figure 1, use two different types of comments: some comments are associated with the whole plot (circle), others with its parts (sectors). The calculation of the specific coefficients is different for each type of comment, and depends on the shape of the "parent" object, but the idea is exactly the same.

It also doesn't matter how many levels of related objects are included into the chain of linked objects. For example, the scientific plots and the bar chart, which can be seen in figure 1, include cases of two and three levels of related objects: the main plotting area, the related scales, and the comments associated either with the scales or with the main plotting area. The number of levels of objects included into the synchronous movements doesn't matter; the individual and synchronous movements in different combinations of all these objects are organized exactly in the same way, as it was shown for simple rectangles with comments.

Certainly, all these things can't work without a reliable identification of all the involved objects. Each object gets its unique identification number, as was already shown in the case of polygons. That was enough for the case of individual movements; it is also required, but not enough for more complex cases.

When any new comment is organized, it receives its personal ID.

C#
public CmntToRectangle (…)
{
    id = Auxi_Common .UniqueID;

When this comment is added to some rectangle, it gets the parent's ID.

C#
public void AddComment (CmntToRectangle comment)
{
    comment.ParentID = id;
    comments.Add (comment);
}

That is the thing that provides the identification of all the related objects in the chain, whenever any of them is picked out for moving, deleting, or any other change.

One more thing which you can find when you start playing with those rectangles: there are two ways to add a new comment to a rectangle; both of them can be started via the context menu. The first one: Add comment (quick) – simply adds a new comment with the "New comment" text, using the current Font and ForeColor of the form. (The font and color of each comment can be changed later via the context menu on this comment.) The second way: Add comment (custom) – allows to put any text into the new comment; at the same time, the font and the color of this comment can be declared. All these things can be done in the small form which is opened on clicking this menu line. Form_NewComment.cs is a simple one with only four standard controls inside (figure 9), but I have to remind you again that there is not a single immoveable object in this application. So all the controls in this form are moveable (by their borders), and two of them are resizable (corners are the best places to try). Two small buttons are left non-resizable, but it can be changed easily (the explanation starts several lines further on); the whole form can be changed in any possible way you want.

Fig.9  Form_NewComment.cs

Fig .9: Form_NewComment.cs

This is the way all the user-driven applications are organized. The purpose of this form is to set the new text, its font, and color. This is done regardless of the positions or sizes of all those controls. Then, why should their positions and sizes be fixed by the designer? If any user would like to change the view of this form, he can do it according to his own taste. The form works according to its purpose regardless of its view. An unlimited customization.

This form has no graphics and includes only controls; yet, it is operated in a similar way to all the previously shown forms, where all graphical objects are moved without any restrictions. But, dealing with controls is slightly different from working with graphics. The next part of the article is about making all controls moveable / resizable and working with such moveable controls.

And controls also

Applications are filled with elements of two different types: graphical objects and controls. In reality, all screen elements are graphical. "All animals are equal, but some animals are more equal than others" (G. Orwell, Animal farm, 1945). Nothing can describe the situation with controls better than this famous statement. Controls are those "more equal" objects, which makes them absolutely different.

When you design and work with applications composed of fixed controls with an occasionally repainted background, you have no problems with the controls. You simply get used to whatever is provided with them (all their properties and events) and what you can get of them, as a developer. Users have got used to the controls view throughout the last quarter of a century, and demonstrate the classical Pavlov's reflex: if they see a button, they click it. And it really works in such a way, which makes the reflex only stronger.

What none of the users try to do is to move the buttons, for example, to another place, even if they think that another place would be better. Well, I think that some of them try when there is no one around to laugh at their attempts, but immediately find out that it is impossible to do it. The reflex is fixed forever: you can click on controls, but not move them.

In reality, this statement about controls is absolutely wrong; they are moveable, they are even developed to be moveable and resizable, but these features are closed from users, and are used by developers occasionally to make an impression. (Like some people in the passed centuries made their living on the ground of knowing which gradients to through into the flame to produce colored smoke. Some of those people were burnt for such knowledge, but that is an absolutely different story.) The big part of the popular dynamic layout is based on using the moveability and resizability of controls.

Controls can be easily moved and resized by using their properties; the demonstration of a button running away from an approaching mouse is just a funny sample of using these properties. (Write the code for one mouse event consisting of several primitive lines, and the users will be amazed with the behaviour of those crazy controls.) But, this is an example of how developers can use these features, because such a running away control is going to move according to the rules predefined by the developer. I am talking about the mechanism that allows users to move controls in any way they would like to do, so it is absolutely different, though I use exactly the same control properties. It's the question of who is managing the control movements: either the algorithm is absolutely predetermined by the developer (the control is moved for a predefined number of pixels in a predetermined direction when the mouse is over it), or all the movements are determined only by users, when they would decide to move a control one way or another. I think that users and only users must get the full control of moving or resizing controls.

Individually moved controls

The idea of moving and resizing controls by a mouse is exactly the same as with all graphical objects, but there is a problem. The standard procedure for graphical objects is to move them by their inner points and resize by the borders. Unfortunately, the whole area of any control is closed for anything new, as controls are designed to use their every inner point for mouse clicks with already declared purposes. The reflex is so strong that can't be changed. Thus, the only chance to move and resize controls is to use the vicinity of their borders. And as we need both moving and resizing, the control's frame is going to be divided between the areas to start moving or resizing.

There is some inconsistency in using the border of graphical objects for resizing only, but the border of controls for both actions. That is one of the consequences of having these "more equal" elements on the screen. The controls can be redesigned and treated like all other objects, but this would be really a big step, though I think it will happen some time in the future. There is absolutely nothing that demands the existence of those controls on a special level. Any one of them can be replaced with a graphical object that looks exactly the same, behaves exactly the same, but does not demand the special status. I have designed such objects before, other people were doing it, and so this is not something absolutely unique and never heard about. It's impossible for users to distinguish current day controls from similarly designed graphical objects; they will not see the difference at all. For programmers, such graphical objects are much better for forms design, as they can be of any arbitrary shape. Certainly, this needs some work to be done, so that new graphical "controls" will be as easy to use for design as our modern day controls, but I think that the word progress is a correct one to describe such a work to be done.

Certainly, the frame around a control is used as a cover. It looks a bit strange to have a cover outside an object's area, but there are two remarks to this situation. First, the cover often goes outside the real object; in fact, it goes outside every time when an object is resizable; the covering of the border makes the area on both sides of the border sensitive. Second, this cover, used for moving / resizing a control, belongs not to the control itself, but to a FramedControl object which wraps this control. This object – a frame around the control – is registered in the mover's queue as any other object. The only difference is that the moving / resizing of this frame is directly translated into the moving / resizing of the associated control, making it moveable and resizable. The only question in organizing such a frame is the placement of nodes for resizing, though several applications use the same technique, so the practice is already known. For example, when you set the size of the proposed control in Visual Studio, you resize it by the corners or by the small squares in the middle of the sides. So, the best and expected places for the nodes to resize any control are obvious; other details depend on the purpose of the needed resizing.

Fig.10 Individually moved controls and their covers

Fig. 10: Individually moved controls and their covers

Form_ControlsIndividual.cs (menu position Controls – Moving individually) demonstrates several individually moveable controls with their covers (figure 10). Each control is wrapped in a FramedControl object; then this object is registered with the mover.

C#
mover.Add (new FramedControl (ctrl, bMove, nodesize, frame));

As you can see, there is no parameter to declare the type of resizing, but the controls in this form can be resized in different ways. The mover determines the type of resizing for a particular control by analyzing its sizes and the values of its MinimumSize and MaximumSize properties. If those properties are not changed from their default values (0, 0) or set to the size of the control, then the control is non-resizable. If there is a range for one direction only (width or height), then this control is resizable in that direction only; otherwise, it is fully resizable.

The frame parameter determines the width of the sensitive frame around the borders; by pressing inside this area, the control can be moved. In figure 10, the covers are visualized and these sensitive areas around the controls are shown by the big red frames. The width of a frame can't be less than 2 pixels (by default, it is 6 pixels), but it can be eliminated if the second parameter bMove is set to false; the control for such a case is shown in the left bottom corner of this figure. It looks like this control without a frame is resizable, but not moveable. In reality, it's not so. There is a funny way to move even this control, but exactly like all the caterpillars move: stretch it in one direction by the small node, then squeeze by the opposite node, stretch again, and so on.

Making everything moveable in the form, consisting exclusively of controls, is really easy. Just register all the controls from the Controls collection with the mover, and everything will work fine.

C#
foreach (Control ctrl in Controls)
{
    mover.Add (new FramedControl (ctrl)); // or mover.Add (ctrl)
}

The code for the three mouse events can't be simpler than it is in this form. In such a way, any form containing only controls can be turned into moveable / resizable / changeable just in seconds. Are there real cases when such a simple turn of all the controls in a form into moveable / resizable would work? Certainly, Form_NewComment.cs, which was mentioned a couple of sections back, and which is used to add new comments to rectangles, is just such a form.

Groups of controls

Now, let's look at the different cases in our kingdom of moveable objects. Controls are often used not as standalone objects, but in groups. For example, when you need to define several parameters of the same object, you often unite a set of needed controls into a group. It's a standard situation that these controls are positioned next to each other, and there is some kind of visual prompt that they are involved in related processes. This group of controls can be positioned either on a Panel or in a GroupBox; the last one can use its title to provide information about the group. Anchoring can be applied to such a Panel or a GroupBox, and anchoring can be applied to the controls inside, so the sizes of those controls can be adjusted to the size of the whole form or its font.

Exactly the same thing can be done with ordinary panels and group boxes in our universe of moveable objects. Panels and GroupBoxes are ordinary controls, so they can be registered with the mover and thus moved and resized exactly the same way as was shown for individual controls. It doesn't matter that they contain other controls; the same rules of dynamic layout will work. It is possible, and it is simple, but from my point of view, it is not a good solution. Though the inner area of a Panel or a GroupBox looks like a part of the form's area (usually they have the same background color), it is the inner area of a control, so it is forbidden to be used by the mover; both elements can be moved only by their borders.

There can be much better solutions to move and resize groups of controls; these unions of elements can be based on different ideas. In this article, I'll introduce only three different classes; there are more in the Test_MoveGraphLibrary application and the related text.

Fig.11  Different variants of organizing groups of elements

Fig. 11: Different variants of organizing groups of elements

Three groups in Form_ControlsGroups.cs (menu position Controls – Groups, figure 11) behave differently. Certainly, you would never put groups with different behaviour into a single form of a real application, but it's not only OK in the demo program, but maybe even better for their comparison. The name of the class used for each group here is shown as its title. There is no need to think about the covers for these groups or the methods for their moving; the only needed thing is to organize them according to the described rules (they are different for each class). All three groups are moveable by any inner point, but they differ in the way of resizing.

Objects of the LinkedRectangles class are not resizable at all. It's a rare situation in the world of moveable / resizable elements, but occasionally, it can happen, when required. There are four controls in this group: three of them are used for selecting the type of a new graphical element and its parameters; an additional button is used to add this new element to the screen. The sample of the proposed figure is also shown inside this group.

There can be any number of elements of which the LinkedRectangles object is composed. There can be controls; there can be rectangles with some painting in them. The drawing of a frame is just one of the possibilities; it is not mandatory, so such an object can be easily shown without any frame. There are absolutely no rules for positioning of all those rectangles: they can be placed next to each other, they can be positioned apart, and they can overlap. The order of assembling rectangles into this object is also not important, as all the nodes of the cover work for one purpose only: to move the whole group. In this form, the LinkedRectangles object is organized on the basis of an array of controls; then, one more rectangle is added to avoid any non-sensitive gaps inside.

C#
Control [] cntrls = new Control [] { comboUnicoloured, numericUD_Apexes,
                                     btnUniColor, btnAddUnicoloured };
lrUnicoloured = new LinkedRectangles (cntrls);
Rectangle rcFrame = Auxi_Geometry .FrameAroundControls (cntrls, spaces);
rcFrame .Inflate (3, 3);
lrUnicoloured .Add (rcFrame, "Area");

For the mover, the whole group is a single moveable element, so it can be registered in the easiest way.

C#
mover.Add(lrUnicoloured);

Objects of the Group class are moveable and resizable. The group has a classical view of the group with a frame surrounding the inner area; it is also the classical type of a moveable / resizable object, which can be moved by any inner point and resized by any border (frame) point. Depending on the need, such groups can be organized with different types of resizing. There is a visual prompt about the possibility of resizing, as the sides of the frame, which can change their lengths, have a piece of dashed line in the middle (there is no dashed line on the upper side if the group has a title).

C#
rcFrame = Auxi_Geometry.FrameAroundControls (
   new Control [] { listColors, comboMulticoloured, 
                    btnAddMultiColoured }, spaces);

groupMulticoloured = new Group (this, rcFrame, 
                     new RectRange (…), "Group", MoveGroup);

The possibility of resizing a particular group is defined on initialization by the RectRange parameter; depending on this parameter, the group can be non-resizable, resizable only in one direction, or in both. The relocation and changing of the sizes of the inner elements are described by another parameter. The moving / resizing of the inner elements happen as the result of the frame's change. These inner elements can't move by themselves independently of the frame's changes, so for the mover, this group looks like a single element; thus, it is also registered in an easy way.

C#
mover.Add(groupMulticoloured);

If the Group class can be looked at as the implementation of dynamic layout for a moveable group, the situation with the ElasticGroup class is absolutely different. In this class, the frame's position and size are determined by the set of inner elements, any one of which is moveable and possibly resizable. You move or resize any text box in the shown sample, or you move / rotate any text associated with these text boxes; the frame watches all the changes and adjusts its position according to all the inner changes.

The ElasticGroup object in figure 11 represents the standard combination of controls to deal with address information. Though this order of writing an address is normal for some western countries, it is unnatural for other countries where the address is written in the opposite order. With such moveable controls inside the group, their positions can be changed by anyone in seconds. This group of individually moveable inner elements also helps to avoid other annoying things. Some users would like to see the comments on the other side of the text boxes, some would prefer to position those boxes in a different way, give them other sizes, or whatsoever. Users can position / resize the inner elements in any way they want; and in any case, they can move the whole group around the screen.

Because the ElasticGroup object contains individually moveable parts, it must be registered by its IntoMover() method.

C#
groupAddress.IntoMover(mover, 0);

The possibilities of organizing moveable / resizable groups of controls is not limited to these three cases. The use of moveable / resizable elements as the basis of construction immediately ends the monopoly of the GroupBox class, and opens a wide area for new ideas. For example, all these three demonstrated classes use the classical idea of a moveable element which can be moved by any inner point. This is an excellent rule for graphical objects, but maybe this rule must be changed when applied to groups. In a couple of minutes, each of those samples can be changed in such a way that the groups will not be moved by inner points, but moved and/or resized only by their frames. The division of the frames on areas for moving / resizing will be done exactly in the same way, as was shown for individual controls, so there will be no problems in using such frames for both things. These are some of my suggestions; there can be others. (I have already designed a set of different classes, but decided not to demonstrate them here; as it will only increase the text.)

Several forms in the accompanying application use ElasticGroup objects, but in all these samples, the objects of this class are used in the most primitive way. Much more interesting cases of using this class will be described and demonstrated in the second article.

Technical note

When you deal only with graphical objects, then the use of standard double buffering solves the problem of screen flickering. When you start to move controls or groups of controls, then the system makes the decision about the proper moment for their redrawing, and it is always done with some delay. To solve this problem and to improve the whole picture, add a couple of lines into the OnMouseMove() method. Throughout the code for these applications, it is done with the ControlsCausedUpdate() method; depending on the form, the types which are checked inside this method can vary, but they include all the classes with controls that can be moved in the particular form.

C#
private void ControlsCausedUpdate ()
{
    if (mover.CaughtSource is ElasticGroup || 
        mover.CaughtSource is FramedControl)
    {
        Update ();
    }
}

Arbitrary grouping

Controls can be moved individually and controls can be organized into groups with different rules for moving. The location and size of these groups can be changed by users, but not the content of each group, which is determined at the design stage. Some applications include a significant number of controls; users can move and resize each of them individually, but in addition, they would like to get an easy to use instrument to rearrange the view of such applications more quickly. Not moving one control after another, but uniting any number of them into a group of synchronously moving controls. These groups can not be predetermined by the designer, but has to be organized by any user at any moment. What is the solution? The familiar ElasticGroup class.

Fig.12  Form with an arbitrary grouping of the controls

Fig.12 Form with an arbitrary grouping of controls

Figure 12 shows a typical view of Form_ArbitraryGrouping.cs (menu position Controls – Arbitrary grouping), in which any control can be moved and resized individually, but at any moment, an arbitrary set of controls can be rounded into a group, organizing an object of the ElasticGroup class. As was mentioned before, such objects with all the inner elements can be moved around the screen by any inner point.

On opening this form, you'll see 20 buttons of the same size, strictly placed in two lines. From that moment, you are the only master of these buttons; you can resize and place them in any possible way. If you want to move any group of controls to a new location, you surround them with a temporary frame. This is done in the standard way, which is common for such operations: press the left button at one point, drag the mouse to another point, and release the button. The two points will be the opposite corners of the rectangle. The rounded controls form an ElasticGroup object. As any control in the form can be moved and resized individually, there is no sense in organizing such an object if there are less than two buttons inside.

Controls inside or outside the frame can be moved and resized individually in exactly the same way. The only difference between them is that the inner elements can change the size of the group, as it is an idea of the ElasticGroup class. This is a demonstration which shows how easy it is to implement such a powerful instrument. I have designed the whole Calculator based on exactly the same instrument. This Calculator can be seen as a part of the Test_MoveGraphLibrary application, and as a standalone application in the mentioned project at www.sourceforge.net. I'll write more about it in the second article, but I think it is obvious that the design of programs on such a principle gives the users amazingly new opportunities in customizing the view of their applications.

Summary

In my approach to turning screen objects into moveable / resizable, each object receives an invisible cover. Here are some rules for organizing such covers and for organizing the whole moving / resizing process for screen objects:

Cover summary

  • A cover consists of an arbitrary number of nodes. The minimum number of nodes is 1. A cover may consist of a single node. The number of nodes is unlimited. Covers of a special type (N-node covers) may include hundreds of nodes.
  • Nodes can be of different shapes: circular, strip with semicircles at the ends, and convex polygon. Circular nodes are defined by a center point and radius. Strip nodes are defined by two middle points at the ends of a strip and a radius of semicircles; the strip's width is equal to the diameter of the semicircles. A convex polygon is defined by the apexes.
  • Nodes of a cover may overlap, they can be placed side by side or apart. The order of nodes in the cover can be very important, as nodes are checked for moving according to this order. In the areas of overlapping, moving / resizing of an object is determined by the first selected node.
  • Nodes do not duplicate the shape of an object, and they are not required to be only inside the object's area. Use of nodes with the transparent property can make the design of covers for the nontrivial areas much simpler.
  • Nodes can be moved individually, thus allowing to reconfigure an object.
  • Nodes can be enlarged to cover as much of the object's area as possible. Such enlarged nodes are often used for moving the whole object.
  • A cover may consist of nodes that are not moved individually, but an attempt to move such a node results in moving of the whole object (the MoveNode() method only calls the Move() method). This makes an object moveable, but not resizable.
  • Each of the nodes has its own parameters. It is easy to allow resizing along one direction but prohibit it along another; this means organizing a limited reconfiguration.
  • It doesn't matter that some covers may represent graphical objects and others controls or groups of controls. All covers are treated in the same way, thus allowing the user to change easily the inner view of any application.
  • Covers can be visualized, though the best design makes moving / resizing of objects obvious without such visualization. Visualization of a cover includes possible filling of the nodes' inner area and drawing of the nodes' perimeter.

Moving / resizing summary

  • To make any graphical object moveable / resizable, it must be derived from the GraphicalObject class, and three methods must be written for such an object: DefineCover(), Move(), and MoveNode().
  • The DefineCover() method defines the cover of an object as a set of nodes; each node has an individual number.
  • The Move() method describes the forward movement of an object as a whole. In reality, it means a simple change of one or several primitives (points, rectangles).
  • The MoveNode() method describes the individual movement of nodes; if the moving of a node results in the moving of a whole object, then the Move() method is called from inside the MoveNode() method. The individual movement of a node may cause relocation of some or even all other nodes; in such cases, the DefineCover() method is often called from inside the MoveNode() method.
  • To organize the moving / resizing process, there must be an object of the Mover class.
  • C#
    Mover mover;
  • To prevent the accidental moving of elements out of view, the mover must be initialized with an additional parameter:
  • C#
    mover = new Mover (this);
  • Three levels of moving objects across the form's borders can be organized: moving outside is not allowed, moving allowed only across the right and lower borders, or moving allowed across all four borders.
  • The mover has a queue of moveable objects, and will supervise the whole moving / resizing process only for objects that are included into this queue.
  • C#
    mover.Add (…);
    mover.Insert (…);
  • To make any control moveable / resizable, it's enough to include it into the mover's queue. Those three methods are not needed for controls.
  • To make any control resizable, the appropriate values must be set to its MinimumSize and MaximumSize properties.
  • Any combination of elements can organize a set of synchronously moving objects.
  • For complicated objects consisting of parts which can be moved both synchronously and independently, and for the objects for which the set of such parts can be changed, it is much better to develop an IntoMover() method, which is used instead of manual registering of all the moveable parts, and which guarantees the correct registering of an object and all its parts regardless of a set of constituents.
  • Moving and resizing are done with a mouse, and the whole process is organized via three standard mouse events: MouseDown, MouseUp, and MouseMove.
  • MouseDown starts moving / resizing of an object by grabbing one of the nodes in its cover. The only mandatory line of code in this method is:
  • C#
    mover.Catch (…);
  • MouseUp ends moving / resizing by releasing any object that could be involved in the process. The only mandatory line of code in this method is:
  • C#
    mover.Release ();
  • MouseMove moves the whole object or a part of it. There is one mandatory line of code in this method, but in order to see the movement, the Paint method must be called.
  • C#
    if (mover.Move (e.Location))
    {
       Invalidate ();
    }
  • The mover can provide a lot of information about the object which is currently moved or just released, and even about an object that is simply underneath the mouse cursor. This data can be used to change the order of objects on the screen, to call the context menus, and so on.
  • If covers must be shown, then one of the available drawing methods can be used, for example:
  • C#
    mover.DrawCovers(grfx);
  • If needed, several mover objects can be used to organize the whole moving / resizing process. Each mover deals only with the objects from its own queue.

Conclusion

This is a brief (concentrated) version of my ideas about turning screen objects into moveable / resizable. The step from objects whose behaviour is absolutely determined by the designer, to objects which can be moved and resized by users, means a big change in the design of applications. For example, if the users get a chance to change a lot of (all!) parameters (positions, sizes, number of objects, their order, …), then there must be an absolutely reliable mechanism for saving and restoring all those objects and their parameters. This article is only about the algorithm of turning objects into moveable / resizable, so I decided not to discuss this item here. For the same reason, the saving / restoring is not included into the accompanying application, though it works in Form_NewComment.cs, where the configuration is saved on the Registry and restored on the next opening of the form. This mechanism is implemented in all the forms for the demo application, which will also come with the second article and will be described in that article. The same mechanism works in nearly every form of the mentioned Test_MoveGraphLibrary application.

Moveable / resizable objects are the basis for design of user-driven applications. Such applications show improvement in comparison with the currently used applications in nearly every area. But there are areas, for example, engineering / scientific or financial applications, in which the design on the basis of moveable elements means a step to another level of development and use.

This article is mostly about the algorithm of turning screen objects into moveable / resizable. It's a very important task by itself, but my work in this area showed me absolutely clearly that the consequences of applying such an algorithm to the design of applications are much more important than the algorithm itself. It doesn't matter whether you use this algorithm or another; in any way, you'll come to the same understanding and the same conclusion: moveable / resizable elements can't coexist with fixed objects. If you start using moveable objects in applications, they will require, demand, and force you into redesigning everything around on the basis of exclusively moveable elements. And as a result, you'll come to a different kind of programs.

But this will happen if you care about the users of your applications and think that they have to receive a chance to organize applications in a way which is personally the best for them. Not as a crowd, but personally for each of them. If you think that you know better than anyone of them and what they need, you can put aside all these new ideas and continue to work in the standard way of fixed design and explain to your users that they have to be happy with your view once and for all.

As for myself, I see all the time how quickly users of user-driven applications get used to the new possibilities and expect this behaviour from all new programs without questions. The design of user-driven applications and the consequences of such a design will be the theme for the second article.

Programs and documents

Several programs and documents have been designed for better explanation of moveable and resizable graphics and their use. All files are available at www.SourceForge.net in the project MoveableGraphics (names are case sensitive there!). The most important files are renewed from time to time (usually every month); others might be older.

  • TheoryOfMoveableObjects.zip: An application (the whole project with all the code in C#) to accompany the current article. The article (current document) is also included into this ZIP file both in DOC and PDF formats. To run the application, only two files are needed: TheoryOfMoveableObjects.exe and MoveGraphLibrary.dll.
  • Moveable_Resizable_Objects.doc: The detailed description of the design and the use of moveable / resizable objects. The explanation is based on the samples and code from the Test_MoveGraphLibrary project. 96 pages.
  • Test_MoveGraphLibrary.zip: Contains the whole project from Visual Studio, with a lot of samples and useful code. The source files are written in C#; all the samples in Moveable_Resizable_Objects.doc are from this project. If you want to run this application, then only two files are needed: Test_MoveGraphLibrary.exe and MoveGraphLibrary.dll. The library is included into this ZIP file, but also is available as a standalone file.
  • MoveGraphLibrary.dll: The library.
  • MoveGraphLibrary_Classes.doc: Description of the classes included into MoveGraphLibrary.dll. 115 pages.
  • LiveCalculator.zip: Calculator, which is included into Test_MoveGraphLibrary.exe for explanation, but presented here as a separate program. To use this application, it must be accompanied by the DLL, which is also inside the zip file.
  • MoveGraphLibrary_Graphics.doc: Description of plotting, implemented in MoveGraphLibrary.dll; description of all the tuning dialogs, which are also included into this library.
  • TuneableGraphics.exe: This application demonstrates the moveable / resizable objects from absolutely different areas. A year ago, it was an absolutely different application; now, a lot of its forms are similar to the forms from the Test_MoveGraphLibrary application, but some are different.
  • TuneableGraphics_Description.doc: Description of the TuneableGraphics application.

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

 
GeneralMy vote of 5 Pin
arokicki23-Jul-10 3:11
arokicki23-Jul-10 3:11 
GeneralSelect multiple objects and move them Pin
wossok22-Apr-10 6:57
wossok22-Apr-10 6:57 
GeneralRe: Select multiple objects and move them Pin
SergeyAndreyev23-Apr-10 20:56
SergeyAndreyev23-Apr-10 20:56 
GeneralRe: Select multiple objects and move them Pin
TSKNaidu23-Feb-11 2:30
TSKNaidu23-Feb-11 2:30 
GeneralGraphicalObject cover member Pin
Sven Bardos11-Apr-10 7:57
Sven Bardos11-Apr-10 7:57 
GeneralRe: GraphicalObject cover member Pin
SergeyAndreyev16-Apr-10 0:49
SergeyAndreyev16-Apr-10 0:49 
GeneralRe: GraphicalObject cover member Pin
SergeyAndreyev21-Apr-10 20:30
SergeyAndreyev21-Apr-10 20:30 
GeneralTake a fiver! Pin
AhsanS15-Feb-10 0:27
AhsanS15-Feb-10 0:27 
Have a five SergeyAndreyev and thanks for pain you took for others while writing this piece of brilliance.

Ahsan Ullah
Senior Software Engineer
MCTS 2.0
GeneralGreate article! Pin
Nematjon Rahmanov12-Feb-10 23:27
Nematjon Rahmanov12-Feb-10 23:27 
GeneralMasterpiece Pin
Marcelo Ricardo de Oliveira5-Feb-10 23:43
mvaMarcelo Ricardo de Oliveira5-Feb-10 23:43 
GeneralGreat Pin
lopanta28-Jan-10 23:09
lopanta28-Jan-10 23:09 
General@#$l74B%F and 3 things to say/ask Pin
Xmen Real 27-Jan-10 16:12
professional Xmen Real 27-Jan-10 16:12 
GeneralRe: @#$l74B%F and 3 things to say/ask Pin
SergeyAndreyev29-Jan-10 22:39
SergeyAndreyev29-Jan-10 22:39 
GeneralNice one. Pin
Sepster26-Jan-10 18:34
Sepster26-Jan-10 18:34 
GeneralGreat article Pin
Rozis25-Jan-10 11:51
Rozis25-Jan-10 11:51 
QuestionMissing something...? Pin
stuffyk25-Jan-10 10:40
stuffyk25-Jan-10 10:40 
AnswerRe: Missing something...? Pin
Sepster26-Jan-10 18:26
Sepster26-Jan-10 18:26 
GeneralRe: Missing something...? Pin
Stanislav Georgiev27-Jan-10 3:04
Stanislav Georgiev27-Jan-10 3:04 
GeneralRe: Missing something...? Pin
Sepster27-Jan-10 11:26
Sepster27-Jan-10 11:26 
AnswerRe: Missing something...? Pin
SergeyAndreyev27-Jan-10 7:11
SergeyAndreyev27-Jan-10 7:11 
GeneralRe: Missing something...? Pin
svarozic27-Jan-10 10:30
svarozic27-Jan-10 10:30 
General5 is not enough, my rate is 10!! Pin
Aamer Alduais25-Jan-10 9:41
Aamer Alduais25-Jan-10 9:41 
GeneralHell of an article man, have a 5 (wish I could do more) Pin
Sacha Barber25-Jan-10 6:31
Sacha Barber25-Jan-10 6:31 
GeneralAlign objects Pin
Cata25-Jan-10 2:37
Cata25-Jan-10 2:37 
AnswerRe: Align objects Pin
SergeyAndreyev25-Jan-10 6:12
SergeyAndreyev25-Jan-10 6:12 

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.