Working with BlackBerry list fields
Posted October 13th, 2009 by NazmulBlackBerry Bootcamp Training Courses by developerlife.com
Want to learn from the best in the business? We’ve created groundbreaking BlackBerry software (we are finalists in the 2009 Developer Challenge). We have a complete set of training programs available for BlackBerry development that will turbo-charge your BlackBerry development efforts. Whether you are a newbie or an experienced developer, we have modules that will fit your needs. We offer 4 training modules (7 days in total) that cover: Architecture, Basic Development, Advanced Development, and Navigating RIM. Contact us if you are interesting in learning more about our courses & schedule.
Introduction
This tutorial will show you how to create non-trivial lists using ListField. I will create a sample program that allows you to create, remove, update, delete the contents of a list (that’s backed by a Vector). The list field contains rows of selectable list items. It allows you to display a list of items, and load this list of objects from an array or vector. When using a ListField you have to provide an implementation of the ListFieldCallback interface to perform drawing tasks. This callback constitutes the view and model (using MVC terminology). The controller is the ListField class.
Source code example
Controller
I’m going to create a new ListField subclass (called MyListField), which is the controller. It’s just a subclass of ListField that makes it easy to bind to menu items that perform actions on the model + view (MyListModel). Then, I will add MyListField and MyListModel to a layout manager so it can be displayed to the screen.
Model and View
Then I’m going to create a model that implements ListFieldCallback (called MyListModel), which will provide methods (actions and menuitems) that allow the list to be mutated. MyListModel really does all the work in this example. It holds all the objects in the list (inside of a backing store Vector). It also exposes actions that can be wired directly into the UI using menuitems or buttons.
Main Class
Here’s the main driver for this example, ListApp, which has a main() method.
public class ListApp extends UiApplication { // main method public static void main(String[] args) { ListApp theApp = new ListApp(); UiApplication.getUiApplication().pushScreen(new MyScreen()); theApp.enterEventDispatcher(); } }//end ListApp // MainScreen public class MyScreen extends MainScreen { public MyScreen(){ // add the model to the list final MyListField myListView = new MyListField(); myListView.setEmptyString("List is empty", DrawStyle.HCENTER); final MyListModel myListModel = new MyListModel(myListView); // create a context menu on the list... myListView.addToContextMenu(myListModel.getAddMenuItem(0, 0)); myListView.addToContextMenu(myListModel.getRemoveMenuItem(0, 0)); myListView.addToContextMenu(myListModel.getModifyMenuItem(0, 0)); myListView.addToContextMenu(myListModel.getEraseMenuItem(0, 0)); // layout components on the screen... { // get the MainScreen's VerticalFieldManager... Manager vfm = getMainManager(); // add components to the vfm vfm.add(myListView); // add a separator vfm.add(new SeparatorField(SeparatorField.LINE_HORIZONTAL)); // add something to the title setTitle(new LabelField("ListDemo title")); } } }
Here’s what’s going on in this code:
- The ListApp simply creates a MyScreen screen object and displays it to the screen.
- The MyScreen class contains a MyListField (controller) object, and a MyListModel (model and view) object. The menu items from MyListModel are added to the screen, so that a user can interact with the contents of the model in ListApp.
- The view(MyListField) is bound to the model + controller (MyListModel) here. The menu items are added to the screen (MyScreen), and are bound to the model + controller (MyListModel).
MyListField – controller
Here’s the listing for MyListField, which is the controller.
/** custom listfield that you can attach context menu items to */ class MyListField extends ListField { private Menu _contextMenu = new Menu(); public void addToContextMenu(MenuItem menuitem) { if (menuitem != null) _contextMenu.add(menuitem); } public ContextMenu getContextMenu() { // DO NOT call ContextMenu.getInstance()... this will produce null in many cases!!! ContextMenu cMenu = super.getContextMenu(); cMenu.clear(); int size = _contextMenu.getSize(); for (int i = 0; i < size; i++) { cMenu.addItem(_contextMenu.getItem(i)); } return cMenu; } }//end class MyListField
Here’s what’s going on in this code:
- The methods in this class just make it easier to add menu items to the screen.
MyListModel – model and view
Here’s the listing for MyListModel, which is the model and view.
/** actionListener interface */ public interface ActionListenerIF { void actionPerformed(Object source); }//end interface ActionListenerIF /** custom listmodel that sync's and auto-binds with the listfield */ class MyListModel implements ListFieldCallback { private Vector _data = new Vector(); private ListField _view; private int _defaultRowHeight = 32; private int _defaultRowWidth = _defaultRowHeight; private int _textImagePadding = 5; private Bitmap _bitmap; /** constructor that saves a ref to the model's view - {@link ListField}, and binds this model to the view */ public MyListModel(ListField list) { // save a ref to the list view _view = list; // bind this model to the given view list.setCallback(this); // set the default row height _view.setRowHeight(_defaultRowHeight); // load the bitmap to use in the cell rendering _bitmap = // load some bitmap of your choice here } //XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX // implement ListFieldCallback interface //XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX /** list row renderer */ public void drawListRow(ListField list, Graphics g, int index, int y, int w) { String text = (String) _data.elementAt(index); // draw the text /w ellipsis if it's too long... g.drawText(text, _defaultRowWidth + _textImagePadding, y, DrawStyle.LEADING | DrawStyle.ELLIPSIS, w - _defaultRowWidth - _textImagePadding); // draw the to the left of the text... g.drawBitmap(0, y, _bitmap.getWidth(), _bitmap.getHeight(), _bitmap, 0, 0); } /** list row data accessor */ public Object get(ListField list, int index) { return _data.elementAt(index); } /** used for filtering list elements */ public int indexOfList(ListField list, String p, int s) { return _data.indexOf(p, s); } /** used for rendering list... provide the width of the list in pixels */ public int getPreferredWidth(ListField list) { return Display.getWidth(); } //XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX // data manipulation methods... not part of the interface //XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX /** mutator, which syncs model and view */ public void insert(String toInsert, int index) { // update the model _data.addElement(toInsert); // update the view _view.insert(index); } /** mutator, which syncs model and view */ public void delete(int index) { // update the model _data.removeElementAt(index); // update the view _view.delete(index); } /** mutator, which syncs model and view */ public void erase() { int size = _data.size(); // update the view for (int i = 0; i < size; i++) { delete(size - i - 1); } } public void modify(String newValue, int index) { // update the model _data.removeElementAt(index); _data.insertElementAt(newValue, index); // update the view _view.invalidate(index); } public int size() { return _data.size(); } //XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX // get sample actions //XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX public ActionListenerIF getAddAction() { return new ActionListenerIF() { public void actionPerformed(Object source) { // add a row to the bottom int size = size(); insert("row data" + size, size); } }; } public ActionListenerIF getRemoveAction() { return new ActionListenerIF() { public void actionPerformed(Object source) { // remove from the top if (size() > 0) { delete(0); } } }; } public ActionListenerIF getEraseAction() { return new ActionListenerIF() { public void actionPerformed(Object source) { // remove all erase(); } }; } public ActionListenerIF getModifyAction() { return new ActionListenerIF() { public void actionPerformed(Object source) { // remove all modify("new value", size() - 1); } }; } //XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX // get sample menuitems //XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX public MenuItem getAddMenuItem(int ordinal, int priority) { return new MenuItem("Add", ordinal, priority) { public void run() { getAddAction().actionPerformed(_view); } }; } public MenuItem getRemoveMenuItem(int ordinal, int priority) { return new MenuItem("Remove", ordinal, priority) { public void run() { getRemoveAction().actionPerformed(_view); } }; } public MenuItem getModifyMenuItem(int oridinal, int priority) { return new MenuItem("Modify", oridinal, priority) { public void run() { getModifyAction().actionPerformed(_view); } }; } public MenuItem getEraseMenuItem(int ordinal, int priority) { return new MenuItem("Erase", ordinal, priority) { public void run() { getEraseAction().actionPerformed(_view); } }; } }// end class MyListModel
- The ActionListenerIF interface is defined just to make it easy to invoke CRUD operations on the model (MyListModel). This make it easy to bind the CRUD operations to menu items as well as buttons (which is not done in this example).
- MyListModel stores all the items in the list in a Vector. You can access this underlying backing store at any time, to retrieve the contents of this model.
- The constructor of MyListModel binds the model (and view) to the ListField (controller). This makes it a little bit simpler to work with.
- A bitmap is provided that’s painted in the drawListRow() method. The drawListRow() method is responsible for painting each row in the list. It draws the bitmap on the left, and the list-item-toString on the right.
- Accessors to CRUD operations are provided that make it easy to bind them to buttons (getXXXAction) and menuitems (getXXXMenuItem).
- The getXXXAction() methods actually provide the implementation of the CRUD operations on the model and are responsible for syncing the Vector backing store with the ListField.
Other things to keep in mind
- In your code, when you need to get access to the currently selected item, be sure to use the following method: myListField.getElementAt(myListField.getSelectedIndex()), where myListField is a reference to the ListField.
- in the drawListRow() method if you tried to set the background color on the currently selected row, and it wasn’t working, you have to do this – after setting the background color (by calling Graphics.setBackgroundColor(..), call clear() on the ListField, just before painting the text.
Further learning
There is lots more that you can do with lists, such as providing complex/rich visualizations of the objects in the list model, by providing more advanced overriding of the drawListRow() method. Also, you can use keyword filtered list, which allows you to perform type-ahead-searching into the contents of your list. You can also load large datasets in the background (not EDT), and perform lots of background processing that update the list. We provide BlackBerry Bootcamp training programs that will teach you how to do this and much more.
Feedback and comments
To share your thoughts on this tutorial, click here.
Training Services
Want to learn from the best in the business? We have training programs available for BlackBerry development, Android development, rich desktop apps, and UX design. We can give your development team a competitive edge, and make them experts in these technologies. Contact us if you are interesting in learning more about our training services & schedule.
Consulting Services
If you need help building web, mobile, and desktop apps that are all connected to the cloud, we can help. We can empower your business by bringing your ideas to life.
Our expertise: architecture, UX design, graphic design, and implementation services for BlackBerry, Android, GWT, desktop Java, and cloud computing.
Our specialties: crafting stunning UXes, LBS, real-time collaboration and syncing between services and web/mobile/desktop apps, location based mobile e-commerce, and location based mobile advertising.
Contact us if you have a project you need our help with and we will consider taking you on as a client.
ONE Platform Licensing
If you are not interested in our consulting or training services, but would like to build web, mobile, and desktop apps that are all connected to the cloud quickly, you can kick-start your development by leveraging the ONE Platform. You won’t have to build and test from scratch!
The ONE Platform SDK will accelerate the development and deployment of your connected/service-enabled web/mobile/desktop projects. Contact us for more information on licensing the ONE Platform.