Elastic Groups - The Groups Which Can Be Arbitrarily Changed by Users





5.00/5 (6 votes)
Elastic groups that can be arbitrarily changed by users
Introduction
Let us start with a bit of history on user-interface. I’ll try to do it in a quick way so I’ll omit some stages which were interesting but which I consider not so important for the main item of this article. I hope to be back on track in three or four paragraphs.
The first prototype of graphical user interface was demonstrated nearly 50 years ago (Douglas Engelbart, 1964). The first products which can be called personal computers were on the market in 1974-77 but for a number of years the interface was absolutely different from what we have now. I wonder how many readers of this article have their own experience of working, for example, under MS-DOS. The first PC with graphical interface appeared in 1983- it was Apple’s Lisa. However, that computer already had some features which are familiar to everyone today: the drop-down menus and icons. Two years later, in 1985, the Microsoft’s Windows appeared.
For the last 30 years, there is only one dominant idea in interface design: adaptive interface. For the first 20 years of this period, the adaptive interface flourished and produced an immense number of ideas; I think there were hundreds of books and thousands of articles. Surprisingly, but then this whole area of ideas was squeezed to one general line - the dynamic layout. This happened mostly for “wise” decisions in Microsoft and the dominant role of this company on the market. (Three years ago, in a private discussion with excellent developers from Microsoft, I was told that they were working under the strict orders from their marketing department. I have big doubts that the staff in that marketing department is wiser than the world-class developers, but it’s their local problem inside Microsoft.)
To some people, the development of a program under the idea of dynamic layout looks easier and thus cheaper, but from my point of view it is a real disaster for interfaces. It’s like a construction of the new highway into the dead-end. Before the era of PC, the interface was one for all users of a program; later users got at least a chance to select. It was a selection only between the variants which were considered good enough by developer and that is why they were coded, but at least users had a choice. Obvious and expected improvement would be to give users the full control over the programs. Instead, we have a 180 degrees turn and now, with the dynamic layout in every program, there are no choices at all. There is only one view for everyone and in addition there is a demand from developer that you have to like what you get. (What about one car model on the market and the same demand? Sounds really nice… for that car maker.)
Several years ago, I formulated the rules of user-driven applications and began to demonstrate the work of such applications. There is a book World of Movable Objects with the accompanying program which includes more than 170 examples. A lot of those examples were developed only to demonstrate one or another feature in the best way but some of the examples are the real programs working in different areas. The book together with its accompanying demo project (all codes in C# are available) and several other very helpful files can be downloaded from here. The main idea of user-driven applications is that each user can easily change any working program; the program continues to work according to its main purpose but the view can be changed by anyone at any moment. Certainly, no one can put into code all variants that dozens (hundreds, thousands, millions) of users can imagine, so this has nothing to do with the adaptive interface. The idea and implementation is absolutely different. Our programs consist of graphical objects and controls. Not long ago, I published an article about simple graphical objects (What can be simpler than graphical primitives?). Then there was an article about complex objects (Movable elements: from primitive to complex objects) and another one about controls (Movable controls). The user-driven applications can be constructed only on the basis of movable elements and in that last article, I demonstrated the standard groups turned into movable. But that was only the first step of group’s evolution; now is the time to explore how the ideas of user-driven applications absolutely change the idea and the design of the groups.
It’s not a rare situation when several screen objects
need to be organized into a group. These objects are positioned next to each
other and there is some kind of visual element that informs users that these
objects work together on some subtask. Throughout the years, there were only
two ways to organize such groups: either to use a Panel
object or a GroupBox
. In both cases, the design starts with organizing a container and then
the inner elements are added. The container plays the main role and defines
the place and the sizes of the group. If a Panel
is used, then only on very rare and really special occasions, the border
of such panel
is hidden; the standard situation is that it is perfectly seen.
In the GroupBox
, there
is a perfectly seen frame.
Any actions which user wants and needs to do are done in the inner elements of the group. The container can make these actions a bit easier (for example, the selection among radio buttons is limited to the set of buttons inside the particular group) but the main purpose of such container is the visual tip. The border of the group does not play any significant role; it is only a decoration.
When the dynamic layout became the dominant idea in interface design, it was applied to all objects. When the form is resized, the objects of the form are changed, for example, according to the anchoring which was defined at the stage of design by the developer of this form. If there is a panel among those objects, then the objects on this panel are changed according to the anchoring rules applied by the same developer. This is a standard way of design which you can see in many programs.
The user-driven applications have very few rules but
the main idea is the full users’ control over the screen elements. For
solitary controls, it means that any control can be moved and resized by user at
any moment. Two mentioned classes of groups – Panel
and GroupBox
– are
also controls, so they are easily turned into movable and resizable as any
other control. (See article Movable controls) It is not a problem to
make any panel
movable and resizable, but what to do with the elements on the
panel
when this panel
is resized? To use the ideas of dynamic layout? It
looks natural for nearly everyone but it is definitely against the rules of
user-driven applications. Developer can think out and fix in code any
scenario, but for user-driven applications any such scenario is wrong simply
because it is thought out not by user. Can a developer predict and put into
code all the innumerable variants that users can think out? Certainly not. Is
there any other solution to this problem? Yes, and it is obvious. Simply look
at this problem from another point.
Users need an easy way to change the configuration of the inner elements of a group. Well, this is easy to organize as any element is movable and resizable. What about the frame of the group? The frame must adjust its size and position in such a way as to show at any moment the combined area of all those inner elements (plus some additional space around them). In this way, the frame behaves like an elastic string around all inner elements and from this behaviour the name of the class was born. The idea of the group is turned upside down.
- The idea of the dynamic layout is to project the change of the outer border into the change of sizes and positions of the inner elements. User can change only one thing – general size – and all the inner elements are affected. It is very easy and looks very powerful as one change affects everything, but everything comes with a price. Whatever is going to happen is simply predetermined by the developer and user is simply stripped of all his power; the full control over application is in the developer’s hands.
- In the groups of the new type user can arbitrary change any inner element, so the full control is in user’s hands.
I started by turning the standard Panel
and GroupBox
objects into movable / resizable (you can see such
examples in the article Movable controls) and eventually came to the ElasticGroup
class in which inner elements are movable / resizable
and the set of these elements determines the position of the frame. What can
be used as elements of such group? Either controls or controls with a primitive
and easily described graphical addition – a control with comment. If you need
to include into such group any other graphical objects, then there is similar
in behaviour ArbitraryGroup
class.
(Maybe I’ll write about it in another article.) Here is the full list of
elements allowed inside any ElasticGroup
object:
SolitaryControl
objectsCommentedControl
objectsCommentedControlLTP
objectsDominantControl
objectsElasticGroup
objects
First three classes of this list are demonstrated in
the article Movable controls. An object of the DominantControl
class consists of several controls
(at least two) of which one plays the dominant role and others are subordinates
to that one; the details of their movements will be shown a bit further. The
last position in the list declares that a group can be an element of another
group; such recursion allows to design the groups with any level of complexity.
The next example demonstrates the group with the elements of all five types.
File: Form_MeetElements.cs
Menu position: Meet the elements
ElasticGroup
objects can be used in many different situations and
the content of such group can vary. To make the design of such group easier,
the ElasticGroup
class
has around 50 different constructors.
The first example of this article has to demonstrate
all five types of allowed inner elements, so each variant is represented by an element
of the specified class and then all these elements are transformed into the
ElasticGroupElement
objects.
ctrls = new Control [] {listviewSolitary,
textBox_CommentedControl,
textBox_CommentedControlLTP,
listviewDominant, button1, button2, button3, button4,
textboxDominant, btnClr, btnFont, btnAdd };
private void DefaultView ()
{
SolitaryControl sc = new SolitaryControl (ctrls [0]);
CommentedControl cc = new CommentedControl (this, ctrls [1], Side .N,
SideAlignment .Right, "CommentedControl");
CommentedControlLTP ccLTP = new CommentedControlLTP (this, ctrls [2],
Side .N, SideAlignment .Left, "CommentedControlLTP");
DominantControl dc = new DominantControl (new Control [] { ctrls [3],
ctrls [4], ctrls [5], ctrls [6], ctrls [7] });
ElasticGroup eg = new ElasticGroup (this, new ElasticGroupElement [] {
new ElasticGroupElement (new DominantControl (new Control [] {
ctrls [8], ctrls [9], ctrls [10], ctrls [11]})) },
12, "ElasticGroup");
groupFiveElements = new ElasticGroup (this,
new ElasticGroupElement [] {new ElasticGroupElement (sc),
new ElasticGroupElement (cc),
new ElasticGroupElement (ccLTP),
new ElasticGroupElement (dc),
new ElasticGroupElement (eg)},
"Five types of elements");
}
![]() |
Figure 1 shows the default view of the Form_MeetElements.cs. There are five inner elements which represent all five possible types.
The ListView
in
the top left corner (listviewSolitary
) is
wrapped into SolitaryControl
object. The
values for
MinimumSize
and MaximumSize
of this control allow to resize this control both
horizontally and vertically. The resizing of the control can be started near
the corners of the control or the middles of the sides while any other point
near the border can be used to start the moving of the control. There are more
details in the article Movable controls.
SolitaryControl sc = new SolitaryControl (ctrls [0]);
The TextBox
in the
top right corner (textBox_CommentedControl
) together with the associated comment are turned into
the
CommentedControl
object. The control
itself can be moved and resized in exactly the same way as the previous
control; on any such movement the comment retains its relative position to the
control. Comment can be rotated around its central point; press it with the
right button and start turning around. Comment can be relocated to nearly any
other position. Only if you try to release the comment when it is entirely
closed from view by the associated control, then the comment is forced outside
that rectangular area. There are more details in the article Movable
controls.
CommentedControl cc = new CommentedControl (this, ctrls [1], Side .N,
SideAlignment .Right, "CommentedControl");
Another TextBox
in the middle of the left side (textBox_CommentedControlLTP
) together with the associated comment are turned into
the
CommentedControlLTP
object. Though the
pair looks similar to the previous one, the rules of their movement are partly different.
The control itself can be moved and resized; on any such movement the comment
retains its relative position to the control. Comment cannot be rotated and if
you try to move the comment, the whole pair will move synchronously. Comment
has 12 allowed positions – three on each side of the associated control; the
selection is organized through a small auxiliary form which can be called via
the context menu on the comment. Because the objects of the CommentedControl
and CommentedControlLTP
classes look identical but behave differently, I
recommend not to use them together in any form. There are more details in the
article Movable controls.
CommentedControlLTP ccLTP = new CommentedControlLTP (this, ctrls [2],
Side .N, SideAlignment .Left, "CommentedControlLTP");
The big ListView
and several smaller buttons nearby in the bottom right corner
are organized into the DominantControl
object. The ListView
control plays the dominant role while the buttons are
its subordinates. Three small buttons are nonresizable; the fourth button is
resizable. When the dominant control is moved, all subordinates move
synchronously; when the dominant control is resized, all subordinates retain
their relative positions. Subordinates can be moved and resized (if their
properties allow) individually; if any subordinate is released inside the area
of dominant control, then this subordinate is returned to the last position
outside the dominant control from which the movement started. There are
interesting examples with the DominantControl
objects
in the book World of Movable Objects.
DominantControl dc = new DominantControl (new Control [] { ctrls [3],
ctrls [4], ctrls[5], ctrls [6], ctrls [7] });
The group in the bottom left corner illustrates
the fact that ElasticGroup
object can be
used as an element of another group of the same class. This smaller group
consists of four controls which are organized into the DominantControl
object with three subordinates. Of them, only one
subordinate button is resizable while two others are non-resizable.
ElasticGroup eg = new ElasticGroup (this, new ElasticGroupElement[] {
new ElasticGroupElement (new DominantControl (new Control [] {
ctrls [8], ctrls[9], ctrls [10], ctrls [11]})) },
12, "ElasticGroup");
The Form_MeetElements.cs is not designed to solve one or another real task; such examples are shown further on. The only aim of this example is to demonstrate the set of possible inner elements of the group, the movements of these elements, and some standard commands of tuning.
When I started to design movable groups, I began with
an obvious transformation of the standard GroupBox
into movable in exactly the same way as I was doing it with any other
control. After it, I constructed several classes of movable groups with
different features and even some auxiliary elements of visualization; some of
those results are described in the book and are demonstrated in its
accompanying program. Later I came to the ElasticGroup
class which looks like any standard group but behaves
absolutely differently. This class turned out to be so powerful and flexible
for design of different programs that I stopped using any other classes of
groups that were invented on the way. It is interesting to look at some of
them and compare their behaviour and view, but there is certainly no question
about the best choice for design of real programs.
The ElasticGroup
class,
as you can see it now, is very flexible, but at the same time it is extremely
simple in design. The sizes of any group can be changed only as a result of
moving / resizing of inner elements. The group by itself is not resizable but
only movable. The shape of the frame is always rectangular, so, to provide its
movability, it would be enough to have a cover consisting of a single
rectangular node. Well, in reality there is one more movable element which I
didn’t mention yet: the title of such group can be moved along the upper line
of the frame and can be positioned in any place of that line. Thus, the cover
of any ElasticGroup
object consists
of two nodes: one provides the movement of the title, another – the movement of
the whole group.
In the user-driven applications all the parameters of
visualization are controlled by users; the ElasticGroup
class has a whole set of such parameters that can be
modified, for example, with the help of an auxiliary tuning form. I opened
this tuning form and in several seconds transformed the group from its default
view into what is shown at figure 2.
At figure 2, the elements of the CommentedControlLTP
and CommentedControl
classes are positioned side by side and perfectly
demonstrate the similarities and difference in view. For comments of both
classes, two parameters are the subjects of tuning: font
and color
. On both
comments, the context menus can be called and then through the commands of these
menus the standard dialogs can be called, but there is a small difference in
calling those menus. Everything starts with the identification of the pressed
element and in this process I rely on the mover. Mover easily identifies the
class of the pressed element and each movable element has its personal ID because
this is one of the parameters of each GraphicalObject
from which all the classes of movable / resizable
objects are derived. In further examples with multiple elements of the same
class, you will see the work of this identification process but here in the Form_MeetElements.cs, we have only one representative for each class of controls with
comments so it is enough to detect the class. Yet, there is a small trick.
An object of the CommentedControl
class, which is on the right at figure 2, is a
complex object consisting of two different parts. When the mouse is pressed
anywhere near the border of its control, the mover identifies the pressed
object as the CommentedControl
element,
but if the mouse is pressed anywhere on comment, then the pressed object is
identified as a CommentToRect
element. An
object of the CommentedControlLTP
class,
which is on the left at figure 2, is not a complex object and,
regardless of whether the mouse is pressed near its control or on comment, the
pressed object is identified as a CommentedControlLTP
element.
private void OnMouseUp(object sender, MouseEventArgs e)
{
ptMouse_Up = e .Location;
double fDist = Auxi_Geometry .Distance (ptMouse_Down, ptMouse_Up);
int iWasObject, iWasNode;
if (mover .Release (out iWasObject, out iWasNode))
{
GraphicalObject grobj = mover .WasCaughtSource;
if (e .Button == MouseButtons .Left)
{
… …
}
else if (e .Button == MouseButtons.Right)
{
if(fDist <= 3)
{
if(grobj is CommentToRect)
{
ContextMenuStrip = menuOnComment;
}
else if (grobj is CommentedControlLTP && iWasNode == 9)
{
ContextMenuStrip = menuOnCommentLTP;
}
else if (grobj is ElasticGroup)
{
(grobj as ElasticGroup).ParametersDialog (this,
ParametersChanged, PointToScreen (ptMouse_Up));
}
}
}
… …
In the above shown code for the case of the
CommentedControlLTP
class, there is an
additional checking of the number of the pressed node. This helps to call the
menu only if the comment of this object is pressed (node number nine). If you
think that the same menu must be called when the mouse is pressed also near the
control of this object, then the additional checking of the node number must be
removed.
The ElasticGroup
class has around 15 parameters to tune, so the tuning
of such objects is organized via the tuning form. Figure 3 demonstrates
the view of this form when it is called on the big group of the current
example. As you can see from this figure, the tuning form contains four
groups; three of them are the ElasticGroup
objects
themselves and only the left group does not belong to this class.
Elements of each group in this tuning form allow to deal with the different sets of parameters: the left group is used to change the background color of the group and transparency, the middle group is used to set the colors and fonts of some parts and elements; the right group is used to set the spaces between the inner elements and the group’s frame, while the group at the bottom is for tuning the title. Certainly, the word left, middle, or right group can be applied only to their positions on this figure; the groups are movable and you can change the view of the form in any way you want.
The left group in this form, though it looks similar
to others, cannot be organized as an ElasticGroup
object; there is a graphical object in this group and
such elements are not allowed inside this class of groups. Instead, the ArbitraryGroup
class can be used; groups of this class have similar
behaviour but their design is different.
The Form_MeetElements.cs example is used only to make an acquaintance with the ElasticGroup
class; in the further examples objects of this class
are used in one or another real task.
Selection of Years
File: Form_YearsSelection_TwoGroups.cs
Menu position: Selection of years – Two groups
Let us consider a well known case when you
have a limited set of items of which you need to select some subset. Such selection
can be implemented for different types of objects; for this example, I decided
to stop on the selection of years. The design of such program is simple (figure
4). The years from the predetermined range are represented by the lines of
one
ListView
control. Any number
of lines in any combination can be selected in this list. When the needed
lines are selected, press the Show button to copy them into another ListView
control. If you need to exclude some of
the previously chosen years, then highlight them in the second list and press
the Remove button. I do not think that anyone would have any problems with
the implementation of such task.
The developers will have no problems with
writing the code for such application, but the questions about the proposed
design can be (and they certainly will be) brought up by the users: half of the
users will like to see the full list of items on the left side and the selected
items on the right; another half of users will prefer those lists to be
positioned in the mirror way. There are also those who will like to see both
lists in one column; they have the right to demand such positioning. In
addition, there is an old problem of the best position for OK button. Three
big groups of designers prefer to place it in the right-top corner, in the
right-bottom corner, or in the middle at the bottom of a form. Members of each
group would never agree with anything else except their opinion.
The task is so primitive that every developer will simply design this form in the way he personally prefers and will ignore any other suggestions. (“The clients will have to like whatever we will give them.”) As a huge respect to the users, the dynamic layout can be applied to this form; the sizes of the lists will be changed according to the used font or resizing of the form. If you have to select the years from bigger range, push the border of the form to increase its height; the height of the lists will also increase so that lists will fill maximum available space inside the groups. I am sure that you can remember either designing similar form or using it in one or another application and there was nothing else except the things that I have mentioned. Such design looks so natural that I don’t expect any public outcry.
But are you sure that this is really the right way to design even such simple programs?
Let us consider the task itself. It is only about the selection of years and nothing else. This selection works correctly regardless of the positions of all the controls and their sizes. As a designer, you are responsible for correct work of all the operations (selection, deselection, and saving). If you will insist that application must look exactly as you would like it to be, then stop talking about the user-friendly interface of your programs. If you will agree that it would be nice to allow each user to change the view of the program in such a way as he personally wants, then ask yourself a very simple question: “What has to be changed to give users the full control of the form’s view?”
The answer is obvious and simple: the screen
elements must become movable and resizable by users. In order to be really
useful, this movability must work in such a way that user can organize any
needed configuration of elements easily and quickly. In the current example, this
is provided by two ElasticGroup
objects.
ElasticGroup groupAll = null;
ElasticGroup groupSelected = null;
ctrls = new Control [] {
listYearsAll, btnShow, listYearsSelected, btnRemove, btnOK };
Each group consists of two elements: a ListView
and much smaller Button
. Both elements are resizable and any user will select the optimum
sizes for both of them, but I think that in any case the button will be either
smaller or much smaller than the List
; that is
why the pair is united into a DominantControl
with
the
ListView
element playing the dominant
role.
private void DefaultView ()
{
Spaces spaces = new Spaces (this);
groupAll = new ElasticGroup (this, new ElasticGroupElement [] {
new ElasticGroupElement (new DominantControl (
new Control [] {ctrls [0], ctrls [1]})) },
12, "All years");
groupAll .Font = fntSelected;
groupAll .Location = new Point (spaces.FormSideSpace, spaces.FormTopSpace);
groupSelected = new ElasticGroup (this, new ElasticGroupElement [] {
new ElasticGroupElement (new DominantControl (
new Control [] {ctrls [2], ctrls [3]})) },
12, "Selected");
groupSelected .Font = fntSelected;
groupSelected .Location = new Point (groupAll .FrameArea .Right +
spaces .Hor_betweenFrames, groupAll .FrameArea .Top);
btnOK .Location = new Point (groupSelected.FrameArea .Right - btnOK .Width,
Math .Max (groupAll .FrameArea .Bottom - btnOK .Height,
groupSelected .FrameArea .Bottom + 2 * spaces .Ver_betweenFrames));
scOK = new SolitaryControl (btnOK);
ClientSize = new Size (scOK .Right + spaces .FormSideSpace,
scOK .Bottom + spaces .FormBtmSpace);
}
Each group can be easily moved around the screen, so anyone can organize in a couple of seconds the preferable way of selection: from left to right, or from right to left, or in one column, or whatever he wants. All the objects are resizable, so every user can set sizes for all objects which are the best from his point of view.
I used this example for a couple of years and then I found out that in one of my programs I needed the same selection of years not as a stand alone task but only as a part of setting many different parameters. In the big form with many different elements, these two groups could be moved to different places but the logic demanded their positioning next to each other. It’s not a problem to relocate one group and then another, but it would be much easier to move them synchronously. Thus a slightly different version was born.
File: Form_YearsSelection_OneGroup.cs Menu position: Selection of years – One group
In the new version of the years’ selection, two groups
from the previous example are united into one; this guarantees the synchronous
movement of all the elements. The construction of such group is easy. There are
only two elements in the group; each ElasticGroupElement
is organized as a DominantControl
in which the ListView
plays the dominant role with a Button
as a subordinate. Below you can see the code for the
default view of this group, but pay attention that the view at figure 5 is
not the default view; it was received after some moving and tuning of the
elements.
ElasticGroup group = null;
ctrls = new Control [] { listYearsAll, btnShow, listYearsSelected, btnRemove };
private void DefaultView ()
{
Spaces spaces = new Spaces (this);
btnShow .Location = new Point (listYearsAll .Left,
listYearsAll .Bottom + 2 * spaces .VerMin);
int cxL = listYearsAll .Right + 2 * spaces .HorMin;
listYearsSelected .Location = new Point (cxL, listYearsAll .Top);
btnRemove .Location = new Point (cxL,
listYearsSelected .Bottom + 2 * spaces .VerMin);
group = new ElasticGroup (this, new ElasticGroupElement [] {
new ElasticGroupElement (new DominantControl (
new Control [] {ctrls [0], ctrls [1]})),
new ElasticGroupElement (new DominantControl (
new Control [] {ctrls [2], ctrls [3]}))},
12, "Years");
group .Location = new Point (spaces .FormSideSpace, spaces .FormTopSpace);
ClientSize = new Size (group .FrameArea .Right + 2 * spaces .FormSideSpace,
group .FrameArea .Bottom + 2 * spaces.FormBtmSpace);
}
Movability of all the screen elements is only one feature of the user-driven applications. The full users’ control over the applications means the easy change of all visual parameters and also the control over the behaviour of the elements. Tuning of the visual parameters of this group is organized throughout the standard auxiliary form (see figure 3). An easy control over the behaviour of the elements inside the group is the new feature demonstrated in this example.
![]() |
Four controls inside the group cover the significant part of its inner area (figure 5). The free area inside the group depends on the sizes and positions of the controls (user defines these things by moving and resizing the controls) and on the spaces between the inner elements and the frame which can be changed in the tuning form. The sensitive frames and nodes near the borders of the controls decrease the free area inside the group and only by pressing somewhere inside the free area the whole group can be moved around the screen. The covers of the inner elements are not visible, so in the normal situation of movable / resizable elements, the areas between the elements look empty but in reality they may be not free because they can be covered by the nodes from the covers of inner elements. Chances are high that while trying to move the whole group, user presses somewhere inside the group but this point can be inside the cover of some inner element and instead of moving the group it starts moving or resizing an inner element. There is an easy way to avoid such accidental movements of inner elements.
User can organize the inner view of the group in the way he really likes and then fix the inner elements via the command of context menu. If the elements are fixed, then their covers are transparent for the mover. In such situation, even if the mouse is pressed anywhere on the node of the inner element, mover skips the cover of this element and looks for the next available object; below any inner element is the cover of the group itself. Thus, whatever looks as free space inside the group is really free. In such situation, there is no chance that instead of moving the whole group user will accidentally move or resize some element. Certainly, if user wants to unfix the elements, he can do it through the same menu at any moment.
These examples for years’ selection demonstrate the
easiness of work with the ElasticGroup
class but they
are too simple to demonstrate the real flexibility and power of this class.
Let us try something bigger.
How To Deal With Personal Data?
File: Form_Main.cs
From time to time, nearly everyone has to deal with some application which asks us to type in some personal data, stores this data somewhere in database, and can display the stored data later on request. If you have to deal with such an application once in several years, then you have a chance to live through each of those encounters regardless of the interface of such program. But there are people, like HR personal, who have to work with such systems for many hours every day and for those people the design of similar applications and the easiness of their change according to personal taste and current task at any moment are crucial.
In any such program, the core is the database itself, but I want to mention from the beginning that such part does not exist in the next example, so don’t look for it or you can add it yourself. For the users of such program, the problems of storage do not exist at all. The data is somewhere inside and must be shown on request. Users are not interested at all in the coding but are extremely interested in the way the data is shown on the screen, in the simplicity of obtaining only the needed part of information, and in the easiness of going from one task to another with simultaneous change of the data presentation. I am interested in organizing the best possible interface not for average user but for each user at any moment; this is the main topic of this article. Let us imagine that all the controls on the screen show the requested data and let us look at the details of this data presentation.
The computers are capable of storing a fantastic quantity of information and it looks like the designers of systems with personal data try to fill the storage as quickly as possible. In general, the amount of data which is stored about each person is several times or many times bigger than can be shown on the largest possible screen even with the smallest available font. Even if you imagine a screen big enough to show simultaneously all the stored information about the person you are interested in, you will be lost in this information. At any particular moment, the user of such program is interested only in some part of the available information and there must be an easy way to determine the part to be shown.
Good information systems have thousands of users and each of them is interested in different parts of information at one or another moment. There are three ways to organize such system:
- Design a good enough interface and organize a very aggressive marketing with an obvious and often even openly declared idea “You have to like whatever you are given”. The majority of currently used systems is designed and promoted in such a way.
- Give users a chance to select between several variants of showing the information. It is a classical adaptive interface implemented in many systems.
- Give users an easy and quick instrument to change the interface according to the particular task at any moment. This is the idea of user-driven applications.
![]() |
In the Form_Main.cs, all the available data from our hypothetical database is shown in one big group. Figure 6 demonstrates the default view of this group and shows all the elements which I included into current version. From the point of link between the database and the program, it doesn’t matter where users will prefer to place each inner element, what sizes each element will have, what font anyone is going to use, and in what color each piece of information is going to appear on the screen. Each control is associated with only one particular type of information from the database and this piece of information appears in the particular element regardless of all the parameters of its visibility. As a programmer, I am responsible for showing the correct information (in the real application of such type which is really linked with the database); you, as a user, can rearrange the whole view in any way you want.
There are several obvious blocks of data in my example. In the list below, the position of each block is mentioned according to its place at figure 6.
Block |
Design |
Position |
Current day and time |
Two |
Top right corner |
Name of person |
Two |
Top left corner |
Day of birth |
|
Middle of the upper part |
Address |
|
Middle on the left |
Contacts |
|
Bottom left corner |
Professional status |
|
Middle on the right |
Projects |
|
Bottom right corner |
Though the outer group – groupData
– is not a small one and consists of many elements, the construction of
this group is simple enough and obvious. I decided to include into this
article only parts of the
DefaultView()
method as the majority of
the inner elements are organized into several ElasticGroup
objects and these inner groups are constructed in a
similar way.
private void DefaultView ()
{
Spaces spaces = new Spaces (this);
… …
textSurname .Location =
new Point (textName .Left, textName .Bottom + spaces .VerMin);
CommentedControl ccName = new CommentedControl (this, ctrls [2],
Resizing .WE, Side .W, "Name");
CommentedControl ccSurname = new CommentedControl (this, ctrls [3],
Resizing .WE, Side .W, "Surname");
LineCommentsOnLeft (new CommentedControl [] { ccName, ccSurname });
int cy = textDay .Top;
textMonth .Location =
new Point (textDay .Right + spaces .Hor_betweenFrames, cy);
textYear .Location =
new Point (textMonth .Right + spaces .Hor_betweenFrames, cy);
CommentedControl [] ccsDOB = new CommentedControl [] {
new CommentedControl (this, ctrls [4], Resizing .WE, Side .N, "Day"),
new CommentedControl (this, ctrls [5], Resizing .WE, Side .N, "Month"),
new CommentedControl (this, ctrls [6], Resizing .WE, Side .N, "Year") };
ElasticGroup groupDOB = new ElasticGroup (this, ccsDOB, "Day of birth");
groupDOB .Location = new Point (
textName .Right + 3 * spaces .Hor_betweenFrames, textName .Top);
… …
groupData = new ElasticGroup (this,
new ElasticGroupElement [] {
new ElasticGroupElement (textDate, Resizing .WE),
new ElasticGroupElement (textTime, Resizing .WE),
new ElasticGroupElement (ccName),
new ElasticGroupElement (ccSurname),
new ElasticGroupElement (groupDOB),
new ElasticGroupElement (groupContacts),
new ElasticGroupElement (groupAddress),
new ElasticGroupElement (groupProfessional),
new ElasticGroupElement (groupProjects) },
"Personal data");
… …
}
The ElasticGroup
class
is extremely flexible.
- All parameters of visibility can be changed via the special tuning form (see figure 3).
- Sizes of all the controls inside can be changed by simple moving of their borders.
- Positions of all the elements can be changed by their moving.
But this is only a small part of what user can do. There
are two more features which make ElasticGroup
objects not only flexible but very powerful.
Especially when you remember that such groups can be used as inner elements of
other groups.
- Any inner element can be hidden and there are two big pluses in such action. First, this saves a very valuable screen space. Second, currently unneeded elements do not distract user’s attention. When later the hidden elements are needed again, they can be resurrected by a single command from context menu.
- Inner elements can be fixed or unfixed on demand; this is also done through the commands of context menus. .
Powerful applications with a lot of possibilities for
users always have the same defect: users have to learn and remember all those
possibilities and the ways to use them. In the case of ElasticGroup
class, I try to solve this well known problem by using
the same few and simple rules regardless of the elements to which these rules
are applied.
- To change some feature for all the elements of some group, you have to call the context menu of this group and click one command in the opened context menu. For example, this is the way to fix or unfix all the elements in the group.
- To change some feature for a particular element, you have to call the context menu on this element and select the corresponding command in this menu. In this way, only one thing cannot be done: previously hidden element cannot be unveiled through its own menu because it is impossible to call a menu on the hidden element. For this particular situation, there is one more rule.
- To unveil the hidden element(s), you need to call the context menu on the parent of this element and then select the command from this menu.
As usual, a context menu is opened when the right button is pressed and released on some object.
private void OnMouseUp (object sender, MouseEventArgs e)
{
ptMouse_Up = e .Location;
double fDist = Auxi_Geometry .Distance (ptMouse_Down, ptMouse_Up);
if (e .Button == MouseButtons .Left)
{
… …
}
else if (e .Button == MouseButtons .Right)
{
if (mover .Release ())
{
if (fDist <= 3)
{
if (mover .WasCaughtSource is TextOnDemand)
{
TuningInfo (e .Location);
}
else
{
MenuSelection (mover .WasCaughtObject);
}
}
}
… …
![]() |
To call the appropriate context menu for any
pressed element, there is the Form_Main.MenuSelection()
method. This method identifies the pressed element
and declares the menu which must be opened. These menus are slightly different
for each class of elements, so there are seven different context menus in the Form_Main.cs.
The code of the
MenuSelection()
method is available in
the file and I don’t think that I need to include it into the text of the
article, but it is interesting to compare the views of those menus (at least
some of them) and see what appears on the screen. Users don’t know and don’t
need to know the names of the classes of the pressed elements, but such
association of menu and the class of the pressed element (figure 7) can
be interesting for developers. The important thing for users is the similarity
in all these menus, whenever it is possible, and the implementation of the
above mentioned rules when some command line in any menu is pressed.
What can be the result of design based on the ElasticGroup
class? As the developer of the Personal data
program, I have to put under users’ control the full power of such design but I
can’t predict how users are going to rearrange the view of the program.
Considering the easiness of movability and resizability of elements and the
possibility of hiding any set of them, there can be an infinitive number of
variants. At figure 8, you can see my versions for two different tasks.
This is my vision of good solutions for these tasks; yours can be absolutely
different and anyone else may have his own opinion. The most important thing
is that each opinion can be transformed into the different view of really
working application just in seconds and it will continue to work according to
the task.
![]() |
Figure 8a shows the same application for the period when Christmas cards have to be sent, while figure 8b demonstrates the view which is more suitable for organizing some professional meeting. Rearranging the application to any of these views takes only a couple of seconds; the default view can be reinstalled in an instant via a command of context menu. There is nothing common between two views at figure 8, but also don’t forget that these are the possible views of the same group that is shown at figure 6. Compare all three pictures and try to answer a simple question: “Is there any type of adaptive interface which can allow such flexibility?”
In all the previous examples, the ElasticGroup
objects are constructed from the beginning and can be
used at any moment. Users can hide those groups (figure 8), but they
still exist. In the next example, there are no groups by default. The ElasticGroup
object is organized only on a special demand from user
and disappears as a result of another user’s command.
Calculator
File: Form_Calculator.cs
Menu position: Calculator
I think that nearly everyone using Windows system is familiar with the Calculator application from Microsoft. Several years ago, during a conversation with one of the colleagues, I heard a complaint: having a poor vision, the colleague had big troubles with the standard Calculator program because even the combined efforts of several specialists did not reveal any way to increase the font used by this application. Another mystery from Microsoft. So I sat down and developed a Calculator which that colleague could use. The program had to work as a normal Calculator with operations and functions; figure 9 demonstrates its default view.
It is obvious that even the positioning of
numbers (buttons with numbers) in this view is non-standard. In any calculator,
the numbers are organized into a grid three by three with the zero number
placed below the bottom left corner of such square. I prefer to have the
numbers in a single row; you can transform the view of this program in any way
you want. Any control is movable (and resizable), so it’s not a problem to
move the elements one by one. But occasionally, it would be easier to move
synchronously a whole group of elements; this is done by uniting them into a
group and then moving the group.
Uniting of elements into a group is done in a standard
way. Press the left button at any empty place and start moving the mouse
without releasing the button. Special flag – bRectInView
– is used to inform about the demand for painting a temporary rectangular
frame.
private void OnMouseDown (object sender, MouseEventArgs e)
{
ptMouse_Down = e .Location;
bMenuByMover = false;
ContextMenuStrip = null;
if (!mover .Catch (e.Location, e.Button) && e.Button == MouseButtons .Left)
{
bRectInView = true;
}
}
This temporary frame is shown only during the period
of time when the left button is pressed. The rectangular area of this frame is
based on two opposite corners; one of them is the initial point where the mouse
was pressed (ptMouse_Down
); another one is the current mouse position (ptMouse_Move
). The temporary frame is painted with a blue pen.
private void OnPaint (object sender, PaintEventArgs e)
{
Graphics grfx = e .Graphics;
if (bRectInView)
{
grfx .DrawRectangle (Pens .Blue,
Math .Min (ptMouse_Down .X, ptMouse_Move .X),
Math .Min (ptMouse_Down .Y, ptMouse_Move .Y),
Math .Abs (ptMouse_Move .X - ptMouse_Down .X),
Math .Abs (ptMouse_Move .Y - ptMouse_Down .Y));
}
if (controlsInFrame .Count > 0)
{
frame .Draw (grfx);
}
}
When at last the left button is released, then the SetGroup()
method is called to check the possible existence of any
controls inside the area of the temporary frame.
private void OnMouseUp (object sender, MouseEventArgs e)
{
ptMouse_Up = e .Location;
double dist = Auxi_Geometry .Distance (ptMouse_Down, ptMouse_Up);
if (mover .Release ())
{
… …
}
else
{
if (bRectInView)
{
SetGroup (new Rectangle (Math .Min (ptMouse_Down .X, e .X),
Math .Min (ptMouse_Down .Y, e .Y),
Math .Abs (e .X - ptMouse_Down .X),
Math .Abs (e .Y - ptMouse_Down .Y)));
bRectInView = false;
}
… …
The SetGroup()
method tries
to divide all the controls of the form between two lists. Controls which are
rounded by the rectangular frame are included into the controlsInFrame
; all other controls are included into the controlsSingle
. If there is only one control in the controlsInFrame
, then it is also moved to the controlsSingle
. Thus, if the controlsInFrame
is
not empty, then there are at least two elements in this list.
private void SetGroup (Rectangle rc)
{
controlsSingle .Clear ();
controlsInFrame .Clear ();
foreach (Control control in Controls)
{
if (rc .Contains (control .Bounds))
{
controlsInFrame .Add (control);
}
else
{
controlsSingle .Add (control);
}
}
if (controlsInFrame .Count == 1)
{
controlsSingle .Add (controlsInFrame [0]);
controlsInFrame .Clear ();
}
RenewMover ();
}
If there are items in the controlsInFrame
, then the ElasticGroup
is organized; the group contains all elements from the
list. The construction of the group is done inside the RenewMover()
method.
private void RenewMover ()
{
mover .Clear ();
… …
if (controlsInFrame .Count > 0)
{
… …
frame = new ElasticGroup (this, controlsInFrame,
new int [] { 10, 6, 10, 10 }, title);
frame .Movable = bMovable;
frame .IntoMover (mover, 0);
}
… …
Thus organized group behaves like any other group of
the
ElasticGroup
class. It can be moved
around the screen; the inner elements can be moved and resized individually as
any other control in the form; the frame of the group adjusts to all changes of
the inner elements (figure 10). The group disappears when you try to
organize another group or simply click at any empty place.
![]() |
There is only one difference between this
temporary group and all other groups of the same ElasticGroup
class demonstrated in the previous examples: this
group cannot be modified. I simply don’t see any sense in organizing the
tuning of such group. If you want, you can do it in exactly the same way as
was demonstrated in the previous examples.
Conclusion
After the development of the ElasticGroup
class, it quickly became one of the most usable
classes in design of my applications. The class is so flexible that it can be
successfully used in the programs for absolutely different areas. These groups
combine easiness of design, which is very important for developers, and
easiness in use. It is common in programming to use some simple instruments
for simple tasks, while for complex tasks much more complex objects are used.
The
ElasticGroup
class stands like an
exception from this rule because it is of high demand for the tasks of any
level of complexity. Among the simplest are the situations when a group
consists of several small buttons. In the most complex cases, there can be many
elements inside the group; though developer does whatever he can to design the
best possible view, chances are high that users will be not satisfied. It is a
standard situation that for the complex groups nearly every user will have his
own vision of the good design and the ElasticGroup
class gives everyone a chance and an easy way to organize
the best view.
In the demo application accompanying the book World
of Movable Objects, there are more than 30 examples which use the ElasticGroup
class. I want to show the images of some of these
groups. The names of the associated forms will help you to find the code and to
see how these groups are constructed and used. While looking at these figures,
don’t forget that there is nothing fixed in the ElasticGroup
objects and if you prefer to change the view of any of
them, you can do it in seconds.
From the From_DataViewerTXT.cs |
From the Form_DefineNewRing |
From the Form_Functions.cs |
|
|
|
From the Form_FuncXrYr.cs |
From the Form_Zoom.cs |
From the Form_SetOfObjects |
|
|
|