Barcode scanning using DocumentFilter in Java

240 views Asked by At

My barcode include "1039723170303CC15-78" The reason I used DocumentFilter is I want to limit the number as 7 digit only. And the rest of the number goes to 2nd textField automatically.

My code only working set 7 digit only and not all the rest of the number goes to next textfield. (i.e, "1039723" go 1st textField and "70303CC15-78" go 2nd textFiled. "1" is missing in the 2nd textFiled.

How can i solve this?

lblTest = new JLabel("Testing : ");
panel.add(lblText, "cell 0 1,alignx trailing");

txtTest = new JTextField(7);

 AbstractDocument d = (AbstractDocument) txtTest.getDocument();
 d.setDocumentFilter(new DocumentFilter() {
     @Override  
        public void insertString(DocumentFilter.FilterBypass fb, int offset, String string, AttributeSet attr) throws BadLocationException  
        {
            if(fb.getDocument().getLength()+string.length()>7)
            {                       
                return;                     
            }
            fb.insertString(offset, string, attr);                  
        }  


        @Override  
        public void remove(DocumentFilter.FilterBypass fb, int offset, int length) throws BadLocationException 
        {                   
            fb.remove(offset, length);
        }


        @Override  
        public void replace(DocumentFilter.FilterBypass fb, int offset, int length, String text, AttributeSet attrs)throws BadLocationException 
        {  
                 if(fb.getDocument().getLength()+text.length()>7)
                 {
                     txtTest.transferFocus();
                    System.out.println("print.." +txtTest.getText());
                    return;
                }
                fb.insertString(offset, text, attrs);
        }
 });

print out put:

print Mo No.:1039723
print Mo No.:1039723
print Mo No.:1039723
1

There are 1 answers

9
MadProgrammer On

The basic problem you have, is the text you're checking is been discard and never applied to anything.

For example, if the text been entered is (assuming one character at a time) 123456789, then 8 will be ignored, as 9 will be send to the other field.

What you need to do, is manually set the text of the next field with the text you are going to ignore.

Now, your current has a two fundamental flaws.

  1. It does not take into account the fact that replace should remove any selected characters
  2. Does not take into account what would happened if text was appended to the field
  3. Does not take into account if text was inserted at an offset other than the end of the field

This example attempts to answer all of those questions

import javax.swing.JFrame;
import javax.swing.JPanel;
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 Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame("Test");
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        public TestPane() {
            JTextField field1 = new JTextField(7);
            JTextField field2 = new JTextField(7);
            ((AbstractDocument) field1.getDocument()).setDocumentFilter(new LimitDocumentFilter(field2));
            add(field1);
            add(field2);
        }

    }

    public class LimitDocumentFilter extends DocumentFilter {

        private JTextField next;

        public LimitDocumentFilter(JTextField next) {
            this.next = next;
        }

        @Override
        public void insertString(FilterBypass fb, int offset, String text, AttributeSet attr) throws BadLocationException {
            super.insertString(fb, offset, text, attr);
            String textValue = fb.getDocument().getText(0, fb.getDocument().getLength());
            if (textValue.length() > 7) {
                remove(fb, 7, fb.getDocument().getLength() - 7);
                String overflow = textValue.substring(7);
                next.requestFocusInWindow();
                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        next.setText(overflow);
                        next.setCaretPosition(overflow.length());
                    }
                });
            }
        }

        @Override
        public void replace(DocumentFilter.FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException {
            fb.remove(offset, length);
            insertString(fb, offset, text, attrs);
        }
    }

}

What this really needs is a delegate model, where by instead of the DocumentFilter changing field focus, it delegates that responsibility to some other observer, passing it the overflow text

Update

Okay, so this is an updated version which provides three ways that the text can be set:

  1. Directly via setText
  2. Pasted from the clipboard
  3. Key strokes injected into the keyboard buffer via Robot

This provides the best possible example of how a barcode scanner "might" work

import java.awt.AWTException;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.UIDefaults;
import javax.swing.UIManager;
import javax.swing.text.AbstractDocument;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.DocumentFilter;
import javax.swing.text.DocumentFilter.FilterBypass;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame("Test");
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        public TestPane() {
            JTextField field1 = new JTextField(7);
            JTextField field2 = new JTextField(7);
            LimitDocumentFilter filter = new LimitDocumentFilter();
            filter.add(new LimitListener() {
                @Override
                public void limitReached(LimitDocumentFilter filter, String overflow) {
                    // This is to overcome the issue of MacOS autoselecting the
                    // text when it gets focus ... dumb
                    FocusListener[] listeners = field2.getFocusListeners();
                    for (FocusListener listener : listeners) {
                        field2.removeFocusListener(listener);
                    }
                    field2.setText(overflow);
                    field2.requestFocusInWindow();
                    field2.setCaretPosition(overflow.length());
                    SwingUtilities.invokeLater(new Runnable() {
                        @Override
                        public void run() {
                            for (FocusListener listener : listeners) {
                                field2.addFocusListener(listener);
                            }
                        }
                    });
                }
            });
            ((AbstractDocument) field1.getDocument()).setDocumentFilter(filter);
            add(field1);
            add(field2);

            JButton sim = new JButton("Simulate");
            sim.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    field1.setText(null);
                    field2.setText(null);
                    field1.requestFocusInWindow();
                    SwingUtilities.invokeLater(new Runnable() {
                        @Override
                        public void run() {
                            Thread t = new Thread(new Simulator());
                            t.start();
                        }
                    });
                }
            });
            JButton paste = new JButton("Paste");
            paste.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    field1.setText(null);
                    field2.setText(null);
                    String text = "1234567abcdefg";
                    Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
                    clipboard.setContents(new StringSelection(text), null);
                    field1.requestFocusInWindow();
                    SwingUtilities.invokeLater(new Runnable() {
                        @Override
                        public void run() {
                            field1.paste();
                        }
                    });
                }
            });
            JButton set = new JButton("Set");
            set.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    field1.setText(null);
                    field2.setText(null);
                    field1.setText("1234567abcdefghijklmnopqrstuvwxyz");
                }
            });

            add(sim);
            add(paste);
            add(set);
        }

    }

    public class Simulator implements Runnable {

        @Override
        public void run() {
            try {
                Robot bot = new Robot();
                type(KeyEvent.VK_1, bot);
                type(KeyEvent.VK_2, bot);
                type(KeyEvent.VK_3, bot);
                type(KeyEvent.VK_4, bot);
                type(KeyEvent.VK_5, bot);
                type(KeyEvent.VK_6, bot);
                type(KeyEvent.VK_7, bot);

                type(KeyEvent.VK_A, bot);
                type(KeyEvent.VK_B, bot);
                type(KeyEvent.VK_C, bot);
                type(KeyEvent.VK_D, bot);
                type(KeyEvent.VK_E, bot);
                type(KeyEvent.VK_F, bot);
                type(KeyEvent.VK_G, bot);
            } catch (AWTException ex) {
                ex.printStackTrace();
            }
        }

        protected void type(int keyStoke, Robot bot) {
            bot.keyPress(keyStoke);
            bot.keyRelease(keyStoke);
        }

    }

    public interface LimitListener {

        public void limitReached(LimitDocumentFilter filter, String overflow);
    }

    public class LimitDocumentFilter extends DocumentFilter {

        private List<LimitListener> listeners = new ArrayList<>(25);

        public LimitDocumentFilter() {
        }

        public void add(LimitListener listener) {
            listeners.add(listener);
        }

        public void remove(LimitListener listener) {
            listeners.remove(listener);
        }

        protected void limitReached(String overflow) {
            for (LimitListener listener : listeners) {
                listener.limitReached(this, overflow);
            }
        }

        @Override
        public void insertString(FilterBypass fb, int offset, String text, AttributeSet attr) throws BadLocationException {
            super.insertString(fb, offset, text, attr);
            String textValue = fb.getDocument().getText(0, fb.getDocument().getLength());
            if (textValue.length() > 7) {
                remove(fb, 7, fb.getDocument().getLength() - 7);
                String overflow = textValue.substring(7);
                limitReached(overflow);
            }
        }

        @Override
        public void replace(DocumentFilter.FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException {
            fb.remove(offset, length);
            insertString(fb, offset, text, attrs);
        }
    }

}