Login | Register   
RSS Feed
Download our iPhone app
Browse DevX
Sign up for e-mail newsletters from DevX


Design and Use of Moveable and Resizable Graphics, Part 2 : Page 4

Windows are moveable and resizable; graphics and controls inside applications are not. The second part of this article explains how to design complex moveable and resizable graphical objects and use moveable and resizable elements in your Windows Forms applications.

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.

Comment and Contribute






(Maximum characters: 1200). You have 1200 characters left.



Thanks for your registration, follow us on our social networks to keep up-to-date