About  Test_MoveGraphLibrary                                                                                       July 2007

Movable and resizable graphics is presented in the form of several programs, one library (DLL file) and some documents.

Test_MoveGraphLibrary.zip     contains the whole project, demonstrating the use of MoveGraphLibrary (DLL file is included).  This application demonstrates the use of some classes from the library and the design of new class of movable / resizable graphical objects.  The description of the program is in Test_MoveGraphLibrary_Description.doc.   It is the current document.

MoveGraphLibrary.DLL            the library itself.  Classes are described in MoveGraphLibrary_Classes.doc

TuneableGraphics.EXE                demonstrates the use of movable / resizable objects in absolutely different areas.   Description of the program is in TuneableGraphics_Description.doc

NewDesignParadigm.doc             an article about the ideas and consequences of using such graphics in complicated programs.

 

Test_MoveGraphLibrary is designed to show in the most simple way how to:

·        use the standard plotting from this graphical library;

·        make these plots movable and resizable;

·        organize your own movable and resizable graphical objects.

For better explanation and easier understanding of this technique there are three different dialogs in addition to the main form.

Unmovable plots    shows how to create plots.  These plots are unmovable (which is a standard feature for widely used plots), but the standard dialogs for changing of all the parameters can be used. 

Main window          shows the way to make one plot movable and resizable.

Movable plots         allows adding the unlimited number of movable and resizable plots.  The controls in this form are also movable.

Houses                      explains the design of new movable and resizable objects.

 

Creation, initialization and drawing of plots (samples from file Form_UnmovablePlots.cs)

Declare two objects for plotting

FullGrArea area_0, area_1;

Initialize these objects

area_0 = new FullGrArea (new Rectangle (50, 30, 350, 180),

                         Side .S, -4, 25, 5,

                         Side .W, 1.2, -1.0, 0.5);

The first parameter defines the real plotting area, others define two scales. Horizontal scale will be at the bottom, and its range will be [-4, 25] with the grid step of 5.  Vertical scale will be on the left, and its range will be [1.2, -1] with the grid step of 0.5.

Huge number of plot’s parameters can be changed either through methods (see description of classes MoveGraphLibrary_Classes.doc) or with the help of additional tuning dialogs while the application is running.

Change some of the parameters programmatically

area_0 .HorScale .DecPlaces = 0;

Numbers on the horizontal scale will be shown without any decimal places after dot, so they will look like integers.

area_0 .HorScale .SetRotationCenter (TextBasicPoint .N, 0, 2);

Numbers on the horizontal scale will be shown as centered to corresponding ticks on the scale.

Draw all the auxiliary parts of the full plotting area

area_0 .Draw (grfx);

The auxiliary parts include both scales, title and some lines (borders and grids) of the main plotting area.  Each of these parts can be added to or excluded from plotting, so there is a wide variety of possible views.  You can omit the drawing of auxiliary parts and draw only the needed functions, but it will look like a smile of the Cat that had already vanished (in some cases it is very useful).

Draw the functions

area_0 .DrawYofX (grfx, new Pen (Color .Blue), new Delegate_DblOfDbl (Math .Sin), …

Draws the standard y = sin(x) function.

area_1 .DrawYofX (grfx, 0, new Delegate_DblOfDbl (Sin10xCosx),…

Draws non-standard function, declared as

        double Sin10xCosx (double x)

        {

            return (Math .Sin (10 * x) * Math .Cos (x));

        }

There are a lot of variations for drawing functions (see description of classes MoveGraphLibrary_Classes.doc).

All viewing parameters can be changed through the set of additional dialogs, which are called by L_DoubleClick on the main plotting area or on scale.

private void OnMouseDoubleClick (object sender, MouseEventArgs e)

{

    if (mea .Button == MouseButtons .Left)

    {

        if (area_0 .ShowHorScale && area_0 .InsideHorScale (mea .Location))

        {

            area_0 .HorScale .ParametersDialog (this, ParametersChanged, null, null);

        }

        else if (area_0 .ShowVerScale && area_0 .InsideVerScale (mea .Location))

        {

            area_0 .VerScale .ParametersDialog (this, ParametersChanged, null, null);

        }

        else if (area_0 .InsideMainArea (mea .Location) ||

                 area_0 .InsideTitle (mea .Location))

        {

            area_0 .ParametersDialog (this, ParametersChanged, null, null);

……..

Text Box:  This is the dialog for changing of general parameters and placing of both scales and title (also for switching these additional parts ON / OFF).  Colored buttons will open standard Color dialog to change the colors of different parts and lines.  To position the title in the new place L_Press the blue mark on the sketch and move it around.  This is only a sketch, so while moving this mark around look at the position of the title on the original form.  Because the sketch itself is recalculated on every change in the original FullGrArea object and the position of the title is used in the defining of the sketch sizes and coordinates, so in some situations (when you try to put the title far away from the plotting area) the process of very accurate placing of the title can become tricky; in such cases use two text boxes in the right-bottom corner of the dialog.

All changes in this dialog have an immediate effect on the original plotting.

Samples of predefined colors in the left bottom corner can be grabbed and moved to another place thus changing their order and effecting the view of the plotted functions (if numbers and not real pens were used as parameters for plotting).

The dialogs for tuning the parameters of horizontal and vertical scales have slightly different view (this was done to avoid confusion); next picture shows the view of such dialog for vertical scale.

Though any changes in all tuning dialogs have an immediate effect on original plotting here is the exception from this rule: to put into life the new boundary values or grid’s step (or minimum number of grid’s lines) you have to type the number and then press Enter; only after that the program will analyze the new value.

On the sketch there is a sample of number with nine possible spots for rotation center and a frame around this sample.  Rotation center is always shown in red; to set the rotation center L_Click on any colored spot.  To move the numbers L_Press on the spot and move the sample, and with it all the scale’s numbers will move.  Your real numbers can be of different length and shorter or longer than the sample, so, while arranging the best view, look at the movements of real numbers and not of the sample.

To rotate the numbers R_Press at any place inside the colored frame (you don’t need to press on the colored spot!) and turn the numbers; for information the angle is shown below the sketch.

Text Box:  Any scale may have a label.  This is a possibility of an additional explanation for the scale (or for the whole plot) which can be put anywhere and may have absolutely different views.  Type the label in the special text box below the sketch; your text (label) will appear on your original plot but on the sketch you’ll see the sample word Label with the blue mark in the middle.  Also the controls at the bottom for setting other label’s parameters will become enabled.

Use the blue mark in the middle of the word Label to move around the real one; just L_Press it and move.  The real label can be a big piece of text; in need of more space on the sketch for better positioning of the label simply enlarge this tuning dialog.  To avoid the mess of different colors on the sketch the label has only one colored sensitive mark and the turning of the label is done not with the mouse (as turning of the numbers) but via control in the right bottom corner.

Title, numbers and labels – all of them may be in different fonts.

Now let’s have a look at how the movable and resizable features can be imposed on FullGrArea objects.  The best way to do it is to look into the code of the main form (file Form_Main.cs).

Moving and resizing of objects (samples from file Form_Main.cs)

The single FullGrArea object is declared and initialized in the same way as was described earlier:

FullGrArea area;

area = new FullGrArea (new Rectangle (80, 70, 450, 310),

                       Side .S, 0.0, 4 * Math .PI, Math .PI,

                       Side .E, 1.0, -1.0, 0.5);

The new and the most important part is the object of class Mover:

Mover Movers = new Mover ();

This is the class to implement all moving and resizing features for graphical objects and controls in the form.  To make the object movable you have to include it into Movers:

Movers .Add (area);

It doesn’t matter if you are making movable some graphical object (as in this case) or any control (as will be shown later).  Any object is turned into MovableObject, and Mover contains the List of all included movable objects, so public methods of this class are very similar to the methods of standard List<> and objects included into Mover can be reached via standard indexing [].

To be movable and resizable any object must have some sensitive areas by which it can be grabbed and moved or resized.  (For more detailed information about these areas - nodes and connections - see NewDesignParadigm.doc.)  FullGrArea objects were designed with the possibility for being moved and resized, so here we are only looking at using these features; later in this document I’ll explain how to design classes of objects with such features.

Three handles are used for all moving and resizing operations.

To grab any object I use MouseDown event

private void OnMouseDown (object sender, MouseEventArgs mea)

{

    if (mea .Button == MouseButtons .Left)

    {

        Movers .CatchMover (mea .Location);

    }

}

In MouseDown event I am checking which mouse button was pressed.  Through all my programs I am using left button to start a forward movement and right button to start the rotation (when I need it).  To distinguish between two cases there is another form of CatchMover() method with two parameters (see MoveGraphLibrary_Classes.doc).  When the second parameter is not declared, then MouseButtons.Left will be substituted as default.

MouseUp event will release any previously grabbed object

private void OnMouseUp (object sender, MouseEventArgs e)

{

    Movers .ReleaseMover ();

}

The third handle – MouseMove - is used to move or resize any previously grabbed object or to inform that something underneath the mouse cursor can be grabbed; this information is signaled by the change of cursor shape.

private void OnMouseMove (object sender, MouseEventArgs mea)

{

    Cursor cursor = Cursors .Default;

    if (area .InsideSensitive (mea .Location))

    {

        Cursor .Current = Cursors .Hand;

    }

    Movers .MovingMover (mea .Location);

    if (Movers .MoverCaught)

    {

        Invalidate ();

    }

}

Now area object is movable and resizable and you can easily move it to any location or change the sizes.  The small problem is that those who are not familiar with such graphics do not know anything about these sensitive areas (nodes and lines) and don’t know where to grab the graphics for moving.  To make contours visible I added small button in the corner of the form; by clicking this button you switch contours ON / OFF.  Such kind of indication is also needed when the contour’s position is not obvious from the shape of the object.  (Later I will show the objects for which such indication is not needed.)

Declare the flag of showing contours

bool bShowContours = false;

Change its value on each button click

void Click_btnContours (object sender, EventArgs e)

{

    bShowContours = !bShowContours;

    Invalidate ();

}

And add several lines into Paint event

if (bShowContours)

{

    for (int i = 0; i < Movers .Count; i++)

    {

        Movers [i] .DrawContour (grfx);

    }

}

I don’t need such loop in this case of single movable object, but it is possible that I’ll decide to add some other objects to the form, and this will always work correctly.

If you have an object with a lot of tunable parameters and spent a lot of time to show this plot in the best possible way, it would be a disaster to lose all these changes every time you close the program (or dialog).  There is a pair of methods in this form to avoid such a disaster:

void WriteToRegistry ()

void ReadFromRegistry ()

This pair of methods is using the pair of FullGrArea methods

area .IntoRegistry (regkey, "A");

area = new FullGrArea (regkey, "A");

The second parameter is used for identification of the area, and to restore the area correctly this parameter must be identical in both calls.  In this case of the single FullGrArea object the parameter is very simple; in the next sample of this program several FullGrArea objects are used, so each of them must be stored and restored with the unique identification.

Text Box:  Multiple movable plots (samples from file Form_MovablePlots.cs)

Though to open this form you have to click the menu position MovablePlots and the form is called Form_MovablePlots, at the beginning you’ll see no plots here, but only the information how to populate the form with them.

However, there are two panels; one of them contains the empty list (no plots yet), another – information about several commands.  Both of the panels are movable because the same technique for moving was used, so it can be used even without any graphical objects at all.

Mover Movers = new Mover ();

Movers .Add (panelWithList);

Movers .Add (panelComments);

Use R_Click on any empty place (at the beginning it is everywhere except those panels) to open another dialog for function selection.

There are 16 predefined functions in this program.  The same functions are used in TuneableGraphics application, but there is also a mechanism to add, draw and store any function you would type in.

Text Box:  When you select (by clicking) any of these functions it will appear in the previous form; the initial viewing parameters of the new plot will be set by the program; you can change them through the tuning dialogs.  The left top corner of the new plot will be in the same place where you made R_Click, so if it was done in the right bottom corner of the form then you’ll see only the tiny part of the plot.  From the beginning each new plot is added to the Movers, so you can move the new plot to any position.  All plots are organized into List, but this is a

List<PlotOnScreen> plotInView = new List<PlotOnScreen> ();

Class PlotOnScreen combines the FullGrArea object with the identification of the function, shown in it.

When there will be some plots inside the form, then R_Click on any plot will open context menu.  Several positions in this menu are duplicating the actions that can be done in another way, like switching the contours ON / OFF or opening the dialogs for parameters’ tuning.  Other positions of menu give the opportunities to do something new, for example, saving different parts into Clipboard.  There is also the position in menu to erase the touched plot.

 

Up till now I was describing mostly the features of FullGrArea class which was designed for very efficient plotting with a lot of possibilities.  There are some other classes for plotting included into MoveGraphLibrary.DLL, like Histogram and PieChart, and may be there will be more in the nearest future.  But MoveGraphLibrary is not only the collection of useful plotting classes, there is an instrument to make movable and resizable the graphical objects of arbitrary form and absolutely different purposes far away from engineering or financial plotting.

Let’s look at the creation of movable graphical objects.

Design of movable / resizable objects or

your own town (samples from files  SimpleHouse.cs  and  Form_Houses.cs)

There was a nice time when you could take a pen and a sheet of paper and build your own house.  No restrictions, just imagination.  (And no bills, repairs, taxes – I want to be back!!!)  If you liked your first house you could put another one near by, you could construct a street, a village, a town (if you had enough space on the paper…).  Let’s do such a thing.

There was only one dark side at that time: if you drew anything wrong you had either to find the way to erase it, or you had to abandon the whole project and start the new one in the new place.  Not a bad idea, but it’s a pity to abandon the nice village only because one cottage got the wrong color.  So let’s do the same but decide from the beginning that all our houses will be movable, resizable and with the changeable colors.

Text Box:  The basic form of our houses will be a rectangle with the simple triangle roof, like this one.  Any house can be wide or narrow (at least two windows), high or low (at least one floor), the roof can be also high or low and its top can move to one side or another.  I decided that the good place for the number will be somewhere on the roof (don’t worry, the postman in our town will be Carlson from Astrid Lindgren; if somebody forgot – this nice guy can fly).

To make our houses movable we have to organize their class as derived from GraphicalObject

public class SimpleHouse : GraphicalObject

Thus we have to override three abstract methods that define the contour and movements of the object as a whole and each contour node separately.

public override void DefineContour ()

public override void Move (int cx, int cy)

public override bool MoveContourPoint (int i, int cx, int cy, Point ptM, MouseButtons catcher)

Contour consists of nodes (class ContourApex) and their connections (class ContourConnection).  There are no limits or rules in the way contour must be organized; the same object can be based on different contours.  But there are several ideas in what is good and what is bad in contour design. 

It’s better to have the contour configuration obvious to the users, as the connections are the lines by grabbing which the whole object is moved around the screen.  If you have an object of a very complex form in which a lot of different parts need to be moved separately, then chances are high that you’ll need a lot of contour nodes, but if there will be a lot of restrictions on possible movements of each node by the positioning of other nodes, then MoveContourPoint() method will demand a lot of coding.  So the simpler the contour the better, but all depends on general requirements. 

In our case of the SimpleHouse the best contour would be the borders of the rectangle plus the edges of the roof, so we have the array of sensitive nodes in the form of ContourApex [5] and six connections between these nodes in the form of ContourConnection [6].  While organizing the ContourApex[] you use numbers as the first parameter for new ContourApex, and these numbers are used for new ContourConnection (i, j).

public override void DefineContour ()

{

    ContourApex [] ca = new ContourApex [5];

    ca [0] = new ContourApex (0, new Point (rcHouse .Left, rcHouse .Top),

                              new Size (0, 0), MovementFreedom .ANY, Cursors .Hand);

    ca [1] = new ContourApex (1, new Point (rcHouse .Right, rcHouse .Top),

                              new Size (0, 0), MovementFreedom .ANY, Cursors .Hand);

    ca [2] = new ContourApex (2, new Point (rcHouse .Right, rcHouse .Bottom),

                              new Size (0, 0), MovementFreedom .ANY, Cursors .Hand);

    ca [3] = new ContourApex (3, new Point (rcHouse .Left, rcHouse .Bottom),

                              new Size (0, 0), MovementFreedom .ANY, Cursors .Hand);

    ca [4] = new ContourApex (4, ptTop, new Size (0, 0),

                              MovementFreedom .ANY, Cursors .Hand);

    ContourConnection [] cc = new ContourConnection [6] {

                      new ContourConnection (0, 1), new ContourConnection (1, 2),

                      new ContourConnection (2, 3), new ContourConnection (3, 0),

                      new ContourConnection (0, 4), new ContourConnection (1, 4) };

    contour = new Contour (ca, cc);

}

If you would like to make the imaginary lines across the house (from one corner to the opposite one) also be sensitive for grabbing and moving the house, then you can add couple more connections like

                      new ContourConnection (0, 2), new ContourConnection (3, 1)

Or you can add extra elements to ContourApex [] ca, but remember that for each new element of this type you have to add some new connections and also there will be much more needed code in the method  MoveContourPoint(), which we have to write.

Initialization of any contour point is using five parameters

ContourApex (int nVal,              identifiction number, that is used to define connections

             Point ptReal,          real point of the node

             Size szRealToSense,    sometimes it’s better to have the sensitive node moved slightly apart from the real point, so as to have better view of the object when contours are shown; this parameter is the shift from real point to the center of the sensitive node

             MovementFreedom mvt,   possible movements of this point

             Cursor cursorShape)    cursor shape above this sensitive area

Regardless of what you put for the particular ContourApex as the fourth parameter this node will move around with the whole object; MovementFreedom parameter describes only the ooportunities for separate movings of this node when you want it (or not) to paricipate in reconfiguration.  The possible values are

enum MovementFreedom { NONE,        node is not used for reconfiguration

                       NS,          can move only Up or Down

                       WE,          can move only Left or Right

                       ANY };       can move in all directions

The last parameter informs the user of your program about the possible movements by changing the cursor shape when mouse cursor is above this node.  There are standard expectations that when cursor has the form of Cursors .SizeWE then the object underneath can be moved only left or right, so it’s better not to organize any confusions by setting a very strange pair of the last two parameters.  At the same time I didn’t want to put strict rule, for example, on how the cursor must look if you want to show that something underneath can be grabbed and moved in any direction, so you have this fifth parameter for your decision.

Method of moving the whole SimpleHouse is really simple.  We don’t need to think here about Contour – this will be done automatically by Mover, but we have to change two fields of our SimpleHouse class.

Text Box:  public override void Move (int cx, int cy)

{

    rcHouse .X += cx;

    rcHouse .Y += cy;

    ptTop += new Size (cx, cy);

}

The third method MoveContourPoint() is for moving each counter node separately, when we want to reconfigure our house.  Here we have to take into consideration all possible restrictions from minimum sizes and positional relationship of the nodes.  I am not going to include this code here (it is nearly 100 lines), but you will see that parts of code for the nodes on the same side of the rectangle are the same, so the development of the code is not difficult.

Houses in our town will have different room size (we have a real world!), and the minimum sizes of each house depend on its room size; roof top can not be closer to the side of the house than half of the minimum room (house must look like a house).  These restrictions are taken into consideration while writing MoveContourPoint().

bool MoveContourPoint (int i, int cx, int cy, Point ptM, MouseButtons catcher)

Text Box:  The last two parameters of MoveContourPoint() method are not used in the case of SimpleHouse, but they can be very useful in other cases.  For forward movement it is enough to use cx and cy parameters (movements along two axes), for rotation it’s much easier to calculate whatever you need from the mouse position ptM.  And the last parameter informs about the button that grabbed the object, so different movements (or configurations) can be organized as the reaction for different mouse clicks.

These three methods make our SimpleHouse movable and resizable.  The house sizes can be changed by moving any corner of the rectangle; number of floors and rooms on each floor will be changed automatically according with the new sizes.  The shape of the roof will be always triangle, but the form can be changed.

Each house is using four different colors, and you can change any of them; simply L_DoubleClick on the house that needs some paint and there will be such an additional dialog to do the job (much faster than in the real world).  There are four buttons to go further on to standard Color dialog; the position of each button makes it obvious which part you are going to repaint.

I didn’t put an additional button to change the font of the number on the roof; city council pronounced very strict rules for the designers of the new houses, but there are no restrictions for the owners, so you can do it.  There is a Font property in SimpleHouse which is just waiting for your commands.

There are several extra things that have nothing to do with movable / resizable features but simply useful to have.  By R_Click on any house you can send the whole picture to the Clipboard (for saving or printing) or you can erase the house you touched.  You can also save the picture as a file and later restore it from this file; it’s better not to lose some excellent results of kids’ efforts.

Conclusion

MoveGraphLibrary is a powerful instrument for design of applications with movable and resizable graphical objects (and controls as well).  In this document I have shown how to:

·        make these plots movable and resizable;

·        construct movable and resizable graphical objects of any form and purpose.

Dr. Sergey Andreyev ( andreyev_sergey@yahoo.com )

All links

Application                   http://levd.members.winisp.net/TuneableGraphics.zip

TuneableGraphics application and its description with a lot of pictures.

Test program                 http://levd.members.winisp.net/Test_MoveGraphLibrary.zip

Includes the whole project of this sample program together with the detailed explanation of all the steps to design and use classes of movable / resizable graphical objects (current document).  Another DOC file contains the description of classes included into MoveGraphLibrary.dll.

Library                           http://levd.members.winisp.net/MoveGraphLibrary.zip

Library and the description of classes included into it.

Article                            http://levd.members.winisp.net/NewDesignParadigm.doc

 

DOC files (with one exception) are also available in the form of HTM

http://levd.members.winisp.net/Test_MoveGraphLibrary_Description.htm

http://levd.members.winisp.net/MoveGraphLibrary_Classes.htm

http://levd.members.winisp.net/NewDesignParadigm.htm