Thursday, June 5, 2008

Customizing LWUITs Focus Behavior

Focus seems trivial until you actually try implementing it correctly, at which point you understand why Java SE replaced its focus architecture in every single version of Java until 5.
Its just a REALLY hard problem that masks itself as a simple problem, and its worse in Java ME!
Unlike the desktop where focus is usually traversed using Tab/Shift Tab and so it has only one direction... In devices we usually have a 4 directional keypad which means the user can traverse up, down, left and right thus creating two different focus axis. To make matters worse, in the desktop where we shift focus using the tab and then navigate in a component with the arrow keys which simplifies things since its very easy to distinguish a transition between components from internal component interaction. In ME there is only one navigational control (ignoring touch based devices which actually simplify matters), so an arrow press might mean I want to change the selection in the list or it might mean I want to move focus outside of the list to the component bellow!

This sort of approach creates very interesting UI problems, e.g. a tab pane with a lists inside it:


Notice that the tab is highlighted when the pane has focus and no longer when the list has focus, there is also a focus rectangle surrounding the list. This is great and it allows for very flexible UI's, however this fails for very simple UI's such as this.
Normally a user wants the right/left navigation to only change the tabs and the up/down navigation to only change the list selection. Here we are really adding complexity by adding focus navigation which is completely redundant when all we have are two components!

This gets worse... Some of our users implemented their own cell renderers that don't properly indicate component focus, they usually disabled the list focus indication (removed its border). This creates a situation where focus is indistinguishable so the user would press a key and won't see any effect (no visual indication of focus switching).
(As a side note I would suggest debugging most issues you have in LWUIT by switching to default looks and feels, renderers etc...).

One solution is to restore the functionality of visually alerting the user to the existence of focus. The second solution is to detect the key presses for left/right and up/down and react on them by moving the focus ourselves!

E.g.:
List list1 = new List(new String[] {"Item 1", "Item 2", "Item 3"});
List list2 = new List(new String[] {"Item 4", "Item 5", "Item 6"});
final TabbedPane tabs = new TabbedPane();
final List[] lists = new List[] {list1, list2};

Form test = new Form("Tab & List") {
public void keyPressed(int keyCode) {
int game = Display.getInstance().getGameAction(keyCode);
if(game == Display.GAME_DOWN || game == Display.GAME_UP) {
List l = lists[tabs.getSelectedIndex()];
if(!l.hasFocus()) {
l.requestFocus();
}
} else {
if(game == Display.GAME_RIGHT || game == Display.GAME_LEFT) {
if(!tabs.hasFocus()) {
tabs.requestFocus();
}
}
}
super.keyPressed(keyCode);
}
};
test.setLayout(new BorderLayout());
test.addComponent(BorderLayout.CENTER, tabs);
tabs.addTab("Tab 1", list1);
tabs.addTab("Tab 2", list2);
test.show();

The code above has a minor issue in the current LWUIT version Chen is releasing a new version today which will fix a minor tab pane issue.

3 comments:

  1. Hi i tried this by overriding keypressed code as mentioned in the blog.. I have a form on top of it i have some buttons and at the middle i have a list, on selection of list i ll show details form and when i come back to the list form the focus ll be on one of the list element which is selected but when i try to scrolldown the focus goes to button on top of list. Even after checking this.

    public void keyPressed(int keyCode) {

    int game = Display.getInstance().getGameAction(keyCode);
    if(game == Display.GAME_DOWN || game == Display.GAME_UP) {

    if(!list.hasFocus()) {
    list.requestFocus();
    }
    } else {
    if(game == Display.GAME_RIGHT || game == Display.GAME_LEFT) {
    //To be implemented later
    }
    }
    super.keyPressed(keyCode);
    }

    I am using latest LWUIT(20081222) & LWUIT 1.2

    ReplyDelete
  2. is it possible to set something to not enabledbut still allow it to be focused?

    ReplyDelete
  3. We currently block focus from disabled components entirely. You can potentially workaround this by overriding isFocusable() to return true for such a case but that might trigger other issues e.g. we have a disabled style and a selected style but no disaledSelected style. Which is pretty much why we tried to avoid it, it adds a layer of complexity to the UI design for a dubious benefit.

    ReplyDelete