Mark Sohm, Research In Motion

 

A graphical user interface manages the relationship between the application and its user. It is important to provide as much information as possible in an easy to read manner using available screen space. An application that is easy to use lets a user perform tasks in an efficient manner, whereas a poor design can easily frustrate that person and prevent them from using the application. A graphical user interface should feel intuitive and familiar to a user. The BlackBerry APIs can help developers by providing many common screen elements for use in an application.

Topics within this section include:

The BlackBerry graphical user interface (GUI) is a three level hierarchical structure in its most basic form:

The Screen class (net.rim.device.api.ui.Screen) is the starting point for the UI. Only one screen is displayed on a BlackBerry device at any given time. Screens are displayed on the device by pushing and popping them from the display stack. The screen at the top of the display stack is the one shown to the user. A screen can only exist once in the display stack, but can be pushed, or popped from, the display stack at any time. Common subclasses of the Screen class are contained in the net.rim.device.api.ui.container package.

The Manager class (net.rim.device.api.ui.Manager) manages the layout and interaction between field objects added to it. A Manager is responsible for the location and layout of its fields, as well as scrolling and focus change. A screen is required to have at least one manager to manage its fields, even if the manager has been provided by the default constructor. Common sub-classes of Manager are located in the net.rim.device.api.ui.container package.

The Field class provides key functionality for all field components. A field represents a region contained by a manager that is used to display output or handle input. The content will vary depending on the field type and any restrictions placed on the field. The net.rim.device.api.ui.component package contains a library of prebuilt UI components and controls for constructing BlackBerry applications.

Screens


A BlackBerry application requires at least one screen to display information. There are various extensions of the Screen class available in the BlackBerry API set.

FullScreen

  • A screen that contains a single VerticalFieldManager as its delegate manager.

MainScreen

  • Extends FullScreen and adds some additional functionality common to many BlackBerry device applications. MainScreen also allows for a title area at the top of the screen followed by a SeparatorField. The main area of the screen is scrollable because this screen contains a VerticalFieldManager as its delegate manager that is inherited from FullScreen. MainScreen also contains an implementation of the makeMenu method to handle menu creation.

PopupScreen

  • A screen that provides dialog or status screen features.

Dialog

  • A screen that provides the behaviour of a dialog box that is used to present information to a user and accept input. The alert, ask, inform and doModal methods of the Dialog class will display the Dialog and block (wait) for user input. The show method of the Dialog class does not block, therefore program execution will continue after the Dialog has been displayed.

Status

  • A screen that provides the features of a dialog but does not accept user input or block. The status screen is designed to display that an action that has taken place. Status screens are set to display for a fixed time or until the user presses the trackwheel, escape key or space key.

One of the most commonly used screen classes for a BlackBerry application is MainScreen which provides functionality to easily add a title and menu functions. The following code sample demonstrates use of these methods.

//Instantiate MainScreen
MainScreen theScreen = new MainScreen();

//Instantiate a LabelField
LabelField theTitle = new LabelField("My application!");

//Set the title of theScreen
theScreen.setTitle(theTitle);

//Instantiate a MenuItem
MenuItem clickMe = new MenuItem("Click Me!", 40, 40)
{
  //The run command will be called when the user
  //clicks on the MenuItem
  public void run()
  {
    Dialog.alert("You clicked me!");
  }
};

//Add clickMe to theScreen
theScreen.addMenuItem(clickMe);

//Push the screen onto the display stack.
pushScreen(theScreen);

A screen that has been pushed to the top of the stack is under the control of the main event thread. This means that only the event thread is able to make changes to the screen. An example of this could be adding a new LabelField to the screen.

theScreen.add(new LabelField("Hello there.");

The add method above can be run from the main event thread. An IllegalStateException will be thrown if a thread other than the main event thread attempts to execute this line of code. The solution is to place the screen change in the screen’s event queue or obtain the event lock.

A screen can be updated from outside of the main event thread by calling the invokeLater() or invokeAndWait() methods respectively. Use invokeLater() to change screens when possible. This adds the screen change to the application’s event queue to be executed. The invokeAndWait() method will force the code to be executed right away, halting other processing. invokeAndWait() can be used when screen updates must occur in a timely fashion, such as when performing an animation. invokeAndWait() should not be held to perform lengthy operations.

Calling invokeLater() repeatedly in rapid succession may cause the event queue to overflow and result in an exception. These methods should only be used when necessary and only for the least amount of time to update the screen. Calculations should be performed outside of these methods. Code on the event thread should not block or execute for long periods of time because the system will not dispatch messages and the event queue could overflow causing an exception to be thrown.

The following code illustrates the use of invokeLater()

UiApplication.getUiApplication().invokeLater(new Runnable() {
  public void run()
  {
    //Add a new LabelField to the screen.
    theScreen.add(new LabelField("Hello there.");

    //Call the screen’s invalidate method to
    //force the screen to redraw itself.
    //Note that invalidate can be called
    //at the screen, manager or field level,
    //which means you can inform the
    //BlackBerry to only redraw the area that
    //has changed.
    theScreen.invalidate();
  }
});

Managers


The next level beneath Screen in the BlackBerry UI heirarchy is the Manager class. Managers are responsible for vertical and/or horizontal scrolling, and the positioning and layout of fields. Fields are added to managers which will place it in an appropriate area on the BlackBerry device's screen. The BlackBerry API contains a set of managers that extend from the Manager class and provide a layout mechanism for common screen designs.

HorizontalFieldManager

  • Lays out fields from left to right in a single row. This manager can provide horizontal scrolling for fields that do not fit on the BlackBerry device screen as well as vertical scrolling for fields that are taller than the screen.

VerticalFieldManager

  • Lays out fields in a single vertical column. This manager can provide vertical scrolling for fields that do not fit on the BlackBerry device screen as well as horizontal scrolling for fields that are wider than screen.

FlowFieldManager

  • Lays out fields in a horizontal then vertical flow. Fields are positioned from left to right, and any fields that do not fit within the allotted horizontal space are placed on the following line, starting from the left. The FlowFieldManager also supports horizontal and vertical scrolling.

DialogFieldManager

  • Handles an icon, a message, and a special area which can hold a list of user-specified custom fields. A VerticalFieldManager is used to layout the fields in the user area. It lays out its icon in the top left corner, and its message label in the top left corner.

Managers are an extension of the Field class and can contain other managers. This is known as nesting. Nesting can be used to create enhanced layout styles such as a column or table layout. The following is a sample that is designed to divide a BlackBerry deviuce screen into two even columns:

//Create an instance of MainScreen.
MainScreen theScreen = new MainScreen();

//Create a HorizontalFieldManager to hold the
//two VerticalFieldManagers.
HorizontalFieldManager backGround = new HorizontalFieldManager();
VerticalFieldManager leftColumn = new VerticalFieldManager();
VerticalFieldManager rightColumn = new VerticalFieldManager();

//The data to be displayed.
String[] names = {
  "Homer",
  "Marge",
  "Bart",
  "Lisa",
  "Maggie"};
int ages[] = {36, 34, 10, 8, 3};

//Arrays of fields to display the names
//and ages.
LabelField displayNames[] = new LabelField[5];
LabelField displayAges[] = new LabelField[5];

int count, width, spaceSize;
StringBuffer sBuffer = new StringBuffer();

//Add the column titles.
LabelField leftTitle = new LabelField("Name");
leftColumn.add(leftTitle);

LabelField rightTitle = new LabelField("Age");
rightColumn.add(rightTitle);

//Determine half of the screen width.
width = Graphics.getScreenWidth() / 2;

//Get the default font.
Font font = Font.getDefault();

//Determine the size of a space in the
//default font.
spaceSize = font.getAdvance(' ');

//Build up a string that contains enough spaces
//that it fills half of the screen. This will be
//used to set the column width.
for(count = 0;count <= width;count += spaceSize)
{
  sBuffer.append(' ');
}

//Add spacers to move to the next line and set
//the column width.
leftColumn.add(new LabelField(sBuffer.toString()));
rightColumn.add(new LabelField(sBuffer.toString()));

//Add some data to the screen.
//Populate and add the name and age fields.
for (count = 0; count < 5; count++)
{
  displayNames[count] = new LabelField(names[count]);
  displayAges[count] = new LabelField(String.valueOf(ages[count]));

  //Add the name LabelField to the left column.
  leftColumn.add(displayNames[count]);

  //Add the age LabelField to the right column.
  rightColumn.add(displayAges[count]);
}

//Add the two vertical columns to the
//horizontal field manager.
backGround.add(leftColumn);
backGround.add(rightColumn);

//Add the horizontal field manager to
//the screen.
theScreen.add(backGround);

The output of the sample code above will appear as two columns of text as shown below:

The Manager class can be extended to allow custom behaviour. This lets you position fields in a unique manner, or add a graphical effect to the screens in your application. The following is an example of a custom manager that works with a custom field that will be discussed later in this article. The custom field allows you to set its x and y coordinates used to position the field in the manager. The custom manager below makes use of these x and y coordinates to determine where to draw the field.

public class CustomManager extends Manager
{
  public CustomManager()
  {
    //Disable scrolling in this manager.
    super(Manager.NO_HORIZONTAL_SCROLL |
      Manager.NO_VERTICAL_SCROLL);
  }

  //Override sublayout.
  protected void sublayout(int width, int height) {
    CustomField field;

    //Get the total number of fields within
    //this manager.
    //Note that this manager only supports the
    //CustomField described within this article.
    int numberOfFields = getFieldCount();

    for (int i = 0; i < numberOfFields; i++)
    {
      //Get the field.
      field = (CustomField)getField(i);

      //Obtain the custom x and y coordinates for
      //the field and set the position for
      //the field.
      setPositionChild(field, field.getXCoord(), field.getYCoord());

      //Layout the field.
      layoutChild(field, width, height);
    }
    //Set the manager's extent
    setExtent(width, height);
  }

  public int getPreferredWidth()
  {
    //This manager is designed to utilize the
    //entire screen width.
    return Graphics.getScreenWidth();
  }

  public int getPreferredHeight()
  {
    //This manager is designed to utilize the
    //entire screen height.
    return Graphics.getScreenHeight();
  }
}

Fields


A field represents a region contained by a manager. All fields are derived from the base Field class. The BlackBerry API contains many specialized fields that have been detailed below.

ActiveAutoTextEditField

  • A field that uses a supplied set of string patterns to scan through simple text and pick out 'active' regions. These active regions are identified with underlining and are designed to supply additional menu items if a user clicks on them. This field supports use of the device’s AutoText dictionary and abides by any AutoText replacment rules. Meaning, when a word is entered that is in the AutoText database for the current locale, this field replaces it, including any effects of macro expansion. Pressing backspace when the cursor is on the most recent autotext expansion reverses all effects of the AutoText substitution.

ActiveRichTextField

  • A field that uses a supplied set of string patterns to scan through a simple text string and pick out 'active' regions. These active regions are identified with underlining and will supply additional menu items if the user clicks on them. This field also supports the formatting of a RichTextField.

AutoTextEditField

  • This field supports the use of the AutoText dictionary on the device and will abide by any AutoText replacement rules. Meaning, when a word is entered that is in the autotext database for the current locale, this field replaces it, including any effects of macro expansion. Pressing backspace when the cursor is on the most recent autotext expansion reverses all effects of the autotext substitution.

BasicEditField

  • An editable text field that can contain a label. The label will appear to the left of the editable area. BasicEditField does not support any text formatting options.

BitmapField

  • A field that will display a bitmap.

ButtonField

  • A clickable button that can contain a text label.

CheckboxField

  • This field consists of a text label and a checkbox. The checkbox appears to the right of the text label.

ChoiceField

  • This field consists of a text label and a choice selector/dropdown list. The choice selector will expand to display a list of choices when clicked on or when the "Change Option" menu item is selected. Using the space bar or holding the Alt key while scrolling the trackwheel can also change choices. Text in the drop down list will be truncated if it does not fit within the available width. ChoiceField is an abstract class and cannot be instantiated directly.

DateField

  • Displays a text label followed by a formatted date value.

EditField

  • An extension of BasicEditField. EditField also allows a user to enter special characters entered by holding a key while rolling the trackwheel. This is common for locales that utilize characters with accents.

EmailAddressEditField

  • An extension of EditField. EmailAddressField is designed to only allow characters to be entered that are valid for an email address.

GaugeField

  • A horizontal bar that can be used for either numerical selection or as a progress indicator.

LabelField

  • A label of unformatted text.

ListField

  • Displays rows of selectable items. Lists will support prefix searching by default where pressing a character selects the next item starting with the character. ListFields must register a ListFieldCallback object to handle repaint tasks. When this field must display a particular item in its list, it invokes the appropriate methods on the registered callback object.

NullField

  • A field of no size that can provide specialized focus changing.

NumericChoiceField

  • An extension of ChoiceField that supports the selection of a range of numbers. It is recommended to keep the number of values in this list to less than 20.

ObjectChoiceField

  • An extension of the ChoiceField that supports a list of objects. All contained objects must support the toString() method.

ObjectListField

  • An extension of ListField that displays a list of objects. All contained objects must support the toString() method.

PasswordEditField

  • An extension of an EditField where entered data is displayed as a mask of asterisks (*) equal to the number of characters entered.

RadioButtonField

  • This field consists of a text label and a radio-button control. The radio-button control appears to the right of a text label. These fields can be grouped using a RadioButtonGroup where only one field may be selected at a time.

RichTextField

  • A read-only text field to show text in a variety of fonts and formatting styles (bold, italics, underlined, etc.).

SeparatorField

  • A field that displays a horizontal line across its width.

TreeField

  • A field that will display a tree structure.

A field must be added to a manager to be shown to a user. This is done by calling the add method of a manager, passing it the field you wish to add. Fields may only be added to one manager at a time but can be removed and re-added during their lifespan. The following sample code illustrates adding a LabelField to a VerticalFieldManager.

LabelField myField = new LabelField("Hello world!");
VerticalFieldManager myManager = new VerticalFieldManager();
myManager.add(myField);

The Field class can also be extended to allow custom behaviour. This may be screen placement, visual changes or other forms of custom functionality. The following is a sample class that extends the Field class. It works with the sample CustomManager class shown earlier in the Manager section of this article. This field creates a non-focusable circle that can be placed anywhere on the screen. It adds additional methods to get and set the x and y coordinates of where the field will be shown in the manager.

public class CustomField extends Field implements DrawStyle
{
  private int fieldWidth, fieldHeight, xCoord, yCoord;

  public CustomField(int xVal, int yVal)
  {
    //Create a non focusable field.
    super(Field.NON_FOCUSABLE);

    //Set the x and y coordinates.
    xCoord = xVal;
    yCoord = yVal;

    //Set the field to be one quarter the screen
    //width and height.
    fieldHeight = Graphics.getScreenHeight()/4;
    fieldWidth = Graphics.getScreenWidth()/4;
  }

  //Set the x coordinate of the field.
  public void setXCoord(int xVal)
  {
    //If the x coordinate is different the the
    //current x coordinate
    //set the x coordinate to the new value and
    //call the invalidate method of this field.
    //Calling invalidate will force the field
    //to be repainted.
    if (xVal != xCoord)
    {
      xCoord = xVal;
      invalidate();
    }
  }
  //Get the x coordinate of the field.
  public int getXCoord()
  {
    return xCoord;
  }
  //Set the y coordinate of the field.
  public void setYCorrd(int yVal)
  {
    //If the y coordinate is different the
    //current y coordinate then set the y
    //coordinate to the new value and call the
    //invalidate method of this field.
    //Calling invalidate will force the field
    //to be repainted.
    if (yVal != yCoord)
    {
      yCoord = yVal;
      invalidate();
    }
    return;
  }
  //Get the y coordinate of the field.
  public int getYCoord()
  {
    return yCoord;
  }
  //Retrieves the preferred width of the button.
  public int getPreferredWidth()
  {
    return fieldWidth;
  }
  //Retrieves the preferred height of
  //the button.
  public int getPreferredHeight()
  {
    return fieldHeight;
  }
  //Lays out this field's contents.
  //This field's manager invokes this method
  //during the layout process to instruct this
  //field to arrange its contents, given an
  //amount of available space.
  public void layout(int width, int height)
  {
    setExtent(getPreferredWidth(),
      getPreferredHeight());
  }
  //Repaints the button.
  //The field's manager invokes this method
  //during the repainting process to instruct
  //this field to repaint itself.
  public void paint(Graphics graphics)
  {
    //Draw a circle the size of the field.
    graphics.fillArc
      (fieldWidth / 2, 0, fieldWidth, fieldHeight, 0, 360);
  }
}

By making use of screens, managers and fields, you will beable to create an application with a familiar look and feel. Extending any of these classes will allow you to add a custom look or function to your application as shown in the CustomManager and CustomField samples. Making use of the graphics class methods and drawing directly to a screen, manager or field can achieve even further customization. Stay tuned for part two of The BlackBerry Graphical User Interface in the next issue to learn about the power of the graphics class!

 


Please email your comments, suggestions and editorial submissions to