Design and Use of Moveable and Resizable Graphics, Part 2

Design and Use of Moveable and Resizable Graphics, Part 2

art 1 of this article introduced the idea of moveable/resizable graphics, covering contour presentation and explaining the design of common and special types of contours, which you can apply to a wide variety of objects. I used simple examples to illustrate the technique of involving these objects in moving/resizing operations. This part of the article describes more complex cases of moveable/resizable graphics, such as engineering plotting, as well as objects involved in both moving and rotation. You’ll see how to apply the same technique to controls, and how you can base form customization on moveable/resizable objects.

Contour Visualization
With the help of contours, you can make any object in a program moveable/resizable. To get the best results from these new features, users must know that any object inside an application is moveable/resizable, and they must understand how to use the move/resize features.

The first problem is more psychological than technical. How can engineers and scientists imagine that everything in an application is moveable/resizable if they have never seen a single scientific program with moveable plotting? Lewis Carroll’s solution to the problem?simply writing “Eat me” on top?is definitely the best for the user, but not for the programming world. In applications, perhaps the best solution is simply to rely on human nature. In the TuneableGraphics sample application, you’ll find a small button in the top-left corner of each form; some people will click this button, just out of curiosity, and they will see the contours. From there, they will try to find out what the contour lines are for, and everything else will become clear.

Users who are familiar with the idea of moveable/resizable objects and expect all objects to have such features, will be looking for the most likely places for nodes and connections. Even if they don’t know these terms, they will still determine the most likely places for moving and reconfiguring objects. The designers’ responsibility is to organize contours according to users’ expectations. Still, it can be convenient to switch contours on and off in nearly any situation, so that tiny button in the corner serves a useful purpose.

Good contour visualization in any possible situation is a problem in itself. To solve it, you can set some default parameters that will give good results in a majority of situations, and add some methods for changing those parameters.

Contours consist of nodes and their connections. By default:

  • A node is a small six-pixel square.
  • Contours are displayed in red, which is the color of connections and borders for all nodes.
  • The inner areas of all nodes are filled with white.

What were the ideas behind these default parameters? Contours are very helpful, but they are just tools; tools must be obvious, but they can’t be the main part of the view. Six pixels square is small enough not to disturb other images, but big enough to grab easily with the mouse. Big images are rarely drawn in red, so this color will be visible in the majority of situations. Filling the inner part of the node with white plus the red border of this tiny area informs the user about this additional but artificial thing. Contours do their work perfectly, but the programmer must give users an easy way (one click) to switch the contours ON and OFF.

You can change all the default contour’s settings. Part 1 discussed how to change the shape of the node from a square to a circle. If you don’t want to fill the inside of the node with white, you have to switch the node’s SenseAreaClearance flag to false:

   ContourApex [] ca = new ContourApex [1];   ca [0] = new ContourApex (0, ?);   ca [0] .SenseAreaClearance = false;

There is also an easy way to change the color of the contour, but I’ll show you how to do it later. For now, I want to discuss the code for painting contours.

The Paint event usually contains the logic about which objects to draw and in what sequence. Here’s the code from one form in the TuneableGraphics application, which shows a very interesting graphical object?Skyscrapers (see Figure 1):

Figure 1. TunableGraphics Screen: TunableGraphics skyscrapers, with the coordinate system and its contour switched on.
   private void OnPaint(object sender,      PaintEventArgs e)   {      Graphics grfx = e.Graphics;      if (bShowAxes)      {         xyzCoor.Draw(grfx);         // coordinates      }      skyscr.Draw(grfx, xyzCoor);      // Skyscrapers      if (bShowContours)      {         Movers[0].DrawContour(grfx);         // contour      }   }

There are two major graphical objects in Figure 1: Skyscrapers, and XYZcoordinates. Skyscrapers are used for financial analysis, and are not normally moveable and resizable graphical types. XYZcoordinates is a class of moveable/resizable objects. In this case, the Skyscrapers class is under the jurisdiction of XYZcoordinates and does whatever it is told to do by the last one.

The MoveGraphLibrary.dll file includes the class XYZcoordinates, described along with the other included classes, in the MoveGraphLibrary_Classes.doc documentation file. You can write your own classes and receive all the benefits of XYZcoordinates by using them together:

   public class XYZcoordinates :       GraphicalObject 

As a class derived from GraphicalObject, the XYZcoordinates object has all the features described in the first part of this article: you can see its contour on the picture, the contour of the coordinate system copies the axes, and the nodes are at the ends of the axes and in the crossing of coordinates. Using the three nodes at the ends of the axes, you can change all the dimensions of this picture. Because of the way the skyscrapers plot, taller towers in front will often block shorter towers behind, but by moving the single node at the crossing of axes, you can find the best view for any data set. Unfortunately, that is also where problems can occur.

Contour belongs to XYZcoordinates, but if it were automatically shown together with the object to which it belongs (in this case xyzCoor), then the Skyscrapers picture would block it from view, making it difficult to locate the node. Even when hidden, it still works in the same way: the changing mouse cursor signals when you have located it. At that point, you can press the mouse button and rotate the whole view, but it’s often tricky to locate the tiny node behind the towers.

This is a very important feature of showing contours: as a programmer you have to not only decide about the queuing sequence of any objects in your form, but you also have to decide about the sequence of contours in the same queue. More often than not, contours will not be next to their “parent” objects in this queue. To make it even more complex: you can exclude the “parent” objects from drawing. By switching off one parameter in the Skyscrapers’ tuning form, you can take axes out of the picture without changing anything else. However, you still have to decide when it would be best to show contours. The code for drawing the Skyscrapers picture first draws the coordinate system, next the Skyscrapers, and then the contour. Though the contour here belongs to xyzCoor (an object derived from GraphicalObject), you can see from the last piece of code that the object responsible for drawing the contour is not xyzCoor, but one of an array of Movers. At first it may look very strange, but here is an explanation for this situation.

Any object derived from GraphicalObject has a contour, but may or may not be included in the list of moveable objects (Mover’s list). It is very common to have both moveable and unmovable objects in your form. For example, you can put small sample objects of moveable types (classes) somewhere at the side of the form, but make them unmovable. Clicking one of these samples adds an identical moveable object to the form, but the sample itself stays in the same position all the time. These unmovable samples will have contours, as do all objects of their class, but because they are unmovable it makes no sense to display their contours. Mover has the list of all moveable objects on the form; objects themselves don’t know whether they are included in the list of moveable elements. Only Mover knows about all the contours that must be painted.

There may be a lot of moveable objects in your form, and in different situations you may want to show all the contours or only some of them. To show all contours use:


To show a particular contour use:


One common situation is when you want to show the contours of the moveable controls, but not to show the contours of the graphical objects. In this case use:

   for (int i = 0; i < Movers .Count; i++)   {      if (Movers[i].Source == Source.Control)      {         Movers[i].DrawContour(grfx);      }   }

For some objects you don't need to show contours at all, because users will know that they can grab, rotate, and move all such objects at any time. You can decide whether to display the contour by checking the graphical object's type.

   for (int i = 0; i < Movers.Count; i++)   {      if (Movers[i].SourceGraphical is TextMR))      {         Movers[i].DrawContour(grfx);      }   }

If you switch on contours in the main form of the Test_MoveGraphLibrary application or experiment with different forms of the TuneableGraphics application, you will see that all contours are shown in one color. You can assign each contour its own color; however, because the object delegates its painting to Mover, all contours are shown in the same color. You can change the color for all contours or for any particular contour by using one of Mover's methods:

   Movers.Color = Color.Magenta;   Movers[i].Color = Color.Blue;
Editor's Note: This article was first published in the May/June 2008 issue of CoDe Magazine, and is reprinted here by permission.

The Colored Doughnuts
Up to now, with the exception of the Skyscrapers class (which came from another application) the samples have been specially designed to explain the whole mechanism as simply as possible. In reality, you'll probably want to add contours to more complicated objects, allowing them to both move and rotate, so here's a more complicated example. One financial plot description shows a picture of several coaxial rings colored by sectors (see Figure 2), which served as another exercise to check whether the design of moveable/resizable graphics is complete. You'll find the description for class DoughnutSet in the file DoughnutSet.cs, and all the code samples for working with this class in the file Form_DoughnutSet.cs.

Figure 2. A DoughnutSet Object: A doughnut set consists of a set of coaxial rings containing an arbitrary number of colored sections.

Each ring consists of a number of sectors (two is the minimum). To distinguish the sectors, each has its own color. You can show values as numbers or as percentages, and you can change the color, text, borders, or font for each sector (you can actually change all viewing parameters; however that's outside the scope of this article). You are free to add features to set these parameters specifically, but for the purposes of this application, the parameters are mostly selected at random:

   public class DoughnutSet :       GraphicalObject   {      Point ptCenter;      List rings =         new List ();      Title title;      ...   

Nodes are located at the points where the sectors' borders cross with the inner or outer border of each ring (see Figure 2). The pairs of nodes on each border are connected to each other, but there are no connections between the pairs, so the contour consists of an unlimited number of disjoint sets.

Author's Note: You can see the best illustration of this strange contour in the main form of the application. Switch ON the contours using the small button in the corner and move the picture of the DoughnutSet object to the same location where you can see Y(x) plotting. The DoughnutSet picture will be blocked from view by that plotting, but its contour will be in perfect view above it.

Clicking the left mouse button on any DoughnutSet node lets you move it along the radius, thus changing the width of the ring. Pressing the right mouse button on any node regardless of whether it belongs to an inner or outer circle of the ring starts rotation of that ring. Grabbing any connection (pressing and holding the left mouse button) moves the entire set of rings. There are several limitations on the individual node movements (minimum radius for the inner border of the smallest ring, minimum distance between rings, minimum width of the ring, etc.), which affect the MoveContourPoint() method.

There is a moveable panel on the form with several controls that allow you to add or delete rings, change the number of sectors in any ring, and update the values for any ring.

The DefineContour() method is relatively simple (see Listing 1), but a bit longer than any previous code example; however, these rules should help you to understand the code:

  • Nodes are calculated beginning from the inner ring.
  • Each new pair of nodes represents the line on the border of two sectors.
  • All even nodes are on the inner borders of the rings; odd nodes are on the outer borders.

Moving the whole object (the Move() method) involves moving the centers of all the rings and the title:

   public override void Move       (int cx, int cy)   {      Size size = new Size (cx, cy);      ptCenter += size;      foreach (DoughnutRing ring in rings)      {         ring .Center = ptCenter;      }      title .Move (cx, cy);   }

The MoveContourPoint() method for the DoughnutSet class is more complicated; forward movement (movement along its radius) of any node must take into consideration all the previously mentioned limitations. Forward movement changes the width of the ring. The code for forward movement is too long to include here, but here's a brief overview. To move any individual node you must:

  • Calculate three generic Lists containing: the number of sectors in each DoughnutRing, their inner radii, and outer radii.
  • Check which ring and which side of the ring the node belongs to.
  • Calculate the new radius according to the mouse movement.
  • Check if the node can really move to this radius according to all the limitations (there is a special situation for the inner ring and another for the outer border of the biggest ring.

The code for rotation is much shorter, because all the rings work under the same rule. First, calculate the angle of the current mouse position (first in radians, and then transfer it into degrees). If this angle differs from the starting degree of the corresponding sector, it changes the starting angle of the ring and you must redefine the whole contour:

   else if (catcher == MouseButtons.Right)   {       // Rotation      double angleNew_Radian = -Math .Atan2         (ptM .Y - ptCenter .Y,         ptM .X - ptCenter .X);      double angleNew_Deg =         angleNew_Radian *          180 / Math .PI;      if (Math .Abs (angleNew_Deg -         fStart_Deg[iSectorInRing]) > 1.0)       {         int nAddAngle = Convert.ToInt32             (angleNew_Deg - fStart_Deg[iSectorInRing]);         rings [jRing].StartingAngle =            Auxi_Common.ChangedAngle (               rings [jRing] .StartingAngle,                nAddAngle);         DefineContour ();      }   }

For rotation, it's more accurate to base calculations on the mouse position rather than shifts; the code uses the mouse position ptM; however, the longer part of the same method that controls forward movement bases the calculations on shifts in position (cx and cy).

That's all the code you need to write, even for this complicated object. All this complexity is handled in the DefineContour() and two movement methods:

   private void OnMouseDown      (object sender, MouseEventArgs mea)   {      Movers.CatchMover(mea.Location, mea.Button);   }      private void OnMouseUp (object sender,      MouseEventArgs mea)   {      Movers .ReleaseMover ();   }      private void OnMouseMove (object sender,      MouseEventArgs mea)   {      Movers.MovingMover(mea.Location);      if (Movers.MoverCaught)      {         Invalidate ();      }   }

Pay extra attention to the OnMouseDown() method. Because you are working with both types of movements, you have to pass the pressed button as a second parameter to Movers.CatchMover().

Y(x) Plots and More
In the first part of this article, I mentioned that the huge demand for moveable/resizable graphics in scientific and engineering applications triggered this work. The results turned out to also be extremely interesting in completely different areas. However, for this particular area the new results are not simply an improvement, they are a revolutionary design for the most complicated applications.

The conflict between the widest variety of constantly changing users' demands and the fixed design of engineering/scientific applications is obvious to both sides. In attempts to soften this conflict, programmers look for improvements in interface design, where there have indeed been huge achievements in this area. But the achievements start with the misguided idea that programmers can design something that would be best for each and every user. You can (and certainly must) give users what you think is best, but also give them the tools to easily construct their own dream environment?that would be the most effective application for their work. That is the main idea of moveable/resizable graphics; scientific and engineering applications may be one of the best to show the real difference in results.

The Test_MoveGraphLibrary application contains three forms that demonstrate different levels of the new features added to standard plotting. These three forms are available via submenus under "Y(x) Plots." All these forms use the FullGrArea class (included in MoveGraphLibrary.dll), which was designed for plotting Y(x) and parametric functions. You can find the detailed description of the FullGrArea class together with the tuning forms, which are also provided, in the file MoveGraphLibrary_Graphics.doc. Here's a look at the steps for turning unmovable standard plots into fully moveable and resizable versions.

The first form in this group is Form_UnmovablePlots.cs which has two standard unmovable FullGrArea class graphical objects. You can easily make these two objects moveable and resizable, because any FullGrArea instance has these features. There is a Mover in this form and it works: the colored information panel in the form is moveable. However, because the two two objects were not added to the Mover list, they are not moveable or resizable. To eliminate all movement in this panel and make it an ordinary form with all fixed elements, simply comment all the code that deals with Movers, including the OnMouseDown() and OnMouseUp() methods and the second part of the OnMouseMove() method. The first part of OnMouseMove()changes the mouse cursor to indicate that it's possible to double-click the left button to open the tuning forms. This tuning mechanism works regardless of whether the FullGrArea object is really moveable; you can see that the variable Movers is not mentioned in the OnMouseDoubleClick() method.

Conversely, to make the unmovable (standard) plots moveable, just include them into the Movers' list. Form_OneMovablePlot.cs, the second form of this group, demonstrates that by adding one line of code:

   Movers.Add (area);

Everything else in this form is exactly the same as in Form_UnmovablePlots (except that it has only one plot rather than two). This form demonstrates how you can quickly change the plotting in any engineering or scientific application from unmovable into moveable, but without changing the main design ideas.

The real power of moveable/resizable graphics for the area of scientific and engineering applications will be unleashed when users get to decide what, when, and how to show the contents. This is the case in Form_MovablePlots.cs.

For any other form of the Test_MoveGraphLibrary application, I can show you a screen image and write definitively that "this is how it looks." However, here you can organize any number of different plots, all of which are moveable and resizable, so there's no way to predict how this form will look. But as a designer, I need to provide the interface by which users will organize and analyze the different functions. This application has one small limitation: users can select the functions only from a predefined set. The main form of the TuneableGraphics application uses the same interface without this limitation; there users can explore any number of arbitrary functions.

From a programming point of view, implementing moveable/resizable plotting for an unlimited number of areas differs only slightly from the previous case of a single plotting area. Here you have the same single Mover and the same simple methods for the MouseDown, MouseUp, and MouseMove events. To organize these without any problems (and mistakes) in moving, visualization, and tuning, there needs to be a reliable link between the function and the area where it is shown. This link is provided by the additional class PlotOnScreen:

   public class PlotOnScreen   {      DemoFuncType funcType;      FullGrArea grArea;

Functions that you decide to show on the screen are organized into a List:

   List plotInView =      new List();

All possible operations?adding functions, painting, moving, tuning, changing the order, deleting?are accomplished using only the objects in this List, so there will be no discrepancies between the real function and its view.

Figure 3. MoveablePlots Form: The form contains moveable plots and panels, shown here with their visualized contours.

Figure 3 shows two plots and two panels; the plots are moveable and resizable. The panels are also moveable, but only the one containing the list of functions is resizable. You can see the difference in the contours of the two panels.

Converting applications from designer-driven into user-driven applications is not a five-minute job; however, simply changing graphics?to make them all moveable and resizable is quick. The Test_MoveGraphLibrary application includes two special cases of Y(x) plotting; they have such special features that each one visually represents its own subgroup.

The first case handles simultaneous plotting in the same area of different functions that use different scales. This is not a rare situation; for some complex analysis you have to compare different processes to look for similar behavior or abnormalities. While you can certainly place two plots side-by-side for visual comparison, doing so requires a lot of extra screen space. It would be much better to put them into the same graphical area. You can do that by using the previously mentioned FullGrArea class, because its drawing methods for Y(x) functions can use border values passed as additional parameters. Those methods only partly solve the problem, because a user can change the visible scales but can do nothing with these additional parameters. So a better solution would be to use the MultiScaleGrArea class, which allows you to organize the same graphical area with an unlimited number of scales.

The behavior and tuning forms of the MultiScaleGrArea class are as close as possible to the FullGrArea class, but there is an addition in the contour. Each scale is represented in the contour by an additional segment with two nodes at the ends of its main line; by using these nodes, users can put each scale in any appropriate position in relation to the main plotting area. Because each scale has its own tuning form, the user is now in full control of all the plotting details.

Figure 4. Profile Class Objects: The Profile class handles objects that are resizable but not separately moveable.

The second case is special not only for Y(x) plotting, but for the whole application. The Profile class perfectly fits the idea of presenting contours; however, it is a unique class within MoveGraphLibrary.dll?the only class of resizable, but not moveable objects. This object is not separately moveable (on purpose), but it can be moved along with the FullGrArea class, on which it can reside. Form_Profiles.cs demonstrates two different cases of using Profile: In the top-left corner you can see the Profile object inside a Rectangle; two other Profile objects reside on the FullGrArea object, which is the subject of all standard moving/resizing transformations. Figure 4 shows a view of this form. The code samples used to explain the Profile class are also from the Form_Profiles.cs file.

A Profile object is a set of unconnected nodes (you can easily take out the lines that you see in Figure 4) by using Pens.Transparent). If you do that, you'll have a set of isolated nodes?exactly what the Profile is in reality. Two end nodes are always located on the left and right borders of the rectangle and you can only move them up or down; you can move all intermediate nodes freely between upper and lower boundaries, but cannot move them farther to the sides than the neighboring nodes. Each node is characterized by its location (Point) and two double values from inside the ranges, defined by the borders' values. You can initialize the Profile object either by two arrays of double values or by an array of points (Point []):

   double [] xs = new double [9] { ... };   double [] ys = new double [9] { ... };   profile [1] = new Profile(rc,       area.ValuesOnBorders, xs, ys,       ContourApexForm.Circle, 3);   pts = new Point [] { ... };   profile [2] = new Profile(rc,       area.ValuesOnBorders, pts,       ContourApexForm.Square, 3);

As in all other cases, you organize node movement with MouseDown, MouseMove, and MouseUp events; however, several additional lines of code in the OnMouseDown() method handle one more unique Profile aspect. For all other moveable/resizable objects the contour is organized at the moment of initialization and users can transform them only later; with the Profile object users can add or delete intermediate nodes at any time. The contour must be redesigned each time, which is done automatically inside a couple of methods:

   private void OnMouseDown(object sender,      MouseEventArgs e)    {      if (mea .Button ==         MouseButtons .Left)       {         for (int i = 0;            i < profile .Length; i++)          {            int iDot = profile[i].InsideDot(               e.Location);            if (iDot < 0) {               iDot =profile[i].InsertNewDot(e.Location);            }            if (iDot >= 0) {               profile[i].Selected = iDot;               break;            }         }         Movers.CatchMover(e .Location);      }

If the user has not pressed the left mouse button inside a node, there is an additional check to see if it is close enough to any segment between consecutive nodes to insert a new node there. If so, the application adds the new node.

There are also several additional lines of code in the OnMouseMove() method. If the Profile object resides on a FullGrArea object, the profile's transformation depends on whether the host area was moved or resized. In the first case, the Profile is moved in exactly in the same way as the host area; in the second case, the profile's area is changed to the new area of the FullGrArea object.

Another Branch of Evolution
All the previous explanations showed that to make an existing class of objects moveable and resizable, or to design a new class with these features, you must derive this class from GraphicalObject and override three methods of this abstract class. That's not strictly true; there's another way that yields the same excellent results. C# does not support multiple inheritance, although interfaces can help you get around the limitation. But if you have spent a lot of time designing your class system and it's working perfectly in your applications, then requiring mandatory inheritance from GraphicalObject order to implement moveable/resizable graphics will not be palatable. You can consider this method a separate branch of moveable/resizable graphics evolution.

The TuneableGraphics application contains a form that you can fill with an unlimited number of colored chatoyant polygons (see Figure 5). You can move them around and reconfigure them in any possible way; you can switch their contours on, and see that they consist of standard nodes and connections. On initialization, each object is a regular polygon; nodes are in the apexes and in the center point. All nodes, including the center point, can be moved anywhere, producing very strange figures. Rotation is always around the center point, but for rotation you can press the right mouse button anywhere inside the figure (not only on other nodes). The polygons look and behave just like all the other moveable and resizable objects described so far, but ChatoyantPolygon is not derived from GraphicalObject:

Figure 5. Chatoyant polygons: These polygons are both moveable and resizable, but are not derived from GraphicalObject.
   public class ChatoyantPolygon   {      Graph graph;      ...      public Graph GetGraph      {         get { return (graph); }      }

To implement the moving/resizing algorithm, ChatoyantPolygon includes a field of type Graph, which is derived from GraphicalObject:

   public class Graph : GraphicalObject

The object of the Graph class is simply a set of points with no connections between them, so all three mandatory methods for this class are very simple. Move() means moving every point of the set. MoveContourPoint() is at the same level of simplicity, because there are no neighboring point restrictions; any point can be moved.

The objects of the ChatoyantPolygon class look interesting and you can transform them into really complicated figures, but from the point of involving them in moving and resizing they differ from all previously mentioned graphical objects only in the direction of simplicity. A paradox? All previously mentioned graphical objects had some basic primitive forms (Rectangle, Point) and some sizes; combination of these values provided an initial shape to which you added the contour. Those classes' Move() methods contained some simple, but necessary lines of code to move those primitive forms. Their MoveContourPoint() methods had to take into consideration all possible restrictions from the neighboring nodes. In ChatoyantPolygon, there is nothing to consider but Graph, so no other move code was needed.

ChatoyantPolygon initialization also initializes the Graph, by calling its DefineContour() method. Class ChatoyantPolygon has a GetGraph property, which returns this designed graph; Mover deals with the contour of this graph.

For example, suppose you initialized an object of the ChatoyantPolygon class in your program:

   ChatoyantPolygon poly=   new ChatoyantPolygon (...);

You can't register this object as moveable/resizable and add it to the Mover's list, because the object itself is not derived from GraphicalObject, but you can use its Graph:

   Movers.Add     (new MovableObject (poly .GetGraph));

The same three mouse events will be used here in the same way as with all other objects, with one minor addition for rotation. When starting a rotation (by clicking the right mouse button on any inner point of the object), the code must remember the initial angle of the mouse and all the nodes:

   private void OnMouseDown       (object sender,      MouseEventArgs e) {   ...   else if (e.Button ==      MouseButtons.Right) {      iPoly = ClickedPolygon (e.Location);      if (iPoly >= 0) {         Graph graph = Polygons[iPoly].GetGraph;         Point ptC = graph.Center;         Point[] ptAp = graph.LoopPoints;         ...         fRotationStart_Radian =             -Math.Atan2(e.Y - ptC.Y, e.X - ptC.X);         for (int k = 0; k < nApexOnMove; k++) {           fApexRadius [k] =              Auxi_Geometry.Distance              (ptC, ptAp[k]);           fApexStart_Rad[k] = -Math.Atan2 (              ptAp[k].Y - ptC.Y, ptAp[k].X - ptC.X);         }

For any new mouse position, you now know the change in its angle, which you can use to adjust the angles of all nodes. At that point, the entire polygon, regardless of its shape, rotates with the mouse. There is a catch: the Graph class has such a primitive MoveContourPoint() method that it doesn't handle rotation. So when rotating a polygon (see Listing 2), after all nodes have been adjusted, you get the new correct graph from the rotated polygon and substitute this new element in the Mover's list for the previous value:

   Movers.RemoveAt(iPoly);   Movers.Insert(iPoly, new MovableObject      (poly .GetGraph));

I can't say that the classical way of organizing moveable/resizable graphical objects by deriving them from GraphicalObject is better or worse than this one. When designing new classes, I tend to use the classical way, but I don't see any downsides to this method.

Controls Work, Too
All the code samples that I showed before and all the explanations were about designing and working with moveable and resizable graphical objects, but many applications rely on the coexistence of graphical objects and controls. If the mechanism for making graphics resizable and moveable simply ignores controls, then the solution loses a significant part of its value. The design process began with the idea of eliminating the difference between graphical objects and controls (which are moveable and resizable by default), so everything was designed so it works identically with both types of elements; in other words, you can apply the mechanism for turning graphical objects into moveable and resizable object to controls just as easily. As an example, see the moveable panels in nearly every form of the Test_MoveGraphLibrary application. However, there is one small difference between organizing contours for graphical objects and controls.

You organize the contour for a graphical object at the moment of the object's initialization in the DefineContour() method. You must also develop two other methods for each class of moveable and resizable graphical objects:

  • Move() describes the movement of an object as a whole.
  • MoveContourPoint() describes separate movements of each node.

Move begins using all three methods the moment you include the graphical object into Mover's list.

Controls do not have such methods, and you don't even need to think about them, but you can include controls in the list of moveable objects:

   Mover.Add (Control control);

When you do that, Mover itself organizes the contour for the control. The contour will be slightly outside the control's boundaries, and it designs all nodes of this contour with the parameter MovementFreedom.None, so the contour will be nonresizable. That solves many situations, where what is really needed is to be able to move the control around the form, and resizing is not required. But any control is resizable by default. In case you need to use this helpful feature, there is another way to organize the control's contour:

   Mover.Add(Control control,       ContourResize resize,      int minW, int maxW,      int minH, int maxH)

The second parameter defines the type of contour you want to link with the control. I hope that the names of the constants in this enumerator list clearly explain each case. Here's an example:

   enum ContourResize { None, NS, WE, Any };

The four parameters for organizing a control's resizable contour define the ranges of possible changes in width and height of the control. There are also variants of the Mover.Add() method with an additional parameter that defines the distance between the control's borders and its contour.

All standard controls are rectangular and get a default contour slightly outside their borders. Nonresizable contours are simply rectangles with no visible nodes; all information panels in the application show this type of contour. When a contour is resizable in only one direction, there will be two small nodes in the middle of the two opposite sides. Fully-resizable contours get eight standard nodes: four in the corners and the other four in the middle of the sides.

You can turn controls into resizable versions individually; however, I prefer to add a contour to a panel or GroupBox, thus giving one contour to a group of controls that I want to keep together. You can easily organize relocation of controls on a panel through the standard ClientSizeChange event. Form_MovablePlots.cs contains an example of such a panel its associated code.

Form Customization
At first, including controls in the same mechanism that you use to turn graphical objects into moveable and resizable ones doesn't seem so significant: you already had moveable and resizable graphics, now you have controls with the same features. Implementing these features gives users much wider flexibility. You can see an excellent example of using these features in their pure form in Form_MovablePlots.cs, which I discussed above (see the section "Y(x) Plots and More"). If you take a complicated form with a lot of different controls and graphical objects and then make the main parts of this form moveable and resizable, you'll see that quantity is really turning into quality. Implementing this technique for all form objects gives users an unparalleled level of dialog customization. It's also very interesting that moveable/resizable graphics turned out to be of exceptional value for forms that consist of only (or mainly) a large number of different controls.

There is a huge demand for customization of forms for users' purposes. There are tons of papers about the ideas in this area, but mostly there are two major directions for organizing this customization:

  • By using standard anchoring/docking features of all forms.
  • Via a context menu or by using several additional controls to allow users to set one of the predefined views of the form.

The first direction is called dynamic layout, something Microsoft has put huge effort into for developing dynamic layout tools for programmers and there are interesting results in this direction. The second direction has various names; the most popular term recently is adaptive interface. The links between the two mentioned types of transformations and the two widely used terms are fairly tentative: dynamic layout is a subset of adaptive interface, but the two mentioned ways of transforming forms use completely different techniques.

Implementing moveable and resizable objects shows a new way to transform forms, but there's no epic battle of "Dynamic layout vs. Moveable graphics"; these two things are orthogonal. Dynamic layout is a programmer's tool to help make forms look good under changing conditions. Under dynamic layout, all transformations are predetermined; they are only as good as the programmer. Moveable/resizable graphics on the other hand, although implemented by the programmer, become a user tool. Each user can determine what he or she wants to see. The beginning of this article discussed the Skyscrapers object (see Figure 1); to illustrate the new ideas as they relate to form customization, here's a look at the way users can fine-tune this object.

Figure 6. Fine-Tuning Skyscrapers: The default view of a tuning form for the Skyscrapers object.
Figure 7. Customized Tuning Form: By rearranging the various parts of the default tuning form, uses can easily access the portions they need without obscuring underlying images.

The Skyscrapers' tuning form (see Figure 6) makes changing any parameter easy. However, if a user has already set most parameters and wants only to change colors, when there are a significant number of different colors, the user will have to enlarge this form?thus covering a significant part of the screen. At the same time the user needs to use only 15-20 percent of the form's area (the "Colors" band in the middle part of the original form), so using screen space in this standard way is very inefficient. However, a user willing to spend 2-3 seconds moving and resizing the parts of this form can design an absolutely different view (see Figure 7).

I want to emphasize that:

  • The two figures both show the same form.
  • The second view is not a programmer-predefined variant.

There is no list of predefined variants for this form: there is one default view (shown in Figure 6), which users can restore at any time by clicking the button in the top-left corner. But there is also an easy way (press-move-resize) to change the whole form to whatever the user needs.

You don't need anything beyond the methods already described to implement this capability. You use the same three mouse events and the Mover object that deals with the mixture of graphical objects and controls. The designer needs only to decide what level of flexibility to organize. In this case, I decided that it would be better to keep several controls together in this form, so I put them all under the control of one moveable, non-resizable frame, without putting any other restrictions on users' choices.

There are different ways to customize a form and the variety of applications is huge. There is no silver bullet, but using moveable/resizable graphics and applying this technique to controls and groups of controls may be extremely helpful in a lot of applications?especially for the most complicated forms.

Any New Problems from the New Features?
Adding new features to the existing graphical objects can cause some conflicts?the same types of problems that occur when adding new buildings into a populated urban area. The screen real estate of most existing applications is already well populated; there are a limited number of available mouse-generated events?and often, each event is already strongly linked with an expected reaction. Adding something new into this organized universe can certainly cause conflicts.

You can eliminate or minimize these conflicts by carefully designing contours and by shrinking the area of new commands to the proposed system of nodes and connections. However, there are other ways of solving such conflicts. Here are some of the rules that I tried not to break while building moveable and resizable solutions, along with some thoughts for and against my decisions:

  • I use a left-button press to start forward movement of the objects or to reconfigure them, and a right-button press to start rotation.
  • Use a left button press for forward movement of an object only in the sensitive area of any connection. While it's no problem to start this movement by pressing anywhere inside the object, left-button clicks are already widely used, primarily to change the z-order of objects to bring the selected object to the top. I found it very useful to be able to grab and slightly move a plot to see more information without having to bring it to the top and alter the entire view of the form. If starting forward movement will affect the entire area of the object, then the object in motion will be automatically brought to the top position. That is exactly what Windows typically does now, so it's definitely a consideration, but I still think that providing users with the possibility of moving an object on the lower layer without disturbing the whole hierarchy is very useful.
  • Object rotation is usually started by a right button press inside a node. Again, it's fairly easy to start rotation by clicking any point inside the graphical object. For example, the TuneableGraphics application uses arbitrary polygons, and you can start the rotation of those polygons by clicking any point inside the object. From the user's point of view, starting the rotation by right-clicking anywhere inside the object would be much easier. But the reason behind not implementing this technique for PieChart, Ring, and so on is simple?I am not sure that it would be correct. Usually, the right-mouse click pops up a context menu. I don't like the idea of starting two different processes with the same mouse click (although with the polygons that's exactly what I'm doing). Also, expanding the start of rotation to the whole object's area while restricting the start of forward movement to the connections is awkward.

Finally, the idea of moveable and resizable graphics will spread quickly (the advantages of such graphics is impossible to overestimate), but the development of applications for such graphics will require some setting of commonly used rules of interaction for moving and rotating the objects. It will be much better for users if all applications implement movement and resizing based on the same system of commands.

Moving/Resizing Summary
Contours, described in Part 1, make objects moveable and resizable. Here's a summary of how the whole moving/resizing process is organized:

  • To make any graphical object moveable/resizable, you must derive it from GraphicalObject.
  • You can include controls into moving/resizing operations just as they are.
  • To organize a moving/resizing process, you need a Mover instance:
   Mover Movers = new Mover();
  • Mover supervises the moving/resizing process for graphical objects and controls that are included in its inner List<>:
   Movers .Add (...);   Movers .Insert (...);
  • Users perform moving and resizing operations with a mouse organized through the three standard mouse events: MouseDown, MouseMove, and MouseUp.
  • MouseDown starts moving/resizing by grabbing either the whole object (moving) or a single node (resizing). The only mandatory line of code in this method is:
   Movers .CatchMover(...);
  • MouseUp ends moving/resizing by releasing any objects that could be involved in the process. The only mandatory line of code in this method is:
  • MouseMove moves either the entire object or a single node. There is one mandatory line of code in this method; to render movement you must call the Paint method:
   Movers.MovingMover(mea.Location);   if (Movers .MoverCaught)   {      Invalidate ();   }
  • If contours must be visible, use one of the drawing methods, for example:
   Movers.DrawContours (grfx);
  • If needed, you can use several Mover objects to organize moving and resizing. Each Mover deals only with the objects from its own List<>.

Final Remarks and Some Ideas
Moveable/resizable graphics are a powerful tool. By adding these features to the objects inside your program, you get the same flexibility on the inner level that Windows provides on the upper level. It's not surprising at all that the result is very similar to what you have on the upper level. The upper level includes all windows in the Z-order; the last one to be called is shown on top of all previous windows. When you click any window the system changes the Z-order and moves the selected window to the top. Now you have a tool to do exactly the same at the inner level; the only difference is that the system doesn't handle it for you behind the scenes; you, as a designer, have to do it explicitly. This article gives you the tools (methods) to do it easily.

The idea of moveable/resizable graphics is not simply new; it is so revolutionary that it's difficult to add it as a basic part of your design work in a week or even a month. I've been using moveable/resizable graphics in applications for a year and a half, and I still find myself solving problems in an ordinary way from time to time. Only later do I understand that with the help of moveable/resizable elements I could achieve a much better solution.

It's very difficult to predict what moveable/resizable graphics can bring into most existing applications. Look back and try to imagine the PC world with Windows stripped of its flexibility? Can you imagine a version of Windows where the system (instead of you) decides what and where to show each window? That's what you have now on the inner level. I'm absolutely sure that after the idea of moveable/resizable graphics catches on, modern-day fixed applications will appear as ancient as DOS applications do today.

The Test_MoveGraphLibrary application shows the details of this new technique of turning static graphical objects (and controls) into moveable and resizable ones. To make things absolutely clear, I tried to keep all sample classes and all forms for demonstration as simple as possible?that's why each form works with only one particular moveable/resizable class of objects. However, in real applications you would often like to use different moveable objects together. To show that it is no problem to do so, Form_Main.cs contains objects of completely different types. It's very strange to see standard scientific plotting y(x), houses, sets of colored rings, and a couple of colored polygons in one form. They have nothing in common except that their design is based on the same described algorithm. Even so, they can all coexist in the same space (form) without any conflicts.

To gain a better understand of what these techniques allow, look into the TuneableGraphics application, which includes a lot of different samples. The idea of that application was not to develop something extraordinary for one particular area; the main idea was to demonstrate the possibility of amazing results for absolutely different objects and areas.

Perhaps the area that can benefit most from moveable/resizable graphics is "financial" graphics. Millions of people analyze financial data and the effectiveness of their analysis greatly depends on how close the view of the numerous plots is to their expectation of the best and the most informative view. Nothing could be better than a system that allows everyone to view plots using the best personal arrangement. When you analyze a lot of information on the screen, the best system is the one that lets you size and organize the entire view at any time, without stopping the application or going somewhere else for extra tuning.

Scientific and engineering applications, which triggered my work on moveable/resizable graphics, can also gain from this new type of plotting. Using this new plotting, programmers can redesign the most complex systems, turning them from designer-driven into user-driven applications, and bring analysis of the most difficult engineering and scientific problems to another level. Instead of developing tiny details within several scenarios for calculations, programmers will design tools that allow any possible scenario or variant of research work in one or another particular engineering/scientific area.

Several documents and programs exist that can help you to understand and use moveable/resizable graphics effectively. You can find these in the MoveableGraphics project.

Many thanks to those who for years helped discuss the design of scientific plotting. Special thanks to Dr. Stanislav Shabunya from the Heat and Mass Transfer Institute. My work on tuneable graphics was triggered by a mutual project years ago, and his remarks during the fall of 2007 caused some significant improvements in clarity.

See also  Comparing different methods of testing your Infrastructure-as-Code

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist