String with numbers and letters to double javafx - javafx

Hi I am trying to read a the numbers from a text field that shows a price e.g. £3.00, and convert the value of the price to a double. Is there a way to do
Double value;
value = Double.parseDouble(textField.getText());
But it won't let me do that because of the £ sign. Is there a way to strip the pound sign then read the digits.
Thanks

There is some TextFormatter and change filter handling logic built into the JavaFX TextField API, you could make use of that.
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.StringConverter;
import java.text.DecimalFormat;
import java.text.ParseException;
class CurrencyFormatter extends TextFormatter<Double> {
private static final double DEFAULT_VALUE = 5.00d;
private static final String CURRENCY_SYMBOL = "\u00A3"; // british pound
private static final DecimalFormat strictZeroDecimalFormat
= new DecimalFormat(CURRENCY_SYMBOL + "###,##0.00");
CurrencyFormatter() {
super(
// string converter converts between a string and a value property.
new StringConverter<Double>() {
#Override
public String toString(Double value) {
return strictZeroDecimalFormat.format(value);
}
#Override
public Double fromString(String string) {
try {
return strictZeroDecimalFormat.parse(string).doubleValue();
} catch (ParseException e) {
return Double.NaN;
}
}
},
DEFAULT_VALUE,
// change filter rejects text input if it cannot be parsed.
change -> {
try {
strictZeroDecimalFormat.parse(change.getControlNewText());
return change;
} catch (ParseException e) {
return null;
}
}
);
}
}
public class FormattedTextField extends Application {
public static void main(String[] args) { launch(args); }
#Override
public void start(final Stage stage) {
TextField textField = new TextField();
textField.setTextFormatter(new CurrencyFormatter());
Label text = new Label();
text.textProperty().bind(
Bindings.concat(
"Text: ",
textField.textProperty()
)
);
Label value = new Label();
value.textProperty().bind(
Bindings.concat(
"Value: ",
textField.getTextFormatter().valueProperty().asString()
)
);
VBox layout = new VBox(
10,
textField,
text,
value,
new Button("Apply")
);
layout.setPadding(new Insets(10));
stage.setScene(new Scene(layout));
stage.show();
}
}
The exact rules for DecimalFormat and the filter could get a little tricky if you are very particular about user experience (e.g. can the user enter the currency symbol? what happens if the user does not enter a currency symbol? are empty values permitted? etc.) The above example offers a compromise between a reasonable user experience and a (relatively) easy to program solution. For an actual production level application, you might wish to tweak the logic and behavior a bit more to fit your particular application.
Note, the apply button doesn't actually need to do anything to apply the change. Changes are applied when the user changes focus away from the text field (as long as they pass the change filter). So if the user clicks on the apply button, it gains, focus, the text field loses focus and the change is applied if applicable.
The above example treats the currency values as doubles (to match with the question), but those serious about currency may wish to look to BigDecimal.
For a simpler solution using similar concepts, see also:
Java 8 U40 TextFormatter (JavaFX) to restrict user input only for decimal number

Related

JavaFx table view: block selection events but only the ones coming from the user interaction

I have been trying to create a javafx.scene.control.TableView such that all the selection events are blocked when their origin is user interaction. In other words, it must be possible for me to programmatically alter the selection in a given table view.
I tried solutions from the following questions:
Setting the whole table view as mouse transparent (see article). This approach is unacceptable, because, for instance, user cannot change the width of the columns
Setting the selection model to null (see article). This one is unacceptable, because the currently selected row is not highlighted properly- see image below:
Originally, I wanted to decorate the default existing table view selection model with my own. Something like this was created:
private final class TableViewSelectionModelDecorator< S >extends TableViewSelectionModel< S >
{
private final TableViewSelectionModel< S > delegate;
private TableViewSelectionModelDecorator( TableViewSelectionModel< S > aDelegate )
{
super( aDelegate.getTableView() );
delegate = Objects.requireNonNull( aDelegate );
}
// Overriding the methods and delegating the calls to the delegate
}
The problem with my decorator is that the function getSelectedIndex() from the selection model is marked as final, which means I cannot override it and delegate the call to my decorated selection model. As a result, whenever a client asks for currently selected index the result is -1.
Requirements that I must meet:
Selection change events coming from either the mouse click or the keyboard (or any other input source) is blocked.
User must be able to interact with the table as long as the selection is not modified (e.g. changing the width of the columns)
Selected entry is properly highlighted (instead of just some frame around the selected index)
For now there is no multiselection support involved, but preferably I'd appreciate a solution that does support it.
Last note is I use Java 11.
Thanks for any pointers.
Please do consider the comments mentioned about the xy problem and other alternatives mentioned.
If you still want to solve this as the way you mentioned, you can give a try as below.
The idea is to
block all KEY_PRESSED events on tableView level and
set mouse transparent on tableRow level
so that we are not tweaking with any default selection logic. This way you can still interact with columns and scrollbar using mouse.
Below is the quick demo of the implementation:
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class TableViewSelectionBlockingDemo extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
ObservableList<Person> persons = FXCollections.observableArrayList();
for (int i = 1; i < 11; i++) {
persons.add(new Person(i + "", "A" + i));
}
TableView<Person> tableView = new TableView<>();
TableColumn<Person, String> idCol = new TableColumn<>("Id");
idCol.setCellValueFactory(param -> param.getValue().idProperty());
idCol.setPrefWidth(100);
TableColumn<Person, String> nameCol = new TableColumn<>("Name");
nameCol.setCellValueFactory(param -> param.getValue().nameProperty());
nameCol.setPrefWidth(150);
tableView.getColumns().addAll(idCol,nameCol);
tableView.setItems(persons);
// Selection Blocking logic
tableView.addEventFilter(KeyEvent.KEY_PRESSED, e->e.consume());
tableView.setRowFactory(personTableView -> new TableRow<Person>(){
{
setMouseTransparent(true);
}
});
ComboBox<Integer> comboBox = new ComboBox<>();
for (int i = 1; i < 11; i++) {
comboBox.getItems().add(i);
}
comboBox.valueProperty().addListener((obs, old, val) -> {
if (val != null) {
tableView.getSelectionModel().select(val.intValue()-1);
} else {
tableView.getSelectionModel().clearSelection();
}
});
HBox row = new HBox(new Label("Select Row : "), comboBox);
row.setSpacing(10);
VBox vb = new VBox(row, tableView);
vb.setSpacing(10);
vb.setPadding(new Insets(10));
VBox.setVgrow(tableView, Priority.ALWAYS);
Scene scene = new Scene(vb, 500, 300);
primaryStage.setScene(scene);
primaryStage.setTitle("TableView Selection Blocking Demo");
primaryStage.show();
}
class Person {
private StringProperty name = new SimpleStringProperty();
private StringProperty id = new SimpleStringProperty();
public Person(String id1, String name1) {
name.set(name1);
id.set(id1);
}
public StringProperty nameProperty() {
return name;
}
public StringProperty idProperty() {
return id;
}
}
}
Note: This may not be the approach for editable table.

javafx combobox checkbox multiselect filtered

I have looked days on any ready solution for the subject of having TOGETHER in javafx (pure) :
Combobox
Multiselect of items through Checkboxes
Filter items by the "editable" part of the Combobox
I have had no luck finding what I was looking for so I have now a working solution taken from different separate solution... Thank you to all for this !
Now I would like to know if what I have done follows the best practices or not... It's working... but is it "ugly" solution ? Or would that be a sort of base anyone could use ?
I tied to comment as much as I could, and also kept the basic comment of the sources :
user:2436221 (Jonatan Stenbacka) --> https://stackoverflow.com/a/34609439/14021197
user:5844477 (Sai Dandem) --> https://stackoverflow.com/a/52471561/14021197
Thank you for your opinions, and suggestions...
Here is the working example :
package application;
import com.sun.javafx.scene.control.skin.ComboBoxListViewSkin;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.layout.HBox;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.util.Callback;
#SuppressWarnings ("restriction") // Only applies for PROTECTD library : com.sun.javafx.scene.control.skin.ComboBoxListViewSkin
public class MultiSelectFiltered2 extends Application {
// These 2 next fields are used in order to keep the FILTERED TEXT entered by user.
private String aFilterText = "";
private boolean isUserChangeText = true;
public void start(Stage stage) {
Text txt = new Text(); // A place where to expose the result of checked items.
HBox vbxRoot = new HBox(); // A basic root to order the GUI
ComboBox<ChbxItems> cb = new ComboBox<ChbxItems>() {
// This part is needed in order to NOT have the list hided when an item is selected...
// TODO --> Seems a little ugly to me since this part is the PROTECTED part !
protected javafx.scene.control.Skin<?> createDefaultSkin() {
return new ComboBoxListViewSkin<ChbxItems>(this) {
#Override
protected boolean isHideOnClickEnabled() {
return false;
}
};
}
};
cb.setEditable(true);
// Create a list with some dummy values.
ObservableList<ChbxItems> items = FXCollections.observableArrayList();
items.add(new ChbxItems("One"));
items.add(new ChbxItems("Two"));
items.add(new ChbxItems("Three"));
items.add(new ChbxItems("Four"));
items.add(new ChbxItems("Five"));
items.add(new ChbxItems("Six"));
items.add(new ChbxItems("Seven"));
items.add(new ChbxItems("Eight"));
items.add(new ChbxItems("Nine"));
items.add(new ChbxItems("Ten"));
// Create a FilteredList wrapping the ObservableList.
FilteredList<ChbxItems> filteredItems = new FilteredList<ChbxItems>(items, p -> true);
// Add a listener to the textProperty of the combo box editor. The
// listener will simply filter the list every time the input is changed
// as long as the user hasn't selected an item in the list.
cb.getEditor().textProperty().addListener((obs, oldValue, newValue) -> {
// This needs to run on the GUI thread to avoid the error described here:
// https://bugs.openjdk.java.net/browse/JDK-8081700.
Platform.runLater(() -> {
if (isUserChangeText) {
aFilterText = cb.getEditor().getText();
}
// If the no item in the list is selected or the selected item
// isn't equal to the current input, we re-filter the list.
filteredItems.setPredicate(item -> {
boolean isPartOfFilter = true;
// We return true for any items that starts with the
// same letters as the input. We use toUpperCase to
// avoid case sensitivity.
if (!item.getText().toUpperCase().startsWith(newValue.toUpperCase())) {
isPartOfFilter = false;
}
return isPartOfFilter;
});
isUserChangeText = true;
});
});
cb.setCellFactory(new Callback<ListView<ChbxItems>, ListCell<ChbxItems>>() {
#Override
public ListCell<ChbxItems> call(ListView<ChbxItems> param) {
return new ListCell<ChbxItems>() {
private CheckBox chbx = new CheckBox();
// This 'just open bracket' opens the newly CheckBox Class specifics
{
chbx.setOnAction(new EventHandler<ActionEvent>() {
// This VERY IMPORTANT part will effectively set the ChbxItems item
// The argument is never used, thus left as 'arg0'
#Override
public void handle(ActionEvent arg0) {
// This is where the usual update of the check box refreshes the editor' text of the parent combo box... we want to avoid this ;-)
isUserChangeText = false;
// The one line without which your check boxes are going to be checked depending on the position in the list... which changes when the list gets filtered.
getListView().getSelectionModel().select(getItem());
// Updating the exposed text from the list of checked items... This is added here to have a 'live' update.
txt.setText(updateListOfValuesChosen(items));
}
});
}
private BooleanProperty booleanProperty; //Will be used for binding... explained bellow.
#Override
protected void updateItem(ChbxItems item, boolean empty) {
super.updateItem(item, empty);
if (!empty) {
// Binding is used in order to link the checking (selecting) of the item, with the actual 'isSelected' field of the ChbxItems object.
if (booleanProperty != null) {
chbx.selectedProperty().unbindBidirectional(booleanProperty);
}
booleanProperty = item.isSelectedProperty();
chbx.selectedProperty().bindBidirectional(booleanProperty);
// This is the usual part for the look of the cell
setGraphic(chbx);
setText(item.getText() + "");
} else {
// Look of the cell, which has to be "reseted" if no item is attached (empty is true).
setGraphic(null);
setText("");
}
// Setting the 'editable' part of the combo box to what the USER wanted
// --> When 'onAction' of the check box, the 'behind the scene' update will refresh the combo box editor with the selected object reference otherwise.
cb.getEditor().setText(aFilterText);
cb.getEditor().positionCaret(aFilterText.length());
}
};
}
});
// Yes, it's the filtered items we want to show in the combo box...
// ...but we want to run through the original items to find out if they are checked or not.
cb.setItems(filteredItems);
// Some basic cosmetics
vbxRoot.setSpacing(15);
vbxRoot.setPadding(new Insets(25));
vbxRoot.setAlignment(Pos.TOP_LEFT);
// Adding the visual children to root VBOX
vbxRoot.getChildren().addAll(txt, cb);
// Ordinary Scene & Stage settings and initialization
Scene scene = new Scene(vbxRoot);
stage.setScene(scene);
stage.show();
}
// Just a method to expose the list of items checked...
// This is the result that will be probably the input for following code.
// -->
// If the class ChbxItems had a custom object rather than 'text' field,
// the resulting checked items from here could be a list of these custom objects --> VERY USEFUL
private String updateListOfValuesChosen(ObservableList<ChbxItems> items) {
StringBuilder sb = new StringBuilder();
items.stream().filter(ChbxItems::getIsSelected).forEach(cbitem -> {
sb.append(cbitem.getText()).append("\n");
});
return sb.toString();
}
// The CHECKBOX object, with 2 fields :
// - The boolean part (checked ot not)
// - The text part which is shown --> Could be a custom object with 'toString()' overridden ;-)
class ChbxItems {
private SimpleStringProperty text = new SimpleStringProperty();
private BooleanProperty isSelected = new SimpleBooleanProperty();
public ChbxItems(String sText) {
setText(sText);
}
public void setText(String text) {
this.text.set(text);
}
public String getText() {
return text.get();
}
public SimpleStringProperty textProperty() {
return text;
}
public void setIsSelected(boolean isSelected) {
this.isSelected.set(isSelected);
}
public boolean getIsSelected() {
return isSelected.get();
}
public BooleanProperty isSelectedProperty() {
return isSelected;
}
}
public static void main(String[] args) {
launch();
}
}

What's a good observable appendable base for a TextArea?

I have a StringBuffer that is occasionally appended with new information.
In a separate module, I have a JavaFX TextArea that displays that StringBuffer.
Right now, I have to manually update the TextArea every time the underlying data is modified.
Is there something like an ObservableList (which I use for TableViews) that I can use as the back-end data for the TextArea instead, so I don't have to manually manage pushing the changes to the display?
I am not attached to using a StringBuffer. I'm glad to use any appendable data structure to hold text.
You can consider something simple like this:
import javafx.beans.binding.StringBinding;
public class ObservableStringBuffer extends StringBinding {
private final StringBuffer buffer = new StringBuffer() ;
#Override
protected String computeValue() {
return buffer.toString();
}
public void set(String content) {
buffer.replace(0, buffer.length(), content);
invalidate();
}
public void append(String text) {
buffer.append(text);
invalidate();
}
// wrap other StringBuffer methods as needed...
}
This enables easy coding for binding to a text area. You can simply do
TextArea textArea = new TextArea();
ObservableStringBuffer buffer = new ObservableStringBuffer();
textArea.textProperty().bind(buffer);
// ...
buffer.append("Hello world");
However, it's important to note here that you don't transfer the efficiency of the buffer API to the text area: the text area simply has a textProperty() representing its text, which can still only really be modified by set(...) and setValue(...). In other words, when you append to the buffer, you essentially end up with textArea.setText(textArea.getText() + "Hello world") (not textArea.appendText("Hello world"). If you're just looking for a clean API, then this should work for you; if you're looking for something efficient, you would have to "wire" the calls to appendText yourself, since that is simply not supported by the text area's textProperty().
Here's a SSCCE using the above class:
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Duration;
public class ObservableStringBufferTest extends Application {
private int counter ;
#Override
public void start(Stage primaryStage) {
ObservableStringBuffer buffer = new ObservableStringBuffer();
TextArea textArea = new TextArea();
textArea.setEditable(false);
textArea.textProperty().bind(buffer);
buffer.set("Item 0");
Timeline timeline = new Timeline(new KeyFrame(
Duration.seconds(1),
e -> buffer.append("\nItem "+(++counter))));
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();
primaryStage.setScene(new Scene(new StackPane(textArea)));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

How to control Caret position in trimmed TextField?

I have a TextField, in which I do a check after any changes are made. I also trim away any double spaces that were typed. Problem is that after I replace text with trimmed one, Caret position refreshes. I added calculating, so that Caret will go to position user last input, but it's not working properly with extra spaces. My code:
EventHandler<InputEvent> fieldChangeListener = new EventHandler<InputEvent>() {
public void handle(InputEvent event) {
TextField field = (TextField) event.getSource();
int caretPos = field.getCaretPosition();
String text = field.getText();
text = text.replaceAll("\\s+", " ").trim();
field.setText(text);
field.positionCaret(caretPos);
event.consume();}};
name.addEventHandler(KeyEvent.KEY_RELEASED, fieldChangeListener);
In picture user puts cursor before word 'Friend', presses space bar and extra space will get trimmed, placing Caret right after 'F'. If space bar will be hold longer, Caret will be placed in the end.
Desired result: Caret placed before word 'Friend'.
How would be better to do that?
The reason why the caret position is not working as expected is because you are not reducing the caret position when there is multiple space in the string. You should remove the caret position by the amount of removed multiple spaces.
In the code below, I check if the string has at least double spaces and if it does then i remove the spaces and also reduce the caret position by the number of spaces removed from the string.
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.input.InputEvent;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
public class Miner extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
AnchorPane pane = new AnchorPane();
TextField tf = new TextField("HELLO FRIEND");
int CaretPos = tf.getCaretPosition();
System.out.println("CaretPos = " + CaretPos);
pane.getChildren().add(tf);
EventHandler<InputEvent> fieldChangeListener = new EventHandler<InputEvent>() {
public String AtLeastDoubleSpace = " ";
public void handle(InputEvent event) {
TextField field = (TextField) event.getSource();
String text = field.getText();
String originalString = text;
int caretPos;
if(text.contains(AtLeastDoubleSpace)) {
caretPos = field.getCaretPosition();
text = text.replaceAll("\\s+"," ").trim();
field.setText(text);
int spacesRemoved = originalString.length() - text.length();
field.positionCaret(caretPos - spacesRemoved);
event.consume();
} else {
caretPos = field.getCaretPosition();
field.setText(text);
field.positionCaret(caretPos);
event.consume();
}
}};
tf.addEventHandler(KeyEvent.KEY_RELEASED, fieldChangeListener);
Scene scene = new Scene(pane);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I provided this solution depending upon my understanding to the question you provided. Let me know, if you need any further help.
how about caretPositionProperty and add listener??
textField.caretPositionProperty().addListener((ob, old1, new1) -> {
System.out.println("old:" + old1 + " new " + new1);
});

JavaFX ChoiceBox add separator with type safety

I'm looking to add a separator into a choice box and still retain the type safety.
On all of the examples I've seen, they just do the following:
ChoiceBox<Object> cb = new ChoiceBox<>();
cb.getItems().addAll("one", "two", new Separator(), "fadfadfasd", "afdafdsfas");
Has anyone come up with a solution to be able to add separators and still retain type safety?
I would expect that if I wanted to add separators, I should be able do something along the following:
ChoiceBox<T> cb = new ChoiceBox<T>();
cb.getSeparators().add(1, new Separator()); // 1 is the index of where the separator should be
I shouldn't have to sacrifice type safety just to add separators.
As already noted, are Separators only supported if added to the items (dirty, dirty). To support them along the lines expected in the question, we need to:
add the notion of list of separator to choiceBox
make its skin aware of that list
While the former is not a big deal, the latter requires a complete re-write (mostly c&p) of its skin, as everything is tightly hidden in privacy. If the re-write has happened anyway, then it's just a couple of lines more :-)
Just for fun, I'm experimenting with ChoiceBoxX that solves some nasty bugs in its selection handling, so couldn't resist to try.
First, add support to the ChoiceBoxx itself:
/**
* Adds a separator index to the list. The separator is inserted
* after the item with the same index. Client code
* must keep this list in sync with the data.
*
* #param separator
*/
public final void addSeparator(int separator) {
if (separatorsList.getValue() == null) {
separatorsList.setValue(FXCollections.observableArrayList());
}
separatorsList.getValue().add(separator);
};
Then some changes in ChoiceBoxXSkin
must listen to the separatorsList
must expect index-of-menuItem != index-of-choiceItem
menuItem must keep its index-of-choiceItem
At its simplest, the listener re-builds the popup, the menuItem stores the dataIndex in its properties and all code that needs to access a popup by its dataIndex is delegated to a method that loops through the menuItems until it finds one that fits:
protected RadioMenuItem getMenuItemFor(int dataIndex) {
if (dataIndex < 0) return null;
int loopIndex = dataIndex;
while (loopIndex < popup.getItems().size()) {
MenuItem item = popup.getItems().get(loopIndex);
ObservableMap<Object, Object> properties = item.getProperties();
Object object = properties.get("data-index");
if ((object instanceof Integer) && dataIndex == (Integer) object) {
return item instanceof RadioMenuItem ? (RadioMenuItem)item : null;
}
loopIndex++;
}
return null;
}
Well you can work around it by creating an interface and then subclassing Separator to implement this interface:
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.Separator;
import javafx.scene.layout.GridPane;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class ChoiceBoxIsSafe extends Application {
interface FruitInterface { }
static public class Fruit implements FruitInterface {
private StringProperty name = new SimpleStringProperty();
Fruit(String name) {
this.name.set(name);
}
public StringProperty nameProperty() {
return name;
}
#Override
public String toString() {
return name.get();
}
}
static public class FruitySeparator extends Separator implements FruitInterface { }
#Override
public void start(Stage primaryStage) throws Exception {
GridPane grid = new GridPane();
grid.setHgap(10); grid.setVgap(10); grid.setPadding(new Insets(10));
ChoiceBox<FruitInterface> cb = new ChoiceBox<>();
cb.getItems().addAll(new Fruit("Apple"), new Fruit("Orange"), new FruitySeparator(), new Fruit("Peach"));
Text text = new Text("");
ReadOnlyObjectProperty<FruitInterface> selected = cb.getSelectionModel().selectedItemProperty();
text.textProperty().bind(Bindings.select(selected, "name"));
grid.add(cb, 0, 0);
grid.add(text, 1, 0);
Scene scene = new Scene(grid);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
but that is hardly an "elegant" solution and cannot be done in all cases (e.g. ChoiceBox<String>).
From the implementation of ChoiceBox it certainly looks like it wasn't a good idea to treat Separators like items in the ChoiceBox :-(.
FOR THE REST OF US:
There is a MUCH easier way to do this using code (there are easy ways to do it using FXML too, doing it in code offers more flexibility).
You simply create an ObservableList, then populate it with your items, including the separator then assign that list to the ChoiceBox like this:
private void fillChoiceBox(ChoiceBox choiceBox) {
ObservableList items = FXCollections.observableArrayList();
items.add("one");
items.add("two");
items.add("three");
items.add(new Separator());
items.add("Apples");
items.add("Oranges");
items.add("Pears");
choiceBox.getItems().clear();
choiceBox.getItems().addAll(items);
}

Resources