Related
i m writing one application in which i have created custom list field for displaying listview.
my CustomListField contains one image and text in a row. i m gettiing field change listener on click of listfield row but i want to put fieldchange listener on image too..
can anyone tell me how can i do that.
here is my code.
public class CustomListField extends ListField implements ListFieldCallback {
private Vector _listData;
private int _MAX_ROW_HEIGHT = 60;
public CustomListField(Vector data) {
_listData = data;
setSize(_listData.size());
setSearchable(true);
setCallback(this);
setRowHeight(_MAX_ROW_HEIGHT);
}
protected void drawFocus(Graphics graphics, boolean on) {
XYRect rect = new XYRect();
graphics.setGlobalAlpha(150);
graphics.setColor(Color.BLUE);
getFocusRect(rect);
drawHighlightRegion(graphics, HIGHLIGHT_FOCUS, true, rect.x, rect.y, rect.width, rect.height);
}
public int moveFocus(int amount, int status, int time) {
this.invalidate(this.getSelectedIndex());
return super.moveFocus(amount, status, time);
}
public void onFocus(int direction) {
super.onFocus(direction);
}
protected void onUnFocus() {
this.invalidate(this.getSelectedIndex());
}
public void refresh() {
this.getManager().invalidate();
}
public void drawListRow(ListField listField, Graphics graphics, int index, int y, int w) {
listField.setBackground(BackgroundFactory.createBitmapBackground(Bitmap.getBitmapResource("listing_bg.png")));
ListRander listRander = (ListRander) _listData.elementAt(index);
graphics.setGlobalAlpha(255);
graphics.setFont(Font.getDefault().getFontFamily().getFont(Font.PLAIN, 24));
final int margin = 5;
final Bitmap thumb = listRander.getListThumb();
final String listHeading = listRander.getListTitle();
final Bitmap nevBar = listRander.getNavBar();
// list border
graphics.setColor(Color.GRAY);
graphics.drawRect(0, y, w, _MAX_ROW_HEIGHT);
// thumbnail border & thumbnail image
graphics.setColor(Color.BLACK);
// graphics.drawRoundRect(margin-2, y+margin-2,thumb.getWidth()+2, thumb.getHeight()+2, 5, 5);
graphics.drawBitmap(margin, y + margin, thumb.getWidth(), thumb.getHeight(), thumb, 0, 0);
// drawing texts
// graphics.setFont(Font.BOLD);
graphics.drawText(listHeading, margin + thumb.getWidth(), y + margin);
graphics.setColor(Color.GRAY);
// graphics.setFont(Font.smallFont); // graphics.drawText(listDesc, 2*margin+thumb.getWidth(), y+ margin+20); // //
// graphics.drawText(listDesc2, 2*margin+thumb.getWidth(), y+ margin+32);
// draw navigation button
final int navBarPosY = y + (_MAX_ROW_HEIGHT / 2 - nevBar.getHeight() / 2);
final int navBarPosX = Graphics.getScreenWidth() - nevBar.getWidth() + margin;
graphics.drawBitmap(navBarPosX, navBarPosY, nevBar.getWidth(), nevBar.getHeight(), nevBar, 0, 0);
}
public Object get(ListField listField, int index) {
String rowString = (String) _listData.elementAt(index);
return rowString;
}
public int indexOfList(ListField listField, String prefix, int start) {
for (Enumeration e = _listData.elements(); e.hasMoreElements();) {
String rowString = (String) e.nextElement();
if (rowString.startsWith(prefix)) {
return _listData.indexOf(rowString);
}
}
return 0;
}
public int getPreferredWidth(ListField listField) {
return 3 * listField.getRowHeight();
}
/*
protected boolean trackwheelClick(int status, int time) {
invalidate(getSelectedIndex());
Dialog.alert(" U have selected :" + getSelectedIndex());
return super.trackwheelClick(status, time);
}
*/
}
i want to put click listner on star image of listfield row
and following is output of abbove code.
I did something very similar to this on a past project:
Background
As Arhimed said in his answer, and as you can read about on the BlackBerry forums here, you can't have full-fledged Field objects within the ListField. The content of ListField rows is just drawn directly in drawListRow() as text, and Bitmaps, etc. The contents aren't Field instances, and therefore, are not focusable.
So, what I did was to replace ListField with a subclass of Manager. Originally, I used a VerticalFieldManager, but I ran into problems with that. I've also been seeing a lot of issues on stack overflow, where people subclass VerticalFieldManager, customize just one small behaviour, and everything starts breaking. It seems to me that VerticalFieldManager works well if you accept its normal behaviour, and if you need something more, just extend Manager directly. Performing layout for vertically stacked rows is pretty easy.
I then made each row its own Manager, and implemented custom layout in sublayout() to place the row's Fields where I wanted them. I could then also make the row focusable, and then a bitmap/button on the row separately focusable (like your star). Clicking the row invokes one action, and clicking the star invokes another one.
I will note, however, that in my app, performance was not an issue, because I only had 10-20 rows. Also, I did have to modify my code to match your example, so consider this code only lightly tested. However, I did build it into an app, so it should perform fine as long as my assumptions, and your description were valid.
Implementation
First, it wasn't clear to me what your ListRander is (you didn't show that code). However, in my code, I need a data class to contain details about one row. It looked like that's how you used ListRander, so that's what I used:
public class ListRander {
private String _title;
private Bitmap _thumb;
public ListRander(String title, Bitmap thumb) {
_title = title;
_thumb = thumb;
}
public String getTitle() {
return _title;
}
public Bitmap getThumb() {
return _thumb;
}
}
Then, I replaced your CustomListField class with my own:
public class CustomListField extends Manager implements FocusChangeListener {
private int _MAX_ROW_HEIGHT = 60;
private boolean _searchable = false;
private Vector _listData;
private FieldChangeListener _fieldListener;
public CustomListField(Vector data) {
super(FOCUSABLE | VERTICAL_SCROLL | VERTICAL_SCROLLBAR);
setSearchable(true);
setEditable(false);
setListData(data);
}
public void setChangeListener(FieldChangeListener listener) {
// we need to save this listener, because we set it to listen to all new rows
_fieldListener = listener;
int numFields = getFieldCount();
for (int f = 0; f < numFields; f++) {
getField(f).setChangeListener(listener);
}
super.setChangeListener(listener);
}
public int getRowHeight() {
return _MAX_ROW_HEIGHT;
}
public void setSearchable(boolean searchable) {
_searchable = searchable;
}
public int getSelectedIndex() {
return getFieldWithFocusIndex(); // TODO??
}
public Object get(int index) {
return _listData.elementAt(index);
}
public int indexOfList(String prefix, int start) {
if (start >= _listData.size() || !_searchable) {
return -1;
} else {
int result = getSelectedIndex(); // the default result if we find no matches
for (Enumeration e = _listData.elements(); e.hasMoreElements(); ) {
String rowString = (String) e.nextElement();
if (rowString.startsWith(prefix)) {
return _listData.indexOf(rowString);
}
}
return result;
}
}
protected boolean navigationClick(int status, int time) {
CustomListRow focus = (CustomListRow) getFieldWithFocus();
if (focus != null) {
// see if the row wants to process this click
if (!focus.navigationClick(status, time)) {
// let our FieldChangeListener know that this row has been clicked
fieldChangeNotify(getFieldWithFocusIndex());
}
return true;
} else {
return false;
}
}
protected void sublayout(int width, int height) {
int w = Math.min(width, getPreferredWidth());
int h = Math.min(height, getPreferredHeight());
int rowHeight = getRowHeight();
int numRows = getFieldCount();
setExtent(w, h);
setVirtualExtent(w, rowHeight * numRows);
for (int i = 0; i < numRows; i++) {
Field f = getField(i);
setPositionChild(f, 0, rowHeight * i);
layoutChild(f, w, rowHeight);
}
}
public int getPreferredWidth() {
return Display.getWidth();
}
public int getPreferredHeight() {
return Display.getHeight();
}
public void setListData(Vector listData) {
_listData = listData;
if (listData != null) {
int listSize = listData.size();
int numRows = getFieldCount();
for (int s = 0; s < listSize; s++) {
if (s < numRows) {
// we can reuse existing CustomListRows
CustomListRow row = (CustomListRow) getField(s);
row.setData((ListRander) listData.elementAt(s));
} else {
CustomListRow row = new CustomListRow((ListRander) listData.elementAt(s));
row.setChangeListener(_fieldListener);
row.setFocusListener(this);
add(row);
}
}
if (listSize < numRows) {
// delete the excess rows
deleteRange(listSize, numRows - listSize);
}
} else {
deleteAll();
}
invalidate();
}
public void focusChanged(Field field, int eventType) {
// we handle scrolling here, when focus changes between rows
if (eventType == FOCUS_GAINED) {
if (field.getTop() < getVerticalScroll()) {
// field is off the top of the screen, so scroll up
setVerticalScroll(field.getTop());
} else if (field.getTop() >= getVerticalScroll() + getVisibleHeight()) {
// field is off the bottom of the screen, so scroll down
setVerticalScroll(field.getTop() - getVisibleHeight() + getRowHeight());
}
}
}
}
Finally, one row is represented by my CustomListRow class:
public class CustomListRow extends Manager implements FieldChangeListener {
private static final int _MAX_ROW_HEIGHT = 60;
private ListRander _data;
private BitmapField _thumb;
private LabelField _title;
private FocusableBitmapField _star;
private static final Bitmap _starImg = Bitmap.getBitmapResource("star.png");
private static final Bitmap _bgImg = Bitmap.getBitmapResource("listing_bg.png");
private SeparatorField _separator;
private int _fontColor = Color.BLACK;
private boolean _highlighted = false;
private int _width;
// subclass exists to expose focus methods (make public)
private class FocusableBitmapField extends BitmapField {
public FocusableBitmapField() {
super(_starImg, BitmapField.FOCUSABLE | BitmapField.EDITABLE);
}
public void onFocus(int direction) {
super.onFocus(direction);
}
public void onUnfocus() {
super.onUnfocus();
}
}
public CustomListRow(ListRander data) {
super(Field.FOCUSABLE | Manager.NO_VERTICAL_SCROLL | Manager.NO_VERTICAL_SCROLLBAR);
setBackground(BackgroundFactory.createBitmapBackground(_bgImg));
_width = Display.getWidth();
long labelStyle = (DrawStyle.LEFT | DrawStyle.TOP | DrawStyle.ELLIPSIS);
_title = new LabelField("", labelStyle) { // custom anonymous class to change font color
protected void paint(Graphics g) {
int c = g.getColor();
g.setColor(_fontColor);
super.paint(g);
g.setColor(c);
}
};
_title.setFont(Font.getDefault().getFontFamily().getFont(Font.PLAIN, 24));
_thumb = new BitmapField();
_star = new FocusableBitmapField();
_star.setChangeListener(this);
_separator = new SeparatorField() { // custom anonymous class to change separator color
protected void paint(Graphics g) {
int c = g.getColor();
g.setColor(Color.GRAY);
super.paint(g);
g.setColor(c);
}
};
setData(data);
add(_thumb);
add(_title);
add(_star);
add(_separator);
}
public ListRander getData() {
return _data;
}
public void setData(ListRander value) {
if (value != _data) {
_data = value;
_title.setText(value.getTitle());
_thumb.setBitmap(value.getThumb());
}
}
private void onStarClicked() {
Dialog.alert("Star has been clicked or tapped!");
}
private void onRowClicked() {
Dialog.alert("Row has been clicked or tapped!");
}
public void fieldChanged(Field field, int context) {
if (field == _star) {
onStarClicked();
}
}
public boolean navigationClick(int status, int time) {
if (_star.isFocus()) {
onStarClicked();
return true;
} /* else {
onRowClicked();
return true;
} */
return false; // we will not consume this event
}
protected void highlight(boolean onRow) {
_fontColor = onRow ? Color.WHITE : Color.BLACK; // change font color for contrast
_highlighted = onRow;
invalidate();
}
protected void onFocus(int direction) {
// called when focus first transfers to this row, from another Field
if (direction == 1) {
// coming from top to bottom, we highlight the row first, not the star
highlight(true);
} else if (direction == -1) {
// coming from bottom to top, we highlight the star button first, not the row
_star.onFocus(direction);
highlight(false);
}
}
protected void onUnfocus() {
// remove highlighting of the row, if any
highlight(false);
super.onUnfocus();
}
protected int moveFocus(int amount, int status, int time) {
// called when this row already has focus (either on row, or star button)
if (amount > 0) {
// moving top to bottom
if (!_star.isFocus()) {
// we were on the row, now move to the star button
_star.onFocus(1);
highlight(false);
amount--; // consume one unit of movement
}
} else {
// moving from bottom to top
if (_star.isFocus()) {
// we were on the star button, now move back over to the row
_star.onUnfocus();
highlight(true);
amount++; // consume one unit of movement
}
}
return amount;
}
protected boolean touchEvent(net.rim.device.api.ui.TouchEvent event) {
// We take action when the user completes a click (a.k.a. unclick)
int eventCode = event.getEvent();
if ((eventCode == TouchEvent.UNCLICK) || (eventCode == TouchEvent.DOWN)) {
// Get the touch location, within this Manager
int x = event.getX(1);
int y = event.getY(1);
if ((x >= 0) && (y >= 0) && (x < _width) && (y < _MAX_ROW_HEIGHT)) {
int field = getFieldAtLocation(x, y);
if ((field >= 0) && (getField(field) == _star)) {
// Let event propagate to (star) button field
return super.touchEvent(event);
} else {
if (eventCode == TouchEvent.UNCLICK) {
// A completed click anywhere else in this row should popup details for this selection
fieldChangeNotify(1);
onRowClicked();
} else {
// This is just a soft touch (TouchEvent.DOWN), without full click
setFocus();
}
// Consume the event
return true;
}
}
}
// Event wasn't for us, let superclass handle in default manner
return super.touchEvent(event);
}
protected void sublayout(int width, int height) {
height = Math.min(getPreferredHeight(), height);
setExtent(_width, height);
final int margin = 5;
int thumbWidth = _thumb.getPreferredWidth();
layoutChild(_thumb, thumbWidth, _thumb.getPreferredHeight());
setPositionChild(_thumb, margin, margin);
int starWidth = _star.getPreferredWidth();
int starHeight = _star.getPreferredHeight();
layoutChild(_star, starWidth, starHeight);
setPositionChild(_star, width - starWidth - margin, (height - starHeight) / 2);
// this assumes you want margin between all fields, and edges
layoutChild(_title, width - thumbWidth - starWidth - 4 * margin, _title.getPreferredHeight());
setPositionChild(_title, margin + thumbWidth /* + margin */, margin); // TODO?
}
protected void paintBackground(Graphics g) {
super.paintBackground(g);
if (_highlighted) {
// you can't override drawFocus() for a Manager, so we'll handle that here:
int oldColor = g.getColor();
int oldAlpha = g.getGlobalAlpha();
XYRect rect = new XYRect();
g.setGlobalAlpha(150);
g.setColor(Color.BLUE);
getFocusRect(rect);
drawHighlightRegion(g, HIGHLIGHT_FOCUS, true, rect.x, rect.y, rect.width, rect.height);
g.setGlobalAlpha(oldAlpha);
g.setColor(oldColor);
}
}
public int getPreferredWidth() {
return _width;
}
public int getPreferredHeight() {
return _MAX_ROW_HEIGHT;
}
}
Usage
This is how you might use the whole list field (maybe in a Screen class):
public class ListScreen extends MainScreen implements FieldChangeListener {
public ListScreen() {
try {
Vector data = new Vector();
Bitmap icon = Bitmap.getBitmapResource("list_icon.png");
for (int i = 0; i < 15; i++) {
ListRander lr = new ListRander("Product Name " + i, icon);
data.addElement(lr);
}
CustomListField list = new CustomListField(data);
add(list);
list.setChangeListener(this);
} catch (Exception e) {
e.printStackTrace();
}
}
public void fieldChanged(Field field, int context) {
if (field instanceof CustomListRow) {
CustomListRow row = (CustomListRow) field;
Dialog.alert(row.getData().getTitle() + " was selected!");
}
}
}
In my app, it made sense for the CustomListRow itself to handle the equivalent of your star click. However, for me, it did not make sense to have the row click handled that way. So, I let you set a FieldChangeListener on the CustomListField itself, which is called back when any row is selected. See the example above in my screen class. If you want to handle the row click inside the CustomListRow class, too, that's fine. I laid out a onRowClicked() method there. Search in the code for where that's commented out, and you can reactivate, an implement that method (onRowClicked()).
Issues
My app didn't require list searching. I laid out a sample implementation of that, like ListField has. But, I didn't test it. That's your job, if you need it. I just got you started with the CustomListField implementation (see indexOfList()).
I didn't see what your "nav bar" was for. A bar is usually a full-width item, like a status bar, or toolbar. I don't see anything like that in your screenshot. A nav item might be a little arrow at the right side of each row, to bring up details. But, I didn't see that in your screenshot either. So, I ignored that code. If you need a nav bar, you obviously know what it should be, and can add that to my code above.
I couldn't tell whether or not you just added the star as part of the row's background image, or if you had a separate image for that. I added a separate star.png to represent the star. I would assume that clicking the star fills it in, or highlights it, or something. But, you didn't describe that problem, so I assume you can handle that. If you need a custom field to represent the star, that can have selected and unselected images, just post that as a new question.
You had some code that appeared like it was trying to set the row width to 3x the row height, but that didn't match your screen shot. Most lists are full-screen width anyway. So, I remove that code. My CustomListRow class implements getPreferredWidth() and requests the full screen width. Change if you like.
Unlike Android's ListView the BB's ListField is not designed to have a focusable/clickable fields inside of list items. So any attempt to workaround this will have some negative side effects.
A relatively easy/quick workaround would be to switch to VerticalFieldManager (check this other stack overflow question). But if the list is too long (more than several hundreds, I believe) you risk to "eat" too much memory.
If the app is designed for touch screens only, then you can try to stay with ListField + do some manual tracking of touch event coordinates. So when you detect a list field click (in a way you would normally do it) you can check whether the touch coordinates correspond to the star image area (at least on the X axis). I am not going to invent/provide an implementation, but just giving an idea.
I'm working on my own custom manager, and I've gotten it complete so far, but it setsMargins using a percentage of the screen resolution.
Here's how I call the following class:
LabelIconCommandManager licm3 = new LabelIconCommandManager("Address blah bklahblah ", 0);
licm3.add(new ImageButtonField(b1, b2, b3, Field.FIELD_LEFT | ImageButtonField.CONSUME_CLICK));
Here's the class [I've marked in a comment where it returns 0 and where it returns 219. please tell me why this happens:
public class LabelIconCommandManager extends HorizontalFieldManager implements BCMSField
{
LabelIconCommandManager me = this;
EvenlySpacedHorizontalFieldManager buttonManager = new EvenlySpacedHorizontalFieldManager(0);
LabelField labelField;
int side = 0;
int HPADDING = 3;
int VPADDING = 4;
int screenWidth = Display.getWidth();
int labelField_width = 40;
public LabelIconCommandManager()
{
this("", 0);
}
public LabelIconCommandManager(String label, long style)
{
super(USE_ALL_WIDTH| FOCUSABLE);
this.setBorder(BorderFactory.createBitmapBorder(new XYEdges(15, 20, 15, 20),Bitmap.getBitmapResource( "border_edit.png" )));
this.setMargin(1,10,1,10);
labelField = new LabelField(label,LabelField.ELLIPSIS)
{
public void layout(int width, int height)
{
// Done because otherwise ellipses dont work with labelfields
super.layout((int)(screenWidth * 0.61), getHeight());
setExtent((int)(screenWidth * 0.61), getHeight());
labelField_width = labelField.getWidth();
DisplayDialog.alert("labelField_width = " + labelField_width); // returns 219
}
};
// Top Right Bottom Left
labelField.setMargin(VPADDING, HPADDING, VPADDING, 0);
// super because we want this horizontalfieldManager to add it
super.add(labelField);
super.add(buttonManager);
}
public void alternateConstructor(Attributes atts)
{
labelField = new LabelField(atts.getValue("label"), 0);
}
public void onFocus(int direction)
{
this.setBorder(BorderFactory.createBitmapBorder(new XYEdges(15, 20, 15, 20),Bitmap.getBitmapResource( "border_edit_select.png" )));
// uses the same color as listStyleButtonField selections
this.setBackground(BackgroundFactory.createSolidBackground(0x186DEF));
super.onFocus(direction);
}
//Invoked when a field loses the focus.
public void onUnfocus()
{
//top, right,bottom,left
this.setBorder(BorderFactory.createBitmapBorder(new XYEdges(15, 20, 15, 20),Bitmap.getBitmapResource( "border_edit.png" )));
this.setBackground(BackgroundFactory.createSolidTransparentBackground(Color.GRAY, 0));
super.onUnfocus();
invalidate();
}
// Overrride this managers add function
public void add(Field imageButton)
{
// Add a button to the evenly spaced manager
buttonManager.add(imageButton);
// Based on how many buttons there are, set the margin of where the manager holding the buttons start [offset from labelField]
if(buttonManager.getFieldCount() == 1)
{
//side = (int)(screenWidth * 0.1388);
side = screenWidth - labelField_width - 32 - 10 - 15;
DisplayDialog.alert("Screen Width = " + screenWidth);
DisplayDialog.alert("labelField_width2 = " + labelField_width); // returns 0
DisplayDialog.alert("Side = " + side);
}
else side = (int)(screenWidth * 0.05);
buttonManager.setMargin(0,0,0,side);
}
public int getLabelWidth()
{
return labelField_width;
}
}
Here's a picture just to be more clear:
Note: when I ran your code, I didn't actually see labelField_width set to 0. You initialize the value to 40 in the code you posted above. So, I do sometimes see it set to 40, or 219 (on a 360 px wide screen).
But, the problem is that I think you're trying to access the value of labelField_width too soon. The only place it's properly assigned is in the layout() method of your anonymous LabelField. Just because you declare and implement the layout() method in line with the instantiation, doesn't mean that it's called when the LabelField is created. This is actually one of the reasons I don't like anonymous classes.
Anyway, this code:
LabelIconCommandManager licm3 = new LabelIconCommandManager("Address blah bklahblah ", 0);
licm3.add(new ImageButtonField(b1, b2, b3, Field.FIELD_LEFT | ImageButtonField.CONSUME_CLICK));
Will first instantiate the LabelField (inside the LabelIconCommandManager constructor). As I said, that does not trigger the layout() method. The second line above (add()) will trigger your overridden method:
// Overrride this managers add function
public void add(Field imageButton)
{
which is where you see the bad value for labelField_width. That method gets called before layout(). That's the problem.
Since it looks like you only use that width to set the buttonManager margin, you could just wait a little longer to do that. If you wait until the LabelIconCommandManager sublayout() method is called, your LabelField will have had its layout() method called, and labelField_width assigned correctly:
protected void sublayout(int maxWidth, int maxHeight) {
// make sure to call superclass method first!
super.sublayout(maxWidth, maxHeight);
// now, we can reliably use the label width:
side = screenWidth - labelField_width - 32 - 10 - 15;
buttonManager.setMargin(0,0,0,side);
}
That method goes in the LabelIconCommandManager class. And then, you can remove the other place you call buttonManager.setMargin().
Some brief summary from Nate post.
When you construct manager and add fields don't expect that it will be layouted correctly. Manager doesn't know the context - where it will be placed. So layout method for field will be called only when you add his manager to the screen (when layout for manager will be also called). And this is correct.
Move the calculation of your side variable to layout method.
If you really need side value before you put manager to screen. You could precalculate it by using Field.getPrefferedWidth() which returns meaningful values for standard fields (getFont().getAdvance(text) for LabelField, probably also with borders please check yourself). But be careful with this values.
Please review code below. It's manager which has label and buttons. And it puts label at the left side and buttons at the right.
import net.rim.device.api.ui.Field;
import net.rim.device.api.ui.Manager;
import net.rim.device.api.ui.component.ButtonField;
import net.rim.device.api.ui.component.LabelField;
import net.rim.device.api.ui.decor.Border;
import java.util.Vector;
public class TabFieldManager extends Manager {
public TabFieldManager(long style) {
super(style);
}
protected void sublayout(int width, int height) {
LabelField label = null;
Vector tabs = new Vector();
int tabsWidth = 0;
int tabHeight = 0;
int tabPaddingTop = 0;
int tabPaddingLeft = 0;
for (int i=0; i < getFieldCount(); i++) {
Field field = getField(i);
if (field instanceof LabelField) {
label = (LabelField) field;
} else if (field instanceof ButtonField){
tabs.addElement(field);
layoutChild(field, width, height);
int fieldwidth = field.getWidth() > 0 ? field.getWidth() : field.getPreferredWidth() ;
tabsWidth += fieldwidth + getBorderAndPaddingWidth(field);
int fieldHeight = field.getHeight() > 0 ? field.getHeight() : field.getPreferredHeight();
if (fieldHeight > tabHeight) {
tabHeight = getBorderAndPaddingHeight(field) + fieldHeight;
}
int fieldPaddingTop = field.getPaddingTop();
if (fieldPaddingTop > tabPaddingTop) {
tabPaddingTop = fieldPaddingTop;
}
int fieldPaddingLeft = field.getPaddingLeft();
if (fieldPaddingLeft > tabPaddingLeft) {
tabPaddingLeft = fieldPaddingLeft;
}
}
}
if (label != null) {
layoutChild(label, width - tabsWidth, height);
int y = tabHeight - label.getHeight() >> 1;
setPositionChild(label, tabPaddingLeft , y);
}
for (int i = 0; i < tabs.size(); i++) {
Field tabField = (Field) tabs.elementAt(i);
setPositionChild(tabField, width - tabsWidth, getBorderAndPaddingHeight(tabField));
tabsWidth -= tabField.getWidth() + getBorderAndPaddingWidth(tabField);
}
setExtent(width, tabHeight);
}
private int getBorderAndPaddingHeight( Field field ) {
int height = field.getPaddingTop() + field.getPaddingBottom();
Border border = field.getBorder();
if( border != null ) {
height += border.getTop() + border.getBottom();
}
return height;
}
private int getBorderAndPaddingWidth( Field field ){
int width = field.getPaddingLeft() + field.getPaddingRight();
Border border = field.getBorder();
if( border != null ) {
width += border.getLeft() + border.getRight();
}
return width;
}
protected int moveFocus(int amount, int status, int time) {
if ((status & Field.STATUS_MOVE_FOCUS_VERTICALLY) == Field.STATUS_MOVE_FOCUS_VERTICALLY && amount > 0) {
return amount;
} else
return super.moveFocus(amount, status, time);
}
protected int nextFocus(int amount, int axis) {
if (amount > 0 && axis == Field.AXIS_VERTICAL)
return -1;
else
return super.nextFocus(amount, axis);
}
}
I have been having this annoying problem when trying to implement a picture gallery on BlackBerry 6.
Everything works, however when the focus changes from the top buttons to say the pictures further down the screen, the images seem to glitch and not paint themselves correctly. Please see the images below for an example:
(Focus is on the top of the screen(not shown))
(Focus is now on the bottom left image, note that the top image is now blank for an unknown reason)
And this happens no matter how many pictures I add to the tumbnail gallery.
Now here is my code, (a part of it concerning the drawing of the thumbnails)
public ProductImage(String productName){
super(VERTICAL_SCROLL|VERTICAL_SCROLLBAR);
currentProduct = productName;
createGUI();
}
public void createGUI(){
deleteAll();
try{
Storage.loadPicture();
}catch(NullPointerException e){
e.printStackTrace();
}
this.setTitle(new LabelField(_resources.getString(PRODUCT_IMAGE), Field.FIELD_HCENTER));
if(ToolbarManager.isToolbarSupported())
{
Toolbar tb = new Toolbar();
setToolbar(tb.createToolBar());
}
else{
Toolbar tb = new Toolbar();
add(tb.createNavBar());
}
picVector = Storage.getPicture(currentProduct);
EncodedImage enc = EncodedImage.getEncodedImageResource("camera.png");
EncodedImage sizeEnc = ImageResizer.sizeImage(enc, Display.getHeight(), Display.getHeight());
takenPicture = new BitmapField(enc.getBitmap());
vfMain = new VerticalFieldManager();
vfMain.add(logo);
vfMain.add(new SeparatorField());
add(vfMain);
prepareBmpFields();
}
private void prepareBmpFields() {
System.out.println("This is the vector size: " + picVector.getPicVector().size());
LayoutManager manager = new LayoutManager();
FieldChangeListener itemListener = new ButtonListener();
mBmpFields = new ImageButtonField[picVector.getPicVector().size()];
for (int i = 0; i < picVector.getPicVector().size(); i++) {
/*EncodedImage image = EncodedImage
.getEncodedImageResource((String)imageVector.elementAt(i));*/
byte[] data = getData((String)picVector.getPicVector().elementAt(i));
//Encode and Resize image
EncodedImage eImage = EncodedImage.createEncodedImage(data,0,data.length);
eImage = ImageResizer.resizeImage(eImage, mImgWidth, mImgHeight);
ImageButtonField currentImage = new ImageButtonField(eImage.getBitmap());
currentImage.setAssociatedPath((String)picVector.getPicVector().elementAt(i));
mBmpFields[i] = currentImage;
mBmpFields[i].setChangeListener(itemListener);
manager.add(mBmpFields[i]);
}
vfMain.add(manager);
}
private class LayoutManager extends VerticalFieldManager {
public LayoutManager() {
super(VERTICAL_SCROLL | VERTICAL_SCROLLBAR);
}
protected void sublayout(int width, int height) {
int columns = mScrWidth / (mImgWidth + 2 * mImgMargin);
int scrWidth = Display.getWidth();
int rows = mBmpFields.length / columns
+ (mBmpFields.length % columns > 0 ? 1 : 0);
int counter = 0;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < columns; j++) {
int posX = j * (mImgWidth + 2 * mImgMargin) + mImgMargin;
int posY = i * (mImgHeight + 2 * mImgMargin) + mImgMargin;
if(mBmpFields.length > counter){
Field field = mBmpFields[counter];
layoutChild(field, mImgWidth, mImgHeight);
setPositionChild(field, posX, posY);
counter++;
};
}
}
if(Display.getWidth() < Display.getHeight()){
setExtent(mScrWidth, (int)(mScrHeight*1.25));
}
else{
setExtent(mScrWidth, (int)(mScrHeight*2));
}
}
public int getPreferredWidth() {
return mScrWidth;
}
public int getPreferredHeight() {
return mScrHeight;
}
}
}
I have removed many non relevant parts of the code, but the needed code is there.
Does anyone know what could be causing this problem? Thanks for your help!
Edit: as requested, here is my implementation of ImageButtonField class:
import net.rim.device.api.system.Bitmap;
import net.rim.device.api.system.Characters;
import net.rim.device.api.ui.Graphics;
import net.rim.device.api.ui.component.BitmapField;
public class ImageButtonField extends BitmapField{
String associatedPath ="";
BitmapField image2;
public ImageButtonField(Bitmap image) {
super(image);
}
public void setAssociatedPath(String path){
associatedPath = path;
}
public String getAssociatedPath(){
return associatedPath;
}
public boolean isFocusable() {
return true;
}
protected void applyTheme(Graphics arg0, boolean arg1) {
}
protected void drawFocus(Graphics graphics, boolean on) {
}
protected void onFocus(int direction) {
// only change appearance if this button is enabled (aka editable)
if (isEditable()) {
invalidate(); // repaint
}
super.onFocus(direction);
}
public void onUnfocus() {
invalidate(); // repaint
super.onUnfocus();
}
protected boolean navigationClick(int status, int time) {
fieldChangeNotify(0);
return true;
}
protected boolean trackwheelClick(int status, int time) {
fieldChangeNotify(0);
return true;
}
protected void paint(Graphics graphics) {
super.paint(graphics);
if (isFocus()) {
graphics.setGlobalAlpha(128);
graphics.setColor(0x888888);
graphics.fillRect(0, 0, getWidth(), getHeight());
}else{
graphics.setGlobalAlpha(0);
graphics.setColor(0x000000);
graphics.fillRect(0, 0, getWidth(), getHeight());
//graphics.drawBitmap(0, 0, getWidth(), getHeight(), image2.getB, 0, 0);
}
}
protected boolean keyChar(char character, int status, int time) {
if(Characters.ENTER == character || Characters.SPACE == character) {
fieldChangeNotify(0);
return true;
}
return super.keyChar(character, status, time);
}
}
Ok, so you can disregard my first answer, but since I didn't have your ImageButtonField code at the time, I don't want to throw it out ... maybe someone else will find it useful.
In the end, I didn't need to make any changes to ImageButtonField, but I did change your LayoutManager class. The way I figured out that it was the problem was I just started replacing your custom UI classes with built-in ones. I replaced ImageButtonField with BitmapField. That didn't fix it. Then, I replaced LayoutManager with FlowFieldManager and that fixed it. So, I knew where the problem was.
My solution:
private class LayoutManager extends Manager {
public LayoutManager() {
super(VERTICAL_SCROLL | VERTICAL_SCROLLBAR);
}
protected void sublayout(int width, int height) {
setExtent(width, height);
// TODO: maybe always set the same virtual extent?
if (Display.getWidth() < Display.getHeight()) {
setVirtualExtent(mScrWidth, (int) (mScrHeight * 1.25));
} else {
setVirtualExtent(mScrWidth, (int) (mScrHeight * 2));
}
int columns = mScrWidth / (mImgWidth + 2 * mImgMargin);
// int scrWidth = Display.getWidth();
int rows = mBmpFields.length / columns + (mBmpFields.length % columns > 0 ? 1 : 0);
int counter = 0;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < columns; j++) {
int posX = j * (mImgWidth + 2 * mImgMargin) + mImgMargin;
int posY = i * (mImgHeight + 2 * mImgMargin) + mImgMargin;
if (mBmpFields.length > counter) {
Field field = mBmpFields[counter];
layoutChild(field, mImgWidth, mImgHeight);
setPositionChild(field, posX, posY);
counter++;
}
}
}
}
public int getPreferredWidth() {
return mScrWidth;
}
public int getPreferredHeight() {
return mScrHeight;
}
}
I can't say for sure that I understand why your original code wasn't working, but I can say that I wouldn't have done a few of the things in the original code:
The original code was extending VerticalFieldManager but was doing all the work itself, in sublayout(). So, I don't think there was any point extending VerticalFieldManager. I changed it to just extend Manager.
The original code was calling setExtent() with different sizes. I don't think that's what you wanted. Extent is the actual size of the Field. Virtual extent is the virtual size, which is what you want to set larger than the actual extent, in order to enable scrolling. You don't need to dynamically calculate different extents for portrait vs. landscape because the width and height parameters passed to sublayout() will already reflect that. I'm not sure you really even need to be setting different virtual extents either. I think you should probably always set the virtual extent height to the number of rows times picture height, accounting for margins.
You had an unused variable scrWidth in your original code. I commented it out above.
You also posted this question recently, right? Am I correct in assuming that the ImageButtonField you refer to here is the same one you were working on in the other question?
I can't see your full implementation of ImageButtonField, which you should probably post here, too. However, looking at the answers to your other question, I have a feeling that you're doing some custom focus handling in ImageButtonField, and maybe it's not being done quite right. In any case, that class may be where the problem is.
I have a similar Field subclass of my own, and here are the focus handling methods I define:
public class CustomButtonField extends Field {
private Bitmap _button; // the currently displayed button image
private Bitmap _on; // image for 'on' state (aka in-focus)
private Bitmap _off; // image for 'off' state (aka out-of-focus)
protected void onFocus(int direction) {
// only change appearance if this button is enabled (aka editable)
if (isEditable()) {
_button = _on;
invalidate(); // repaint
}
super.onFocus(direction);
}
protected void onUnfocus() {
_button = _off;
invalidate(); // repaint
super.onUnfocus();
}
protected void drawFocus(Graphics graphics, boolean on) {
// override superclass implementation and do nothing
}
public boolean isFocusable() {
return true;
}
I also have a custom implementation of paint(). I won't show it all here, because a lot of the code probably has nothing to do with your problem, but my paint() does include this call:
graphics.drawBitmap(_padding, _padding, _fieldWidth, _fieldHeight, _button, 0, 0);
You might not care about the fact that I have separate images for focused, and unfocused states ... maybe you show the same image at all times.
But, probably the thing to check is your onFocus() and onUnfocus() methods. You may need to add a call to invalidate() as I have.
Looking at Rupak's answer to your other question, it would also be good to check your ImageButtonField.paint() method, and make sure you aren't neglecting to do important drawing steps if the field is not in focus.
I have a question about the BlackBerry VerticalScrollField and scrolling which seems to lock or make the UI unstable. The following code is a BlackBerry screen with worlds as content on the left (in a scroll field) and a jumpbar off to the right that allows clicking into the content.
When a jump letter is clicked the setVerticalScroll method is called, it performs the scroll but has the unfortunate side effect of rendering the UI unstable or unusable. The scroll call is done on the UI thread so its not clear what the source of the error is. The app is being tested in a 6.0 simulator.
I've included the class which can be copied into BB Eclipse for hacking/testing.
The section that kicks of the scrolling can be found towards the bottom with the following code:
UiApplication.getUiApplication().invokeLater(new Runnable(){
public void run() {
scroller.setVerticalScroll(y, true);
}});
Here's the full class:
package test;
import java.util.Vector;
import net.rim.device.api.system.ApplicationManager;
import net.rim.device.api.ui.Field;
import net.rim.device.api.ui.Font;
import net.rim.device.api.ui.Graphics;
import net.rim.device.api.ui.TouchEvent;
import net.rim.device.api.ui.UiApplication;
import net.rim.device.api.ui.component.LabelField;
import net.rim.device.api.ui.component.Status;
import net.rim.device.api.ui.container.HorizontalFieldManager;
import net.rim.device.api.ui.container.MainScreen;
import net.rim.device.api.ui.container.VerticalFieldManager;
public class Startup extends UiApplication {
private int[] jump;
static final String[] words = new String[]{
"auto", "apple", "bear", "car", "farm", "ferret", "gold",
"green", "garden", "hedge", "happy", "igloo", "infrared",
"jelly", "kangaroo", "lemon", "lion", "marble", "moon",
"nine", "opera", "orange", "people", "puppy", "pear",
"quince", "race", "run", "sunset", "token", "willow", "zebra"
};
private final static String[] alphabet = new String[]{"A","B","C","D","E",
"F","G","H","I","J","K","L","M","N","O","P","Q","R",
"S","T","U","V","W","X","Y","Z","#"};
private VerticalFieldManager scroller;
public Startup() {
UiApplication.getUiApplication().invokeLater(new Runnable() {
public void run() {
UiApplication.getUiApplication().pushScreen(new ScrollScreen());
}
});
}
public static void main(String[] args) {
ApplicationManager app = ApplicationManager.getApplicationManager();
while (app.inStartup()) {
try { Thread.sleep(200); } catch (Throwable e) {}
}
Startup startup = new Startup();
startup.enterEventDispatcher();
}
/**
* Screen with content in a scrollbar left and a letters on the right that
* can be used to jump into the content.
*/
class ScrollScreen extends MainScreen {
public ScrollScreen() {
super(NO_HORIZONTAL_SCROLL | NO_VERTICAL_SCROLL);
HorizontalFieldManager hfm = new HorizontalFieldManager(USE_ALL_HEIGHT | NO_VERTICAL_SCROLL | NO_HORIZONTAL_SCROLL){
protected void sublayout(int maxWidth, int maxHeight) {
Field scroll = getField(0);
Field alpha = getField(1);
layoutChild(alpha, maxWidth, maxHeight);
layoutChild(scroll, maxWidth-alpha.getWidth(), maxHeight);
setPositionChild(scroll, 0, 0);
setPositionChild(alpha, maxWidth-alpha.getWidth(), 0);
setExtent(maxWidth, maxHeight);
}
};
hfm.add(createScrollContent());
hfm.add(createAlphabetJumpBar());
add(hfm);
}
private Field createScrollContent() {
Vector vocabulary = new Vector();
for (int ii=0; ii<alphabet.length; ii++)
vocabulary.addElement(alphabet[ii]);
scroller = new VerticalFieldManager(VERTICAL_SCROLL | USE_ALL_WIDTH) {
protected void sublayout(int maxWidth, int maxHeight) {
// Record the jump offsets
int y = 0;
for (int ii=0; ii<getFieldCount(); ii++) {
Field field = getField(ii);
layoutChild(field, maxWidth, maxHeight);
setPositionChild(field, 0, y);
if (field instanceof WordField) {
WordField object = (WordField)field;;
char character = object.getWord().toLowerCase().charAt(0);
int offset = ((int)character)-(int)alphabet[0].toLowerCase().charAt(0);
if (offset < 0 || offset > jump.length)
offset = jump.length-1;
while (offset >= 0 && offset < jump.length && jump[offset] == 0) {
jump[offset] = y;
offset--;
}
}
y += field.getHeight();
}
int offset = jump.length-1;
do {
jump[offset] = y;
offset--;
} while (offset >= 0 && jump[offset] == 0);
setExtent(maxWidth, maxHeight);
setVirtualExtent(maxWidth, y+10);
}
};
jump = new int[alphabet.length];
Font largeFont = Font.getDefault().derive(Font.PLAIN, 46);
for (int ii=0; ii<words.length; ii++) {
WordField wordField = new WordField(words[ii]);
wordField.setFont(largeFont);
scroller.add(wordField);
}
return scroller;
}
private Field createAlphabetJumpBar() {
VerticalFieldManager vfm = new VerticalFieldManager() {
protected void sublayout(int maxWidth, int maxHeight) {
int y = 0;
int width = 0;
double allowedAlphaHeight = (double)maxHeight / (double)getFieldCount();
for (int ii=0; ii<getFieldCount(); ii++) {
WordField field = (WordField)getField(ii);
layoutChild(field, maxWidth, (int)allowedAlphaHeight);
setPositionChild(field, 0, y);
y += field.getHeight();
double paddedY = Math.floor(allowedAlphaHeight*(ii+1));
if (y < paddedY) y = (int)paddedY;
width = Math.max(width, field.getWidth());
}
setExtent(width, maxHeight);
}
};
for (int ii=0; ii<alphabet.length; ii++) {
vfm.add(new AlphaField(alphabet[ii]){
protected boolean touchEvent(TouchEvent message) {
if (message.getEvent() == TouchEvent.UP) {
int startOffset = (int)alphabet[0].charAt(0);
int offset = ((int)getWord().charAt(0)) - startOffset;
final int y = offset == 0 ? 0 : jump[offset - 1];
UiApplication.getUiApplication().invokeLater(new Runnable(){
public void run() {
scroller.setVerticalScroll(y, true);
}});
}
return true;
}
});
}
return vfm;
}
class WordField extends LabelField {
private final String word;
public WordField(String word) {
super(word);
this.word = word;
}
public String getWord() { return word; }
}
Font alphaFont = null;
class AlphaField extends WordField {
public AlphaField(String word) {
super(word);
}
protected void layout(int width, int height) {
if (alphaFont == null)
alphaFont = Font.getDefault().derive(Font.PLAIN, height);
setExtent(alphaFont.getAdvance(getWord()), alphaFont.getHeight());
}
protected void paint(Graphics graphics) {
graphics.setFont(alphaFont);
graphics.drawText(getWord(), 0, 0);
}
}
/**
* For debugging.
* #see net.rim.device.api.ui.Screen#keyChar(char, int, int)
*/
protected boolean keyChar(char c, int status, int time) {
if ('o' == c) { // shows the jump offsets into the scroll field
UiApplication.getUiApplication().invokeLater(new Runnable(){
public void run() {
StringBuffer buf = new StringBuffer();
for (int ii=0; ii<jump.length; ii++) {
buf.append(alphabet[ii]+"="+jump[ii]);
if (ii<jump.length-1)
buf.append(",");
}
Status.show("offsets="+buf.toString());
}});
}
return super.keyChar(c, status, time);
}
}
}
You're using UiApplication.invokeLater in a few places where you're already on the UI event thread, so those are redundant - the debug code in keyChar and the setVerticalScroll call from the touchEvent handler. The Runnable is executed synchronously when you do an invokeLater from the UI thread, with no delay specified.
Are you sure you want to set the scroll explicitly? One option would be to set the focus on the WordField you are interested in, by calling setFocus(), then the OS will do the scrolling events to move that field on screen for you.
If you really need to explicitly set the vertical scroll, your problem may be that the touch event is already causing scroll, so setting it again causes problems. You can get around this by specifying a one millisecond delay for your invokeLater(...). This means your Runnable will be added to the event queue, instead of executing synchronously. That way the scroll won't be changed in the middle of another event call-stack.
Finally tracked down the issue - if the touchEvent for the alphabet label field returns a true then it locks up the main scroll field, if however return super.touchEvent(message) is called the scrolling happens and the scroll field can still be scrolled up and down by clicking on the screen.
This may be a bug in the BlackBerry OS or just the simulator. The Field.touchEvent() documentation for 6.0 recommends returning true if the method consumes the event; however doing so (at least in the above code) causes another UI field to loose the ability to detect touch events which would cause it to scroll.
I want a basic edit field where the first character entered goes to the right most position ...something like this..
2
23
234
can any one tell me how to do this...
Here's one way to do it:
import net.rim.device.api.system.Characters;
import net.rim.device.api.ui.Color;
import net.rim.device.api.ui.DrawStyle;
import net.rim.device.api.ui.Field;
import net.rim.device.api.ui.Graphics;
import net.rim.device.api.ui.XYRect;
import net.rim.device.api.ui.component.BasicEditField;
public class RightInsertTextField extends BasicEditField {
private String lastGoodTxtInputEntry = null;
private int maxCharInputLength = 20;
private Field nextFieldForFocus = null;
/**
* Basic constructor.
*
* #param defaultValue
*/
public RightInsertTextField(String defaultValue) {
super("", defaultValue);
}
public void paint(Graphics g) {
String txt = this.getText();
// 1. (Optional) keeping a check on input length can help
// minimize custom code needed to handle wrap-around text.
if (txt.length() > this.getMaxCharInputLength() && this.getMaxCharInputLength() > 0) {
txt = this.lastGoodTxtInputEntry;
this.setText(txt);
} else {
this.lastGoodTxtInputEntry = txt;
}
// 2. get rid of the default cursor painting by coloring over it with
// the same color as the background
XYRect xy = g.getClippingRect();
g.setBackgroundColor(Color.WHITE);
g.fillRect(xy.x, xy.y, xy.width, xy.height);
g.clear();
// 3. Align text to the right.
g.setColor(Color.BLACK);
g.drawText(txt, 0, 0, (DrawStyle.RIGHT + DrawStyle.ELLIPSIS), getWidth());
}
/**
* Look out for 'ENTER' being pressed.
*/
public boolean keyChar(char key, int status, int time) {
// Prevent new lines in input field.
if (Characters.ENTER == key) {
if (this.nextFieldForFocus != null) {
this.nextFieldForFocus.setFocus();
}
return true;
} else {
return super.keyChar(key, status, time);
}
}
public void setMaxCharInputLength(int maxCharInputLength) {
this.maxCharInputLength = maxCharInputLength;
}
public int getMaxCharInputLength() {
return maxCharInputLength;
}
/**
* #param nextFieldForFocus
* the nextFieldForFocus to set if 'ENTER' is pressed.
*/
public void setNextFieldForFocus(Field nextFieldForFocus) {
this.nextFieldForFocus = nextFieldForFocus;
}
}
This would help
editField = new EditField("", "", maxChars, EditField.NO_NEWLINE | EditField.NON_SPELLCHECKABLE){
private String text = "";
protected boolean keyChar(char key, int status, int time){
switch (key){
case Characters.BACKSPACE:{
try {
text = text.substring(0,text.length()-1);
invalidate();
} catch (IndexOutOfBoundsException e) {}
return true;
}
}
text = text + key;
invalidate();
return true;
}
protected void paint(Graphics graphics) {
graphics.drawText(text,0, 0, DrawStyle.RIGHT, width - 10);
super.paint(graphics);
}
};
Use style Field.FIELD_RIGHT.
Pretty sure that there is no built in way to do this, you gona have to implement your own text box