How to display a temporary baloon tooltip during input validation? - java

I'm somewhat used to the GUI idiom where when I type something wrong in a text input field a balloon pops up from the field with info about what I got wrong / expected input. It remains visible until I type again.
But now I couldn't find any example to point to.
Given a JTextfield do you know of any library/code that would display such a balloon when triggered by my keylistener?
#see http://www.javapractices.com/topic/TopicAction.do?Id=151

Balloon Tip is a library that does that:
balloonTip = new BalloonTip(f, "Tooltip msg");
That was all needed! If you insist on a having it be a tooltip too:
tooltipBalloon = new BalloonTip(someComponent, "I'm a balloon tooltip!");
// Now convert this balloon tip to a tooltip, such that the tooltip shows up after 500 milliseconds and stays visible for 3000 milliseconds
ToolTipUtils.balloonToToolTip(tooltipBalloon, 500, 3000);

The link given by James Poulson probably provides a better solution, but I had to see if this were possible with some simple Java code using a DocumentFilter and a JWindow. Here's one possible way to do this:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Window;
import javax.swing.*;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.DocumentFilter;
import javax.swing.text.PlainDocument;
public class InfoBalloon extends JPanel {
private static final int PREF_WIDTH = 400;
private static final int PREF_HEIGHT = 300;
private static final String REGEX_TEST = "\\d*";
private static final String ERROR_TEXT = "Please only add numbers to the text field";
private JTextField textField = new JTextField(10);
private JWindow errorWindow;
public InfoBalloon() {
add(new JLabel("Please Enter Number"));
add(textField);
((PlainDocument)textField.getDocument()).setDocumentFilter(new MyNumberDocFilter());
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_WIDTH, PREF_HEIGHT);
}
private void showErrorWin() {
if (errorWindow == null) {
JLabel errorLabel = new JLabel(ERROR_TEXT);
Window topLevelWin = SwingUtilities.getWindowAncestor(this);
errorWindow = new JWindow(topLevelWin);
JPanel contentPane = (JPanel) errorWindow.getContentPane();
contentPane.add(errorLabel);
contentPane.setBackground(Color.white);
contentPane.setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY));
errorWindow.pack();
}
Point loc = textField.getLocationOnScreen();
errorWindow.setLocation(loc.x + 20, loc.y + 30);
errorWindow.setVisible(true);
}
private boolean textOK(String text) {
if (text.matches(REGEX_TEST)) {
return true;
}
return false;
}
private class MyNumberDocFilter extends DocumentFilter {
#Override
public void insertString(FilterBypass fb, int offset, String string,
AttributeSet attr) throws BadLocationException {
if (textOK(string)) {
super.insertString(fb, offset, string, attr);
if (errorWindow != null && errorWindow.isVisible()) {
errorWindow.setVisible(false);
}
} else {
showErrorWin();
}
}
#Override
public void replace(FilterBypass fb, int offset, int length, String text,
AttributeSet attrs) throws BadLocationException {
if (textOK(text)) {
super.replace(fb, offset, length, text, attrs);
if (errorWindow != null && errorWindow.isVisible()) {
errorWindow.setVisible(false);
}
} else {
showErrorWin();
}
}
#Override
public void remove(FilterBypass fb, int offset, int length)
throws BadLocationException {
super.remove(fb, offset, length);
if (errorWindow != null && errorWindow.isVisible()) {
errorWindow.setVisible(false);
}
}
}
private static void createAndShowUI() {
JFrame frame = new JFrame("Info Balloon");
frame.getContentPane().add(new InfoBalloon());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
createAndShowUI();
}
});
}
}
Constructive or destructive criticism is most welcome!

Related

Using DocumentFilter iteratively

I am trying to run this code:
How to change the color of specific words in a JTextPane?
private final class CustomDocumentFilter extends DocumentFilter
{
private final StyledDocument styledDocument = yourTextPane.getStyledDocument();
private final StyleContext styleContext = StyleContext.getDefaultStyleContext();
private final AttributeSet greenAttributeSet = styleContext.addAttribute(styleContext.getEmptySet(), StyleConstants.Foreground, Color.GREEN);
private final AttributeSet blackAttributeSet = styleContext.addAttribute(styleContext.getEmptySet(), StyleConstants.Foreground, Color.BLACK);
// Use a regular expression to find the words you are looking for
Pattern pattern = buildPattern();
#Override
public void insertString(FilterBypass fb, int offset, String text, AttributeSet attributeSet) throws BadLocationException {
super.insertString(fb, offset, text, attributeSet);
handleTextChanged();
}
#Override
public void remove(FilterBypass fb, int offset, int length) throws BadLocationException {
super.remove(fb, offset, length);
handleTextChanged();
}
#Override
public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attributeSet) throws BadLocationException {
super.replace(fb, offset, length, text, attributeSet);
handleTextChanged();
}
/**
* Runs your updates later, not during the event notification.
*/
private void handleTextChanged()
{
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
updateTextStyles();
}
});
}
/**
* Build the regular expression that looks for the whole word of each word that you wish to find. The "\\b" is the beginning or end of a word boundary. The "|" is a regex "or" operator.
* #return
*/
private Pattern buildPattern()
{
StringBuilder sb = new StringBuilder();
for (String token : ALL_WORDS_THAT_YOU_WANT_TO_FIND) {
sb.append("\\b"); // Start of word boundary
sb.append(token);
sb.append("\\b|"); // End of word boundary and an or for the next word
}
if (sb.length() > 0) {
sb.deleteCharAt(sb.length() - 1); // Remove the trailing "|"
}
Pattern p = Pattern.compile(sb.toString());
return p;
}
private void updateTextStyles()
{
// Clear existing styles
styledDocument.setCharacterAttributes(0, yourTextPane.getText().length(), blackAttributeSet, true);
// Look for tokens and highlight them
Matcher matcher = pattern.matcher(yourTextPane.getText());
while (matcher.find()) {
// Change the color of recognized tokens
styledDocument.setCharacterAttributes(matcher.start(), matcher.end() - matcher.start(), greenAttributeSet, false);
}
}
}
And
((AbstractDocument) yourTextPane.getDocument()).setDocumentFilter(new CustomDocumentFilter());
I would like to use it iteratively, that is, that any new string ALL_WORDS_THAT_YOU_WANT_TO_FIND will be automatically colored. I thought of deleting
styledDocument.setCharacterAttributes(0, yourTextPane.getText().length(), blackAttributeSet, true);
(that is, to not destroy the previous colored words) but it does not work: it only keeps colored the input words given at the last iteration. How could I do that?
Edit: updated after two questions in the comments
So you want to add words to the list and update the JTextPane? In that case you would want to make sure that the list gets updated and used each time the updateTextStyles method runs.
You can use multiple lists of words that can apply unique formatting to the text. The code that you started with uses a regular expression, which you could expand to multiple regular expressions. You can also search for exact case sensitive matches of sub strings (or text fragments) without looking at word boundaries, as is used in the code below.
This means that the formatting of some text might be changed multiple times by matches from different groups. The order in which you search will determine the end result. For example, this small example allows you to fill a text pane and add new words to three highlight groups (with colors red, orange, and blue):
Here is the code of the three classes in the example (using Java 8):
IterativeDocumentFilter.java:
import java.awt.*;
import java.util.List;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.text.*;
public class IterativeDocumentFilter {
public static void main(String[] arguments) {
SwingUtilities.invokeLater(
() -> new IterativeDocumentFilter().createAndShowGui()
);
}
private void createAndShowGui() {
JFrame frame = new JFrame("Stack Overflow");
frame.setBounds(100, 100, 1000, 600);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
JPanel mainPanel = new JPanel(new BorderLayout());
JTextPane textPane = new JTextPane(new DefaultStyledDocument());
CustomDocumentFilter documentFilter = new CustomDocumentFilter(textPane);
textPane.setBorder(new LineBorder(Color.BLACK, 1));
enlargeFont(textPane);
mainPanel.add(textPane, BorderLayout.CENTER);
mainPanel.add(createBottomPanels(documentFilter), BorderLayout.PAGE_END);
frame.getContentPane().add(mainPanel);
frame.setVisible(true);
}
private JPanel createBottomPanels(CustomDocumentFilter documentFilter) {
JPanel bottomPanels = new JPanel();
bottomPanels.setLayout(new BoxLayout(bottomPanels, BoxLayout.PAGE_AXIS));
for (HighlightGroup highlightGroup : documentFilter.getHighlightGroups()) {
List<String> textFragments = highlightGroup.getTextFragments();
JPanel groupPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
JLabel textFragmentsLabel = new JLabel("Current text fragments: "
+ textFragments);
textFragmentsLabel.setForeground(highlightGroup.getColor());
JLabel addTextFragmentLabel = new JLabel("Additional text fragment:");
addTextFragmentLabel.setForeground(highlightGroup.getColor());
JTextField addTextFragmentTextField = new JTextField(28);
JButton addTextFragmentButton = new JButton("Add text fragment");
addTextFragmentButton.setForeground(highlightGroup.getColor());
addTextFragmentButton.addActionListener(actionEvent -> {
String newTextFragment = addTextFragmentTextField.getText().trim();
if (!textFragments.contains(newTextFragment)) {
textFragments.add(newTextFragment);
documentFilter.handleTextChanged();
textFragmentsLabel.setText("Current text fragments: "
+ textFragments);
}
addTextFragmentTextField.setText("");
});
groupPanel.add(addTextFragmentLabel);
groupPanel.add(addTextFragmentTextField);
groupPanel.add(addTextFragmentButton);
textFragmentsLabel.setBorder(new EmptyBorder(0, 42, 0, 0));
groupPanel.add(textFragmentsLabel);
enlargeFont(addTextFragmentLabel);
enlargeFont(addTextFragmentTextField);
enlargeFont(addTextFragmentButton);
enlargeFont(textFragmentsLabel);
bottomPanels.add(groupPanel);
}
return bottomPanels;
}
private void enlargeFont(Component component) {
component.setFont(component.getFont().deriveFont(16f));
}
}
CustomDocumentFilter.java:
import java.awt.*;
import java.util.*;
import java.util.List;
import javax.swing.*;
import javax.swing.text.*;
public class CustomDocumentFilter extends DocumentFilter
{
private final JTextPane textPane;
private final List<HighlightGroup> highlightGroups;
private final StyleContext styleContext = StyleContext.getDefaultStyleContext();
private final AttributeSet blackAttributeSet
= styleContext.addAttribute(styleContext.getEmptySet(),
StyleConstants.Foreground, Color.BLACK);
public CustomDocumentFilter(JTextPane textPane) {
this.textPane = textPane;
highlightGroups = createHighlightGroups();
((AbstractDocument) textPane.getDocument()).setDocumentFilter(this);
}
private List<HighlightGroup> createHighlightGroups() {
List<HighlightGroup> groups = new ArrayList<>();
groups.add(new HighlightGroup(Arrays.asList("one", "two", "three"), Color.RED));
groups.add(new HighlightGroup(Arrays.asList("a", "the"), Color.ORANGE));
groups.add(new HighlightGroup(Arrays.asList("th", "o"), Color.BLUE));
return groups;
}
public List<HighlightGroup> getHighlightGroups() {
return highlightGroups;
}
#Override
public void insertString(FilterBypass fb, int offset, String text,
AttributeSet attributeSet) throws BadLocationException {
super.insertString(fb, offset, text, attributeSet);
handleTextChanged();
}
#Override
public void remove(FilterBypass fb, int offset, int length)
throws BadLocationException {
super.remove(fb, offset, length);
handleTextChanged();
}
#Override
public void replace(FilterBypass fb, int offset, int length, String text,
AttributeSet attributeSet) throws BadLocationException {
super.replace(fb, offset, length, text, attributeSet);
handleTextChanged();
}
/**
* Runs your updates later, not during the event notification.
*/
public void handleTextChanged()
{
SwingUtilities.invokeLater(this::updateTextStyles);
}
private void updateTextStyles()
{
// Reset the existing styles by using the default black style for all text.
StyledDocument document = textPane.getStyledDocument();
document.setCharacterAttributes(0, textPane.getText().length(),
blackAttributeSet, true);
// Apply styling for the different groups (the order of the groups is relevant).
for (HighlightGroup highlightGroup : highlightGroups) {
highlightGroup.highlightWords(textPane);
}
}
}
HighlightGroup.java:
import java.awt.*;
import java.util.*;
import java.util.List;
import javax.swing.*;
import javax.swing.text.*;
public class HighlightGroup {
private final List<String> textFragments;
private final Color color;
private final AttributeSet attributeSet;
public HighlightGroup(List<String> textFragments, Color color) {
this.textFragments = new ArrayList<>(textFragments);
this.color = color;
StyleContext styleContext = StyleContext.getDefaultStyleContext();
this.attributeSet = styleContext.addAttribute(styleContext.getEmptySet(),
StyleConstants.Foreground,
color);
}
public List<String> getTextFragments() {
return textFragments;
}
public Color getColor() {
return color;
}
public void highlightWords(JTextPane textPane) {
String text = textPane.getText();
StyledDocument styledDocument = textPane.getStyledDocument();
for (String textFragment : textFragments) {
int fromIndex = 0;
int startIndex = text.indexOf(textFragment, fromIndex);
while (startIndex != -1) {
// Change the color of recognized text fragments.
styledDocument.setCharacterAttributes(startIndex, textFragment.length(),
attributeSet, false);
fromIndex = startIndex + textFragment.length();
startIndex = text.indexOf(textFragment, fromIndex);
}
}
}
}

JTextArea - enable edit only at the end of document

I use a JTextArea where using double click I can able to select the word at any place but I don't want to enable edit. Which means text can be entered only at the end of text area and not anywhere in between.
I have tried with mouse listeners like below:
#Override
public void mouseClicked(MouseEvent me) {
if(SwingUtilities.isLeftMouseButton(me)){
System.err.println("clicked");
int pos = textArea.getCaretPosition();
if(pos < textArea.getDocument().getLength()){
textArea.setCaretPosition(textArea.getDocument().getLength());
}
}
}
This makes double click not to select the word. I understand it is because caret position is moved to end. But how can I achieve this?
Check out the Protected Text Component which allows you to protect multiple areas of a Document from change.
Or if you don't need to be able to "select" any of the protected text than a simpler solution is to use a NavigationFilter:
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
{
#Override
public void actionPerformed(ActionEvent e)
{
JTextComponent component = (JTextComponent)e.getSource();
if (component.getCaretPosition() > prefixLength)
{
deletePrevious.actionPerformed( null );
}
}
}
private static void createAndShowUI()
{
JTextField textField = new JTextField("Prefix_", 20);
textField.setNavigationFilter( new NavigationFilterPrefixWithBackspace(7, 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);
}
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
createAndShowUI();
}
});
}
}
Okay, this is slightly hacky...
Basically what this does is installs a "protected" DocumentFilter, which will only allow input to put in from a certain point in the Document.
It overrides the JTextArea's insert-break key binding (Enter) and records a marker. The "protected" DocumentFilter then ensures that the content does not precede this point
I was forced to implement a KeyListener on the field to move the cursor to the end of the input, while the DocumentFilter was capable of handling this, it did present some issues with deleting and general usability. This also ensures that the selection is un-highlighted when new content is added...
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.text.AbstractDocument;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.DocumentFilter;
public class Terminal {
public static void main(String[] args) {
new Terminal();
}
public Terminal() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JTextArea terminal = new JTextArea(20, 40);
ProtectedDocumentFilter.install(terminal);
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new JScrollPane(terminal));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public static interface UserInput {
public int getUserInputStart();
public void setUserInputStart(int lastPoint);
}
public static class DefaultUserInput implements UserInput {
private final JTextArea textArea;
private int userInputStart;
public DefaultUserInput(JTextArea ta) {
textArea = ta;
ActionMap am = ta.getActionMap();
Action action = am.get("insert-break");
am.put("insert-break", new ProxyAction(action));
ta.addKeyListener(new KeyAdapter() {
#Override
public void keyPressed(KeyEvent e) {
if (textArea.getCaretPosition() != textArea.getDocument().getLength()) {
textArea.setCaretPosition(textArea.getDocument().getLength());
}
}
});
}
#Override
public void setUserInputStart(int userInputStart) {
this.userInputStart = userInputStart;
}
#Override
public int getUserInputStart() {
return userInputStart;
}
public class ProxyAction extends AbstractAction {
private final Action proxy;
public ProxyAction(Action proxy) {
this.proxy = proxy;
}
#Override
public void actionPerformed(ActionEvent e) {
proxy.actionPerformed(e);
int range = textArea.getCaretPosition() - userInputStart;
userInputStart += range;
}
}
}
public static class ProtectedDocumentFilter extends DocumentFilter {
protected static void install(JTextArea textArea) {
UserInput ui = new DefaultUserInput(textArea);
((AbstractDocument) textArea.getDocument()).setDocumentFilter(new ProtectedDocumentFilter(ui));
}
private UserInput userInput;
public ProtectedDocumentFilter(UserInput userInput) {
this.userInput = userInput;
}
public UserInput getUserInput() {
return userInput;
}
#Override
public void insertString(FilterBypass fb, int offset, String string, AttributeSet attr) throws BadLocationException {
if (offset < getUserInput().getUserInputStart()) {
offset = fb.getDocument().getLength();
}
System.out.println("Insert");
super.insertString(fb, offset, string, attr);
}
#Override
public void remove(FilterBypass fb, int offset, int length) throws BadLocationException {
if (offset >= getUserInput().getUserInputStart()) {
super.remove(fb, offset, length);
}
}
#Override
public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException {
if (offset < getUserInput().getUserInputStart()) {
offset = fb.getDocument().getLength();
length = 0;
}
super.replace(fb, offset, length, text, attrs);
if (text.contains("\n")) {
int lastPoint = offset + text.lastIndexOf("\n");
if (lastPoint > getUserInput().getUserInputStart()) {
getUserInput().setUserInputStart(lastPoint + 1);
}
}
}
}
}
This is just an example, you're going to need to play with it and tweak it to meet your own needs....

JTextField: How to limit the number of characters?

Please have a look at the following code.
import java.awt.FlowLayout;
import java.awt.GridLayout;
import javax.swing.*;
import javax.swing.text.AbstractDocument;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.DocumentFilter;
public class Bean extends JFrame
{
private JTextField field1, field2, field3, field4;
private JLabel text;
private JButton ok, cancel;
private JPanel centerPanel,southPanel, textPanel;
private GridLayout grid;
private FlowLayout flow1, flow2;
public Bean()
{
//Declaring instance Variables
field1 = new JTextField(10);
field2 = new JTextField(5);
field3 = new JTextField(5);
field4 = new JTextField(5);
text = new JLabel("Insert Your Numbers Here");
AbstractDocument d = (AbstractDocument) field1.getDocument();
d.setDocumentFilter(new Bean.Field1Listener());
ok = new JButton("OK");
cancel = new JButton("Cancel");
/***********************Creating the main view*************************/
centerPanel = new JPanel();
grid = new GridLayout(2,1,1,1);
//Adding TextFields
textPanel = new JPanel();
flow1 = new FlowLayout(FlowLayout.CENTER);
textPanel.setLayout(flow1);
textPanel.add(field1);
//Adding Buttons
southPanel = new JPanel();
southPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
southPanel.add(ok);
southPanel.add(cancel);
//Creating Center View
centerPanel.setLayout(grid);
centerPanel.add(text);
centerPanel.add(textPanel);
//Gathering everything together
getContentPane().add(centerPanel,"Center");
getContentPane().add(southPanel,"South");
this.setSize(500,200);
this.validate();
this.setVisible(true);
this.setLocationRelativeTo(null);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
private class Field1Listener extends DocumentFilter
{
#Override
public void insertString(DocumentFilter.FilterBypass fb, int offset, String string, AttributeSet attr) throws BadLocationException
{
if(fb.getDocument().getLength()+string.length()>5)
{
return;
}
fb.insertString(offset, string, attr);
}
#Override
public void remove(DocumentFilter.FilterBypass fb, int offset, int length) throws BadLocationException
{
fb.insertString(offset, "", null);
}
#Override
public void replace(DocumentFilter.FilterBypass fb, int offset, int length, String text, AttributeSet attrs)throws BadLocationException
{
if(fb.getDocument().getLength()+text.length()>5)
{
System.out.println("OK");
return;
}
fb.insertString(offset, text, attrs);
}
}
public static void main(String[]args)
{
new Bean();
}
}
In here, I am trying to limit the number of character to 5. OK, it stops inserting anymore characters when it reaches 5, but the case is, it also doesn't allow to delete the inserted characters, replace, or anything. How to correct this issue?
simply change your current remove method:
#Override
public void remove(DocumentFilter.FilterBypass fb, int offset, int length) throws BadLocationException
{
fb.insertString(offset, "", null);
}
for this one:
#Override
public void remove(DocumentFilter.FilterBypass fb, int offset, int length) throws BadLocationException
{
fb.remove(offset, length);
}
it should now work.
You should make your own class that checks whether you gave more input than the maximum allowed length: See an example on http://www.java2s.com/Tutorial/Java/0240__Swing/LimitJTextFieldinputtoamaximumlength.htm.

Make JSpinner only read numbers but also detect backspace

I'm trying to make a JSpinner that will only accepts numbers but I also want it to read/respond to backspace.
public class test {
JFrame frame;
JPanel panel;
JSpinner spinner;
public test()
{
frame = new JFrame("test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setPreferredSize(new Dimension(200,200));
panel = new JPanel();
SpinnerNumberModel numSpin = new SpinnerNumberModel(10, 0,1000,1);
spinner = new JSpinner(numSpin);
JFormattedTextField txt = ((JSpinner.NumberEditor) spinner.getEditor()).getTextField();
((NumberFormatter) txt.getFormatter()).setAllowsInvalid(false);
panel.add(spinner);
frame.setContentPane(panel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args)
{
test test = new test();
}
}
The code above works to make only numbers but this doesn't allow me to backspace. I found some examples on this site but they were written for C.
you are right JFormattedTextField isn't correctly implemented to JSpinner, you have implements DocumentFilter for filtering of un_wanted Chars typed from keyboad or pasted from ClipBoard, (thanks to #StanislavL)
you have solve by yourself issues with selectAll() on focusGained() wrapped into invokeLater(),
example
import java.awt.BorderLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingUtilities;
import javax.swing.text.*;
public class TestDigitsOnlySpinner {
public static void main(String... args) {
SwingUtilities.invokeLater((Runnable) new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame("enter digit");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JSpinner jspinner = makeDigitsOnlySpinnerUsingDocumentFilter();
frame.getContentPane().add(jspinner, BorderLayout.CENTER);
frame.getContentPane().add(new JButton("just another widget"), BorderLayout.SOUTH);
frame.pack();
frame.setVisible(true);
}
private JSpinner makeDigitsOnlySpinner_BasicAttempt() {
JSpinner spinner = new JSpinner(new SpinnerNumberModel());
return spinner;
}
private JSpinner makeDigitsOnlySpinnerUsingDocumentFilter() {
JSpinner spinner = new JSpinner(new SpinnerNumberModel(0, 0, 20, 1));
JSpinner.NumberEditor jsEditor = (JSpinner.NumberEditor) spinner.getEditor();
final Document jsDoc = jsEditor.getTextField().getDocument();
if (jsDoc instanceof PlainDocument) {
AbstractDocument doc = new PlainDocument() {
private static final long serialVersionUID = 1L;
#Override
public void setDocumentFilter(DocumentFilter filter) {
if (filter instanceof MyDocumentFilter) {
super.setDocumentFilter(filter);
}
}
};
doc.setDocumentFilter(new MyDocumentFilter());
jsEditor.getTextField().setDocument(doc);
}
return spinner;
}
});
}
private static class MyDocumentFilter extends DocumentFilter {
#Override
public void insertString(FilterBypass fb, int offset, String string, AttributeSet attr) throws BadLocationException {
if (stringContainsOnlyDigits(string)) {
super.insertString(fb, offset, string, attr);
}
}
#Override
public void remove(FilterBypass fb, int offset, int length) throws BadLocationException {
super.remove(fb, offset, length);
}
#Override
public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException {
if (stringContainsOnlyDigits(text)) {
super.replace(fb, offset, length, text, attrs);
}
}
private boolean stringContainsOnlyDigits(String text) {
for (int i = 0; i < text.length(); i++) {
if (!Character.isDigit(text.charAt(i))) {
return false;
}
}
return true;
}
}
private TestDigitsOnlySpinner() {
}
}

How do I limit the amount of characters in JTextPane as the user types (Java)

I need to not allow any characters to be entered after X have been typed. I need to send a beep after X characters have been typed. I know how to do this after the user presses enter, but I need to do it before the user presses enter. The approach I found from Oracle's site is to add a DocumentSizeFilter to the JTextPane. I can't get this to notify the user when they have gone over (it doesn't work until they press enter). This is a sample of what I have.
public class EndCycleTextAreaRenderer extends JTextPane
implements TableCellRenderer {
private final int maxNumberOfCharacters = 200;
public EndCycleTextAreaRenderer() {
StyledDocument styledDoc = this.getStyledDocument();
AbstractDocument doc;
doc = (AbstractDocument)styledDoc;
doc.setDocumentFilter(new DocumentSizeFilter(maxNumberOfCharacters ));
}
Override the insertString method of the document in the JTextPane so that it doesn't insert any more characters once the maximum has been reached.
For example:
JTextPane textPane = new JTextPane(new DefaultStyledDocument() {
#Override
public void insertString(int offs, String str, AttributeSet a) throws BadLocationException {
if ((getLength() + str.length()) <= maxNumberOfCharacters) {
super.insertString(offs, str, a);
}
else {
Toolkit.getDefaultToolkit().beep();
}
}
});
Update:
You can change your class as follows:
public class EndCycleTextAreaRenderer extends JTextPane implements TableCellRenderer {
private final int maxNumberOfCharacters = 200;
public EndCycleTextAreaRenderer() {
setStyledDocument(new DefaultStyledDocument() {
#Override
public void insertString(int offs, String str, AttributeSet a) throws BadLocationException {
if ((getLength() + str.length()) <= maxNumberOfCharacters) {
super.insertString(offs, str, a);
} else {
Toolkit.getDefaultToolkit().beep();
}
}
});
}
}
Here is a sample program, for you where, as you enter the fourth time into the TextPane it will beep, without even you pressing the Enter key:
import javax.swing.*;
import javax.swing.text.*;
import java.awt.Toolkit;
public class TextPaneLimit extends JFrame
{
private JPanel panel;
private JTextPane tpane;
private AbstractDocument abDoc;
public TextPaneLimit()
{
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
panel = new JPanel();
tpane = new JTextPane();
Document doc = tpane.getStyledDocument();
if (doc instanceof AbstractDocument)
{
abDoc = (AbstractDocument)doc;
abDoc.setDocumentFilter(new DocumentSizeFilter(3));
}
panel.add(tpane);
add(panel);
pack();
}
public static void main(String... args)
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
new TextPaneLimit().setVisible(true);
}
});
}
}
class DocumentSizeFilter extends DocumentFilter {
private int max_Characters;
private boolean DEBUG;
public DocumentSizeFilter(int max_Chars) {
max_Characters = max_Chars;
DEBUG = false;
}
public void insertString(FilterBypass fb
, int offset
, String str
, AttributeSet a)
throws BadLocationException {
if (DEBUG) {
System.out.println("In DocumentSizeFilter's insertString method");
}
if ((fb.getDocument().getLength() + str.length()) <= max_Characters)
super.insertString(fb, offset, str, a);
else
Toolkit.getDefaultToolkit().beep();
}
public void replace(FilterBypass fb
, int offset, int length
, String str, AttributeSet a)
throws BadLocationException {
if (DEBUG) {
System.out.println("In DocumentSizeFilter's replace method");
}
if ((fb.getDocument().getLength() + str.length()
- length) <= max_Characters)
super.replace(fb, offset, length, str, a);
else
Toolkit.getDefaultToolkit().beep();
}
}
Hope this might help.
I would suggest checking the # of characters with every key entered by adding a keyReleasedListener, it is something I used in a recent GUI of mine to check bounds seemingly instantly and display errors to the user as they typed.
Here is how I implemented it on one of my TextFields:
carbonTextField.addKeyListener(new java.awt.event.KeyAdapter()
{
public void keyReleased(java.awt.event.KeyEvent evt)
{
carbonTextFieldKeyReleased(evt);
}
});
Check the following code:
txtpnDesc.addKeyListener(new KeyAdapter() {
#Override
public void keyTyped(KeyEvent e) {
if(txtpnDesc.getText().length() == 30)
{
e.consume();
}
}
});

Categories