How do I get a JavaFX TextField for editing a currency which is stored without factional digits (a long for example)? Using Databinding, TextFormatter and the other javaFX Stuff.
The goal should be:
Bo with a LongProperty (currency Value in cents)
a editable TextField, in the Users known format (optinal leading minus,
thousands separator, decimal separator, (currency symbol), and no other
chars possible)
BiDirectionalBinding between Bo and the TextField.
Here is a solution (maybe not the best, pls comment if I could improve it)
The Bo:
import java.util.Random;
import javafx.beans.property.LongProperty;
import javafx.beans.property.SimpleLongProperty;
public class SimpleBo {
//a simple LongProperty to store the currency without fractional digits (56,81 € would be 5681)
private LongProperty currencyLong = new SimpleLongProperty();
public SimpleBo() {
setCurrencyLong(new Random().nextLong());
}
public final LongProperty currencyLongProperty() {
return this.currencyLong;
}
public final long getCurrencyLong() {
return this.currencyLongProperty().get();
}
public final void setCurrencyLong(final long currencyLong) {
this.currencyLongProperty().set(currencyLong);
}
}
A Number to String Converter:
import java.text.NumberFormat;
import java.util.Locale;
import javafx.util.converter.NumberStringConverter;
public class MyNumberStringConverter extends NumberStringConverter {
public MyNumberStringConverter() {
super();
}
public MyNumberStringConverter(Locale locale, String pattern) {
super(locale, pattern);
}
public MyNumberStringConverter(Locale locale) {
super(locale);
}
public MyNumberStringConverter(NumberFormat numberFormat) {
super(numberFormat);
}
public MyNumberStringConverter(String pattern) {
super(pattern);
}
#Override
public Number fromString(String value) {
//to transform the double, given by the textfield, just multiply by 100 and round if any left
Number rValue = Math.round(super.fromString(value).doubleValue() * 100);
return rValue.longValue();
}
#Override
public String toString(Number value) {
if(value == null) {
return "";
}
//Check for too big long value
//If the long is too big, it could result in a strange double value.
if(value.longValue() > 1000000000000l || value.longValue() < -1000000000000l ) {
return "";
}
BigDecimal myBigDecimal = new BigDecimal(value.longValue());
//to convert the long to a double (currency with fractional digits)
myBigDecimal = myBigDecimal.movePointLeft(2);
double asDouble = myBigDecimal.doubleValue();
if(asDouble == Double.NEGATIVE_INFINITY || asDouble == Double.POSITIVE_INFINITY) {
return "";
}
return super.toString(asDouble);
}
Util Class:
import java.util.function.UnaryOperator;
import javafx.scene.control.TextFormatter;
public class Util {
// This will filter the changes
public static UnaryOperator<TextFormatter.Change> createFilter() {
//this is a simple Regex to define the acceptable Chars
String validEditingStateRegex = "[0123456789,.-]*";
return change -> {
String text = change.getText();
//Check if something changed and just return if not
if (!change.isContentChange()) {
return change;
}
//check if the changed text validates against the regex
if (text.matches(validEditingStateRegex) || text.isEmpty()) {
//if valid return the change
return change;
}
//otherwise return null
return null;
};
}
}
Test Application:
import java.text.NumberFormat;
import javafx.application.Application;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class BindingExample extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
Scene scene = new Scene(createBindingExample());
primaryStage.setScene(scene);
primaryStage.show();
}
//Creates just a sample gui with a Business Objekt
public static Parent createBindingExample() {
VBox vbox = new VBox();
SimpleBo myBo = new SimpleBo();
TextField myTextField = new TextField();
Label fooLabel = new Label();
//Setting up the textField with a Formatter
NumberFormat nFormat = NumberFormat.getInstance();
//Define the integer and fractional digits
nFormat.setMinimumIntegerDigits(1);
nFormat.setMaximumFractionDigits(2);
//setting up the TextFormatter with the NumberFormat and a Filter to limit the inputchars
TextFormatter<Number> textFormatter = new TextFormatter<>(new MyNumberStringConverter(nFormat), 0l,
Util.createFilter());
//Bind (Bidirectional) the BO currency value to the textformatter value
textFormatter.valueProperty().bindBidirectional(myBo.currencyLongProperty());
myTextField.setTextFormatter(textFormatter);
//just to show the currency value, bind it to the label
fooLabel.textProperty().bind(myBo.currencyLongProperty().asString());
vbox.getChildren().add(myTextField);
//just for spacing
vbox.getChildren().add(new Label(" "));
vbox.getChildren().add(fooLabel);
return vbox;
}
}
You could go ahead a put the TextField inside a HBox and a Label for the Currency Symbol. Or with a dropbox of Currency Symbols or what ever. It would be possible to use a NumberFormat with Currency, so the format would add the symbol. But this has some other drawbacks, so I headed this way.
Related
I want to set javaFX text field upto two decimal places . I found the answer but it is for numeric value . e-g
// force the field to be numeric only
textField.textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
if (!newValue.matches("\\d*")) {
textField.setText(newValue.replaceAll("[^\\d]", ""));
}
}
});
In above code, what is replacement for limit value upto two decimal.
Or is there any other solution for limit the textField.
I have Binding TextField Here is My partial Code ...
#FXML public TextField InvoiceTotal;
private DoubleProperty invTotal;
invTotal = new SimpleDoubleProperty(0);
netAmount.bind(grossAmount.subtract(disc));
StringConverter<? extends Number> converter= new DoubleStringConverter();
Bindings.bindBidirectional(InvoiceTotal.textProperty(),invTotal,(StringConverter<Number>)converter);
Now I want to set two decimal limitation on InvoiceTotal textfield
Use a text formatter on the text field. The pattern just has to match any possible decimal value with up to two decimal places. (Something like optional negative sign, followed by any number of digits, then optionally followed by a decimal point and 0-2 digits.) Just have the text formatter accept changes if the resulting text matches that pattern, and reject them otherwise.
import java.util.function.UnaryOperator;
import java.util.regex.Pattern;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.control.TextFormatter.Change;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class DecimalTextField extends Application {
#Override
public void start(Stage primaryStage) {
Pattern decimalPattern = Pattern.compile("-?\\d*(\\.\\d{0,2})?");
UnaryOperator<Change> filter = c -> {
if (decimalPattern.matcher(c.getControlNewText()).matches()) {
return c ;
} else {
return null ;
}
};
TextFormatter<Double> formatter = new TextFormatter<>(filter);
TextField textField = new TextField();
textField.setTextFormatter(formatter);
StackPane root = new StackPane(textField);
root.setPadding(new Insets(24));
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I couldn't resist. This is aboves answer in two lines (the ones that do all the work)
private static TextFormatter<Double> new3DecimalFormatter(){
Pattern decimalPattern = Pattern.compile("-?\\d*(\\.\\d{0,3})?");
return new TextFormatter<>(c -> (decimalPattern.matcher(c.getControlNewText()).matches()) ? c : null );
}
I am somewhat new to programming and new to OOP (2nd Java project over all right now) and would love any hints or help.
I am currently working on a character creation program for my very own pen&paper game. I am using JavaFX (without FXML and thus without SceneBuilder) for the GUI part. I am working with Eclipse Neon on JDK1.8.0_131.
Here is my issue:
tl;dr: How to increase the number of possible selections within a ToggleGroup?
I am about to create a list of options for the user to choose from. The list consist of about 30 different advantages he or she can choose to improve their character. The allowed maximum of chosen options depends on the character and varies around 5. I already implemented an array of pairs (I know about HashMaps), where each entry is a pair consisting of the advantage's name and an integer representing its costs (they vary in their values, so in their costs).
The list itself should now be implemented via
ScrollPane scrollAdv = new ScrollPane();
VBox vBoxAdv = new VBox();
scrollAdv.setContent(vBoxAdv);
Pair<String, Integer>[] listAdv = info.getAdvantages();
for (int i = 0; i < listAdv.length; i++) {
String name = listAdv[i].getKey(); // delivers the 1st entry of a pair
int costs = listAdv[i].getValue(); // delivers the 2nd entry of a pair
ToggleButton toggleButton = new ToggleButton();
toggleButton.setUserData(name);
toggleButton.setText(name + " (" + costs + ")");
vBoxAdv.getChildren().add(toggleButton);
}
Note that I don't care too much about ToggleButtons and they could easily be replaced with RadioButtons. Both work the same way, if I understood the documentation correctly. They both use ToggleGroup to make sure, only one option is selected.
So while you probably guessed so by now, what I want to do is give the user the possibility to chose more than one option at once. I do not want to make it so the user has to chose one option after the other, while resetting the list in between.
Thanks for reading and thanks in advance for any help or hints.
edit: I could always just add a counter which refreshes whenever one option is selected or deselected and blocks any selection if it's < 1, but I thought that there should be a better solution, e.g. increase the built-in limit of 1 which ToggleGroup seems to be using.
One way would be to disable the remaining toggles when the limit is reached. Here's a ToggleSet class that does that:
import java.util.ArrayList;
import java.util.List;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.IntegerBinding;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.scene.Node ;
import javafx.scene.control.Toggle;
public class ToggleSet<T extends Node & Toggle> {
private final ObservableList<T> toggles = FXCollections.observableArrayList(t -> new Observable[] {t.selectedProperty()});
private final FilteredList<T> selectedToggles = toggles.filtered(t -> ((Toggle)t).isSelected());
private final IntegerProperty maximumSelectable = new SimpleIntegerProperty(0);
private final IntegerBinding numSelected = Bindings.size(selectedToggles);
public ToggleSet(int maximumSelectable) {
this.maximumSelectable.addListener((obs, oldMax, newMax) -> {
if (newMax.intValue() < numSelected.get()) {
List<Toggle> togglesToClear = new ArrayList<>(selectedToggles.subList(0, numSelected.get() - newMax.intValue()));
togglesToClear.forEach(t -> t.setSelected(false));
}
});
setMaximumSelectable(maximumSelectable);
}
public ToggleSet() {
this(0);
}
public ObservableList<T> getSelectedToggles() {
return FXCollections.unmodifiableObservableList(selectedToggles) ;
}
public IntegerProperty maximumSelectableProperty() {
return maximumSelectable ;
}
public final int getMaximumSelectable() {
return maximumSelectableProperty().get();
}
public final void setMaximumSelectable(int maximumSelectable) {
maximumSelectableProperty().set(maximumSelectable);
}
public void addToggle(T toggle) {
if (numSelected.get() >= getMaximumSelectable()) {
toggle.setSelected(false);
}
toggles.add(toggle);
toggle.disableProperty().bind(toggle.selectedProperty().not().and(numSelected.greaterThanOrEqualTo(maximumSelectable)));
}
public void removeToggle(T toggle) {
toggles.remove(toggle);
toggle.disableProperty().unbind();
}
}
Here's an example testing it:
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.RadioButton;
import javafx.scene.control.Spinner;
import javafx.scene.control.ToggleButton;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class ToggleSetTest extends Application {
#Override
public void start(Stage primaryStage) {
ToggleSet<ToggleButton> toggleSet = new ToggleSet<>(5);
GridPane grid = new GridPane() ;
Spinner<Integer> maxSelectedSpinner = new Spinner<>(0, 20, 5);
maxSelectedSpinner.getValueFactory().valueProperty().bindBidirectional(toggleSet.maximumSelectableProperty().asObject());
grid.add(new HBox(2, new Label("Maximum selected"), maxSelectedSpinner), 0, 0, 2, 1);
grid.addRow(1, new Label("Selection"), new Label("Include in set"));
for (int i = 1; i <= 20 ; i++) {
RadioButton button = new RadioButton("Button "+i);
CheckBox checkBox = new CheckBox();
checkBox.selectedProperty().addListener((obs, wasChecked, isNowChecked) -> {
if (isNowChecked) {
toggleSet.addToggle(button);
} else {
toggleSet.removeToggle(button);
}
});
checkBox.setSelected(true);
grid.addRow(i + 1, button, checkBox);
}
grid.setPadding(new Insets(10));
grid.setHgap(5);
grid.setVgap(2);
Scene scene = new Scene(grid);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
If you want the same behavior as the ToggleGroup, where the previous selection becomes unselected, it's a little trickier, but the following should work:
import java.util.ArrayList;
import java.util.List;
import javafx.beans.Observable;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Node ;
import javafx.scene.control.Toggle;
public class ToggleSet<T extends Node & Toggle> {
private final ObservableList<T> toggles = FXCollections.observableArrayList(t -> new Observable[] {t.selectedProperty()});
private final ObservableList<T> selectedToggles = FXCollections.observableArrayList();
private final IntegerProperty maximumSelectable = new SimpleIntegerProperty(0);
private final ChangeListener<Boolean> toggleListener = (obs, wasSelected, isNowSelected) -> {
#SuppressWarnings("unchecked")
T toggle = (T) ((Property<?>)obs).getBean();
if (isNowSelected) {
selectedToggles.add(toggle);
ensureWithinMax();
} else {
selectedToggles.remove(toggle);
}
};
public ToggleSet(int maximumSelectable) {
this.maximumSelectable.addListener((obs, oldMax, newMax) -> ensureWithinMax());
setMaximumSelectable(maximumSelectable);
}
private void ensureWithinMax() {
if (this.maximumSelectable.get() < selectedToggles.size()) {
List<Toggle> togglesToClear = new ArrayList<>(selectedToggles.subList(0, selectedToggles.size() - this.maximumSelectable.get()));
togglesToClear.forEach(t -> t.setSelected(false));
}
}
public ToggleSet() {
this(0);
}
public ObservableList<T> getSelectedToggles() {
return FXCollections.unmodifiableObservableList(selectedToggles) ;
}
public IntegerProperty maximumSelectableProperty() {
return maximumSelectable ;
}
public final int getMaximumSelectable() {
return maximumSelectableProperty().get();
}
public final void setMaximumSelectable(int maximumSelectable) {
maximumSelectableProperty().set(maximumSelectable);
}
public void addToggle(T toggle) {
if (toggle.isSelected()) {
selectedToggles.add(toggle);
ensureWithinMax();
}
toggle.selectedProperty().addListener(toggleListener);
toggles.add(toggle);
}
public void removeToggle(T toggle) {
toggle.selectedProperty().removeListener(toggleListener);
toggles.remove(toggle);
}
}
(Use the same test code.)
I trying to build the text dialog where the user enter the event name, event size, and the selected the venue.
My problem is in how can I gather the inputs; here what I did so far:
eventName = new TextField();
eventSize = new TextField();
ObservableList<Venue> options =
FXCollections.observableArrayList(model.getVenuesList());
VeunueList = new ComboBox<Venue>(options);
I create a class that encapsulate all my inputs:
public class MyResult {
String eventname;
String eventsize;
Venue venue;
}
I define the variable to be object of class Myresult:
private Dialog<MyResult> dialog ;
private Optional<MyResult> EventInput;
The problem is how to write return statement in the result converter; it gives me error:
dialog.setResultConverter(dialogButton -> {
if (dialogButton == submit) {
return new MyResult(eventName.getText(),eventSize.getText(),VeunueList.getValue())
}
return null;
});
EventInput = dialog.showAndWait();
It's not clear where your fragment goes awry, but getting the types correct for a call to setResultConverter() is sometimes problematical. The example below illustrates a Dialog that collects inputs from a TextField, DatePicker and ComboBox<Venue>. In the ComboBox<Venue>, the choice of Venue comes from an enum, and the corresponding ComboBox model is constructed using the enum's implicit values() method. The resultConverter property's Callback returns a new instance of Results having the current values of the various view components. The Optional<Results> shows those values ifPresent(). Some related examples may be found here and in the tutorial, JavaFX improvements in Java SE 8u40.
Console: Name 2017-05-24 Elsewhere
import java.time.LocalDate;
import java.util.Optional;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.control.ButtonType;
import javafx.scene.control.ComboBox;
import javafx.scene.control.DatePicker;
import javafx.scene.control.Dialog;
import javafx.scene.control.DialogPane;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
/**
* #see http://stackoverflow.com/q/44147595/230513
* #see http://www.javaworld.com/article/2991463/
*/
public class DialogTest extends Application {
#Override
public void start(Stage primaryStage) {
Dialog<Results> dialog = new Dialog<>();
dialog.setTitle("Dialog Test");
dialog.setHeaderText("Please specify…");
DialogPane dialogPane = dialog.getDialogPane();
dialogPane.getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL);
TextField textField = new TextField("Name");
DatePicker datePicker = new DatePicker(LocalDate.now());
ObservableList<Venue> options =
FXCollections.observableArrayList(Venue.values());
ComboBox<Venue> comboBox = new ComboBox<>(options);
comboBox.getSelectionModel().selectFirst();
dialogPane.setContent(new VBox(8, textField, datePicker, comboBox));
Platform.runLater(textField::requestFocus);
dialog.setResultConverter((ButtonType button) -> {
if (button == ButtonType.OK) {
return new Results(textField.getText(),
datePicker.getValue(), comboBox.getValue());
}
return null;
});
Optional<Results> optionalResult = dialog.showAndWait();
optionalResult.ifPresent((Results results) -> {
System.out.println(
results.text + " " + results.date + " " + results.venue);
});
}
private static enum Venue {Here, There, Elsewhere}
private static class Results {
String text;
LocalDate date;
Venue venue;
public Results(String name, LocalDate date, Venue venue) {
this.text = name;
this.date = date;
this.venue = venue;
}
}
public static void main(String[] args) {
launch(args);
}
}
I want to set javaFX text field upto two decimal places . I found the answer but it is for numeric value . e-g
// force the field to be numeric only
textField.textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
if (!newValue.matches("\\d*")) {
textField.setText(newValue.replaceAll("[^\\d]", ""));
}
}
});
In above code, what is replacement for limit value upto two decimal.
Or is there any other solution for limit the textField.
I have Binding TextField Here is My partial Code ...
#FXML public TextField InvoiceTotal;
private DoubleProperty invTotal;
invTotal = new SimpleDoubleProperty(0);
netAmount.bind(grossAmount.subtract(disc));
StringConverter<? extends Number> converter= new DoubleStringConverter();
Bindings.bindBidirectional(InvoiceTotal.textProperty(),invTotal,(StringConverter<Number>)converter);
Now I want to set two decimal limitation on InvoiceTotal textfield
Use a text formatter on the text field. The pattern just has to match any possible decimal value with up to two decimal places. (Something like optional negative sign, followed by any number of digits, then optionally followed by a decimal point and 0-2 digits.) Just have the text formatter accept changes if the resulting text matches that pattern, and reject them otherwise.
import java.util.function.UnaryOperator;
import java.util.regex.Pattern;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.control.TextFormatter.Change;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class DecimalTextField extends Application {
#Override
public void start(Stage primaryStage) {
Pattern decimalPattern = Pattern.compile("-?\\d*(\\.\\d{0,2})?");
UnaryOperator<Change> filter = c -> {
if (decimalPattern.matcher(c.getControlNewText()).matches()) {
return c ;
} else {
return null ;
}
};
TextFormatter<Double> formatter = new TextFormatter<>(filter);
TextField textField = new TextField();
textField.setTextFormatter(formatter);
StackPane root = new StackPane(textField);
root.setPadding(new Insets(24));
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I couldn't resist. This is aboves answer in two lines (the ones that do all the work)
private static TextFormatter<Double> new3DecimalFormatter(){
Pattern decimalPattern = Pattern.compile("-?\\d*(\\.\\d{0,3})?");
return new TextFormatter<>(c -> (decimalPattern.matcher(c.getControlNewText()).matches()) ? c : null );
}
I am looking for a simple solution to making first and last line of TextArea uneditable.
As seen on the picture, I need to keep the first and last line, user can edit or input whatever he wants in the curly brackets.
I have actually come up with this simple class, but it kinda breaks when user manages to get the closing curly bracket on the second line, leaving no lines between the first and the last one, rendering the user unable to edit anything.
Thanks for all responses.
public static class ScriptArea extends TextArea {
#Override
public void replaceText(int start, int end, String text) {
String currentToStart = getText().substring(0, start);
String startToEnd = getText().substring(start);
if (currentToStart.contains("\n") && startToEnd.contains("\n")) {
super.replaceText(start, end, text.equals("\n")?"\n\t":text);
}
}
}
Use a TextFormatter with a filter that vetoes any changes that don't leave the text in the correct form:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextFormatter;
import javafx.scene.control.TextFormatter.Change;
import javafx.stage.Stage;
public class TextAreaFixedStartEndLines extends Application {
private final String start = "function collideWith(mobj, tar, dir) {\n";
private final String end = "\n}";
#Override
public void start(Stage primaryStage) {
TextArea textArea = new TextArea();
textArea.setTextFormatter(new TextFormatter<String>((Change c) -> {
String proposed = c.getControlNewText();
if (proposed.startsWith(start) && proposed.endsWith(end)) {
return c;
} else {
return null ;
}
}));
textArea.setText(start+"\n"+end);
primaryStage.setScene(new Scene(textArea, 600, 600));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}