JTextField with prefix

Hi,

I need to have a JTextField, which content has a not editable prefix.

e.g. if the prefix is "123_" and you call .setText("test"), "123_test" should be display. Only the text after the prefix ("test") should be editable and getText() should deliver the text without prefix.

I already experimented with extending JTextField and using a custom Document, but I still have problems with caret and marking text.

Maybe some of you has some hints for me. Here's my current code:

package dev;

import java.awt.event.WindowAdapter;

import java.awt.event.WindowEvent;

import javax.swing.JFrame;

import javax.swing.JTextField;

import javax.swing.event.DocumentEvent;

import javax.swing.text.BadLocationException;

import javax.swing.text.Document;

import javax.swing.text.PlainDocument;

import javax.swing.text.Segment;

publicclass CPrefixFieldTestextends JFrame{

publicstaticvoid main(String[] args){

new CPrefixFieldTest().setVisible(true);

}

public CPrefixFieldTest(){

super("Prefix field test");

setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);

this.addWindowListener(new WindowAdapter(){

publicvoid windowClosing(WindowEvent e){

System.exit(0);

}

});

this.setSize(300, 300);

JTextField textField =new PrefixTextField("123_");

textField.setBounds(10, 10, 200, 20);

super.getContentPane().setLayout(null);

super.getContentPane().add(textField);

}

class PrefixTextFieldextends JTextField{

public PrefixTextField(String prefix){

setPrefix(prefix);

}

publicvoid setPrefix(String prefix){

((ExDocument) getDocument()).setPrefix(prefix);

}

@Override

protected Document createDefaultModel(){

returnnew ExDocument();

}

}

class ExDocumentextends PlainDocument{

private String prefix ="";

@Override

public String getText(int offset,int length)throws BadLocationException{

StringBuilder text =new StringBuilder(super.getText(offset, length));

if (offset == 0){

text.insert(0, prefix);

}

return text.toString();

}

@Override

publicvoid getText(int offset,int length, Segment txt)

throws BadLocationException{

super.getText(offset, length, txt);

if (offset == 0){

StringBuilder text =new StringBuilder();

text.append(prefix.toCharArray());

text.append(txt.array);

txt.array = text.toString().toCharArray();

txt.count += prefix.length();

}

}

public String getPrefix(){

return prefix;

}

publicvoid setPrefix(String prefix){

this.prefix = prefix;

if (this.prefix ==null){

this.prefix ="";

}

super.fireChangedUpdate(new DefaultDocumentEvent(0, getLength(),

DocumentEvent.EventType.CHANGE));

}

}

}

[5870 byte] By [Bruno168a] at [2007-11-26 17:20:43]
# 1
Do you want this text field to be editable by the user?You need to implement insertString in ExDocument for that.If you just want it to work with setText and getText you're going in the wrong direction.
Rodney_McKaya at 2007-7-8 23:48:44 > top of Java-index,Desktop,Core GUI APIs...
# 2

maybe(?) something like this

import javax.swing.*;

import javax.swing.border.*;

import java.awt.*;

class Testing

{

public void buildGUI()

{

JTextField tf = new JTextField(5);

tf.setBorder(BorderFactory.createCompoundBorder(tf.getBorder(),new TextBorder()));

JFrame f = new JFrame();

f.getContentPane().add(tf);

f.pack();

f.setLocationRelativeTo(null);

f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

f.setVisible(true);

}

public static void main(String[] args)

{

SwingUtilities.invokeLater(new Runnable(){

public void run(){

new Testing().buildGUI();

}

});

}

}

class TextBorder implements Border

{

public void paintBorder(Component c, Graphics g, int x, int y, int width, int height)

{

g.setColor(Color.BLACK);

g.drawString("123_",0,15);

}

public Insets getBorderInsets(Component c)

{

return new Insets(0,25,0,0);

}

public boolean isBorderOpaque()

{

return true;

}

}

Michael_Dunna at 2007-7-8 23:48:44 > top of Java-index,Desktop,Core GUI APIs...
# 3

I think I like Michael_Dunn's solution better, but here's one way to do it without playing with text outside the text field:

import javax.swing.*;

import javax.swing.event.*;

import javax.swing.text.*;

import javax.swing.text.DocumentFilter.FilterBypass;

public class PrefixDocFilter extends DocumentFilter implements CaretListener {

private static final String PREFIX = "Prefix_";

private JTextField text;

private boolean changingCaret;

public PrefixDocFilter() {

text = new JTextField(PREFIX, 30);

Document doc = text.getDocument();

if (doc instanceof AbstractDocument) {

((AbstractDocument)doc).setDocumentFilter(this);

}

text.addCaretListener(this);

JFrame f = new JFrame("test");

f.add(text);

f.pack();

f.setLocationRelativeTo(null);

f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

f.setVisible(true);

}

public void caretUpdate(CaretEvent e) {

if (!changingCaret) {

changingCaret = true;

System.out.println(e);

int start = Math.min(e.getDot(), e.getMark());

int end = Math.max(e.getDot(), e.getMark());

if (start < PREFIX.length()) {

text.setCaretPosition(PREFIX.length());

if (start != end) {

text.moveCaretPosition(Math.max(end, PREFIX.length()));

}

}

changingCaret = false;

}

}

public void insertString(FilterBypass fb, int offset, String string, AttributeSet attr) throws BadLocationException {

if (offset < PREFIX.length()) {

java.awt.Toolkit.getDefaultToolkit().beep();

return;

}

super.insertString(fb, offset, string, attr);

}

public void remove(FilterBypass fb, int offset, int length) throws BadLocationException {

if (offset < PREFIX.length()) {

java.awt.Toolkit.getDefaultToolkit().beep();

length -= (PREFIX.length() - offset);

offset = PREFIX.length();

}

super.remove(fb, offset, length);

}

public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException {

if (offset < PREFIX.length()) {

offset = PREFIX.length();

}

super.replace(fb, offset, length, text, attrs);

}

public static void main(String[] args) {

new PrefixDocFilter();

}

}

Jasprea at 2007-7-8 23:48:44 > top of Java-index,Desktop,Core GUI APIs...
# 4

Hi guys,

thanks for your replies!

@Jaspre: Your solution looks nearly perfect for my needs. I did just a little changes in the caretUpdate() method (see below), but I still can't come around one problem:

When selecting text in the field using the keyboard (via SHIFT key), the last character of the prefix gets selected as well - any ideas?

package dev;

import javax.swing.JFrame;

import javax.swing.JTextField;

import javax.swing.WindowConstants;

import javax.swing.event.CaretEvent;

import javax.swing.event.CaretListener;

import javax.swing.text.AbstractDocument;

import javax.swing.text.AttributeSet;

import javax.swing.text.BadLocationException;

import javax.swing.text.Document;

import javax.swing.text.DocumentFilter;

public class PrefixDocFilter extends DocumentFilter implements CaretListener {

private static final String PREFIX = "Prefix_";

private JTextField text;

private boolean changingCaret;

public PrefixDocFilter() {

text = new JTextField(PREFIX, 30);

Document doc = text.getDocument();

if (doc instanceof AbstractDocument) {

((AbstractDocument) doc).setDocumentFilter(this);

}

text.addCaretListener(this);

JFrame f = new JFrame("test");

f.add(text);

f.pack();

f.setLocationRelativeTo(null);

f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

f.setVisible(true);

}

public void caretUpdate(CaretEvent e) {

if (!changingCaret) {

changingCaret = true;

System.out.println(e);

int start = Math.min(e.getDot(), e.getMark());

int end = Math.max(e.getDot(), e.getMark());

if (start < PREFIX.length()) {

if (start != end) {

text.setCaretPosition(end);

text.moveCaretPosition(PREFIX.length());

} else {

text.setCaretPosition(PREFIX.length());

}

}

changingCaret = false;

}

}

public void insertString(FilterBypass fb, int offset, String string,

AttributeSet attr) throws BadLocationException {

if (offset < PREFIX.length()) {

java.awt.Toolkit.getDefaultToolkit().beep();

return;

}

super.insertString(fb, offset, string, attr);

}

public void remove(FilterBypass fb, int offset, int length)

throws BadLocationException {

if (offset < PREFIX.length()) {

java.awt.Toolkit.getDefaultToolkit().beep();

length -= (PREFIX.length() - offset);

offset = PREFIX.length();

}

super.remove(fb, offset, length);

}

public void replace(FilterBypass fb, int offset, int length, String text,

AttributeSet attrs) throws BadLocationException {

if (offset < PREFIX.length()) {

offset = PREFIX.length();

}

super.replace(fb, offset, length, text, attrs);

}

public static void main(String[] args) {

new PrefixDocFilter();

}

}

Bruno168a at 2007-7-8 23:48:44 > top of Java-index,Desktop,Core GUI APIs...
# 5

yeah, changing the caret needs to be in an invokeLater because of downstream caret event handlers. This should work better anyway:

public void caretUpdate(CaretEvent e) {

if (!changingCaret) {

System.out.println(e);

int start = Math.min(e.getDot(), e.getMark());

int end = Math.max(e.getDot(), e.getMark());

if (start < PREFIX.length()) {

changingCaret = true;

final int cstart = PREFIX.length();

final int cend = Math.max(end, cstart);

SwingUtilities.invokeLater(new Runnable() {

public void run() {

text.setCaretPosition(cstart);

text.moveCaretPosition(cend);

changingCaret = false;

}

});

}

}

}

Jasprea at 2007-7-8 23:48:44 > top of Java-index,Desktop,Core GUI APIs...
# 6
Yeah!You're my man Jasper - thanks a lot!
Bruno168a at 2007-7-8 23:48:44 > top of Java-index,Desktop,Core GUI APIs...
# 7
BTW, you don't really need the CaretListener. I provided that as a way to prevent the caret from entering the prefix area, but it really doesn't matter. What if the user wants to copy the text? The DocumentFilter is enough to prevent modification.
Jasprea at 2007-7-8 23:48:44 > top of Java-index,Desktop,Core GUI APIs...
# 8

This may seem like a little bit of an unsophisticated approach... but why can't you just extend the JTextField and override the setText(String string) method?

i.e. instead of putting:

JTextField textField1 = new JTextField("123_");

in your code, put something like:

JTextField textField1 = new JTextField("123_") {

public void setText(String text) {

super("123_" + text);

} };

?

Drossa at 2007-7-8 23:48:44 > top of Java-index,Desktop,Core GUI APIs...
# 9

> This may seem like a little bit of an unsophisticated

> approach... but why can't you just extend the

> JTextField and override the setText(String string)

> method?

Because it wouldn't work... setText does not get called when the user is inserting/removing text. In fact, setText never has to be called by a program either since you can directly modify the underlying document.

Jasprea at 2007-7-8 23:48:44 > top of Java-index,Desktop,Core GUI APIs...
# 10

> BTW, you don't really need the CaretListener. I provided that as a way

> to prevent the caret from entering the prefix area, but it really doesn't

> matter. What if the user wants to copy the text? The DocumentFilter is

> enough to prevent modification

As an alternative to the CaretListener the JDK supports a class for this type of restriction (its how the JFormattedTextField handles caret placement):

import javax.swing.*;

import javax.swing.text.*;

public class NavigationFilterPrefix extends NavigationFilter

{

private int prefixLength;

public NavigationFilterPrefix(int prefixLength)

{

this.prefixLength = prefixLength;

}

public void setDot(NavigationFilter.FilterBypass fb, int dot, Position.Bias bias)

{

fb.setDot(Math.max(dot, prefixLength), bias);

}

public void moveDot(NavigationFilter.FilterBypass fb, int dot, Position.Bias bias)

{

fb.moveDot(Math.max(dot, prefixLength), bias);

}

public static void main(String args[]) throws Exception {

JTextField textField = new JTextField("Prefix_", 20);

textField.setCaretPosition(7);

textField.setNavigationFilter( new NavigationFilterPrefix(7) );

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

}

}

Of course the posted example won't allow text selection of the prefix. If you want to be able to select the prefix, then don't override the moveDot(...) method.

camickra at 2007-7-8 23:48:44 > top of Java-index,Desktop,Core GUI APIs...
# 11

> As an alternative to the CaretListener the JDK

> supports a class for this type of restriction (its

> how the JFormattedTextField handles caret

> placement):

Excellent... this is why i come here. Why did I not know that existed? Wish I knew about this two years ago.

Jasprea at 2007-7-8 23:48:44 > top of Java-index,Desktop,Core GUI APIs...