Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Decorating a JTextField with an image and hint

I'm trying to create some nicer looking JTextFields with an image and a hint. To do this I made a decorator that overrides the paintComponent method. The reason I used a decorator is that I wanted to apply it to other types of JTextField such as JPasswordField.

Here is what I've made so far;

enter image description here

The problem as seen in the form on the left is that, even though I have used a JPasswordField the paintComponent seems to ignore what I assume is the passwords paintComponent which presumably does the password masking symbols.

So the question is, how can I avoid duplicating the code for JTextFields and JPasswordFields but still have the different functionality such as password masking.

This is the decorator code;

public class JTextFieldHint extends JTextField implements FocusListener{
private JTextField jtf;
private Icon icon;
private String hint;
private Insets dummyInsets;

public JTextFieldHint(JTextField jtf, String icon, String hint){
    this.jtf = jtf;
    setIcon(createImageIcon("icons/"+icon+".png",icon));
    this.hint = hint;

    Border border = UIManager.getBorder("TextField.border");
    JTextField dummy = new JTextField();
    this.dummyInsets = border.getBorderInsets(dummy);

    addFocusListener(this);
}

public void setIcon(Icon newIcon){
    this.icon = newIcon;
}

@Override
protected void paintComponent(Graphics g) {
        super.paintComponent(g);

        int textX = 2;

        if(this.icon!=null){
            int iconWidth = icon.getIconWidth();
            int iconHeight = icon.getIconHeight();
            int x = dummyInsets.left + 5;
            textX = x+iconWidth+2;
            int y = (this.getHeight() - iconHeight)/2;
            icon.paintIcon(this, g, x, y);
        }

        setMargin(new Insets(2, textX, 2, 2));

        if ( this.getText().equals("")) {
            int width = this.getWidth();
            int height = this.getHeight();
            Font prev = g.getFont();
            Font italic = prev.deriveFont(Font.ITALIC);
            Color prevColor = g.getColor();
            g.setFont(italic);
            g.setColor(UIManager.getColor("textInactiveText"));
            int h = g.getFontMetrics().getHeight();
            int textBottom = (height - h) / 2 + h - 4;
            int x = this.getInsets().left;
            Graphics2D g2d = (Graphics2D) g;
            RenderingHints hints = g2d.getRenderingHints();
            g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
            g2d.drawString(hint, x, textBottom);
            g2d.setRenderingHints(hints);
            g.setFont(prev);
            g.setColor(prevColor);
        }

}

protected ImageIcon createImageIcon(String path, String description) {
    java.net.URL imgURL = getClass().getResource(path);
    if (imgURL != null) {
        return new ImageIcon(imgURL, description);
    } else {
        System.err.println("Couldn't find file: " + path);
        return null;
    }
}

@Override
public void focusGained(FocusEvent arg0) {
    this.repaint();
}

@Override
public void focusLost(FocusEvent arg0) {
    this.repaint();
}


}

And this is where I create the fields;

JTextField usernameField = new JTextFieldHint(new JTextField(),"user_green","Username");
JTextField passwordField = new JTextFieldHint(new JPasswordField(),"bullet_key","Password");

Hopefully i've not went completely off in the wrong direction here!

Thanks!

EDIT : Again the more I look at it, it is obvious that calling super.paintComponent(g) is going to call the JTextFields paintcomponent, but I can't see how to solve this without duplicating the code.

like image 800
Aidan Avatar asked May 22 '11 16:05

Aidan


2 Answers

Text Prompt works with a JPasswordField.

One difference is that the displayed icon disappears when text is entered. If you want the icon to be permanent then I suggest you create a custom "IconBorder* class to paint an Icon rather then do custom painting in the paintComponent() method.

You approach will not work unless you duplicate the code for both JTextField and JPasswordField.

Edit:

Actually you don't need to create a custom IconBorder. The MatteBorder supports the painting of an Icon in a Border.

like image 196
camickr Avatar answered Oct 21 '22 18:10

camickr


In order to paint an icon inside a text field you need to add some insets. You don't want to hard-code insets in your component but just add a little bit of space for the icon, letting clients and subclasses to set their own.

enter image description here

In the figure above I painted original insets in green and additional insets in red. First thing you want to extend JTextField. We keep track of two things: the original insets (the green ones) mBorder, and the icon.

public class IconTextField extends JTextField {
    private Border mBorder;
    private Icon mIcon;

    // ...
}

Then you need to override setBorder() method.

@Override
public void setBorder(Border border) {
    mBorder = border;

    if (mIcon == null) {
        super.setBorder(border);
    } else {
        Border margin = BorderFactory.createEmptyBorder(0, mIcon.getIconWidth() + ICON_SPACING, 0, 0);
        Border compound = BorderFactory.createCompoundBorder(border, margin);
        super.setBorder(compound);
    }
}

Here, if we have an icon (the field mIcon is not null), we add our additional insets using a compound border. Then, you should also override the paintComponent() method.

@Override
protected void paintComponent(Graphics graphics) {
    super.paintComponent(graphics);

    if (mIcon != null) {
        Insets iconInsets = mBorder.getBorderInsets(this);
        mIcon.paintIcon(this, graphics, iconInsets.left, iconInsets.top);
    }
}

Finally, you need a setIcon() method.

public void setIcon(Icon icon) {
    mIcon = icon;
    resetBorder();
}

private void resetBorder() {
    setBorder(mBorder);
}

What we are doing here is saving the icon and recalculating the borders.

If you want to do the same same thing with JPasswordField, the most elegant thing is probably to create a helper class with all the methods discussed above.

class IconTextComponentHelper {
    private static final int ICON_SPACING = 4;

    private Border mBorder;
    private Icon mIcon;
    private Border mOrigBorder;
    private JTextComponent mTextComponent;

    IconTextComponentHelper(JTextComponent component) {
        mTextComponent = component;
        mOrigBorder = component.getBorder();
        mBorder = mOrigBorder;
    }

    Border getBorder() {
        return mBorder;
    }

    void onPaintComponent(Graphics g) {
        if (mIcon != null) {
            Insets iconInsets = mOrigBorder.getBorderInsets(mTextComponent);
            mIcon.paintIcon(mTextComponent, g, iconInsets.left, iconInsets.top);
        }
    }

    void onSetBorder(Border border) {
        mOrigBorder = border;

        if (mIcon == null) {
            mBorder = border;
        } else {
            Border margin = BorderFactory.createEmptyBorder(0, mIcon.getIconWidth() + ICON_SPACING, 0, 0);
            mBorder = BorderFactory.createCompoundBorder(border, margin);
        }
    }

    void onSetIcon(Icon icon) {
        mIcon = icon;
        resetBorder();
    }

    private void resetBorder() {
        mTextComponent.setBorder(mOrigBorder);
    }
}

And use it like this:

public class IconTextField extends JTextField {
    private IconTextComponentHelper mHelper = new IconTextComponentHelper(this);

    public IconTextField() {
        super();
    }

    public IconTextField(int cols) {
        super(cols);
    }

    private IconTextComponentHelper getHelper() {
        if (mHelper == null)
            mHelper = new IconTextComponentHelper(this);

        return mHelper;
    }

    @Override
    protected void paintComponent(Graphics graphics) {
        super.paintComponent(graphics);
        getHelper().onPaintComponent(graphics);
    }

    public void setIcon(Icon icon) {
        getHelper().onSetIcon(icon);
    }

    public void setIconSpacing(int spacing) {
        getHelper().onSetIconSpacing(spacing);
    }

    @Override
    public void setBorder(Border border) {
        getHelper().onSetBorder(border);
        super.setBorder(getHelper().getBorder());
    }
}
like image 34
mneri Avatar answered Oct 21 '22 18:10

mneri