0

I'm attempting to create a dynamic JTextField. This JTextField appends a wildcard ('*') when its max characters haven't been filled. I'm wanting to remove this wildcard when it does have its max characters, but if the user backspaces, I want the wildcard to be appended again. The wildcard should also be "permanent" in the sense that it can't be deleted or backspaced and the caret position should begin before the wildcard.

TLDR; A dynamic wildcard appended in a JTextField

What I Have So Far (4/17/2017)
Program initalizes
JTextField with max characters

This JTextField is alphanumeric and its max characters is 10. I want it so that when it hits 10, the wildcard is deleted. And if I backspace, it should be appended again.

The problem is that the wildcard is permanently there. Whenever I type anything AND if it it's max characters.

AlphaNumericTextField.java

public class AlphaNumericTextField extends DocumentFilter{
    int maxDigits;

    public AlphaNumericTextField(int maxDigits) {
        this.maxDigits = maxDigits;
    }

    public void AlphaNumericTextField(){
        this.maxDigits = Integer.MAX_VALUE;
    }

    @Override
    public void insertString(FilterBypass fb, int off, String str, AttributeSet attr) throws BadLocationException {
        if(str == null)
            return;
        if ((fb.getDocument().getLength() + str.length()-1) <= maxDigits) {
            fb.insertString(off, str.replaceAll("[^\\w*]", ""), attr);
        }
    } 

    @Override
    public void replace(FilterBypass fb, int off, int len, String str, AttributeSet attr) throws BadLocationException {
        if(str == null)
            return;
        if ((fb.getDocument().getLength() + str.length()-1 - len) <= maxDigits) {
            fb.replace(off, len, str.replaceAll("[^\\w*]", ""), attr);
        }
    }
}

WildCard.java (NavigationFilter)
The NavigationFilter is my incredibly makeshift attempt to making the wildcard "permanent" or "uneditable".

public class WildCard extends NavigationFilter
{
    private Action deletePrevious;
    private Action deleteNext;
    public JTextComponent component;

    public WildCard(JTextComponent component)
    {
        this.component = component;
        deletePrevious = component.getActionMap().get("delete-previous");
        deleteNext = component.getActionMap().get("delete-next");
        component.getActionMap().put("delete-previous", new BackspaceAction());
        component.getActionMap().put("delete-next", new DeleteAction());
        component.setCaretPosition(0);
    }

    public void setDot(NavigationFilter.FilterBypass fb, int dot, Position.Bias bias)
    {
        if(dot < component.getText().length()) {
            fb.setDot(Math.min(dot, component.getText().length()), bias);
        }
    }

    public void moveDot(NavigationFilter.FilterBypass fb, int dot, Position.Bias bias)
    {
        if(dot < component.getText().length()) 
            fb.moveDot(Math.min(dot, component.getText().length()), bias);
    }

    class BackspaceAction extends AbstractAction
    {
        @Override
        public void actionPerformed(ActionEvent e)
        {
            JTextComponent component = (JTextComponent)e.getSource();
            if (component.getCaretPosition() > 0)
                deletePrevious.actionPerformed( null );
        }
    }

    class DeleteAction extends AbstractAction
    {
        @Override
        public void actionPerformed(ActionEvent e)
        {
            JTextComponent component = (JTextComponent)e.getSource();
            if (component.getCaretPosition() < component.getText().length()-1)
                deleteNext.actionPerformed( null );
        }
    }
}

Resources and Where I've Looked:
Limited selection in a JTextField/JTextComponent?
How to implement in Java ( JTextField class ) to allow entering only digits?
remove last character from jtextfield

I saw a lot of places discouraging keyListeners due to copy and pasted information. I also saw a solution dealing with a propertyChangeListener but I couldn't get that to work.

I also attempted both:

setText(getText().substring(0, getText().length() - 1)) 
setText(getText.replaceAll("*", ""))

However, there's this weird issue with the caret restarting all the way to index 0 and then I begin typing backwards into my JTextField. Another issue was due to my NavigationFilter. Once I deleted the wildcard, I wasn't able to edit the last digit.

Current Environment
- NetBeans IDE 8.2 (for the GUI generator)
- Java 1.8.0_111

Additional Notes
A crude example of what I'm hoping to implement...
The '|' symbol is the caret symbol and max characters in this example is 6:

a|*  
ab|* 
abc|*
abc1|*
abc12|*
abc123| (NO WILDCARD)
abc12|* (Backspaced, so there is a wildcard) 
OR
ab|123* (Deleted some character so the length is less than 6)

Thank you so much!

EDIT Here is the solution. Thank you so much!

public class AlphaNumericTextField extends DocumentFilter{
    int maxChar;
    JTextComponent field;

    public AlphaNumericTextField(int maxChar, JTextComponent field) {
        this.maxChar = maxChar;
        this.field = field;
    }

    public void AlphaNumericTextField(){
        this.maxChar = Integer.MAX_VALUE;
    }

    @Override
    public void insertString(FilterBypass fb, int off, String str, AttributeSet attr) 
        throws BadLocationException {
        if(str == null)
            return;
        fb.insertString(off, str.replaceAll("[^\\w*]", ""), attr);
    } 
    @Override
    public void replace(FilterBypass fb, int off, int len, String str, AttributeSet attr) 
        throws BadLocationException {
        if(str == null)
            return;
        if ((fb.getDocument().getLength() + str.length()-1 - len) < maxChar) {
            if(fb.getDocument().getText(0, fb.getDocument().getLength()).indexOf("*") == -1) {
                fb.replace(off, len, str.replaceAll("[^\\w*]", "") + "*", attr);
                field.setCaretPosition(off+str.length());
            }
            else {
                fb.replace(off, len, str.replaceAll("[^\\w*]", ""), attr);
            }            
        }
        else if ((fb.getDocument().getLength() + str.length()-1 - len) == maxChar) {
            fb.replace(off, len+1, str, attr);
        }
    }

    @Override
    public void remove(DocumentFilter.FilterBypass fb, int off, int len) throws BadLocationException{
        fb.remove(off, len);
        if (off == maxChar - 1) {
            // Not really sure why this works... Adds 2 asterisks if field.getText() + "*"
            field.setText(field.getText());
            field.setCaretPosition(off);
        }
    }

}
Community
  • 1
  • 1
Yuki
  • 33
  • 2
  • 7
  • Post an [mcve] demonstrating the problem. I would suggest you don't need the custom delete/backspace Actions. Instead, in the DocumentFilter you would also need to implement the remove(...) method. First you invoke super.remove(...). Then you invoked super.insertString(...) to add back in the "*". – camickr Apr 17 '17 at 14:55
  • You may want to consider using a [JFormattedTextField](http://docs.oracle.com/javase/8/docs/api/javax/swing/JFormattedTextField.html) with a [MaskFormatter](http://docs.oracle.com/javase/8/docs/api/javax/swing/text/MaskFormatter.html). It would not be the same as what you’re proposing; instead, you would have asterisks for every unfilled character position, but I’m thinking that might actually be clearer to users. – VGR Apr 17 '17 at 15:21
  • @camickr Hi! Thank you so much for that link. I will edit my post as necessary. So unfortunately, your explanation isn't super clear, but I did attempt the super.remove() and received a little progress. I will continue working with the super.remove()! – Yuki Apr 17 '17 at 15:34
  • @VGR Hi! Thank you so much for that suggestion!! I did try MaskFormatter, but MaskFormatter has the default placeholder as a space, but my project leader said he wanted those spaces gone since they "pre-populate" the fields with spaces (even though that only happens when you double-click the field)... Thank you so much for your input!! – Yuki Apr 17 '17 at 15:36
  • Yes, the default placeholder character is a space. You can and should change it with [setPlaceholderCharacter](http://docs.oracle.com/javase/8/docs/api/javax/swing/text/MaskFormatter.html#setPlaceholderCharacter-char-). – VGR Apr 17 '17 at 15:38
  • @VGR Oh I apologize. I didn't fully read your comment. Unfortunately, I'm not able to fill the entire field with asterisks. The project leader wanted it to be blank with at most 1 wildcard at all times. All these inputs are essentially going to passed to a database so it would be cleaner to the database if there was only 1 asterisk. So I attempted to setPlaceholderCharacter("") but it's not allowed. I tried to sanitize the text within these fields as well before it is passed to the database, but my project leader explicitly wants the user interface to look as clean as possible in addition. – Yuki Apr 17 '17 at 15:42

0 Answers0