Click here to Skip to main content
15,867,291 members
Articles / Programming Languages / C#

Movable Controls

Rate me:
Please Sign up or sign in to vote.
5.00/5 (1 vote)
6 May 2013CPOL45 min read 15.6K   27   11   2
An article that discusses in detail about movable controls

Introduction 

I started to write an article about different types of movable groups, mentioned that some of them are moved in exactly the same way as all the controls, and immediately understood that that was not enough. It was impossible to mention in two words the movability of controls and then start to explain exactly the same technique on some of the groups. That type of covers was designed especially to move and resize the controls and only later it was used to move two special types of groups which are in reality the controls themselves and only look like groups. The technique was designed for controls, so the explanation must be given on the examples with controls. Thus I had to postpone an article about groups and move slightly back into the area of solitary controls movability. Between the solitary controls and groups, there is an intermediate variant of a pair “control + graphical text”. In some cases, it can be looked at as the simplest group; in other cases it is a control with a tiny addition, so these pairs are also discussed in the current article. Those two types of groups which are controls in reality are also discussed at the end of this article.

Examples for this article are mostly taken from the book World of Movable Objects but nearly all of them are changed to some extent. The book together with its accompanying demo project can be downloaded from here. Several files can be downloaded from there. One zip file contains the book in the DOC format and the whole demo project with all its files which are written in C#. There is a separate file with the same book in PDF format; there are several other valuable files.

I am writing a series of articles about the movability of different objects. The first article (oh, yes, it came out in three parts) was about the graphical primitives (What can be simpler than graphical primitives?). The second article was about complex objects (Movable elements: from primitive to complex objects). This one is about controls. Each time the main question is: “What for do we need the movability of such objects?”

Before the era of multi-window operating systems we all worked in a single screen area and didn’t feel any discomfort. When the multi-window operating systems arrived, we quickly understood the advantages and now everyone positions the areas of separate programs on the screen only in such a way that this user considers the best at each particular moment. The work and the outcome of each application do not depend on the size or position of rectangular area in which the results are shown. Program works as it is supposed to do, the content of this rectangular area is mostly controlled by developers, but the size and position of the rectangle is under the full user’s control.

Inside the rectangular area of an application, the situation is different. In this area, the elements are positioned according to the designer’s ideas and any variants are only the results of some sort of adaptive interface which can be applied only by developer. I advocate for giving full control over the applications to users; such full control is possible only when users can freely manipulate the screen elements. Not to choose between the possibilities allowed by developer but to move and resize those elements in an arbitrary way and at any moment when user would like to do it. We have two types of screen elements: graphical objects and controls; this article is only about controls. In the user-driven applications, the moving and resizing of controls must be decided only by users.

Can the sizes and positions of the controls change the results that we get from programs? No, the reaction of the program on the mouse click inside any control does not depend at all on the size or position of the button, the check box, or the list box. If these controls are easily movable at any moment, then each user places them in the way he personally prefers and at the same time the program continues its normal work. The view of the program can be changed in such a way which no developer would even dream about, but at the same time the program continues to work and give out the needed results as it is supposed to do. The main goal of any program is not to look according to the developer’s taste but to work correctly and give out the results. User of any application has the right to rearrange the view of an application to the one he personally prefers. Movability of the controls helps him to organize such view.

Solitary Controls

When you design and work with the applications composed of the fixed controls, then you have no problems with them. You simply get used to whatever is provided with them (all their properties and events) and what you can get of them as a developer. All popular controls were introduced at least 25 years ago. Throughout this period, users got used to their view and behaviour and now demonstrate the classical Pavlov’s reflex: if users see a button on the screen, they click it with a mouse; if they see some kind of a list, they scroll it and press the needed line. And it really works in such a way, which makes the reflex only stronger.

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

In reality, this statement about the controls being not movable is absolutely wrong. Moving is the change of location; if you change the position of any element, then it means that you move it. Location and sizes are among the parameters of any control and there are properties to change them. These features are hidden from users and are occasionally used by the developers to make an impression. (Like some people in the past centuries made their living on the ground of knowing, which gradients to throw into the flame to produce the colored smoke. Some of those people were burnt for such knowledge, but that is an absolutely different story.) The popular dynamic layout is based on the ability of controls to be moved and resized but only indirectly. Users can change the sizes of the form (dialogj) and then the sizes of the controls inside this form are changed but all those changes are going according to the rules that were formulated and coded by developers. In user-driven applications, the full control is passed to users and this is possible only if users get an easy instrument of changing the mentioned parameters – location and sizes – for each and all controls.

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

File: Form_SolitaryControls.cs
Menu position: Solitary controls

The areas of our applications are filled with graphical objects and controls. The developers have to distinguish them because they have to deal with the objects of these two groups differently, but users don’t know the difference and they don’t need to know the difference. There are many objects on the screen and users are not interested in their origin but only in the system of commands to deal with them. It’s all simple with the movability of graphical objects: press anywhere inside and move or press anywhere on the border and resize. Users expect that these simple rules work for all the screen elements, but there is a small problem. Each control has a well known reaction on a mouse click inside its area, so the areas of the controls cannot be used for their moving. Thus, the only chance to move and resize the controls is to use the vicinity of their borders. And as we need to distinguish the commands for moving and resizing, then the frame of a control is going to be divided between the areas to start both operations. Such change of the rules for moving graphical objects and controls is not a good thing but there is a small positive feature concerning controls: all standard controls have rectangular shape, so their covers and rules of moving / resizing are absolutely identical for all of them.

Any control can be moved by a thin frame around its border and the only question is in organizing some nodes for resizing. There are several well known applications (Paint and Visual Studio are among them) in which user can resize a rectangle; in all these applications, the small areas of resizing are placed in the corners of rectangle and in the middle of its sides, so the expected places are already known. Whether to use all of these places or only part of them depends on the type of resizing which is needed in each particular case.

Figure 1 shows the default view of the Form_SolitaryControls.cs. To say some words about each element, I’ll mention the place of the element on this figure, but don’t forget that all these elements, except one, are movable and can be placed anywhere else.

The small button in the top left corner allows to switch the visualization of covers ON / OFF. Covers are never shown in the real applications but their visualization is very helpful for this discussion.

Slightly to the right is another small button with a question on it; inside this form we have a symbiosis of this button and the information shown at the bottom. This information with a small cross in its top right corner is an object of the ClosableInfo class. After reading this helpful information, you can click the small cross in its corner and hide the information; this allows to save the valuable screen space. If later you want to unveil this information, you can click the small button with the question.

To the right of those two buttons, there is a small group of the ElasticGroup class. Without cover visualization, it looks like an ordinary group but its behaviour is certainly not so ordinary. It can be moved around, its title can be moved along the upper line of the frame, and all the inner elements of the group are individually movable. ElasticGroup class is one of the most interesting among the groups and you can find its detailed description in the book World of Movable Objects.

Other six big controls are those elements on which I am going to explain the movability and resizability of the controls.

Image 1

In several previous articles and in the mentioned book, I have demonstrated many times how a graphical object can be turned into movable / resizable. In order to do this, you need to cover an object by a set of sensible nodes. Three shapes of nodes are used: circles, rounded strips, and convex polygons. There are no limitations on the sizes of nodes or their placement. Each node is used for reconfiguring, resizing, or forward movement of an object; rotation can start at any node but the rotation is started by another mouse button.

Any class of movable objects must be derived from the GraphicalObject class and three methods must be included into the new class. The DefineCover() method describes the cover design. The MoveNode() method describes the reaction on pressing each node. The Move() method describes the forward movement of the whole object. Objects of thus designed class can be registered in the mover’s queue with the Mover.Add() or Mover.Insert() methods. (Complex objects need their own IntoMover() method.)

This all sounds easy enough for the new classes of graphical objects which you entirely develop yourself but what to do with the controls which are provided by somebody else, for example, by Microsoft? You can’t enter the code of those classes and change anything there. Well, you can’t and you don’t need to. In the MoveGraphLibrary.dll, there is a special class SolitaryControl which can be used as a wrapper around controls. This class is derived from the GraphicalObject class, so any SolitaryControl object can be registered with the mover in a standard way. The main idea is that a SolitaryControl object is moved and resized as any other graphical object and at the same time transforms its moving / resizing into the moving / resizing of the control which is inside.

Initialization of any SolitaryControl object is easy. The simplest constructor requires only the control itself while all other parameters are set by default. Among those other fields of the class are the width of the frame around the borders of the control and the size of special nodes which are used for resizing. The next piece of code shows the initialization of all the SolitaryControl objects of this example.

C#
public void DefaultView ()
{
    … …
    scCovers = new SolitaryControl (btnCovers);
    scQuestion = new SolitaryControl (btnHelp);
    … …
    bool bMove;
    int nodesize = Convert .ToInt32 (numericUD_NodeSize .Value);
    int frame = Convert .ToInt32 (numericUD_Frame .Value);
    for (int i = 0; i < rcDefault .Length; i++)
    {
        ctrlsSample [i] .Bounds = rcDefault [i];
        bMove = (i != 4) ? true : false;
        scSamples [i] = new SolitaryControl (ctrlsSample [i], bMove,
                                             nodesize, frame);
    }
    … …
}

Image 2

Figure 2 shows the covers of all the SolitaryControl objects in the Form_SolitaryControl.cs. For two SolitaryControl objects around the small buttons in the top left corner the simplest constructor is used; variant of constructor for objects around all other controls includes three additional parameters.

C#
SolitaryControl (Control ctrl,  // control to become movable
                 bool bMove,    // declares an object to be movable or not
                 int nodesize,  // size of the nodes in the corners
                 int frame) // the frame’s width

As you can see, there is no parameter to declare the type of resizing, but the controls in this form can be resized in different ways. The mover determines the type of resizing for a particular control by analysing the values of its MinimumSize and MaximumSize properties. Mover is making a decision about possible resizing according to such rules.

  • If these MinimumSize and MaximumSize properties are not changed from their default values (0, 0) or their values are the same as the size of a control, then this control is non-resizable. This is the button in the center of figure 2 – btnMoveNoResize.
  • If these properties provide a range for one direction only (width or height), then this control is resizable in this direction only. For the button in the middle of the left side (btnResizeNS) only the height can be changed; for the text box in the top right corner (textboxResizeWE) only the width can be changed.
  • If the properties provide the ranges for both directions, then this control is fully resizable; this is the case of the button in the middle of the right side (btnResizeAny).

There is one exception in the last case of resizing in both directions: if the second parameter in the above shown constructor is false, then this control becomes unmovable and automatically non-resizable regardless of the size range provided by its properties! This is the case of the ListView control in the bottom left corner. According to its properties, it has to be fully resizable, but it is forced to be unmovable and, as a consequence, becomes non-resizable.

The frame parameter of the constructor determines the width of the sensitive frame around the borders of a control; by pressing inside this area, the control can be moved. In figure 2, the covers are visualized and these sensitive areas around the controls are shown by the big red frames. The width of a frame cannot be less than 2 pixels; by default it is 6 pixels. If the second parameter in the constructor – bMove – is set to false, then such control is unmovable. The unmovable control is automatically non-resizable regardless of those two properties of a control. But it still has a frame around the borders; mover recognizes this area (and thus a control), so, for example, a context menu can be called for such control.

The process of making any control movable / resizable differs from the same process for graphical objects in several aspects.

  1. There is no need to write the DefineCover(), Move(), and MoveNode() methods. Simply forget about these methods when you deal with the individual controls.
  2. The resizing ranges are determined by the MinimumSize and MaximumSize properties of a control and by comparing these values with the sizes of a control.
  3. A control can be registered with the mover only indirectly through the SolitaryControl class:
C#
mover .Add (new SolitaryControl (ctrl, …));
mover .Insert (iPos, new SolitaryControl (ctrl, …));

There is a shorter form of registration for controls which uses this control as a parameter, but this is only a shorter notation and nothing else.

C#
mover .Add (ctrl);
mover .Insert (iPos, ctrl);

Even in such a case the control is wrapped into the SolitaryControl object, but this is done by the mover itself. Thus prepared SolitaryControl object is registered with the mover, but in such a case this wrapper gets the default parameters which means, among other things, the resizing in any direction (certainly, if the two mentioned properties of a control allow to do it).

Depending on the type of the needed (organized) resizing, the covers in figure 2 show the different number of nodes, but this is only on visualization. A cover for any SolitaryControl object always has eight small nodes along the frame. If you do not see part of these nodes next to some of the controls at the figure, then it means that the parameters of some of the nodes were changed so, as to make them invisible and not working, but the number of nodes did not change. These eight nodes are numbered from the top left corner and going clockwise, so the node in the top left corner has the number 0, in the middle of the upper side – 1, and so on. The Form_SolitaryControls.cs was designed especially for demonstration, so you can switch the visualization of covers ON / OFF, but in real applications I never show the covers, so the easiness of moving and resizing the controls depends on the design decision and the selection of several parameters.

The default size of those eight nodes (nine pixels) is bigger than the default width of the frame which is equal to six pixels. The enlarged size of the corner nodes makes them preferable places for resizing. The corner nodes are used not only for Resizing.Any but also for two other types of resizing (Resizing.WE and Resizing.NS); for these three types of resizing the cursors over the corner nodes are different and the resizing works according to the demand. The size of the nodes in the middle of the sides depends on the length of the appropriate side: the longer the side, the longer the node, so it will be easier to find it somewhere next to the big control.

I have explained that there is no parameter in the SolitaryControl constructor that determines the type of resizing; but the combination of sizes and two properties of the control determine it. Well, this is true in the majority of cases, but the SolitaryControl class has another constructor, which has a resizing parameter.

C#
SolitaryControl (Control ctrl,  // control to become movable
                 Resizing resize,   // resize type
                 bool bMove,    // declares an object to be movable or not
                 int nodesize,  // size of the nodes in the corners
                 int frame) // the frame’s width

There is one limitation on setting the resizing via the parameter of this constructor: thus specified resizing cannot be more general than the resizing estimated in the standard way. If two properties allow the full resizing of the control, then you can limit the allowed resizing by passing as a parameter Resizing.NS, Resizing.WE, or Resizing.None. In such case, the control gets the type of resizing that is passed as a parameter. But if, for example, the properties of control allow it to be resized only horizontally (Resizing.WE) and you try to change it with the Resizing.NS or Resizing.All parameter, this has no effect on the resizing of such control.

The type of resizing for a control can be set not only at the moment of the SolitaryControl initialization but also later with the help of the SolitaryControl.Resizing property. What can demand such change of resizing in the working application? Consider a case of fully resizable control. While an application is running, you change the size of the control to whatever you prefer and do not want to change its width accidentally after it. You can change its resizing status to Resizing.NS and there will be no change of the width even if you press the corner node. The yellow button in the bottom right corner of figure 2 can be used to demonstrate some possibilities. I purposely set a different background color of this button to distinguish it from others. This is the only control in the form on which and around which the context menu can be called; through the commands of this menu, you can change the resizing allowed for this control.

In the Form_SolitaryControls.cs, there is a small group with three controls inside (figure 1). Two of these controls allow to change the width of the frame around all the controls and the sizes of those nodes which are used for resizing of controls. By changing the values in these two controls, it is easy to select such parameters that, from your point of view, are optimal for moving and resizing of controls. User-driven applications are based on the idea of personal tuning of programs. For some people, the frame of two or three pixels is enough for moving the controls; other people may prefer the frame of 10 – 12 pixels width. I am strongly against the survey of users’ preferences by the marketing department and then using of the obtained average values as fixed parameters. User-driven application is not aimed at an average user; such application has to work according to personal demand of any user at each particular moment.

The third control in the group allows to regulate the appearance on the screen of the small visual tips at the places of nodes for resizing. When shown, these auxiliary marks are placed in the areas of nodes but they are smaller than those nodes. They are really small and, from my point of view, do not damage the whole view but they can help. The use of these visual tips is also according to the rules of user-driven applications: their appearance or disappearance is decided individually by each user.

If you are familiar with other examples which use only movable graphical objects (for example, from previous articles), then maybe you noticed the new if statement inside the OnMouseMove() method.

C#
if (mover .CaughtSource is SolitaryControl)
{
    Update ();
}

When any control is moved around the screen, then sooner or later the operating system will redraw it in the new position but this moment of redrawing is decided by the system itself. If nothing special is done, the control which is caught and moved by mouse is going through a set of intermediate positions and can be shown in some of these positions. Also the control can move across other controls and on the way damage their views. When the operating system decides to update the view of the form, all the intermediate images disappear and the images of all the controls are renewed. There are no mistakes in the work but temporarily corruption of the view does not look good. To avoid those view corruptions, I added the mandatory update of the form on any move of the control.

Control + Graphical Text

I can’t say whether the controls without comments are used more often than controls with comments or vice versa. I have no statistics from the wide range of applications and I don’t want to guess. I know that both types are used all the time and the possible addition of comments to the controls depends on many factors. The wide use of controls with comments can be illustrated by such fact. The big demo program which is designed to accompany the book World of Movable Objects contains more than 170 different forms (dialogs). In 52 of them, the SolitaryControl class is used; in 40 forms the controls with comments are used. As I’ll show a bit later, the comments to controls may use either arbitrary positioning or fixed positioning limited to several predetermined places; the distribution between these two groups is nearly equal: 22 and 18. I want to underline that all these numbers cannot be looked at as some basis for statistical analysis because the whole application is written by one person and for a very special purpose of better explanation. Thus, it can need a bit more comments than other programs, but in any case there is a demand for controls with comments in many applications. The next part of this article is about such objects.

Different types of controls can be used with or without comments, but the probability of seeing some text at the side of the TextBox control is nearly 100 per cent. These controls are often used for typing in some needed parameters; if there is more than one TextBox control in the form, each of them needs some explanation about the parameter that must be typed inside.

Textual comments next to the controls became a standard element of interface many years ago. There are two standard ways to organize such comments: either as Label controls or as painted texts. Visually these cases are undistinguishable, so the question is only in the easiness of using one or another and the personal preferences of each developer.

When you work in the realm of fixed design, the use of Label controls is easier: position all the needed controls with the help of Visual Studio and do not think about the Paint message.

With the design based on movable elements, the situation is different. All the screen elements are movable. All the graphical objects are movable by any inner point; but the controls can be moved only by their borders. It is not the problem with the controls which have distinct borders (ordinary buttons, ListView, …), but the Label controls are often used without visible borders. If the text is organized as a Label control, then users have to know that this object can be moved only by its invisible borders. It is not a huge problem but it is obviously inconvenient. It would be much easier if any text can be moved by any point. For this reason, I never use the Label controls now: all the texts in my applications are painted. So, when I write about the textual comment, comment, or a text, it is always a painted object but not a Label.

Usually, but not always, the comment is smaller than the control with which it is associated. The pair looks like a planet (control) with a satellite (comment) and their movements remind the mentioned astronomical objects. User can change the relative position of comment and place it nearly anywhere, so I call this case an arbitrary positioning of comments. (I’ll write about this nearly anywhere a bit later.)

Arbitrary Positioning of Comments

File: Form_CommentedControls.cs
Menu position: Control with comment – Arbitrary positioned comments

Image 3

When a comment in the pair “control + comment” can be moved individually, then such pair behaves like a classical complex object. All standard controls have a rectangular shape, so the behaviour of control with such comment is similar to rectangles with comments demonstrated in the previous article Movable elements: from primitive to complex objects. Moving / resizing of a control with arbitrary positioned comment can be described by several rules.

  • The moving / resizing of the control of such pair are identical to the same operations with the solitary controls.
  • On any moving / resizing of a control, the comment preserves its relative position to the control.
  • The comment can be moved (and rotated) individually. There are no restrictions on positioning of the comment in relation to its control except one: the comment cannot be totally covered by the associated control because in such case it slips out from the user’s control. To avoid this situation, whenever the comment is moved around and released while totally covered by its associated control, then such comment is forcedly moved slightly outside so that it becomes visible and accessible. I want to underline that this enforced relocation is used only when the comment is closed from view by its own associated control and not by any other.

Figure 3 demonstrates the Form_CommentedControls.cs with several objects of the CommentedControl class. This class has a lot of different constructors which allow to organize in different ways the initial positioning of comment in relation to the control and specify for this comment the font, color, and angle. A comment used in the CommentedControl class belongs to the CommentToRect class. As this class is derived from the TextMR class, then rotation of the comment is done automatically without mentioning it anywhere in the code of a form.

A CommentedControl object is a complex object consisting of two parts: control and text. Control has exactly that type of cover which was shown for solitary controls in figure 2. Control in a pair can be declared with different types of resizing as was demonstrated in the previous example. As any complex object, this one cannot be registered with the mover by the simple Mover.Add() or Mover.Insert() methods, but instead the CommentedControl.IntoMover() method must be used.

C#
CommentedControl ccName, ccSurname, ccList, ccBtn;
public Form_CommentedControls ()
{
    … …
    ccBtn .IntoMover (mover, 0);
    ccList .IntoMover (mover, 0);
    ccName .IntoMover (mover, 0);
    ccSurname .IntoMover (mover, 0);
    mover .Add (info);
}

The CommentedControl class looks simple; at the same time it has 50(!) constructors. Maybe it is a bit too much but it gives a chance to select the one you really need in one or another case. Any constructor of this class has to describe (directly or with default values) two main things: a cover around the control and the position and view of the comment.

The cover around the control is of the same type that was demonstrated for the solitary controls in the previous example. There are not too many variants. You can specify the sizes of nodes and frame and you can declare the resizing which gives not more choices than the one calculated from the sizes of a control and its two properties MinimumSize and MaximumSize. The majority of constructors are the variations on position and view of the comments.

First of all, a comment is a text which has font, color, and angle. You can specify all these parameters or omit them in different combinations; the default values are the form’s font, the form’s ForeColor, and zero degree angle.

C#
public Form_CommentedControls ()
{
    … …
    ccName = new CommentedControl (this, textboxName, Side .E, "Name");
    ccSurname = new CommentedControl (this, textboxSurname, Resizing .WE,
                                      Side .E, 4, "Surname");
    ccList = new CommentedControl (this, listView1, Side .N,
                                   SideAlignment .Left, 3, "With ListView",
            new Font ("Times New Roman", 10, FontStyle .Bold), Color .Violet);
    ccBtn = new CommentedControl (this, btnResizeAny, Side .E,
                                  SideAlignment .Center, 4, "With Button",
                 new Font ("Times New Roman", 12,
                       FontStyle .Bold | FontStyle .Italic), 30, Color .Blue);
    … …

Comment used in the CommentedControl class belongs to the CommentToRect class which is derived from the TextMR class, so any comment is located by its central point. Comment has two mechanisms of positioning: either by the coordinates or by coefficients that describe its relative position to the rectangular area. These two descriptions of comment’s position exist all the time but are used in different ways throughout different movements.

  • If the comment is moved individually, then its absolute position is used to calculate the coefficients of relative positioning.
  • If the control is moved or resized, then the comment’s relative position does not change and the new absolute (real) position of comment is calculated by using the unchanged coefficients of relative positioning.

Nearly always a pair of a control with individually movable comment behaves like a rectangle with comment, but there is one special situation which causes a problem and requires a special solution.

In the ordinary case of rectangle with comments, all the involved elements are graphical; the comments precede the associated rectangle in the mover’s queue and are painted after the rectangle. With such order of elements, if the comment is placed inside the associated rectangle, then this comment is painted atop the rectangle, it is perfectly seen, it can be pressed with a mouse, and moved to any other location on the screen.

In the case of the CommentedControl object, the rectangular area belongs not to the graphical rectangle but to a control. Controls are always shown atop all graphical objects and all the graphical elements that happen to be inside the area of a control are blocked from view and from mover. Thus if a comment to control is moved and released in a position when it is entirely inside the area of its associated control then it will stay there forever and there is no way to bring it out. You can’t grab and move out such comment because it is totally blocked from mover by the control. There is also no way to change the relative position of comment by moving or resizing the control because on such movement the associated comment retains its relative position. Thus, there is no reliable way to unveil such comment. There is only a tiny chance that by squeezing the control, you can unveil some part of the comment and then take it out by pressing this part. This possibility depends on the relative sizes of the control and comment and also on the allowed squeezing of the control.

I would not rely on all these tiny possibilities in development of my programs so I added one feature into the CommentedControl class to prevent possible deadlock: if the comment is released in such position, that it is totally covered by its associated control, then this comment is forcedly moved to another location outside the area of this control. Such enforced relocation of an element is definitely against rule three of user-driven applications, but this happens extremely rarely and such rare break of the rule is definitely better than the broken program. (The whole situation reminds me the second law from the Three Laws of Robotics: A robot must obey the orders given to it by human beings, except where such orders would conflict with the First Law.)

This special situation between the control and comment of the CommentedControl object is analysed by mover; if the forced relocation of comment is needed then it appears above the top left corner of the control.

In the Form_CommentedControl.cs, there is no way to change any visual parameters of the involved objects. I did it on purpose to demonstrate the simplicity of code that provides all the movements. Without anything additional, the code for three mouse events is pure.

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

While explaining the previous example with solitary controls, I wrote about the need of mandatory update of the form when any control is moved around. Absolutely the same thing is used in the Form_CommentedControls.cs only in this case another class is checked inside the if statement of the OnMouseMove() method because a control is moved when some CommentedControl object is caught.

The next example will be similar to this one and I will add the tuning of visual parameters. By comparison of codes from two examples, you can easily find the added part. I am not saying that this part is not needed in the current example; on the contrary, one of the rules of user-driven applications demand the tuning of all the visual parameters, so you can find it in all the examples of all my applications. This purification of the code in the methods of the Form_CommentedControl.cs from all the auxiliary parts is used only to give a clear picture of the code which provides the moving and resizing.

Fixed Comments or Comments with Limited Positioning

File: Form_CommentedControlsLTP.cs
Menu position: Control with comment – Limited positioning of comments

Image 4

All the controls except one in the Form_CommentedControlsLTP.cs (figure 4) are the same as in the previous example, but the objects in the new example belong to the CommentedControlLTP class. Comments in this pair “control + text” are always shown horizontally and cannot be rotated. Abbreviation LTP in the name of the new class stands for Limited Text Positioning. This means that a text cannot be placed arbitrary around the control but only in some predefined positions. There are exactly 12 predefined positions around any control; 3 positions on each side. These three positions include lining the text to one of the ends of the side or putting the text in the center of a side. At figure 4, the comment to the ListView is positioned above the control and lined with its left side, the comment to the TextBox aimed at typing the name is also positioned above the control but lined with its right side, and the comment to the big Button is lined with the middle of the right side.

Visually the CommentedControl and CommentedControlLTP objects are indistinguishable but you will find the difference at the very first moment when you try to move the comment: the result of such attempt is the synchronous movement of both control and its comment. This means that there is no individual movement of comment in the CommentedControlLTP class; here are the rules of all the allowed movements.

  • On any moving / resizing of a control, the comment preserves its relative position to the control.
  • An attempt to move the comment causes the synchronous movement of the associated control.

There are several constructors in the CommentedControlLTP class. Not as much as for the class of controls with arbitrary positioning of comments, but there are enough different constructors to define all visual parameters or skip some of them and let them take the default values. I purposely use different constructors of the CommentedControlLTP class to set the default view of the Form_CommentedControlsLTP.cs. Positions of two comments at figure 4 differ from their positions defined in the DefaultView() method.

C#
public void DefaultView ()
{
    … …
    ccName = new CommentedControlLTP (this, textboxName, Side .E,
                           SideAlignment .Left, 4, "Name", Font, Color .Blue);
    ccSurname = new CommentedControlLTP (this, textboxSurname, Side .E,
                        SideAlignment .Left, 4, "Surname", Font, Color .Blue);
    ccList = new CommentedControlLTP (this, listView1, Side .N,
                                      SideAlignment .Left, "With ListView");
    ccBtn = new CommentedControlLTP (this, btnResizeAny, "With Button");
    ccCheck = new CommentedControlLTP (this, checkBox1, Side .W,
                                       "Text for CheckBox");
    … …

Obviously, the CommentedControl class gives greater flexibility in positioning the text than the CommentedControlLTP class. Then why do we need this new class with lesser flexibility? The answer is in figure 4 in the case of a check box. This is not a standard CheckBox but a combination of such an element without text at all plus the needed text which is painted. Why do I prefer such a combination?

As any other control, the standard CheckBox object can be moved around the screen only by the border of this control. The border is invisible, and though the changing of a cursor at the side of the text gives a clear signal that this is the right place to press the mouse, grab the check box, and move it, but it is still not the best decision. If you have a chance to move the same element by any point of its text, it is much easier and, from my point, much better. Usually the area of the text in any check box is significantly bigger than a small square for a check mark; when such object can be moved by any point inside the text area, then it is very close to the situation when an object can be moved by any inner point.

There can be pluses and minuses in every solution. When a standard CheckBox is stripped of its textual part and then a graphical text is added to the small square area of the check mark, then such “control + comment” pair can be moved around by any inner point of the textual part. At the same time, a CommentedControlLTP object does not allow to switch the CheckBox by clicking its textual part. This behaviour is different from the standard CheckBox, but I prefer the case with much easier moving of the element. (It is possible to combine both versions by using slightly different logic of clicking. Consider the clicking of the textual part with the distance between the points of the MouseDown and MouseUp events of not more than three pixels as the command for a switch. Exactly the same logic is used to distinguish the rotation of any graphical object and the calling of context menu on this object. Yet, I decided not to apply the same logic to the possible switch of the check boxes inside the CommentedControlLTP objects.)

There is no individual movement of the comment in the CommentedControlLTP class, so such pair “control + text” is not a complex object and any object of this class is registered with the mover by the standard Mover.Add() or Mover.Insert() methods.

C#
public void RenewMover ()
{
    mover .Clear ();
    mover .Add (ccCheck);
    mover .Add (ccBtn);
    mover .Add (ccList);
    mover .Add (ccName);
    mover .Add (ccSurname);
    scQuestion .IntoMover (mover, 0);
    mover .Add (info);
}

Because controls are always on top of all the graphical objects, then in the mover’s queue the controls are always at the head and graphical objects – in the tail. Commented controls has parts of both origins, so I always place them in the queue between those two groups.

Controls with different rules for positioning of comments look identical. Are there any preferences in using one or another class in each particular case? Well, my decision is usually based on the relative sizes of controls and their comments. For the cases of controls which are much bigger than their comments (it can be Panel, ListView, or a big multilane TextBox), I prefer to use the CommentedControl class. For the cases when the control is smaller than its comment (small single-line TextBox, small Button, NumericUpDown, or CheckBox), I prefer to use the CommentedControlLTP class, but there is one more thing to be considered.

The elements of two classes – CommentedControl and CommentedControlLTP – look similar, but they have the different rules for moving, so do not mix them in the same form; use either one or another. Which one better suits the goal of form’s design depends on the type of controls that are going to be used. Throughout several years of very active design on the basis of movable elements, there were only one or two forms in which I had to use both classes simultaneously. I would prefer to have no exceptions but I could not find better solution for those cases.

I demonstrate two different classes for the pair “control + comment”. Each class has its own advantages and the developer makes a decision about using one or another class. If the CommentedControlLTP class is chosen, then user has a chance only to select among 12 possible positions for comment and nothing else. There is an obvious conflict with the rule of user-driven applications that only user makes decisions about the positions of the screen elements. To avoid such break of the main rule, two classes can be combined and give the full control to the users. Such class is demonstrated in the demo application and discussed in the book World of Movable Objects. In such combined class, the comment can be moved anywhere and so such pair “control + comment” has all the flexibility of the CommentedControl class, but at any moment user can fix the current position of comment and from this moment the pair can be moved around as easy as any CommentedControlLTP object by any point of its textual part. If user decides to change the relative position of comment, he can easily unfix the comment and move it anywhere else. I decided not to enlarge this article with such an example; you can easily find it in the book and its application.

Controls - Groups

I have explained the movability of solitary controls and controls with graphical comments; now is the time to make the next small step and discuss the controls that look like groups or the groups that behave like controls.

It’s not a rare thing when several controls need to be organized into a group. They are positioned next to each other and there is some kind of visual element that informs users that several screen objects work together on some subtask. Throughout the years, there were only two ways to organize such groups: either to use Panel or GroupBox. Elements of these two classes are controls so whatever was explained about the transformation of any solitary control into movable / resizable can be applied to the objects of these classes.

Panels

File: Form_Panels.cs Menu position: Controls-groups – Panels

Image 5

There is no visual indication of movability of objects in user-driven applications. Such indication is not needed because it is enough simply to know that all elements of such programs are movable. There is no question of whether one or another object is movable or not; they are all movable without any doubts. So when you look at the Form_Panels.cs (figure 5), you see the same objects which you have seen many times throughout the years.

The main objects in the Form_Panels.cs are the three big Panel elements. Each panel has inner elements and we’ll deal with the movability of those elements a bit later, but first we are interested in making the panels movable. Each panel is a control, so they can be declared movable in exactly the same way as was shown in the first example of this article. When you need to declare panels movable, you may ignore the existence of any elements on these panels and deal only with the sizes of the panels and their two properties which define their minimum and maximum sizes. Figure 6 shows the covers of the objects and from the view of these covers, you can see that there is some difference in movability of the panels.

Image 6

To make objects inside the Form_Panels.cs movable, I need to initialize a Mover object inside this form. When mover is initialized with a Form parameter, it means that this mover deals only with the objects which are directly under the jurisdiction of this form. If there is a panel with inner elements, then those elements are under the jurisdiction of this panel; they are in the form, but only indirectly. The mover which deals with the elements of the first (upper) level has nothing to do with the elements of the second (inner) and all other levels. Thus, in the case of the Form_Panels.cs our mover of the upper level has to deal with three panels and one small button; all other elements simply do not exist for this mover. For mover, a panel is a simple control regardless of the number of its inner elements and any panel is registered as any other solitary control.

C#
public Form_Panels ()
{
    InitializeComponent ();
    mover = new Mover (this);
    mover .Add (panelResizeMoveInside);
    mover .Add (panelResizable);
    mover .Add (panelNonresizable);
    mover .Insert (0, btnCovers);
    … …

There are no context menus in the Form_Panels.cs, there is nothing additional like tuning of some parameters, so the only needed thing is the moving / resizing of the objects registered in the mover’s queue. For these purposes, the simplest variants of three mouse events are enough.

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

Panel is a normal control, so its resizing is defined by the values in its minimum and maximum sizes. On each of our three panels, there are labels with the text informing about the features of each panel. Panel on the left is non-resizable (panelNonresizable); panel on top is resizable (panelResizable). When the sizes of this panel are changed, something must be done with the sizes and positions of its inner elements. For this panel, the solution is very simple and is based entirely on using the anchoring: the label is always at the top of the panel, next to it is the TextBox object, and the whole remaining area of the panel is filled by the ListView object. This is an absolutely normal solution; you saw something similar in many programs throughout the years and I don’t expect any objections from the readers of this article. At the same time, I would never do such thing in my programs because it is absolutely against the rules of user-driven applications. Maybe you didn’t even notice, but this decision about the change of the inner elements is made by developer and not by user who resizes the panel. In any user-driven application, you will see another solution. I am not saying that such resizing of the inner elements of the upper panel is correct and can be duplicated in other similar situations; it is only one of the examples of turning a panel into movable and resizable.

The third panel in the Form_Panels.cs demonstrates a better solution for changing the inner elements, but figure 6 shows that something additional was done for it because inner elements of this panel (panelResizeMoveInside) are shown with covers and thus they are movable and resizable. To organize the moving of these inner elements, there must be a mover; as these elements were not registered with the first mover, then there must be another one. On initializing this new mover, the panel is used as a parameter. To make it obvious that objects on the panel are supervised by another mover, I changed the color of the covers for this second mover.

C#
public Form_Panels ()
{
    … …
    moverInner = new Mover (panelResizeMoveInside);
    moverInner .Color = Color .Blue;
    … …

All the objects on the panel are registered with this mover.

C#
moverInner .Add (labelTitle);
moverInner .Add (listView3);
ccMove = new CommentedControlLTP (this, checkboxMove, Side .E, "Move");
ccAnywhere = new CommentedControlLTP (this, checkboxAnywhere, Side .E,
                                      "Anywhere");
moverInner .Add (ccMove);
moverInner .Add (ccAnywhere);

The same three mouse events but applied to the panel are used to organize the moving / resizing of the elements on this panel.

C#
private void MouseDown_panel (object sender, MouseEventArgs e)
{
    moverInner .Catch (e .Location, e .Button);
}
// -------------------------------------------------        MouseUp_panel
private void MouseUp_panel (object sender, MouseEventArgs e)
{
    moverInner .Release ();
}
// -------------------------------------------------        MouseMove_panel
private void MouseMove_panel (object sender, MouseEventArgs e)
{
    if (moverInner .Move (e .Location))
    {
        (sender as Panel) .Update ();
        (sender as Panel) .Invalidate ();
    }
 }

Any mover deals only with the objects which are included into its queue, so two movers work with different groups of elements: one mover regulates the moving / resizing of three panels in the form; another mover supervises the moving / resizing of the elements on one of the panels.

One thing is important to understand in the case of panels: regardless of the number of elements on the panel and their behaviour, the panel itself is always looked at as an individual control. It looks like a group of controls for us and there can be a lot of elements on a panel but for mover it is a solitary control which is registered in the simplest way and which behaviour is quite simple.

For users, it is also a very simple object which can be moved and resized, if it is allowed, only by the parts of its border. Panels, as any other controls, cannot be moved by their inner points; this is the biggest flaw in otherwise simple design.

Small technical remark. Two panels in the Form_Panels.cs are the objects of the Panel class. When you have some movable elements on the panel, then you have to do something to avoid flickering throughout their movements; for this reason the third panel in the form belongs to the PanelWithoutFlickering class. This class, derived from the Panel class, is included into the MoveGraphLibrary.dll.

GroupBox Objects

File: Form_GroupBoxes.cs
Menu position: Controls-groups – GroupBox objects

Image 7

The GroupBox class is another standard possibility to show a set of controls as a group united to work together on some subtask. Any GroupBox object is a control so it can be turned into movable / resizable in exactly the same way as any other solitary control. Figure 7 shows two such groups; one of them is non-resizable; another is resizable and its possibility and limits of resizing are defined by minimum and maximum sizes which are set during initialization. As any other control, a GroupBox object can be registered with mover simply by Mover.Add() or Mover.Insert() methods.

C#
public Form_GroupBoxes ()
{
    InitializeComponent ();
    mover = new Mover (this);
    mover .Add (groupboxNonresizable);
    mover .Add (groupboxResizable);
    mover .Insert (0, btnCovers);
}

Figure 7 shows that GroupBox objects have the same type of cover as any other control; at the same time the figure perfectly illustrates the specific problems of these movable objects.

A GroupBox has a frame which is painted inside its area; the main idea of this frame is to make it obvious which controls are included into the group; for this purpose the frame works perfectly. It is a very rare situation that the background color of the GroupBox differs from the background color of the form in which it is used. When these two colors are the same (and this happens nearly always), you cannot see the real border of a GroupBox. In the standard design based on fixed elements, there is no difference for users whether the frame is exactly on the border or not; the frame only informs about the filling of the group and for this purpose the possible shift of the frame for several pixels aside from the real border does not mean anything at all.

For the purpose of moving a control, we need to see its real border because a control can be moved or resized only by the areas next to the border. Usually the covers are not shown at all; users look to the frame as the border of the group and try to grab a GroupBox for moving or resizing somewhere next to the visible frame. On three sides, the frame is very close to the real border and anyone trying to grab the group “by the frame” will do it without any problems because there are only two or three pixels between the visible line and the sensitive frame, but at the top, the cover is farther away from the frame (figure 7). If you try to grab the group with the invisible cover by its frame at the top, it will be impossible. You will find that you can grab such group if the mouse cursor is moved somewhere up to the place with no visible border. Such inability to grab the movable object at the place which you think is absolutely right for it becomes very annoying. That was one of the reasons why I quickly stopped using ordinary GroupBox objects among movable elements and started to design other types of groups. But those much better groups are not controls, so they are not discussed here. Those groups will be discussed in another article. You can also look into the book "World of Movable Objects" and especially at the examples of such new groups in the application which accompanies the book.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Questionwhere is the source code for this demo? Pin
Southmountain11-May-22 15:45
Southmountain11-May-22 15:45 
GeneralSABRUS Pin
SergeyAB13-May-13 20:50
SergeyAB13-May-13 20:50 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.