In GUI applications it is often necessary to prevent the user from being able to carry out some of the actions that the interface offers. This is (often) done by disabling (deactivating) temporarily at run time, groups of controls. This way we often use the state of GUI elements to synchronize the various tasks of the application. We can speak here without any loss of generality about menus (menu items) as such GUI items; the discussion can easily apply to any type of control. Run-time menu synchronization is usually done by hand in code, in every place we need to have such feature. In GUI applications this is usually implemented by some enable/disable code spread around the code routines that need to do some from of synchronization. This means that the cost of making a change in such an application, when we need to add a new menu or to define a new logic group, is high. This situation happens often during development phase and more rarely during maintenance. Below an automated system is represented which tries to facilitate this kind of task in a GUI application. The logic behind this system is transparent to the programmer.
We will use the fact that most of the knowledge about the exclusive groups of menus can be deduced form simple logic statements made about each menu element that is present or is added in a program. For every new menu item added, we need to specify the group of menus where it belongs. For example let us suppose that we define the first menu item m1. There are no other menu items before this, thus this forms a group by itself, which we will denote as: {m1}. Then we add another menu item m2. This can be in the same group as the first or in a new one. If m2 is in the same group as m1 then we have still only one group {m1,m2}. Other wise m2 is in a new group we have two groups {m1} and {m2}.
For every two menus only one of two relations may stand: they are 'friends', that is they belong to the same group, or they are 'enemies' and belong to two different groups. Usually we do not need to specify the kind of relation for every pair of menus, since this knowledge can be deduced. Let consider the case when we have three menu items m1, m2, and m3. If we say that m1 'is friend of' m2 and m2 'is enemy of' m3, then we would group these three items in two 'action' groups: {m1,m2} and {m3}. Thus we need not to say explicitly that m1 'is enemy of' m3.
We can make used of this knowledge to build a system for managing the action groups where the menu relations are deduced based on the relations specified. In such a system the groups are created automatically and transparently, by examining all of 'friend' and 'enemy' relations defined by the user. The system should then offer the possibility to de/activate the implicit groups, based only on a given menu item, which belongs to one or more of the implicit groups. The details how the implementation of this is done are not important to the user of such system.
In practice these two relations can be projected in four operations (relations):
The four operations above can be used to change the state of groups (clusters) in any time and a new successive declaration can change the state set by the previous declarations. But note that the order in which the operations are defined is not important.
The system should build the action groups implicitly from these operations. This process should be transparent. However some mean for debugging the state of the manager (the groups formed in a moment of time) would be useful during development, so that it could be implemented and exposed to the users.
// Legend:
// <+> - declareFriend
// <-> - declareEnemy
// <*> - declareMutualFriend
// <%> - declareAntiEnemy
//
m1<+>m2 # this forms group: {m1,m2}
m2<+>m1 # this is the same: {m1,m2}
m2<->m3 # then: {m1,m2};{m3}
m1<+>m3 # this here causes the group merge of {m1,m2} and {m3} so {m1, m2, m3}
m4<+>m2 # {m1,m2,m3,m4};
m4<->m1 # {m1,m2,m3};{m4}
m1<+>m1 # m1 creates a new group if not already a member of another: {m1,m2,m3};{m4}
m1<->m1 # m1 creates a new group if {m1} does not exists: {m1};{m1,m2,m3};{m4}
m5<->m1 # {m1};{m1,m2,m3};{m4};{m5}
m6<->m1 # {m1};{m1,m2,m3};{m4};{m5};{m6}
m6<+>m5 # {m1};{m1,m2,m3};{m4};{m5,m6}
m1<*>m5 # {m1};{m1,m2,m3};{m4};{m1,m5,m6} - mutual friends: m1 belong to two groups.
m1<%>m2 # {m1};{m2,m3};{m4};{m1,m5,m6}
m1<%>m1 # {m2,m3};{m4};{m1,m5,m6}
m1<+>m3 # {m1,m2,m3,m5,m6};{m4}
The main components of the implementation are shown in the figure below. They belong to the namepace com_vpcepa::actionGroup.

A detailed description follows:
Member<T> (files: member.h) is a wrapper class around the specific GUI components T to be synchronized.
The T class is required to have only two methods:
* Each member object of class T must have these methods:
* string (T::*pf)(); // eg. string Menu::getName();
* void (T::*pf)(bool); // eg. Void Menu::setState(bool newState);
*
* Also operator << must be defined for specific member objects T.
They will be used like this:
// this is the first thing we must do before // we use any of other gmanager objects !!! Member<Menu>::setNameMethod(&Menu::getName); Member<Menu>::setStateMethod(&Menu::setState);
A trivial class Menu (files: menu.h, menu.cpp) is used as a type T in demo.
Group<T> (files: group.h) - We save objects not pointers here. This may not be always preferable. The implementation can be changed to make use of pointers, that is to store types of Member<T>*, instead of Member<T> as it does now.
Since a member item can not be in a group more than once, than a group is just a set.
Group<T> class should NOT be accessed directly in code.
GroupManager<T> (files: groupmanager.h, groupmanager.cpp) implements the required logic for clustering the groups of components based on their '<+>' (friend) and '<->' (enemy), '<*>' (mutual friend) and <%> (anti-enemy) operations. The operations can be specified by calling its methods
declareFriend(), declareEnemy(), declareMutualFriend() and declareAntiEnemy() direclty in code, or by using an external action file which is the preferred way.
Various '*activate()' methods of this class are used to de/activate groups.
ParseClusters (files: parseclusters.h, parseclusters.cpp) allows us to initialize a GroupManager<T> object based on an external
action file, not calling thus declareFriend(), declareEnemy(), declareMutualFriend() and declareAntiEnemy() directly. Only the names of T objects need to be in this file along with their relations.
The method:
void parseAction(GroupManager<T>&, map<string, Member<T> >&, char *)
is used to initialize a 'GroupManager<T>' from a action file 'char *'. The pairs of components (name, Member<T>) should be provided in a map object.
The format of the actions file is:
# The grammar:
#
# associationsfile := (association_line)*;
# association_line := comment | operation | operation comment | empty;
# comment := '#' + (alfanumeric)*
# operation := name operator name;
# name := (alfanumeric)+;
# operator := '<+>' | '<->' | '<*>' | '<%>'
# alfanumeric := all keyboard chars
# empty := an empty line
#
# Spaces may separate tokens.
To use this feature in code 'parseclusters.h' should be included and 'parseclusters.cpp' code should be included in the list of to be compiled files.
MemberCollection<T> (files: membercollection.h, membercollection.cpp) a utility class for using the gmanager system. The 'gmanager-demo.cpp' uses this class. For more details manual operations see 'manager.cpp'. This is the recommended way to use the code functionality.
Various other files are used:
To use gmananger system in another application, you do not need the 'gmanager-demo.cpp' file.
To compile the demo use:
CC gmanager-demo.cpp menu.cpp vutils.cpp parseclusters.cpp membercollection.cpp
Where CC is any C++ compiler, but the code was compiled only by Borland BCC32 5.5.1. The code makes use of C++ exceptions and may not be compiled by all compilers. The exceptions may be omitted, by editing the code.
The demo code is not thread safe. The code needs some critical-session wrapper code to be used in multi-thread applications, that enable/disable controls from many threads.
| You must Sign In to use this message board. | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||