1

I have a ImageButton in a VerticalFieldManager and VFM in a HorizontalFieldManager. The ImageButton click event is firing when I click anywhere in the surrounding field. I want that click event to only fire when clicking the ImageButton, not the field (VFM or HFM).

Following is the code:

ImageButton Login = new ImageButton(configModel.getLoginButton(), FOCUSABLE|Field.FIELD_RIGHT, "login.png", "plogin.png",0x9cbe95);
HorizontalFieldManager hfm = new HorizontalFieldManager(USE_ALL_WIDTH);
VerticalFieldManager loginButtonVfm = new VerticalFieldManager(USE_ALL_WIDTH);

loginButtonVfm.add(Login);
hfm.add(loginButtonVfm);
add(hfm);


FieldChangeListener loginListener = new FieldChangeListener() { 
public void fieldChanged(Field field, int context) {
     Dialog.alert("Fired");
    }
}
Login.setChangeListener(loginListener); 

ImageButton Class Code is here:

public class ImageButton extends Field{

        //Image Button Class 
        private String _label;
        private int _labelHeight;
        private int _labelWidth;
        private Font _font;

        private Bitmap _currentPicture;
        private Bitmap _onPicture;
        private Bitmap _offPicture;
        int color;

        public ImageButton(String text, long style ,String img, String img_hvr, int color){
            super(style);

            _offPicture = Bitmap.getBitmapResource(img);
            _onPicture = Bitmap.getBitmapResource(img_hvr);

            _font = Font.getDefault().derive(Font.BOLD, 7, Ui.UNITS_pt);
            _label = text;


            _labelHeight = _onPicture.getHeight();  
            _labelWidth = _onPicture.getWidth();

            this.color = color;

            _currentPicture = _offPicture;
        }
        public void setImage(String img){

            _offPicture = Bitmap.getBitmapResource(img); 
            _currentPicture = _offPicture;
        }

        /**
         * @return The text on the button
         */
        public void setText(String text){
            _label = text;
        }
            String getText(){
            return _label;
        }

        /**
         * Field implementation.
         * @see net.rim.device.api.ui.Field#getPreferredHeight()
         */
        public int getPreferredHeight(){
            return _labelHeight;
        }
        /**
         * Field implementation.
         * @see net.rim.device.api.ui.Field#getPreferredWidth()
         */
        public int getPreferredWidth(){
            return _labelWidth;
        }

        /**
         * Field implementation.  Changes the picture when focus is gained.
         * @see net.rim.device.api.ui.Field#onFocus(int)
         */
        protected void onFocus(int direction) {

             _currentPicture = _onPicture;
          //  invalidate();
            super.onFocus(direction);
        }

        /**
         * Field implementation.  Changes picture back when focus is lost.
         * @see net.rim.device.api.ui.Field#onUnfocus()
         */
        protected void onUnfocus() {
            _currentPicture = _offPicture;
            invalidate();
            super.onUnfocus();
        }

        /**
         * Field implementation.  
         * @see net.rim.device.api.ui.Field#drawFocus(Graphics, boolean)
         */
//      protected void drawFocus(Graphics graphics, boolean on) {
//          // Do nothing
//      }
        protected void drawFocus(Graphics graphics, boolean on) {
            if (on) {
                     //draw your own custom focus.
                    }
                }
        /**
         * Field implementation.
         * @see net.rim.device.api.ui.Field#layout(int, int)
         */
        protected void layout(int width, int height) {
            setExtent(Math.min( width, getPreferredWidth()),
            Math.min( height, getPreferredHeight()));
        }
        /**
         * Field implementation.
         * @see net.rim.device.api.ui.Field#paint(Graphics)
         */
        protected void paint(Graphics graphics){      
            // First draw the background colour and picture
            graphics.setColor(this.color);
            graphics.fillRect(0, 0, getWidth(), getHeight());
            graphics.drawBitmap(0, 0, getWidth(), getHeight(), _currentPicture, 0, 0);

            // Then draw the text
            graphics.setColor(Color.WHITE);
            graphics.setFont(_font);
            graphics.setFont(graphics.getFont().derive(Font.BOLD)); 
            graphics.drawText(_label, 5,9,
                (int)( getStyle() & DrawStyle.ELLIPSIS | DrawStyle.VALIGN_MASK | DrawStyle.HALIGN_MASK),
                getWidth() - 6 );

        }

        /**
         * Overridden so that the Event Dispatch thread can catch this event
         * instead of having it be caught here..
         * @see net.rim.device.api.ui.Field#navigationClick(int, int)
         */
        protected boolean navigationClick(int status, int time){
            fieldChangeNotify(1);
            return true;
        } 
}
Nate
  • 31,017
  • 13
  • 83
  • 207
Ahmad Shahwaiz
  • 1,432
  • 1
  • 17
  • 35

1 Answers1

1

This kind of thing is actually pretty easy to find bugs in, when you try to write custom fields yourself. (It's not your fault ... BlackBerry Java makes this stuff more difficult than it really should be!)

I think the easiest way to solve this problem is to reuse something that someone else has already tested. I would recommend looking at the BlackBerry Advanced UI library, in particular, these two classes:

BaseButtonField

BitmapButtonField

As I think they do exactly what you need. I believe the important code you need to change, or add to your ImageButton class is this:

protected boolean keyChar( char character, int status, int time ) 
{
    if( character == Characters.ENTER ) {
        clickButton();
        return true;
    }
    return super.keyChar( character, status, time );
}

protected boolean navigationClick( int status, int time ) 
{
    if (status != 0) clickButton(); 
    return true;    
}

protected boolean trackwheelClick( int status, int time )
{        
    if (status != 0) clickButton();    
    return true;
}

protected boolean invokeAction( int action ) 
{
    switch( action ) {
        case ACTION_INVOKE: {
            clickButton(); 
            return true;
        }
    }
    return super.invokeAction( action );
}    

protected boolean touchEvent( TouchEvent message )
{
    int x = message.getX( 1 );
    int y = message.getY( 1 );
    if( x < 0 || y < 0 || x > getExtent().width || y > getExtent().height ) {
        // Outside the field
        return false;
    }
    switch( message.getEvent() ) {

        case TouchEvent.UNCLICK:
            clickButton();
            return true;
    }
    return super.touchEvent( message );
}

/**
 * A public way to click this button
 */
public void clickButton() 
{
    fieldChangeNotify( 0 );
}

You will then be able to use a FieldChangeListener exactly as you are now, but the image button will not call it unless the click occurs on your button.

If you want, you can certainly change the code above to use

    fieldChangeNotify( 1 );

if 1 is a meaningful context value for you. The important thing is to add the other methods, and change navigationClick().

Nate
  • 31,017
  • 13
  • 83
  • 207
  • Pure awesomeness, what was the issue if you could describe a bit your solution? – Ahmad Shahwaiz May 15 '13 at 09:38
  • 2
    If you're seeing this problem on a **touch** screen, I think the problem was the missing implementation of `touchEvent()`. If you look at that code, you'll see that it does a check to make sure the touch is **inside** the button, not outside. You should include the other stuff, too. For example, `keyChar()` makes sure a user can have focus on your button, and *click* it by pressing ENTER. `trackwheelClick()` makes handling uniform for trackwheel devices, etc. – Nate May 15 '13 at 10:38
  • @Nate One problem with the above code is that you have handled `navigationClick` and not `navigationUnClick`, whereas in the touch event, you are handling UNCLCIK. This would result in the clickButton being called twice, once during the click and then again during the unclick on touch devices. I generally recommend people to only handle `navigationUnclick()` to conform to the standard UI behavior. – Adwiv May 15 '13 at 13:47
  • @adwiv, [I agree 100% that using UNCLICK makes a better UI](http://stackoverflow.com/a/11613344/119114). In this case, it's not my code, but I'm pretty sure it's still correct, although maybe a little unclear. The check `status != 0` in `navigationClick()` I believe prevents this problem. For a touch device, that filters out events, and leaves it to be handled in `touchEvent()`. For a keypad device, `navigationClick()` is called with nonzero `status`. I ran a quick test on a 5.0 device and 6.0 simulator, and there were no double calls to `clickButton()`. It also triggers on the unclick. – Nate May 15 '13 at 16:40
  • @Nate You are right.. But `status != 0` is a very weird way :) Something I would call a hack :) – Adwiv May 16 '13 at 11:08
  • 1
    @adwiv, it does *smell* a little hackish, I admit. Part of it is that this check is basically asking if **none** of the status bits have been set, and they don't define a constant value for `STATUS_NONE` to use. One reason you normally don't want to make assumptions about the numerical representation of values like this, is that it could be changed in future versions, and break your code. In this case, though, we know there won't be new BlackBerry Java versions, so if the code works now, it always will. So, just something to consider, *in this special case*. Good points, though. – Nate May 16 '13 at 21:03