I have written a small program for our company, which takes care of the sale of drinks and every user has his own account. To top up his account there is a JSpinner, which looks like this:
An employee asked me if I could add the currency to this spinner. So I implemented it, but now you can only deposit with the currency symbol and not without it, which disturbed other staff members, so let's get to my question, how do I manage to accept both entries with currency and without?
Basic Spinner(like in the image i posted above):
final SpinnerNumberModel spinnerModel = new SpinnerNumberModel( 1, 1, 1000, 1 );
final JSpinner valueSpinner = new JSpinner( spinnerModel );
To add the currency I used this code snippet, which works fine
String pattern = "0€";
JSpinner.NumberEditor editor = new JSpinner.NumberEditor( valueSpinner, pattern );
valueSpinner.setEditor( editor );
I have already tried to write a custom JSpinner, but I couldn't achieve that the Spinner would take both Entries.
Following this answer of a question about measuring units of length, you can do it in a similar way for currencies:
import java.text.ParseException;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.JButton;
import javax.swing.JFormattedTextField;
import javax.swing.JFormattedTextField.AbstractFormatter;
import javax.swing.JFormattedTextField.AbstractFormatterFactory;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.JSpinner.DefaultEditor;
import javax.swing.SpinnerNumberModel;
public class MainWithFormatter {
//For more currencies and their ISO codes, visit https://en.wikipedia.org/wiki/List_of_circulating_currencies
public static enum Currency {
EUR, //Euro
USD, //United States Dollar
GBP, //British Pound
JPY //Japanese Yen
}
public static class CurrencyFormatter extends AbstractFormatter {
private static final Pattern PATTERN;
static {
//Building the Pattern is not too tricky. It just needs some attention.
final String blank = "\\p{Blank}"; //Match any whitespace character.
final String blankGroupAny = "(" + blank + "*)"; //Match any whitespace character, zero or more times and group them.
final String digits = "\\d"; //Match any digit.
final String digitsGroup = "(" + digits + "+)"; //Match any digit, at least once and group them.
final String digitsSuperGroup = "(\\-?" + digitsGroup + "\\.?" + digitsGroup + "?)"; //Matches for example "-2.4" or "2.4" or "2" or "-2" in the same group!
//Create the pattern part which matches any of the available currencies...
final Currency[] currencies = Currency.values();
final StringBuilder currenciesBuilder = new StringBuilder(Pattern.quote("")); //Empty currency value is valid.
for (int i = 0; i < currencies.length; ++i)
currenciesBuilder.append('|').append(Pattern.quote(currencies[i].name()));
final String currenciessGroup = "(" + currenciesBuilder + ")";
final String full = "^" + blankGroupAny + digitsSuperGroup + blankGroupAny + currenciessGroup + blankGroupAny + "$"; //Compose full pattern.
PATTERN = Pattern.compile(full);
}
private final Currency defaultCurrency;
private Currency lastCurrency;
private boolean verbose; //Show the default currency while spinning or not?
public CurrencyFormatter(final Currency defaultCurrency) {
this.defaultCurrency = Objects.requireNonNull(defaultCurrency);
lastCurrency = defaultCurrency;
verbose = true;
}
@Override
public Object stringToValue(final String text) throws ParseException {
if (text == null || text.trim().isEmpty())
throw new ParseException("Null or empty text.", 0);
try {
final Matcher matcher = PATTERN.matcher(text.toUpperCase());
if (!matcher.matches())
throw new ParseException("Invalid input.", 0);
final String amountStr = matcher.group(2),
currencyStr = matcher.group(6);
final double amount = Double.parseDouble(amountStr);
if (currencyStr.trim().isEmpty()) {
lastCurrency = defaultCurrency;
verbose = false;
}
else {
lastCurrency = Currency.valueOf(currencyStr);
verbose = true;
}
return amount;
}
catch (final IllegalArgumentException iax) {
throw new ParseException("Failed to parse input \"" + text + "\".", 0);
}
}
public Currency getLastCurrency() {
return lastCurrency;
}
@Override
public String valueToString(final Object value) throws ParseException {
final String amount = String.format("%.2f", value).replace(',', '.');
return verbose ? (amount + ' ' + lastCurrency.name()) : amount;
}
}
public static class CurrencyFormatterFactory extends AbstractFormatterFactory {
@Override
public AbstractFormatter getFormatter(final JFormattedTextField tf) {
if (!(tf.getFormatter() instanceof CurrencyFormatter))
return new CurrencyFormatter(Currency.USD);
return tf.getFormatter();
}
}
public static void main(final String[] args) {
final JSpinner spin = new JSpinner(new SpinnerNumberModel(0d, -1000000d, 1000000d, 0.01d));
final JFormattedTextField jftf = ((DefaultEditor) spin.getEditor()).getTextField();
jftf.setFormatterFactory(new CurrencyFormatterFactory());
//Added a button to demonstrate how to obtain the value the user has selected:
final JButton check = new JButton("Check!");
check.addActionListener(e -> {
final CurrencyFormatter cf = (CurrencyFormatter) jftf.getFormatter();
JOptionPane.showMessageDialog(check, Objects.toString(spin.getValue()) + ' ' + cf.getLastCurrency().name() + '!');
});
final JPanel contents = new JPanel(); //FlowLayout.
contents.add(spin);
contents.add(check);
final JFrame frame = new JFrame("JSpinner currencies.");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(contents);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
You just need to create a custom AbstractFormatter
for the formatted text field of the default editor of the spinner that will handle such strings.
Although you could do it simply by putting two JSpinner
s, one for the amount and the other for the currency.
Edit 1 : or, you can work with the built-in java.util.Currency
class:
import java.text.ParseException;
import java.util.Currency;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.swing.JButton;
import javax.swing.JFormattedTextField;
import javax.swing.JFormattedTextField.AbstractFormatter;
import javax.swing.JFormattedTextField.AbstractFormatterFactory;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.JSpinner.DefaultEditor;
import javax.swing.SpinnerNumberModel;
public class MainWithCurrency {
public static class CurrencyFormatter extends AbstractFormatter {
private static final Pattern PATTERN;
static {
//Building the Pattern is not too tricky. It just needs some attention.
final String blank = "\\p{Blank}"; //Match any whitespace character.
final String blankGroupAny = "(" + blank + "*)"; //Match any whitespace character, zero or more times and group them.
final String digits = "\\d"; //Match any digit.
final String digitsGroup = "(" + digits + "+)"; //Match any digit, at least once and group them.
final String digitsSuperGroup = "(\\-?" + digitsGroup + "\\.?" + digitsGroup + "?)"; //Matches for example "-2.4" or "2.4" or "2" or "-2" in the same group!
//Create the pattern part which matches any of the available currencies...
final String currencyCodes = "[A-Z]{3}|" + Pattern.quote(""); //Currency code consists of 3 letters, or is empty for default value.
final String currenciessGroup = "(" + currencyCodes + ")";
final String full = "^" + blankGroupAny + digitsSuperGroup + blankGroupAny + currenciessGroup + blankGroupAny + "$"; //Compose full pattern.
PATTERN = Pattern.compile(full);
}
private final Set<String> supportedCurrencies;
private final String defaultCurrency;
private String lastCurrency;
private boolean verbose; //Show the default currency while spinning or not?
public CurrencyFormatter(final Set<Currency> supportedCurrencies,
final Currency defaultCurrency) {
if (!supportedCurrencies.contains(defaultCurrency))
throw new IllegalArgumentException("Default currency is not supported.");
this.supportedCurrencies = supportedCurrencies.stream().map(currency -> currency.getCurrencyCode()).collect(Collectors.toSet());
this.defaultCurrency = defaultCurrency.getCurrencyCode();
lastCurrency = this.defaultCurrency;
verbose = true;
}
@Override
public Object stringToValue(final String text) throws ParseException {
if (text == null || text.trim().isEmpty())
throw new ParseException("Null or empty text.", 0);
try {
final Matcher matcher = PATTERN.matcher(text.toUpperCase());
if (!matcher.matches())
throw new ParseException("Invalid input.", 0);
final String amountStr = matcher.group(2).trim(),
currencyStr = matcher.group(6).trim();
final double amount = Double.parseDouble(amountStr);
if (currencyStr.isEmpty()) {
lastCurrency = defaultCurrency;
verbose = false;
}
else {
if (!supportedCurrencies.contains(currencyStr))
throw new ParseException("Unsupported currency.", 0);
lastCurrency = currencyStr;
verbose = true;
}
return amount;
}
catch (final IllegalArgumentException iax) {
throw new ParseException("Failed to parse input \"" + text + "\".", 0);
}
}
public Currency getLastCurrency() {
return Currency.getInstance(lastCurrency);
}
@Override
public String valueToString(final Object value) throws ParseException {
final String amount = String.format("%.2f", value).replace(',', '.');
return verbose ? (amount + ' ' + lastCurrency) : amount;
}
}
public static class CurrencyFormatterFactory extends AbstractFormatterFactory {
@Override
public AbstractFormatter getFormatter(final JFormattedTextField tf) {
if (!(tf.getFormatter() instanceof CurrencyFormatter))
return new CurrencyFormatter(Currency.getAvailableCurrencies(), Currency.getInstance("USD"));
return tf.getFormatter();
}
}
public static void main(final String[] args) {
final JSpinner spin = new JSpinner(new SpinnerNumberModel(0d, -1000000d, 1000000d, 0.01d));
final JFormattedTextField jftf = ((DefaultEditor) spin.getEditor()).getTextField();
jftf.setFormatterFactory(new CurrencyFormatterFactory());
//Added a button to demonstrate how to obtain the value the user has selected:
final JButton check = new JButton("Check!");
check.addActionListener(e -> {
final CurrencyFormatter cf = (CurrencyFormatter) jftf.getFormatter();
JOptionPane.showMessageDialog(check, Objects.toString(spin.getValue()) + ' ' + cf.getLastCurrency().getCurrencyCode() + '!');
});
final JPanel contents = new JPanel(); //FlowLayout.
contents.add(spin);
contents.add(check);
final JFrame frame = new JFrame("JSpinner currencies.");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(contents);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
In this code (of the first edit) I am parsing the currency ISO code and checking it against a Set
of supported currencies.
Note : I've read DecimalFormat
documentation and the corresponding Java Tutorial , but cannot find anything about specifying optional currency symbols, so that's why I think you have to work with a custom Pattern
like the preceding sample codes in this answer and I'm also posting those links here, in case someone else finds the solution within them.
I'm assuming that your desired functionality of the JSpinner is something like this:
User Input<\/th> | Actual Value<\/th> | Display Text<\/th><\/tr><\/thead> |
---|---|---|
10€ <\/td> | 10 <\/td> | 10€ <\/td><\/tr> |
10 <\/td> | 10 <\/td> | 10€ <\/td><\/tr><\/tbody><\/table> In that case, you can use
|
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.