Error adding placeholder to a text component

Asked

Viewed 264 times

2

I am implementing the component of this topic together with the following instruction:

PromptSupport.setPrompt("Digite..", field); 

This command is from biblioteca swingx-core-1.6.2 and adds a sort of placeholder. But when using it together with the event FocusListener of a IconTextField he gives me the following mistake:

  Exception in thread "AWT-EventQueue-0" java.lang.StackOverflowError
    at javax.swing.text.DefaultCaret$Handler.propertyChange(DefaultCaret.java:1846)
    at java.beans.PropertyChangeSupport.fire(PropertyChangeSupport.java:335)
    at java.beans.PropertyChangeSupport.firePropertyChange(PropertyChangeSupport.java:327)
    at java.beans.PropertyChangeSupport.firePropertyChange(PropertyChangeSupport.java:263)
    at java.awt.Component.firePropertyChange(Component.java:8428)
    at javax.swing.JComponent.setBorder(JComponent.java:1796)
    at geral.IconTextField.setBorder(IconTextField.java:45)
    at org.jdesktop.swingx.plaf.BuddyLayoutAndBorder.replaceBorderIfNecessary(BuddyLayoutAndBorder.java:56)
    at org.jdesktop.swingx.plaf.BuddyLayoutAndBorder.propertyChange(BuddyLayoutAndBorder.java:245)
    at java.beans.PropertyChangeSupport.fire(PropertyChangeSupport.java:335)
    at java.beans.PropertyChangeSupport.firePropertyChange(PropertyChangeSupport.java:328)
    at java.beans.PropertyChangeSupport.firePropertyChange(PropertyChangeSupport.java:263)
    at java.awt.Component.firePropertyChange(Component.java:8428)
    at javax.swing.JComponent.setBorder(JComponent.java:1796)
    at geral.IconTextField.setBorder(IconTextField.java:45)
    at org.jdesktop.swingx.plaf.BuddyLayoutAndBorder.replaceBorderIfNecessary(BuddyLayoutAndBorder.java:56)
    at org.jdesktop.swingx.plaf.BuddyLayoutAndBorder.propertyChange(BuddyLayoutAndBorder.java:245)
    at java.beans.PropertyChangeSupport.fire(PropertyChangeSupport.java:335)
    at java.beans.PropertyChangeSupport.firePropertyChange(PropertyChangeSupport.java:328)
    at java.beans.PropertyChangeSupport.firePropertyChange(PropertyChangeSupport.java:263)
    at java.awt.Component.firePropertyChange(Component.java:8428)
    at javax.swing.JComponent.setBorder(JComponent.java:1796)
    at geral.IconTextField.setBorder(IconTextField.java:45)

What can I do to solve?

Library link(scroll the page almost at the end, and download the 1st link)

Below I’ll leave the classes I’m using:

Main class:

public class JTextFieldDecoratedIcon {

    public void start() throws IOException {

        final JFrame frame = new JFrame();
        frame.setPreferredSize(new Dimension(500, 350));

        JTextField field2 = new JTextField();
        IconTextField field = new IconTextField();

        URL path = new URL("https://i.imgur.com/WKfl8uV.png");
        Image icone = ImageIO.read(path);

        field.setIcon(new ImageIcon(icone));

        frame.add(field, BorderLayout.NORTH);
        field.setPreferredSize(new Dimension(250, 30));

        //bibilioteca swingx-core-1.6.2 ↓
        PromptSupport.setPrompt("Digite..", field);

        frame.add(field2, BorderLayout.SOUTH);
        field2.setPreferredSize(new Dimension(100, 30));

        field.addFocusListener(new FocusListener() {
            @Override
            public void focusGained(FocusEvent e) {
                field.setBorder(new LineBorder(new Color(108, 85, 255)));
                field.setBackground(Color.LIGHT_GRAY);
            }

            @Override
            public void focusLost(FocusEvent e) {
                field.setBorder(new LineBorder(Color.GRAY));
                field.setBackground(new Color(255, 255, 255));
            }
        });

        frame.pack();
        frame.setVisible(true);
        frame.setLocationRelativeTo(null);

        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    }

    public static void main(String[] args) {

        try {
            for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
                if ("Nimbus".equals(info.getName())) {
                    javax.swing.UIManager.setLookAndFeel(info.getClassName());
                    break;
                }
            }
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | javax.swing.UnsupportedLookAndFeelException ex) {
            ex.printStackTrace();
        }

        EventQueue.invokeLater(() -> {
            try {
                new JTextFieldDecoratedIcon().start();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        });
    }
}

Icontextcomponenthelper class :

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);
    }
}

Icontextfield class:

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());
    }
}
  • The problem is that you have no way of knowing because your version has no documentation. Either you give up this modification and leave only the lib one or update the lib to a newer version and adapt your code to it.

  • when you say adapt, is to find an instruction or the way to implement that command ?

  • No, the class PromptSupport manipulates focus events to display the placeholder, you will get the same error on any version of that lib, unless you discover the source code of that class and modify it. Of course you run the risk of "mocking" the class and it fails to function properly. I think it’s best if you choose if you want the placeholder or if you want to apply it with custom focus on your own(which isn’t even that hard to do)

  • So, with setText("") I can do, only that there is one but, at a given moment, I use with Listener Ocument this field, then it mocks the searches that I do.

  • 2

    Well, I found an alternative solution if using no lib, but the question is about an error when using the lib, if I answer, I run the risk of being negatively affected by escaping the scope. If I may, I can make a small change to the question so that my alternative answer is not meaningless to her, that’s fine?

  • @diegofm can yes, feel free, and I appreciate your help !

Show 1 more comment

1 answer

3


After testing several times, I realized that the class of this lib uses listeners focus to apply the placeholder, and when you try to override the focus methods, you go into an infinite loop, as your code changes the field and notifies the Listener, that activates the class event to apply the placeholder, and so keep repeating endlessly.

This could be solved if we understood better how the class works PromptSupport until we display the placeholder and change it directly, something that I find more complicated than creating a class of its own with this functionality.

And to implement a placeholder in JTextfield, you need to monitor component focus. Based in this answer and in this other at Soen, I designed the class PlaceHolderSupport as an alternative to this lib:

import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Rectangle;

import javax.swing.FocusManager;
import javax.swing.text.JTextComponent;

/**
 * Classe responsável por definir um placeholder a um componente de texto
 * 
 * @author diego
 *
 */
public class PlaceHolderSupport {

    private static JTextComponent textComponent;
    private static String placeHolder = "";

    /**
     * Aplica o texto recebido como placeholder ao componente de texto
     * 
     * @param comp - Componente de texto
     * @param strPlaceHolder - texto do placeholder
     */
    public static void setPlaceHolder(JTextComponent comp, String strPlaceHolder) {

        textComponent = comp;
        placeHolder = strPlaceHolder;
    }

    /**
     * Desenha uma string centralizada no meio do componente representado pelo
     * retangulo
     * 
     * @param g - Instancia de Graphics.
     * @param text - String a ser desenhada.
     * @param rect - Retangulo para centralizar o texto.
     * @param font - Fonte a ser aplicada ao texto
     */
    private static void drawPlaceHolderString(Graphics g, String text, Rectangle rect, Font font) {
        // Obtém as métricas da fonte do texto
        FontMetrics metrics = g.getFontMetrics(font);
        // Determina a coordenada X do texto conforme
        // o tamanho da borda interna esquerda
        int x = textComponent.getBorder().getBorderInsets(textComponent).left;
        // Determina a coordenada Y do texto para que
        // fique centralizado verticalmente
        int y = rect.y + ((rect.height - metrics.getHeight()) / 2) + metrics.getAscent();
        // aplica a fonte
        g.setFont(font);
        // desenha a string
        g.drawString(text, x, y);
    }

    /**
     * Desenha o placeholder no componente de texto
     * 
     * @param g - instancia de Graphics
     */
    public static void onPaintComponent(Graphics g) {

        if (textComponent != null) {
            //verifica se o campo está vazio e se 
            //o foco atual do teclado pertence a ele
            if (textComponent.getText().isEmpty()
                    && !(FocusManager.getCurrentKeyboardFocusManager().getFocusOwner() == textComponent)) {
                Font font = textComponent.getFont().deriveFont(Font.ITALIC);
                g.setColor(Color.gray);
                drawPlaceHolderString(g, placeHolder, textComponent.getBounds(), font);
            } else {
                textComponent.repaint();
            }
        }
    }
}

To use, you need to pass the text of placeholder and the instance of the text component in the constructor of this class, using the method setPlaceHolder. In your class example IconTextField, was like this:

public IconTextField() {
    super();
    PlaceHolderSupport.setPlaceHolder(this, "Preencha este campo...");
}

public IconTextField(int cols) {
    super(cols);
    PlaceHolderSupport.setPlaceHolder(this, "Preencha este campo...");
}

Then, in the method paintComponent of your text component, add the following excerpt:

PlaceHolderSupport.onPaintComponent(graphics);

The method PlaceHolderSupport.onPaintComponent checks that the component is empty and out of focus. If the condition is true, it applies the placeholder, and when that condition is not true, it forces the component to redesign without the text.

I also suggest you make a small modification by adding the focus event to the component, as I noticed that you try to recover background colors and pattern edges by losing focus and depending on the Look And Feel applied, may not have the expected result. To always get the default background color, you can change as below:

field.addFocusListener(new FocusListener() {

    Color defaultBg = field.getBackground();

    @Override
    public void focusGained(FocusEvent e) {
        field.setBorder(new LineBorder(new Color(108, 85, 255)));
        field.setBackground(Color.LIGHT_GRAY);
    }

    @Override
    public void focusLost(FocusEvent e) {
        field.setBorder(new LineBorder(Color.GRAY));
        field.setBackground(defaultBg);
    }
});

With these changes, your component is already functional with the placeholder applied:

inserir a descrição da imagem aqui

  • so a doubt, that public void method setIconSpacing(int Spacing) { getHelper(). onSetIconSpacing(Spacing); } he asks me to create onSetIconSpacing, makes it different to comment on that line ?

  • 1

    @Java in the original code in Soen, the author of the code did not implement this method in the helper. I believe he’s given up, since he’s using a constant ICON_SPACING to set this spacing, and have forgotten to remove in his answer. Can remove without problem.

  • excuse my ignorance, just to review the steps, I think I "ate belly", inside the iconText I pass those two builders, and in the class I use, I only instantiate normal ?

  • @Java the important thing is to add the line PlaceHolderSupport.setPlaceHolder(this, "Preencha este campo..."); in the constructor (or constructors) of your component, and since yours has more than one, I only copied the two to avoid the risk of you adding only in one, using the other and not working. Of course, if you want, you can customize the constructs, so that they receive the placeholder as parameter instead of passing the direct value as I did, but there is your choice of customization.

  • do not know what happens, does not appear the text

  • @Java is working normally in your code as long as you haven’t changed anything in the class, of course: https://gist.github.com/diegofelipem/a349619d30b523347e72143feabf49d0

  • to set the Placeholder, it requires the component to start with focus ?

  • @Java no, you just have to follow what I explained in the answer. The placeholder will appear when the component is out of focus. Compile and run this code from the ai link, it is your code with the applied class.

  • Yes, I’ve done it, your example is correct. It’s probably something in my project that’s not right. Just one more question, to set the image can not use icon instead of URL ?

  • @Java I always use Imageio: Image icone = ImageIO.read(getClass().getResource("path-da-imagem.extensao"));

  • 1

    @Java made a small change in the code, to make it more independent. Then take a look.

  • I couldn’t tell the difference, which was changed ?

  • @Java https://answall.com/posts/220707/revisions Before I was asking you to add an excerpt of if in the method paintComponent text field, I moved it to class PlaceHolderSupport, and now just need to add a line in this method: PlaceHolderSupport.onPaintComponent(graphics);

  • vixi, here bugo everything, will be the lookFeel ?

  • @java, if the way it was before it worked normal, leave it with was even before. It’s just that I altered and tested it here and as the result was the same, I thought it wouldn’t affect anything for you either.

Show 10 more comments

Browser other questions tagged

You are not signed in. Login or sign up in order to post.