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);
……..
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.
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.
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.
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.
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.
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)
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