Working with PlayBook QNX UI components

In a previous post I talked about all sorts of lists available to developers when using the BlackBerry Tablet OS SDK for Adobe AIR. In this article I will focus on the remaining UI controls. I have an idea for a PlayBook application and before writing a single line of code I wanted to make sure that I understood all the UI components that are available as part of the BlackBerry SDK. By the way, in case you didn’t know, RIM offers a free PlayBook device to any developer who gets his application on BlackBerry App World. I thought I’d share with you the results of my research.

I won’t cover how to skin these components. This is a topic for another blog post. In the meantime you can read Ryan’s posts on skinning ( buttons/text fields and lists) and Renaun’s post.

Buttons

There are seven buttons in the package qnx.ui.buttons. If you want to do something when a buttons is clicked you have to register an event listener for MouseEvent.CLICK, Event.CHANGE (only for SegmentedControl), or Event.SELECT (for ToggleSwitch); for example:

myButton.addEventListener(MouseEvent.CLICK, doSomethingOnClick);

private function doSomethingOnClick(e:MouseEvent):void {
    //do something
}

LabelButton and IconButton, as the names suggest, enable you to create buttons with either text labels or icons. Both these buttons extend the Button class (if you use the Button class  itself you will get a button without a label or icon). You can enable toggle by setting the the property with the same name to true. This will give you a button, that once clicked, stays selected and then is unselected when you click it again. In the image below you can see these two buttons with toggle on in the selected and unselected state.

You can use a flash.Text.TextFormat object to change the label settings for a LabelButton. You set the image to be used by the IconButton by calling the setIcon() method and passing the path to the image to be used or a Bitmap/BitmapData instance. Here is the code used for rendering the example above:

var format:TextFormat = new TextFormat();
format.font = "BBAlpha Sans";
format.size = 22;
format.color = 0x990000;
format.align = TextFormatAlign.CENTER;

var btn:LabelButton = new LabelButton();
btn.size = 50;
btn.sizeMode = SizeUnit.PIXELS;
btn.label = "Label Button";
btn.toggle = true;
btn.setTextFormatForState(format,SkinStates.DISABLED);
btn.setTextFormatForState(format,SkinStates.UP);
btn.setTextFormatForState(format,SkinStates.DOWN);
btn.setTextFormatForState(format,SkinStates.SELECTED);
btn.setTextFormatForState(format,SkinStates.DISABLED_SELECTED);
container.addChild(btn);

var btn2:IconButton = new IconButton();
btn2.size = 100;
btn2.sizeMode = SizeUnit.PIXELS;
btn2.setIcon("assets/icon.png");
btn2.toggle = true;
container.addChild(btn2);

BackButton extends the LabelButton and you can set the label if you want (the default label is Back). It also has an arrow icon to the left of the button. As in the case of LabelButton you can use a TextFormat object and play with the toggle property.

var btn3:BackButton = new BackButton();
btn3.label = "Go Back";
container.addChild(btn3);

CheckBox extends LabelButton too and has the toggle property set to true (you can’t change this one). However, you can control where to display the text relative to the check mark by using the labelPlacement property. Using the labelPadding property you can set the space between the label and icon. And of course TextFormat can be used to customize the label look. You have to pay attention to the width property if you don’t want to have the label cropped.

var cb:CheckBox = new CheckBox();
cb.label ="Select Me";
cb.width = 150;
cb.labelPlacement = LabelPlacement.RIGHT;
cb.labelPadding = 30;
container.addChild(cb);

The last component that extends the LabelButton is RadioButton. You use this component to force the user make a single selection from a group of at least two radio buttons. You can use TextFormat to customize the look of the label. You assign a radio button to a group by setting the same string to the groupname property. If you want to have one option preselected, you set the selected property to true.

var rb1:RadioButton = new RadioButton();
rb1.size = 30;
rb1.sizeUnit = SizeUnit.PIXELS;
rb1.groupname = "group1";
rb1.label = "Yes";
rb1.selected = true;
container.addChild(rb1);

var rb2:RadioButton = new RadioButton();
rb2.size = 30;
rb2.sizeUnit = SizeUnit.PIXELS;
rb2.groupname = "group1";
rb2.label = "No";
container.addChild(rb2);

SegmentedControl displays a series of connected radio buttons and the user can select only one of them. As with lists you use a DataProvider to add the labels you want. An alternative is to add labels one by one using the  addItem() or addItemAt() methods. Each label is set using an instance of Object with one property named label. You can preselect an option using the selectedIndex property and you listen for user selection by registering an event listener for the flash.events.Event.CHANGE event. You can skin this component by setting up a new skin for the background and buttons.

var segData:Array= [
		{label:"< 10"},
  		{label:"10-19"},
  		{label:"> 20"}
	];
var seg:SegmentedControl = new SegmentedControl();
seg.dataProvider = new DataProvider(segData);
seg.selectedIndex = 1;
container.addChild(seg);

The last button is the ToggleSwitch. This component extends the Slider component. It enables you to create a button with two labels and slider that always hides one of the two labels. To set the label from the left you use the selectedLabel property, and for the one on the right you use the defaultLabel property. You can control the default selection by setting selected property to true/false and you react to component changes by listening for Event.SELECT events. You can see in the image below the component using the default skin in the two states:

var tog:ToggleSwitch = new ToggleSwitch();
tog.selectedLabel = "On";
tog.defaultLabel = "Off";
tog.selected = true;
container.addChild(tog);

Text Controls

In the package qnx.ui.text you’ll find two UI components: Label and TextInput. You can use the Label component for displaying text (it can’t receive focus). There are a number of properties you can use for fine control over how the text will be displayed:

  • By default a label is 100 pixels wide. If you don’t set the autoSize property or adjust the width, then it is quite possible you will not see the whole text. The easiest way is to use the autoSize property and set how the label should grow so the text will be fitted; for example: label.autoSize = TextFieldAutoSize.CENTER;
  • If you want to set the font family name/size/type-face/color you use an instance of TextFormat to set these values and then you assign it to the label’s format property
  • You set the text you want to be displayed using either the text property or the htmlText property. After playing with the htmlText property, I found that the main benefit is that you can throw in some HTML formatted text and it will be displayed just like text – this is useful if you don’t want to see the tags.
  • When you set the multiline property to true, and display HTML text like this <p>one</p><br/><p>two</p> then you will see the text in two paragraphs with one line between them
  • The selectable property, when set to true, allows user to select the text
  • You can use the wordWrap property to allow wrapping the words so the text can be displayed without increasing the width of the label

Here is a code example and its rendering:

var format:TextFormat = new TextFormat();
format.font = "BBAlpha Sans";
format.bold = true;
format.size = 19;
format.color = 0x000099;

var lbl:Label = new Label();
lbl.format = format;
lbl.autoSize = TextFieldAutoSize.CENTER;
lbl.wordWrap = true;
lbl.multiline = true;
lbl.selectable = true;
lbl.htmlText =
"<p><strong>This is a label!!!</strong></p><br/><p>Second Paragraph</p>";
container.addChild(lbl);

If you want to let the user type some text or edit it, then you’ll use the TextInput component. This component is pretty powerful because it lets you add custom icons inside of it (on the left/right sides) and control when they are displayed. You can also control what type of the keyboard should be displayed when the text field gets the focus (the keyboard is pulled out automatically when a TextInput is selected). Just set the keyboardType property to one of the following values; for example: KeyboardType.DEFAULT, KeyboardType.EMAIL, KeyboardType.URL, KeyboardType.PIN, or KeyboardType.PHONE. You can customize the label displayed on the Enter key by using the returnKeyType property: txt.returnKeyType = ReturnKeyType.GO. If you want to use the text field for passwords you can set the displaysAsPassword property to true.

When you want to format the text you use a TextFormat object, exactly as in the case of the Label. By default you can have a Clear icon inside of the text field that you can use for deleting the entire text. You can control when it is displayed using the clearIconMode property; for example: txt.clearIconMode = TextInputIconMode.ALWAYS. If you want to add a custom icon to the left or right side, one way to do it is to create a transparent PNG file with the icon you want and then embed the PNG file and set the class to the leftIcon or rightIcon properties. From my tests it seems that is better to have the icon bigger rahter than smaller compared to what will be actually rendered. And you can control when it is displayed using the leftIconMode or rightIconMode properties. For example:

[Embed(source="assets/arrow48x48.png")]
private var ArrowIcon:Class;
...
txt.leftIcon = new ArrowIcon();
txt.leftIconMode = TextInputIconMode.UNLESS_EDITING;

Here is the code used to create the example displayed in the pictures:

var format:TextFormat = new TextFormat();
format.font = "BBAlpha Sans";
format.bold = true;
format.size = 19;
format.color = 0x000099;

var txt:TextInput = new TextInput();
txt.format = format;
txt.width = 400;
txt.keyboardType = KeyboardType.PHONE;
txt.returnKeyType = ReturnKeyType.GO;
txt.clearIconMode = TextInputIconMode.ALWAYS;
txt.leftIcon = new ArrowIcon();
txt.leftIconMode = TextInputIconMode.UNLESS_EDITING;

If you want to do something when the user introduces or edits the text, then you have to register an event listener for the Event.CHANGE event:

txt.addEventListener(Event.CHANGE, onChange);

Here are the shortcomings I found so far for the TextInput (as of writing this post the SDK is in beta):

  1. You can’t test it outside of the emulator without a hack – see Renaun’s post on this topic. This is  because this component requires a class that is part of the RIM AIR runtime.
  2. When setting the autoSize property to TextFieldAutoSize.CENTER you can’t select the field and type in it.
  3. It doesn’t support multiple lines. Some people found a way to get around this, but I think we need official support from RIM in the form of a different component or an addition to the TextInput.

Sliders

In the package qnx.ui.slider you’ll find Slider and VolumeSlider components. VolumeSlider extends the Slider and provides a default skin suited for media applications. You can set the minimum and maximum values using the properties with the same name. You can’t set the step when moving the thumb around. You can retrieve the current value using the value property and keep in mind it is a Number and not Integer (so you can get 6.7, for example). If you want to have the slider’s thumb position to something other than the left side by default, you set the value property to what you need.

var slider:Slider = new Slider();
slider.width = 300;
slider.height = 40;
slider.minimum = 1;
slider.maximum = 10;
slider.value = 5.5;
slider.addEventListener(SliderEvent.START, onDragStart);
slider.addEventListener(SliderEvent.END, onDragEnd);
slider.addEventListener(SliderEvent.MOVE, onMove);

var volume:VolumeSlider = new VolumeSlider();
volume.minimum = 0;
volume.maximum = 1;

While you can set the width of the component, setting the height doesn’t change anything. You need to create skins for thumb and track if you need more customization, and use the setThumbSkin() and setTrackSin() methods to apply them.

There are three events you can listen for: SliderEvent.START (when the thumb starts to move), SliderEvent.END (when the user has finished the dragging), and SliderEvent.MOVE (while the thumb is moving). It seems that both these sliders can be only horizontal.

Progress Bars

When you want to display progress to the user you can use components available in the qnx.ui.progress package. The ActivityIndicator component lets you display visual feedback when a time-consuming operation is executing. You start the animation by calling the animate(true) method and stop it with animate(false). You can check if the animation is running by calling the isAnimating() method. It should be possible to skin it by extending ActivityIndicatorSkin.

var activity:ActivityIndicator = new ActivityIndicator();
activity.setSize(30, 30);
activity.animate(true);

Using the ProgressBar and PercentageBar you can give a sense of progress of how much of the work has been completed and how much is remaining. You change this by setting the progress property to a number between 0 and 1, where 1 represents 100% of the job done. PercentageBar has a label as well, so the user knows exactly what the progress is – you can hide it by setting the showPercent property to false.

var prog:ProgressBar = new ProgressBar();
prog.width = 200;
prog.progress = 0.3;

var per:PercentageBar = new PercentageBar();
per.width = 200;
per.progress = 0.9;

Extending the skins from the qnx.ui.skins.progress package is the way to customize the look and feel.

Image Controls

In the qnx.ui.display you’ll find two components: Image and TilingBackground. You use the Image class when you want to display an image. By calling the setImage() method you set a local path to an image, an URL, or a Bitmap/BitmapData object. There are two events you can listen for:

  • Event.COMPLETE – this event is triggered once the image was loaded and ready to use
  • IOErrorEvent.IO_ERROR – dispatched when the image can’t be loaded

If you plan to scale the image (up or down) you might want to set the smoothing property to true. To cache the images, create a single instance of ImageCache and assign it to the Image.cache property. You can force the cache to reload an image if you need.

Here is an example that loads an image from the application folder:

private static var imageCache:ImageCache = new ImageCache();
private static const IMAGE_PATH:String =  "assets/picture.jpg";
...
var img:Image = new Image();
img.smoothing = true;
img.cache = imageCache;
img.addEventListener(Event.COMPLETE, onComplete);
img.setImage(IMAGE_PATH);
container.addChild(img);
...
private function onComplete(e:Event):void {
    container.layout();
}

Please note that in the above example I didn’t explicitly set the width and height of the image. These were set automatically to the values taken from the loaded image. If you want to set them to something different you have to do it after the image is loaded when the Event.COMPLETE event is thrown. Otherwise they will be overwritten.

When you want to create a tile from images, you use the TilingBackground component. You don’t have much control on how the image will be tiled (it will be tiled on both X and Y depending on how much space it is available and how large the image to be tiled is). What surprised me was the way you set the image. I was expecting to have the same options available as for Image.setImage(). Instead, you use the bitmapData property and set it to a BitmapData object. In the following example I’m using the ImageCache.getImage() API to get the BitmapData object loaded by the Image object and set it to the bitmapData property of the TilingBackground:

tile = new TilingBackground();
tile.containment = Containment.BACKGROUND;
container.addChild(tile);
//inside the complete event handler of the Image
tile.bitmapData = imageCache.getImage(IMAGE_PATH, false);

Dialog

The QNX UI library provides a number of classes for creating a floating window on top of your application. I think that this is a useful feature, because with the real estate available on the PlayBook tablet you may want to use one of these dialogs instead of “pushing” an entirely new screen (screen metaphor approach).

There are five dialog boxes to choose from, and you have to remember that the appearance is controlled by the BlackBerry Tablet OS. You can control the position, size, and the type of the window:

  • system modal – blocks the entire tablet until the window is closed – use Dialog.open()
  • application modal – blocks the interaction only with the application – use Dialog.open(IowWindow.getAirWindow().group)

Using DialogAlign and the align property of the dialog you can specify where on the vertical axis it should be placed (it is always centered horizontally). You can add buttons and set properties to them like this:

var myDialog:AlertDialog = new AlertDialog();
myDialog.addButton("Yes");
myDialog.addButton("No");
myDialog.addButton("Cancel");
myDialog.setButtonPropertyAt("enabled", false, 2);

Once the user clicks one of the buttons you add to the window, if you registered an event listener on the dialog box for the Event.SELECT event you can determine what button was pressed:

myDialog.addEventListener(Event.SELECT, alertButtonClicked);
...
private function alertButtonClicked(e:Event):void {
	trace((e.target as AlertDialog).selectedIndex)
}

The life cycle for a dialog box is:

  1. You create a new instance of the dialog you want
  2. You configure the content (messages, buttons, labels…) and the positioning/sizing of the dialog
  3. You register an event listener for the Event.SELECT event
  4. You call the show() method on the dialog to display it

AlertDialog is the simplest window. You use this window in order to display some text to the user and allow him to click on one of the buttons you add to it. You set the title and message using the properties with the same names.

And here is the code:

var myDialog:AlertDialog = new AlertDialog();
myDialog.title = "AlertDialog";
myDialog.message = "This is an alert dialog. Do you like it?";
myDialog.addButton("Yes");
myDialog.addButton("No");
myDialog.addButton("Cancel");
myDialog.setButtonPropertyAt("enabled", false, 2);
myDialog.dialogSize = DialogSize.SIZE_MEDIUM;
myDialog.align = DialogAlign.TOP;
myDialog.addEventListener(Event.SELECT, alertButtonClicked1);
myDialog.show(IowWindow.getAirWindow().group);
...
private function alertButtonClicked1(e:Event):void {
	trace((e.target as AlertDialog).selectedIndex)
}

LoginDialog presents the user an interface for providing the information necessary for a login operation: user name, password, remember me. It extends the AlertDialog class so everything you can do with it applies to LoginDialog too. Using the rememberMe, password, and username properties you can retrieve the information the user introduced. You can set the text used for username/password/remember me using these properties: usernameLabel, passwordLabel, passwordPrompt, and rememberMeLabel.

var login:LoginDialog = new LoginDialog();
login.title = "Login";
login.message = "Please enter your username and password:";
login.addButton("OK");
login.addButton("Cancel");
login.passwordPrompt = "password";
login.rememberMeLabel = "Remember me";
login.rememberMe = true;
login.dialogSize = DialogSize.SIZE_SMALL;
login.addEventListener(Event.SELECT, alertButtonClicked2);
login.show(IowWindow.getAirWindow().group);
...
private function alertButtonClicked2(e:Event):void {
	var login:LoginDialog = e.target as LoginDialog;
	trace(login.password); //retrieve the password
	trace(login.username); //retrieve the username
	//retrieve whather the remember me option was selected
	trace(login.rememberMe);
	trace(login.selectedIndex); //retrieve the button was clicked
}

PasswordChangeDialog, as the name suggests, is handy when you want to let the user change his password. It extends the LoginDialog. As with its superclass, you can customize the labels of the predefined UI components.

Here is the code to create the dialog box above:

var password:PasswordChangeDialog = new PasswordChangeDialog();
password.title = "Password Change";
password.message = "Please choose your new password:";
password.addButton("OK");
password.addButton("Cancel");
password.dialogSize= DialogSize.SIZE_MEDIUM;
password.addEventListener(Event.SELECT, alertButtonClicked3);
password.show(IowWindow.getAirWindow().group);

And here is how you retrieve the user input:

private function alertButtonClicked3(e:Event):void {
	var login:PasswordChangeDialog = e.target as PasswordChangeDialog;
	trace(login.password); //retrieve the old password
	trace(login.username); //retrieve the username
	trace(login.newPassword); //retrieve the new password
	trace(login.confirmation); //retrieve the confirmation for the new password
	trace(login.selectedIndex); //retrieve the button was clicked
}

The PromptDialog extends AlertDialog and provides a way to capture user text input. You can set the hint to be used by the TextField using the prompt property. And you can retrieve the text entered using the text property.

Here is the code:

var prompt:PromptDialog = new PromptDialog();
prompt.title = "Choose your username!";
prompt.message = "Please enter an username between 6-10 chars:";
prompt.prompt = "username";
prompt.addButton("OK");
prompt.addButton("Cancel");
prompt.dialogSize= DialogSize.SIZE_SMALL;
prompt.addEventListener(Event.SELECT, alertButtonClicked4);
prompt.show(IowWindow.getAirWindow().group);
...
private function alertButtonClicked4(e:Event):void {
	var d:PromptDialog = e.target as PromptDialog;
	//retrieve the text introduced by the user
	trace(d.text);
}

The last dialog window is the PopupListcomponent. This dialog creates a window that presents a scrollable list and the user can select either a single entry or multiple entries. You set the items that will be rendered in the list by assigning an Array of strings to the items property. If you want to allow multiple selections then you set the multiSelect property to true. If you want to preselect an item or a number of items, just set the selectedIndices property to an array of indices of the items you want selected.

Here is the code:

var popUp:PopupList = new PopupList();
popUp.title = "Chose your title:";
popUp.items = ["Mr.", "Ms.", "Miss", "Dr.", "Phd"];
//allow multiple selection
popUp.multiSelect = true;
//preselect the second item
popUp.selectedIndices = [1];
popUp.addButton("OK");
popUp.addButton("Cancel");
popUp.dialogSize= DialogSize.SIZE_SMALL;
popUp.addEventListener(Event.SELECT, alertButtonClicked5);
popUp.show(IowWindow.getAirWindow().group);
...
private function alertButtonClicked5(e:Event):void {
	var d:PopupList = e.target as PopupList;
	//retrieve the selected index/indices
	trace(d.selectedIndex);
	trace(d.selectedIndices.toString());
}

One last note on dialog box components: it doesn’t look like you can skin them or extend them. It would be nice to be able to create a custom dialog with the components you want in it. On the other hand they really cover most use cases you might think of.

Download

If you want the code used for this article, just download this ActionScript mobile project for Flash Builder 4.5 (you’ll need the PlayBook simulator and the BlackBerry plug-in for Flash Builder installed in order to run and compile the code).

Conclusions

These are not all the components available in the QNX libraries. Though I feel that with this post and the previous one on lists I’ve covered the vast majority. Check Ryan‘s and Renaun‘s blogs for articles on containers and playing multimedia.

I have to say that I really think that RIM did an amazing job with this QNX UI library. I think it includes pretty much all you need to create great applications. Especially the list components seemed to be almost complete and the dialog components are a nice touch particularly for tablet devices (though I’m not sure how well they will perform on smartphone screens).

In general everything seems to be well engineered and designed. The biggest caveat of all this is that we don’t have the library source code. Any Flex developer will tell you how important it is that the Flex framework is open source and you can inspect all the code you wish. It’s great when learning the framework and it is definitely great when debugging. Right now even a process that shouldn’t be that complicated (skinning) is a trial and error process.

What do you think?

11 thoughts on “Working with PlayBook QNX UI components

  1. Pingback: Tweets that mention Working with PlayBook QNX UI components : Mihai Corlan -- Topsy.com

  2. Thanks for the great run down, I just started looking into playbook myself

    Is it possible to use the QNX UI library with a Flex Mobile project or is it limited to AS3 only?

  3. Pingback: Cool Stuff with the Flash Platform – 2/18/11 | Finding Out About

  4. @Mihai Corlan is it possible to change the activity indicator color and its background color.
    if it is possible please help me.

  5. @Valery

    I don’t know the answer because it is a matter of license. You have to check with RIM if they allows developer using their library outside PlayBook.

    Technically it is possible – using the UI components at least.

  6. This code doesnt work for me

    var login:LoginDialog = new LoginDialog();
    login.title = “Login”;
    login.message = “Please enter your username and password:”;
    login.addButton(“OK”);
    login.addButton(“Cancel”);
    login.passwordPrompt = “password”;
    login.rememberMeLabel = “Remember me”;
    login.rememberMe = true;
    login.dialogSize = DialogSize.SIZE_SMALL;
    login.addEventListener(Event.SELECT, alertButtonClicked2);
    login.show(IowWindow.getAirWindow().group);

    can you please tell me what might solve the problem, i keep getting error that IowWindow is not defined…and my swc link is set to External

  7. o guess what it works…in order to test the code you have to run it not as desktop but in the playbook simulator.

  8. Hi Mihai,

    I have a question in reguards to using the import qnx.ui.text.KeyboardType; In my app I have a whole bunch of text inputs that require the phone keyboard type – do I have to asign KeyboardType.PHONE to each of my text inputs or can I say something like stage.KeybardType = KeyboardType.PHONE so its appears the same way for all of my text inputs?

    Thanks,
    Donald

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>