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.
# 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;
}
}
# 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();
}
}
# 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();
}
}
# 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;
}
});
}
}
}
# 6
Yeah!You're my man Jasper - thanks a lot!
# 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.
# 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);
} };
?
# 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.
# 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.
# 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.