User:Thepigdog/GUI Theory
Contents |
[edit] Purpose
The goal of this page is to describe the general theory of designing and implementing Graphical User Interfaces.
[edit] Introduction
A graphical user interface is an interface that a human uses to interact with a computer that allows the user to see a representation of objects and select and interact with the objects by pointing to them using a graphical input device.
Implementation of GUIs is normally done using the Model View Controller architecture. There are certain limitations to this architecture that I would like to highlight.
[edit] The Starting Point
A user wishes to interact with an object.
- See a representation of an object.
- Select parts of the object.
- Setting values for attributes of the object.
- Request actions of the object,
The simplest approach is for the object to draw itself. Every object implements a draw method. Drawing an object proceeds by drawing all the component objects out of which the object is made. Drawing is a traversal of the object structure.
There are problems with this approach.
- The drawing code is in the same classes as all the other logic.
- There can only be one representation of the object.
- The drawing can only support one style of interaction.
These considerations lead directly to the View/Model separation. The view is one appearance of the object. The object exists independently of its appearance.
But this approach has problems.
- Drawing the object is no longer traversing the object tree, asking each part to draw.
- Interaction with the View of the object must be connected back to the object.
These are both serious problems that have added significantly to the mess in GUI code.
[edit] GUI Objects
We require the Model to be a description of the object independent of any requirement to display the object. The view must then be everything else. And there might be multiple views. The common functionality goes into the GUI Objects. The logic for display of a view is called the presentation layer.
The common logic is related to attributes of an object.
- Enabled - Is the attribute available for modification.
- Mandatory - Must a value be entered.
- Defaulting - When we set a value what other values should be given a default value.
The defaulting may only be appropriate in the context of a GUI. All the above functionality should be implemented by functions in the GUI object.
- CalcEnabled - Calculate if an attribute is enabled.
- CalcMandatory - Calculate If an attribute is mandatory.
- Get - Get the value.
- Set - Set the value.
The implementation of these functions should logically be based on the object. It makes sense for the GUI Object to inherit from the Object.
We need CalcEnabled, CalcMandatory, Get and Set methods for each attribute. To avoid confusion we can include the attribute name in the function name. For example for attributes called Quantity and Amount we would have methods.
- CalcEnabledQuantity - Calculate if the Quantity attribute is enabled.
- CalcMandatoryQuantity - Calculate If the Quantity attribute is mandatory.
- GetQuantity - Get the Quantity value.
- SetQuantity - Set the Quantity value.
- CalcEnabledAmount - Calculate if the Amount attribute is enabled.
- CalcMandatoryAmount - Calculate If the Amount attribute is mandatory.
- GetAmount - Get the Amount value.
- SetAmount - Set the Amount value.
The Get and Set methods may be inherited from the Object. The Set method may be overridden in the GUI object to provide defaulting functionality specific to the GUI.
These functions could be used as Call Back functions. The functions could be stored on controls or widgets to be called when necessary. But the use of Call Back functions uses second order logic. It is also fairly unnatural.
We would like the name of the function to automatically cause it to be called at the right time. We would like when the control/widget for Quantity calls CalcEnabled, the CalcEnabledQuantity function is called.
I call this mechanism "Renaming Inheritance".
[edit] Renaming Inheritance
Renaming Inheritance is similar to VB events. In VB a variable may be declared using the WithEvents keyword.
For example we could have a VB class called ControlView, which has methods,
- CalcEnabled
- CalcMandatory
Then we declare in VB,
private Quantity as ControlView
Then there are two event handling methods we can declare,
private function Quantity_CalcEnabled as boolean private function Quantity_CalcMandatory as boolean
Renaming Inheritance is a slightly different twist on VB events. Instead of considering Quantity to be a member variable we think of it as inheriting Quantity with renaming,
In ControlView we declare functions
CalcEnabled Renamed CalcEnabled% CalcMandatory renamed CalcMandatory% GetString renamed Get% SetString renamed Set%
Then GUIMyObject can be declared
GUIMyObject IsA MyObject HasA ControlView Named Quantity
Which gives us 4 functions that can be overridden,
- CalcEnabledQuantity
- CalcMandatoryQuantity
- GetQuantity
- SetQuantity
The advantage of this approached is that instead of creating an independent event handling mechanism, Renaming Inheritance extends the inheritance idea. It also hides the member variable.
Direct access to member variable builds inflexibility. Code written which accesses a member variable cant be readily changed to use data stored elsewhere. This kind of code is then inflexible and represents poor design.
See,
[edit] The structure of a GUI object
For an object MyObject with attributes Quantity and Amount, we would have,
MyObject { has DoubleAttribute named Quantity has DoubleAttribute named Amount }
GuiMyObject { is MyObject has DoubleControl named Quantity has DoubleControl named Amount }
DoubleAttribute would have Get and Set methods,
class DoubleAttribute { function GetString renamed Get% as String function SetString renamed Set% (newVal as String) }
DoubleControl would have CalcEnabled, CalcMandatory and Get and Set methods,
class DoubleControl { function GetString renamed Get% as String function SetString renamed Set% (newVal as String) function CalcEnabled renamed CalcEnabled% as Boolean function CalcMandatory renamed CalcMandatory% as Boolean }
GuiMyObject would inherit the Get and Set methods twice. But instead of the compiler logging an error, we allow multiple function implementations and interpret a call as calling all definitions. This is called "Sharing".
[edit] Sharing
Whenever there are multiple implementations for a function we interpret a call to mean call all of them. This feature can be used when we want to iterate through a structure.
For example StringControl could have a method called Draw. When we want to draw all controls we can call Draw on MyObject. This will call Draw on the StingControls for each object.