Figure 1: The Unit Converter application for an enhanced user experience.
Contents
Introduction
A recent (march 2004) Code Project poll asked: When writing desktop applications,
which is the most important for you?
"Ease of use" was the most popular choice, followed by “reliability”,
and a “good looking UI”. This inspired me to write this article
on making an application easier to use. This article is in two parts, the second
part will take a closer look at the controls on this application.
A considerable time ago, while learning to programme in java, one of the
first Swing applications I wrote was a Unit Converter. Therefore, it’s
only fitting for my first .Net article that I rebuild that application with
a dot
net flavour. This article is written with new C# programmers in mind, so I
apologise in advance to anyone who feels that I seem to be stating the obvious!
Let’s start by deciding on what the Unit Converter should do and how
it should do it. Before any programming begins it is always wise to determine
and document what the applications functionality should be.
Application
Functionality
Whilst building any application it is very important to keep in mind the end
user. Generally, the application functionality goes hand in hand with the end
application user’s requirements. From the name it’s obvious that
the Unit Converter application converts units of measure, e.g. between metric
metres and imperial feet.
Amongst the questions that need to be asked of the application are:
Q: What are the functionality requirements?
- The Unit Converter should be able to convert many types of units, e.g. length,
area, mass, volume etc…
- The user should be able to specify and extend the range of units that the application
can handle. (This came a second in the same Code Project poll.)
Q: How is the application going to be used?
- In this case it’s very simple; users are going to want to enter data
into the application and expect to see the converted result.
Q: What Actions is the user going to go through on the application to achieve
this?
- Due to the nature of this application the answer to this question is also
straight forward. On a complex application this would be modelled using UML
and “Use
Cases” would help visualise this process. But for our unit converter
we don’t need to go into this level of detail. However, we still need
to think through and understand how the application is going to be used.
- Choose a conversion type, e.g. Length or Weight etc...
- Enter data.
- Select the units of the entered data.
- Select the desired conversion units.
- Execute the conversion.
- View the result.
UI Design
At a bare minimum the Unit Converter needs an input field (TextBox), two controls
that allow us to select given data (possibly ListView or ComboBox), a control
to display the Data (a Label or read-only TextBox) and a button to instruct
the application to convert the entered data value.
Figure
2: Bare minimum UI design. A poor UI design is likely to lead to a poor user
experience.
This design (figure 2) is functional, it has all the necessary controls, but
it does look poor. Another problem, from the user’s perspective, is that
the two ComboBoxes contain a long list of various units of measure. This can
lead to exceptions being thrown if the user is not careful in choosing the
conversion units, e.g. if the user accidentally tries to convert from a weight
to a length. This can lead to a poor user experience.
User Experience in Mind (while Designing the Application)
Once the application functionality has been established it is time to reflect
on how the application is going to be used. How the user will interact with
the application. The way to make an application’s UI user-friendly is
to make sure the user does little as possible. The less the user needs to think
about and do, the more user-friendly the application. Even the number of mouse
clicks, and the distance the mouse is moved/dragged across the screen, all
go towards improving the application user’s experience.
The UI colour scheme is very important. With the boom of the web, users have
been exposed to web applications that look great, with smart colour schemes
and layouts. There is no reason why windows developers now days have to restrict
themselves to a mono-tone UI. At the same time it is important not to go
over board with garish colours. Colours such as certain shades of Green
and Blue
are soothing to the eye. So if the application being designed is one that
has the user working with it for prolonged periods of time, it is advisable
to
use an eye-friendly colour scheme. If, however, the application does not
require the user to stare at the UI for long periods of time then, possibly,
more adventurous
colour scheme may be adopted.
UI controls on an application screen should, where possible, fit into a neat
layout. Use of the Visual Studio.Net automatic alignment (“align to grid”)
should be taken advantage of. Visually, if the controls don’t line up
with each other or are haphazardly laid out on the screen then the user may
find it difficult to visually navigate the screens flow logic. This will decrease
the usability of the application screen.
Now let’s look at the Unit Converter
application. The Unit Converter may have more than one conversion type, and
will need to provide the user with
the ability to select a given conversion type. A good candidate for this type
of action would be the ComboBox control. A universal rule in clean UI design
is not to clutter the application screen with excessive controls; only show
the controls that are used. In the Unit Converters case it should only show
a “conversion-type” selector control (ComboBox) when there is more
than one type to choose from.
The layout of the Unit Converter’s controls
should flow naturally, keeping in line with the user’s actions. This
makes the layout easier to read.
The next logical action that the user will take is to input data (that requires
conversion) into the Unit Converter. For this situation a TextBox control is
the logical choice. If the TextBox only accepted numeric values this would
help catch erroneous data and avoid exceptions being thrown in such scenarios.
As the Unit Converter deals in numeric data the TextBox control should be extended
to only allow numeric inputs. If non numeric data is entered, the TextBox can
either flag up a message box or provide some other type of indication that
erroneous data has been entered (e.g. validation using the “ErrorProvider” class)
and wait for the user to respond to the alert. The user experience would be
greatly enhanced if this TextBox field intelligently handled the data and allowed
the user to continue with the conversion process uninterrupted.
Once the data is entered the user will select its unit of measure, e.g. “metres” or “kilometres” for
a “Length” conversion. Following this the user will choose the
desired conversion-to type, e.g. if the input data’s unit of measure
was “metres” then the conversion-to unit of measure may be “millimetres”.
This type of choice is ideal for a ComboBox control.
Remember from school science experiments, when writing down the distance
between two points or the mass of an object, System International (SI) unit
abbreviations
were used, as opposed to writing down the full name of the units e.g. m for “metres” or
kg for “kilograms”. Therefore, displaying the unit of measure in
an abbreviated form would make the application more intuitive to many users.
The Unit Converter would be further enhanced if a full description of the unit
of measure was also available to the application user, so if they are not fully
familiar with the abbreviated units then it would not be a problem. This smart
display can be achieved by creating a custom control using the ComboBox
as
the base object. Call this control a SplitComboBox
.
At any one time the SplitComboBox
should only display a single conversion
type. Therefore, if the selected conversion type is "Length", then both
SplitComboBoxes
must only be populated with "Length" units. On change
of conversion type, the SplitComboBoxes must be repopulated and display the
change.
Minimising the number of actions a user need to carry out improves the usability
of the application. So making the unit converter execute the conversion process
on change of selected unit of measure will reduce the need for the user to
click the convert button.
When the Unit Converter is launched automatic focus should be placed upon
the first control the user is likely to interact with, and subsequent focus
should move onto controls which follow the logical user path, and hopefully UI control
layout!
Another way to enhance the Unit Converter (and the user’s experience)
is by allowing the end users to add their own conversion types. A simple way
of achieving this is by using an xml file to store the Conversion data. If
the user is not careful whilst adding their own data, this approach may cause
problems. If correct data values and types are not adhered to or the xml file
structure is accidentally broken this can lead to Unit Converter falling over.
For application reliability thought needs to be given to exception handling.
To reduce application fall over, the application must have ways of gracefully
resolving problems that it faces. These problems could be a result of the
conversion xml file or its data not being correctly formatted.
UI Design; a Second Look
Figure 3: An enhanced UI design for the Unit Converter application.
Figure 3 shows the Unit Converter with the custom SplitComboBox
and
DecimalBox
field, for an enhanced user interface. The Unit Converter’s look and
colour scheme are based around a blue palette, consisting of 4 different shades.
These colours project a fresh clean design. The Background is set using a jpeg;
this is a great improvement on the default Windows form background of Control
Grey. A word of caution: Using background images can dramatically increase
the size of your final application. Therefore, if an image is to be use, it
is very important to optimise it much as possible. Bitmap or png formats are
much larger in terms of file-size than Gif and Jpeg formats. So it is advisable
to use the latter for background images. However, the quality of the image
is poorer in the jpeg and gif formats. The gif image format is suited for images
that are made up from flat blocks of colour, where as the jpeg format is more
suited to images with colour gradients such as photographs.
Coding the Application
At the Core of any application is its data. An application that relies on
large amounts of data typically uses a database to store its data. Using a
database in this case would be overkill. The Unit Converter data is stored
in an xml file ("ConversionData.xml"). This allows the end user to
extend the application with relative ease. The xml file is shown below:
="1.0"="utf-8"
<UNITCONVERTER> <TYPE NAME="Length">
<UNIT NAME="Meters" ABBREVIATION="m" CONVERSIONFACTOR="1" />
<UNIT NAME="Kilometres" ABBREVIATION="km" CONVERSIONFACTOR="1000" />
<UNIT NAME="Millimetres" ABBREVIATION="mm" CONVERSIONFACTOR="0.001" />
<UNIT NAME="Foot" ABBREVIATION="ft" CONVERSIONFACTOR="0.3048" />
<UNIT NAME="Mile" ABBREVIATION="mile" CONVERSIONFACTOR="1609.3" />
</TYPE>
<TYPE NAME="Volume">
<UNIT NAME="Litres" ABBREVIATION="l" CONVERSIONFACTOR="1" />
<UNIT NAME="Decilitres" ABBREVIATION="dl" CONVERSIONFACTOR="0.1" />
<UNIT NAME="Centilitres" ABBREVIATION="cl" CONVERSIONFACTOR="0.01" />
<UNIT NAME="Millilitres" ABBREVIATION="ml" CONVERSIONFACTOR="0.001" />
</TYPE> </UNITCONVERTER>
Within the “UNITCONVERTER” element the “TYPE” tags
are individual conversion types. The “UNIT” element is where the
individual unit of measure’s descriptive name (“NAME”), abbreviated
unit name “ABBREVIATION”, and “CONVERSIONFACTOR” values
are stored as attributes. The conversion factor values are relative to the
base unit of measure, e.g. Conversion type is Length; therefore 'metre' is
the base unit of measure, so its 'CONVERSIONFACTOR' = 1. All the other units
within the Length conversion type must be relative to this base [metre] conversion
unit’s value. As a kilometre contains 1000 meters, the “CONVERSIONFACTOR” value
for kilometres is 1000, likewise the “CONVERSIONFACTOR” values
for centimetres is 0.01.
At start up, the application reads in the XML data and holds this data in
a Hashtable
as a key-value pair; "key" being the conversion-type, and "value" being
the collection of conversion UnitData
objects which are to be used to create
the conversion matrix.
public sealed class UnitData
{
private string sCode, sText;
private decimal iValue;
public string Code
{
get{return sCode;}
}
public string Text
{
get{return sText;}
}
public decimal Value
{
get{return iValue;}
}
public UnitData(string asCode, string asText, decimal aiValue)
{
sCode = asCode;
sText = asText;
iValue = aiValue;
}
}
The Unit Converter ideally wants to load this data once, and keep it in memory,
so it does not need to reload the xml file each time the user changes the
conversion type. This approach is correct for the Unit Converter as the amount
of data
is very small, and it doesn’t change during the active state of the
application. Using the XmlTextReader
the Unit Converter reads in the xml
document into memory.
XmlTextReader Xreader = new XmlTextReader(asFileName);
XmlDocument XmlDoc = new XmlDocument();
XmlDoc.Load(Xreader);
Each conversion unit read in from the XML file is stored in a UnitData
object
and this is how the data is passed around within the application. The GetDataFromEachUnit(...)
method below does exactly this.
private UnitData GetDataFromEachUnit(XmlNode axnDataNode,
string asUnitType)
{
bool bNodeSucess = true;
decimal dConversionVal = 0.0m;
string sDataType = "", sUnit = "",
sAttType = "", sValue = "", sErroInfo = "";
int iAttribute = axnDataNode.Attributes.Count;
for(int k=0; k<iAttribute; k++)
{
sAttType = axnDataNode.Attributes[k].Name.Trim().ToUpper();
sValue = axnDataNode.Attributes[k].Value;
switch(sAttType)
{
case "CONVERSIONFACTOR":
dConversionVal = Convert.ToDecimal(sValue);
break;
case "NAME":
sDataType = sValue;
break;
case "ABBREVIATION":
sUnit = sValue;
break;
default:
bNodeSucess = false;
break;
}
sAttType = "";
sValue = "";
}
if(bNodeSucess)
return new UnitData(sUnit, sDataType, dConversionVal);
else
return null;
}
As the xml file can be modified by the user, there is a higher probability
of an exception occurring due to potential user/xml error within the methods
that carry out xml file reading and populating of the UnitData
objects. Extra
emphasis needs to be placed on exception handling within these sections of
coding, (see the Unit Converter code for basic exception handling). Exception
handling is done through the try{}catch(){}
syntax. The message that an exception
throws up for the application user should, ideally, be in a user-friendly format.
To aid debugging for the programmer, the application could write a separate
error message to a log file containing a more detailed exception message; this
would naturally be hidden from the user.
In the InitializeApplicationData()
method the “conversion type” selector" ComboBox's
Items are populated using a foreach
loop. If there is only a single Item then
the conversion type selector (ComboBox) is hidden and the UI layout is altered
to accommodate for its absence. This results in a cleaner UI.
foreach(string sType in ahtData.Keys)
this.cbx_ConvertionType.Items.Add(sType);
this.cbx_ConvertionType.SelectedIndexChanged +=
new System.EventHandler(this.cbx_ConvertionType_SelectedIndexChanged);
this.cbx_ConvertionType.SelectedIndex = 0;
if(ahtData.Count == 1)
{
this.cbx_ConvertionType.Visible = false;
this.lbl_ConvertionType.Text =
this.cbx_ConvertionType.SelectedItem+ " Conversion";
this.lbl_ConvertionType.Size = new Size(360, 25);
this.lbl_ConvertionType.Location = new Point(24, 80);
this.lbl_ConvertionType.Font =
new Font("Microsoft Sans Serif", 9.75F, FontStyle.Bold);
this.lbl_ConvertionType.TextAlign = ContentAlignment.MiddleCenter;
}
object oItemSelected = this.cbx_ConvertionType.SelectedItem;
this.alUnitObjects = (ArrayList)ahtData[oItemSelected];
this.PopulateControls(oItemSelected);
To see this in action first make a backup copy of ConversionData.xml file
and then modify it so that only a single conversion type exits.
After reading the XML data into the application the Unit Converter creates
a matrix of conversion values for a selected conversion type. The CreateDataMatrix(...)
method is responsible for this. The resulting decimal matrix is used to convert
user input values.
private decimal[,] CreateDataMatrix(ArrayList aalConversionObjects)
{
int iConvUnitCount = aalConversionObjects.Count;
decimal[,] daMatrix = new decimal[iConvUnitCount, iConvUnitCount];
string sCode = "";
decimal dData = 0.0m, dInternalData = 0.0m;
UnitData udUnitCode = null, udInternalCode = null;
for(int i=0; i<iConvUnitCount; i++)
{
udUnitCode = (UnitData)aalConversionObjects[i];
sCode = udUnitCode.Code;
dData = udUnitCode.Value;
for(int j=0; j<iConvUnitCount; j++)
{
udInternalCode = (UnitData)aalConversionObjects[j];
dInternalData = udInternalCode.Value;
daMatrix[i,j] = dInternalData/dData;
udInternalCode = null;
dInternalData = 0.0m;
}
udUnitCode = null;
dData = 0.0m;
}
return daMatrix;
}
The two unit-of-measure SplitComboBox
es and the input DecimalBox
are custom
build for this application. A closer look at the way these controls are build
will be taken in the second part of this article. For now a quick overview
of what they do for the application will be discussed.
The DecimalBox only allows the displays of a decimal value; even if the user
inputs an alpha numeric data the DecimalBox
strips out inappropriate characters
leaving only the inferred decimal value. This greatly improves the user experience,
since if the user does accidentally enter an invalid character then application
can compensate for this, instead of throwing a warning or even an exception.
This approach may not be appropriate in an application where the input data
is being saved back to a database for use later. In such situations the use
of an “ErrorProvider” class would be more suitable as this would
indicate to the user that there was a problem with the input data.
The behaviour of the SplitComboBox
, as discussed earlier, shows only the unit
of measures abbreviated value, while on the dropdown it provides the full descriptive
name. The SplitComboBox
’s datasource is an ArrayList
of
UnitData
objects,
and it is these objects that hold the key information with regards to the conversion
process. This control also has the added benefit of saving on UI space.
The ConvertInput(...)
method shown below carries out the actual conversion,
and updates the relevant UI components.
private void ConvertInput(decimal adInputValue)
{
decimal dResult = 0.0m;
int iIndex1 = 0, iIndex2 = 0;
string sResult = "";
try
{
iIndex1 = this.scbx_Unit1.SelectedIndex;
iIndex2 = this.scbx_Unit2.SelectedIndex;
dResult = adInputValue * this.daDataMatrix[iIndex2, iIndex1];
sResult = dResult.ToString();
if(sResult.Length > 25)
sResult = sResult.Substring(0, 25);
object oItem = this.scbx_Unit2.SelectedItem;
UnitData udCur = (UnitData)oItem;
this.lbl_Display.Text = sResult+" "+udCur.Code;
sResult = "";
}
catch(Exception ex)
{
eLog.Error("ConvertInput", ex.Message, "001");
}
}
This method is called when the "Convert" button is clicked or when
the “convert-to” SplitComboBox’s selected unit of measure
is changed. This allows the user to eliminate the need to click the “Convert” button
and helps towards making the application easier to use.
Summary
As with the majority of applications, the development process does not stop
here, there is always scope for improvement. For example in version 2, the
Unit Converter could have another UI screen to enable the user to modify the
Conversion XML file thus, reduce the possibility of having erroneous data entered
into the conversion xml file. This would reduce the chances of application
failure.
Another enhancement to the application should be allowing the user to determine
the number of “significant figures” of the displayed result.
The Unit Convert is a very simple application and this article has tried to
illustrate that building from the users perspective usually leads to a better
application. In the second part of this article, Enhancing User Experience,
Part 2; Extending Controls, looks at the development and coding of the custom
controls used within this application.
History
- Version 1 of Unit Converter (April 2004).
After a formal education in the field of Physics and Engineering and with aspirations of becoming a Nobel Prize winning physicist Al become tired with the monotony of research in this field. This is when he made a conscious decision to turn what was a hobby into a career.
Graphic Design has always been one of his passions combined with his Vulcan-like (logical) mind; programming emerging web technologies was the perfect choice. In the early days he designed and coded webpages, programmed Java applets and taught HTML and Internet technology courses at university level. Nowadays he works as a Research and Development programmer for a company that produce award winning logistics software.
His current interests include .Net C#, Winforms, Web Services, ASP.net, XML, XSLT, Macromedia Flex and any new software or technology that has a pulse.