8

While going through Java swing I faced this problem. I have a JTextField which has predefined and not editable text. the user should be able to append other text to it but without editing the predefined text. Is there any method to obtain this solution or any other?

mKorbel
  • 109,525
  • 20
  • 134
  • 319
sanjeeda
  • 131
  • 1
  • 10

5 Answers5

5

You can simply use a DocumentFilter:

import java.awt.FlowLayout;

import javax.swing.JFrame;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.text.AbstractDocument;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.DocumentFilter;

public class TestDocumentFilter {

    private static final String TEXT_NOT_TO_TOUCH = "You can't touch this!";

    private void initUI() {
        JFrame frame = new JFrame(TestDocumentFilter.class.getSimpleName());
        frame.setLayout(new FlowLayout());
        final JTextField textfield = new JTextField(50);
        textfield.setText(TEXT_NOT_TO_TOUCH);
        ((AbstractDocument) textfield.getDocument()).setDocumentFilter(new DocumentFilter() {
            @Override
            public void insertString(FilterBypass fb, int offset, String string, AttributeSet attr) throws BadLocationException {
                if (offset < TEXT_NOT_TO_TOUCH.length()) {
                    return;
                }
                super.insertString(fb, offset, string, attr);
            }

            @Override
            public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException {
                if (offset < TEXT_NOT_TO_TOUCH.length()) {
                    length = Math.max(0, length - TEXT_NOT_TO_TOUCH.length());
                    offset = TEXT_NOT_TO_TOUCH.length();
                }
                super.replace(fb, offset, length, text, attrs);
            }

            @Override
            public void remove(FilterBypass fb, int offset, int length) throws BadLocationException {
                if (offset < TEXT_NOT_TO_TOUCH.length()) {
                    length = Math.max(0, length + offset - TEXT_NOT_TO_TOUCH.length());
                    offset = TEXT_NOT_TO_TOUCH.length();
                }
                if (length > 0) {
                    super.remove(fb, offset, length);
                }
            }
        });
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(textfield);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                new TestDocumentFilter().initUI();
            }
        });
    }

}
Guillaume Polet
  • 47,259
  • 4
  • 83
  • 117
  • 2
    It's amazing that, when it comes to Java, using the word `simply` is actually fine, even though the code is maybe 100 lines long... – Radu Murzea Apr 30 '13 at 08:16
  • Actually, I'm editing an already existing code. So, inserting a code this long might be a little risky. Is there any simple solution like using a method or something else? – sanjeeda Apr 30 '13 at 08:23
  • 2
    @sanjeeda You don't have to take the whole code, only the part of the `DocumentFilter`. The rest of the code is only there to show a fully-working example in order to avoid comments such as "I tried but it did not work". – Guillaume Polet Apr 30 '13 at 08:27
2

I have a JTextField which has predefined and not editable text. the user should be able to append other text to it but without editing the predefined text. Is there any method to obtain this solution or any other?

use

  1. JComboBox (non_editable)

  2. JSpinner with SpinnerListModel

originally made by @camickr

enter image description here

import java.awt.event.*;
import javax.swing.*;
import javax.swing.text.*;

public class NavigationFilterPrefixWithBackspace extends NavigationFilter {

    private int prefixLength;
    private Action deletePrevious;

    public NavigationFilterPrefixWithBackspace(int prefixLength, JTextComponent component) {
        this.prefixLength = prefixLength;
        deletePrevious = component.getActionMap().get("delete-previous");
        component.getActionMap().put("delete-previous", new BackspaceAction());
        component.setCaretPosition(prefixLength);
    }

    @Override
    public void setDot(NavigationFilter.FilterBypass fb, int dot, Position.Bias bias) {
        fb.setDot(Math.max(dot, prefixLength), bias);
    }

    @Override
    public void moveDot(NavigationFilter.FilterBypass fb, int dot, Position.Bias bias) {
        fb.moveDot(Math.max(dot, prefixLength), bias);
    }

    class BackspaceAction extends AbstractAction {

        private static final long serialVersionUID = 1L;

        @Override
        public void actionPerformed(ActionEvent e) {
            JTextComponent component = (JTextComponent) e.getSource();
            if (component.getCaretPosition() > prefixLength) {
                deletePrevious.actionPerformed(null);
            }
        }
    }

    public static void main(String args[]) throws Exception {
        JTextField textField = new JTextField(" $ ", 20);
        textField.setNavigationFilter(new NavigationFilterPrefixWithBackspace(textField.getDocument().getLength(), textField));
        JFrame frame = new JFrame("Navigation Filter Example");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(textField);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
}
  • I'd suggest to use OverlayLayout (JLabel over JTextField), by changing Insets (input area) in JTextField for JLabels area, otherwise any formatting in JTextField make code and suggestion in this thread quite useless and with strange output to the Swing GUI

  • e.g. JTextField.setHorizontalAlignment(JTextField.RIGHT);


EDIT

  • put JLabel & JTextField to JPanel, quite simple and without side effects

  • change built in FlowLayout for JPanel

  • required to call revalidate() and repaint() in the case that text in JLabel is changed

enter image description here

import java.awt.BorderLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;

public class NavigationFilterBias {

    private JFrame frame = new JFrame("Navigation Filter Example");
    private JPanel panel = new JPanel();
    private JLabel label = new JLabel(" $ ");
    private JTextField textField = new JTextField();

    public NavigationFilterBias() {
        panel.setBorder(textField.getBorder());
        panel.setBackground(textField.getBackground());
        panel.setLayout(new BorderLayout());
        panel.add(label,BorderLayout.WEST);
        textField.setBorder(null);
        panel.add(textField,BorderLayout.CENTER);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(panel);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public static void main(String args[]) throws Exception {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                NavigationFilterBias exam = new NavigationFilterBias();
            }
        });
    }
}
mKorbel
  • 109,525
  • 20
  • 134
  • 319
  • 1
    I cannot use JComboBox because its strictly needed that I have to use a JTextField with some text "$" in it and I want the user to enter the numbers without editing/removing the dollar symbol. – sanjeeda Apr 30 '13 at 08:06
2

Take a look at DocumentFilter. It should allow you to define a "protected" area of text.

Examples here and here

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
1

Though I believe DocumentFilter is the logical and versatile solution, a short solution here. It simply makes an JTextField with an inner left margin in which the fixed text is written.

public class PrefixTextField extends JTextField {
    private String prefix;
    private JLabel label;

    public PrefixTextField(String prefix) {
        this.prefix = prefix;
        label = new JLabel(prefix + '\u00a0');
    }

    @Override
    protected void paintComponent(Graphics g) {
        int w = SwingUtilities.computeStringWidth(
                getFontMetrics(getFont()), prefix);
        setMargin(new Insets(3, 3 + w, 3, 3));
        super.paintComponent(g);
        SwingUtilities.paintComponent(g, label, this.getParent(),
                2 + 3, 0, getWidth(), getHeight());
    }
}
Joop Eggen
  • 107,315
  • 7
  • 83
  • 138
  • Calling `setMargin(new Insets(3, 3 + w, 3, 3));` within `paintComponent` is really dangerous. You should call that outside the painting mechanism. – Guillaume Polet Apr 30 '13 at 09:47
-1

Alternative solution:
1. Put the $ symbole in the JTextField
2. Remove the dollar symbole when the JTextField get the focus
3. Let the user modify the full text
4. When he's done typing (see here) add the $ symbole back

But it would be way easier to add a label next to the JTextField

Community
  • 1
  • 1
DeadlyJesus
  • 1,503
  • 2
  • 12
  • 26