GRAPHICAL USER INTERFACE DESIGN
Graphical User Interface design - contents
Introduction
This document describes some of the ideas that I have for designing a
Graphical User Interface (GUI). The ideas concern the internal
architecture and implementation design of the GUI.
In this text I often use the term GUI-Control. By this I mean a complete
functioning entity within the userinterface that the user can interact with.
This includes all of it's internal implementation details. Examples of
GUI-Controls might be: Buttons, listboxes and textfields.
Description of the classical Model/View/Controller (MVC) architecture
Today many GUI-development tools and API's use the
structure of the classic Model/View/Controller architecture or some derivative
form of it. Other tools simply aren't using any particular structure.
If there is no such structure all GUI-controls are rather independant
objects with no common functionality. This usually means that there
is less reuse of code and less consistency in the GUI both regarding
look and feel.
I have included this description so that it can serve as background
knowledge for presenting my ideas. There is a lot of information available
about this classical MVC architecture so this is just a basic description.
In this acrhitecture the userinterface is split into a Model part, a View part
and a Controller part. In this text I will assume that this split is done
internally in each GUI-Control, so that a GUI-control can be considered as
a single complete object.
The Model
This entity takes care of storing the data of a GUI-Control. In the example
of a checkbox, the data to be stored might be the text of a label and
an up/down state of the checkbox. One checkbox model might store this
data in memory while another could store it in a database.
When the model part of a GUI-Control is separate it becomes
possible to reuse the rest of the code in the GUI-Control whether the
data of the GUI-control is stored in memory, in a database or somewhere
else.
The View
The View entity defines how GUI-Controls look. It draws GUI-Controls
on the screen and refreshes them as needed. It has to be able to both
reflect changes in the Model and display user interactions.
A good reason to separate the View from the rest of a GUI-Control is
to be able to define GUI-Controls which contains the same data, and
works the same way as other GUI-Controls but looks different. An
example could be to replace the view of a checkbox to make it look
like a toggle button. They have similar data and functionality:
They can both be toggled and usually have text-labels.
The Controller
The functionality of a GUI-Control is defined in the Controller entity. It
represents all logic and userinteraction of a GUI-Control. It translates
userinput into how and when the GUI-Control should change apperance and
how the data in the Model is altered. In short it defines the "feel"
of a GUI-Control.
By replacing a controller in a GUI-Control you can change the way it
behaves, without changing it's look and how it stores data in the model.
It would for instance be possible to replace the controller in a
togglebutton, to change it to a pushbutton. It still looks the same and
is still a button, but it behaves differently.
A modified MVC architecture: The Container/Model/Cell/Editor architecture
This architecture is an alternative to the classic MVC arhcitecture.
The Container/Model/Cell/Editor architecture is derived from
the Model/View/Controller architecture. The Model-part remains the
same, but the View and Controller parts are arranged differently and
everything has been wrapped inside a container.
This design is mainly inspired from the OpenStep API, Delphi and the
Swing API. My goal is to unify the best features of these development
tools.
The Container
The Container is an entity that defines a complete GUI-control
and the interface for manipulating it from within the program.
Possible Containers could be: Pressables (i.e. Buttons, Checkboxes,
Radiobuttons etc.), Listbox, Radiogroup, Textfield,
TextGrid and Progressbar. An application developer who only uses
default GUI-controls will only need to create and use containers.
This simplifies the creation of GUIs from the programmer's point of view.
The Container contains a Cell, an Editor and a Model which are all
described below.
Some Containers could be regarded as being almost the same
from a programmers point of view. An example is PushButton, ToggleButton,
Checkbox and Radiobutton which are just called Pressables above.
They can all be clicked, they all have a
text-label and perhaps an icon. They just look and behave differently.
In this case you would just need one Container object with different
Cells and/or Editors as described below. This simplifies the manipulation
of the GUI even further.
It would be possible and relatively easy to wrap a container into using a
standard interface like
CORBA,
JavaBeans,
ActiveX etc.
When using such a standard interface it is possible for a container to
describe itself at runtime. This is required when building a GUI from a
visual GUI-builder. If a standard interface is not used, then there
should be some other way of describing the properties that can be manipulated
in the container at runtime. The container could for instance provide a
list of strings with a datatype and some functionpointers for each string
in the list to describe all properties that can be modified.
The Cell
A Cell is owned by a container and is tied closely to the Editor.
The Cell defines the look and some of the feel/behaviour of a
GUI-Control. The only behaviour it defines is what is not related
to editing a GUI-Control. It could define behaviour for highlighting
GUI-Controls as the mouse travels across it, but it should not control
how text is edited in a textfield. Examples of cells could be: ButtonCell,
CheckboxCell, RadiobuttonCell, TextFieldCell, LabelCell, TextListCell and
ProgressbarCell. The Cell is what corresponds to the View plus some of
the Controller-functionality in the Model/View/Controller architecture.
When modifying the look&feel of a GUI you will need to replace the
Cells and the Editors in the GUI. Users should be allowed to replace
the standard Cells and Editors to allow them to switch between multiple
look&feels. Availble look&feels could be BeOS, Motif, Windows, AmigaOS,
MacOS, OpenStep and other possibly new look&feels.
To enable Cells to define such things as gridareas and listareas in
a particular implementation, all Cells will need an associated index
when requested for information or when requested to perform an operation.
This may not always be nessesary, but it will sometimes allow for more
flexible or more efficient use of Cells.
A special Multiplexer Cell could also be created, to allow multiplexing
of multiple look&feels. Such a cell would have a list of delegate Cells
that it represents. This could be used for creating an audio-only look&
feel which could be multiplexed with an existing look&feel. This would
add audio to the exsisting look&feel.
The Editor
The Editor is owned by the container like Cells and is closely tied to
Cells. It defines the behaviour of a GUI-control related to editing.
It could for instance define how text is edited in a textfield or
how a button reacts when it is clicked - like whether it should toggle
or be a momentary push. The Editor should use as much information
as possible from the Cell and the Model.
As mentioned, Editors should be replacable to allow for
pluggable look&feel. See the description of "The Cell" above for more
information.
To enable Editors to define such things as gridareas and listareas in
a particular implementation, all Editors will need an associated Index
when requested for information or when requested to perform an operation.
This may not always be nessesary, but it will sometimes allow for more
flexible or more efficient use of Editors.
When implementing a textgrid the Cell should possibly only
be able to draw a grid of textcells. When editing a textcell a
standard TextFieldEditor could be used to do the editing. This will
allow for efficient display of the grid and still reuse the
code for text-editing.
The Model
As mentioned above this entity has the same purpose in the Container/Model/Cell/Editor
architecture as in the Model/View/Controller architecture.
Pseudo implementation example
This is some pseudo C++/Java/Delphi code to illustrate how the Container/Model/Cell/Editor
architecture might be implementated in an API:
AbsContainer
{
PluggableModules
Cell (AbsCell interface)
Editor (AbsEditor interface)
Model (AbsModel interface)
Properties
Left (integer/real)
Top (integer/real)
Width (integer/real)
Height (integer/real)
Misc aligment-properties...
Focused
Highlighted (boolean)
SetHighlighted(highlighted)
{
Model.SetHighlighted(this,0,highlighted); //Model should notify GUI-cell
}
GetHighlighted()
{
return Model.GetHighlighted(this,0);
}
Events
MouseUp //Pass on to the Cell
MouseDown //Pass on to the Cell
MouseMove //Pass on to the Cell
KeyUp //Pass on to the Cell
KeyDown //Pass on to the Cell
(KeyPress //Pass on to the Cell)
ChangeFocus //Pass on to the Editor???
}
AbsListContainer : AbsContainer
{
Properties
HiglightedIndex
FocusedIndex
Elements //Number of elements in list
}
Abs2DGridContainer : AbsContainer
{
Properties
HiglightedRow
HiglightedColumn
FocusedRow
FocusedColumn
Rows // Number of row in 2DGrid
Columns // Number of columns in 2DGrid
}
PressableConainer : AbsContainer
{
PluggableModules
Cell (PressableCell interface)
Editor (PressableEditor interface)
Model (PressableModel interface)
Properties
Label (string)
SetLabel(newLabel)
{
Model.SetLabel(this,0,newLabel); //Model should notify GUI-cell of the change
}
GetLabel()
{
return Model.GetLabel(this,0);
}
Icon (image)
SetIcon(newIcon)
{
Model.SetIcon(this,0,newIcon); //Model should notify GUI-cell
}
GetIcon()
{
return Model.GetIcon(this,0);
}
Pressed (boolean)
SetPressed(pressed)
{
Model.SetPressed(this,0,pressed); //Model should notify GUI-cell
}
GetPressed()
{
return Model.GetPressed(this,0);
}
Functions/Methods
PerformClick()
{
Editor.PerformClick(this,0);//Potentially trigger a PressableClicked event
}
Events
PressableClicked //Passed on to the Editor
}
//These are the functions that might create some standard GUI-Controls
CreateButton()
{
Create an instance of PressableContainer with these PluggableModules:
Cell = ButtonCell
Editor = MomentaryPushEditor
Model = PressableModel
}
CreateToggleButton()
{
Create an instance of PressableContainer with these PluggableModules:
Cell = ButtonCell
Editor = TogglePushEditor
Model = PressableModel
}
CreateCheckBox()
{
Create an instance of PressableContainer with these PluggableModules:
Cell = CheckboxCell
Editor = TogglePushEditor
Model = PressableModel
}
CreateRadioButton()
{
Create an instance of PressableContainer with these PluggableModules:
Cell = RadiobuttonCell
Model = PressableModel
Editor = ToggleRadioEditor
// The implementation of ToggleRadioEditor might be tricky,
// as it will need access to the other radio-buttons in a radio-group.
// It will be easier to implement a radio-group as a "PressableGrid"...
}
//This could create a button that stores it's data in a database:
CreateDBButton()
{
Create an instance of PressableContainer with these PluggableModules:
Cell = ButtonCell
Editor = MomentaryPushEditor
Model = PressableDBModel
// No new interfaces, no change in the button code, just a new model - simple.
}
Final remarks on the Container/Model/Cell/Editor architecture
Due to the performance overhead that it might give to separate
the Cell from the Editor, it could be better to just merge
these two together as a single Cell-Editor (View-Controller).
This is the approach of the
Swing API.
I still believe that it should all be put into a Container entity
to define one interface to multiple implementations of GUI-Controls.
This is the way it is done in the
OpenStep API, except that OpenStep
doesn't separate Model/View/Controller inside the container.
If the functionality of the Editor should be separated from the
Cell in the Container/Model/Cell/Editor architecture, the Editor could problably
be implemented as a primary eventhandler of the Cell. All it requires is that
the Cell is flexible enough to acomplish this. The Editor is after
all just added functionality to the Cell. The Editor could problably
also be implemented with virtual functions in an objectoriented language
or as function-bindings in functional languages.
There is one last important thing to note: If it should be possible for
the user to change the look&feel of an application by replacing the Cells
and the Editors in the userinterface, the Cell and the Editor will have to
be external to the application while the Container and the Model must be
internal. The interface between internal code and external code could be created by using
CORBA to define an API
for external libraries. Other APIs could also be developed for accessing libraries.
Additional GUI architecture ideas
Here I present a few other ideas that I have for implementing a GUI.
Atomic visual GUI elements
A possible way of enhancing the consistency of a GUI and reusing
more code is to build the GUI from atomic visual GUI elements.
Some examples of atomic visual GUI-elements could be: TextFrame (the
frame around a text-editing field), TextBackground (the background of
a textfield),
ButtonFrame (the outline of a button), ButtonFace (the surface of a button),
Checkmark checked, Checkmark unchecked, Scrollarrows up/down/left/right.
Textcursors, mousepointers and focusframes
Some special GUI-elements like the textcursor for editing text, the mousepointer,
focusframes for focusing GUI-controls etc. should be treated separately.
They could also have pluggable look&feel. For instance a textcursor could
be made flashing, non-flashing, as a blockcursor, as a horizontal line-cursor
or something else. If they are referenced as separate GUI-objects some
code could possibly be saved while also allowing to change the look&feel of
those objects.
Saving ressources in a GUI
To save ressources in a GUI, it would possibly be good if all
Cells and Editors implement a flag, which tells whether that particular
Cell or Editor can be reused. It would be possible to reuse a Cell or an
Editor if they do not depend on any internal data within itself, but only operates
from the parameters it gets from function calls and from the data that
it can obtain from the Model or the Container.