I have an editable JavaFX ComboBox.
A user must only be able to
type alphabets ('a' to 'z'), space and round braces ('(', ')') to enter the string
press tab to exit
press enter to exit
How to filter out every other key, modifiers etc?
I have read about and used event handlers like Key_Pressed, Key_Released but I am unable to figure out a straight-forward way to achieve the above.
I am using Mac OS Yosemite, Java 8, latest version of JavaFX and
public static final EventType<KeyEvent> KEY_TYPED just does not work at all.
Below code is my attempt. The variable typedText stores the desired values.
comboBox.addEventHandler(KeyEvent.KEY_RELEASED, new EventHandler<KeyEvent>() {
private final String[] allowedItems = new String[]{"a","b","c","d","e","f",
"g","h","i","j","k","l","m","n","o","p","q","r",
"s","t","u","v","w","x","y","z"," ","(",")"};
private final List data = Arrays.asList(allowedItems);
private String tempInput;
public boolean containsCaseInsensitive(String s, List<String> l){
for (String string : l) {
if (string.equalsIgnoreCase(s)){
return true;
}
}
return false;
}
public void handle(KeyEvent event) {
boolean b;
b = event.isShiftDown();
if (b) {
if (event.getText().equals("(")) {
tempInput = "(";
} else if (event.getText().equals(")")){
tempInput = ")";
}
} else {
tempInput = event.getCode().toString().toLowerCase();
}
System.out.println("tempInput:"+tempInput);
if (containsCaseInsensitive(tempInput, data)) {
typedText = tempInput;
System.out.println("typedText:"+typedText);
}
}
});
}
You can get the Editor, which is a TextField in your case, and add a TextFormatter to it which restricts the input.
Tab works out of the box, but the "enter" keypress is a different matter, I simply request the focus in this example. Usually you'd navigate to the next item in the focus traversal list, but there's no future-proof api for that yet in JavaFX.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class ComboBoxSample extends Application {
#Override
public void start(Stage stage) {
ComboBox<String> comboBox = new ComboBox<>();
comboBox.setEditable(true);
comboBox.getItems().addAll("A", "B", "C", "D", "E");
comboBox.setValue("A");
// restrict input
TextField textField = comboBox.getEditor();
TextFormatter<String> formatter = new TextFormatter<String>(change -> {
change.setText(change.getText().replaceAll("[^a-z ()]", ""));
return change;
});
textField.setTextFormatter(formatter);
// dummy textfield to jump to on ENTER press
TextField dummyTextField = new TextField();
comboBox.addEventFilter(KeyEvent.KEY_PRESSED, e -> {
if( e.getCode() == KeyCode.ENTER) {
dummyTextField.requestFocus();
e.consume();
}
});
HBox root = new HBox();
root.getChildren().addAll(comboBox, dummyTextField);
Scene scene = new Scene(root, 450, 250);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Related
I have a JavaFX Application with many UI-Elements like Buttons, Sliders, TextFields and so on. I have a certain group of UI-Elements that I want the user to be able to change but only after he confirmed once, that he is sure he wants to change them. Any attempted change to any of those elements should be discarded, if the user does not confirm that he knows what he's doing.
I've made a very simple mockup.
public class App extends Application {
private Label label;
boolean changeConfirmed = false;
#Override
public void start(Stage stage) {
BorderPane pane = new BorderPane();
VBox container = new VBox();
Button button = new Button("Something");
Slider slider = new Slider(0,1,0.5);
TextField textField = new TextField();
label = new Label("Empty");
// Here should be the code I'm asking for
container.getChildren().addAll(button,slider,textField);
pane.setRight(container);
pane.setCenter(label);
Scene scene = new Scene(pane,400,300);
stage.setTitle("Alert Test");
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
private void handle(String string) {
label.setText(string);
}
private boolean changeConfirmed(){
if(changeConfirmed) {
return true;
}
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
alert.setTitle("Confirm Change");
alert.setHeaderText("A change occurred.");
alert.setContentText("Do you really want to change the value?");
Optional<ButtonType> buttonType = alert.showAndWait();
if(buttonType.isPresent() && buttonType.get().equals(ButtonType.OK)) {
changeConfirmed = true;
return true;
}
return false;
}
}
I want that, as long as the boolean changeConfirmed is false, any attempted change to any of the UI elements is interrupted by the changeConfirmed()-Method. Once the method returned true, the boolean is true and no further confirmation is needed (it would otherwise become very tiresome to confirm all changes for every UI-Element). The boolean changeConfirmed is made false again at a later time in the program and not relevant here.
What is absolut important to me is that the value of the property doesn't change before the confirmation has been passed.
I've tried using ChangeListeners (obviously) but to my knowledge, the value of the property has already been changed when the listener is executed. So the Alert comes to late (I might be wrong here, it's my current understanding)
I've also tried InvalidationListeners. Those seem to be processed before a change to the property is actually made, but I don't know how to make sure that the property's value doesn't change if the change-Alert is not confirmed.
How can I solve this problem?
Just check in the handlers for the controls if changeConfirmed is false; if it is show the confirm dialog. Then, still in the event handler, check again if changeConfirmed is true; if it is, change the value. If you want to revert the value in the control if the users denies confirmation, then write the code for that.
Here is a quick example based loosely on your example:
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.io.IOException;
import java.util.Optional;
public class HelloApplication extends Application {
private boolean changeConfirmed = false ;
#Override
public void start(Stage stage) throws IOException {
VBox root = new VBox(5);
HBox nameBox = new HBox(5);
HBox ageBox = new HBox(5);
Label nameLabel = new Label();
Label ageLabel = new Label();
Slider slider = new Slider();
slider.setShowTickMarks(true);
slider.setShowTickLabels(true);
slider.setMin(0);
slider.setMax(120);
TextField nameTF = new TextField();
slider.valueChangingProperty().addListener((obs, isFinishedChanging, isNowChanging) -> {
if (isFinishedChanging) {
if (!changeConfirmed) {
showConfirmDialog();
}
if (changeConfirmed) {
ageLabel.setText(String.valueOf((int)slider.getValue()));
} else {
// revert to current value:
String ageStr = ageLabel.getText();
int age = ageStr.isEmpty() ? 0 : Integer.parseInt(ageStr);
slider.setValue(age);
}
}
});
nameTF.setOnAction(e -> {
if (! changeConfirmed) {
showConfirmDialog();
}
if (changeConfirmed) {
nameLabel.setText(nameTF.getText());
} else {
nameTF.setText(nameLabel.getText());
}
});
nameBox.getChildren().addAll(new Label("Name:"), nameLabel);
ageBox.getChildren().addAll(new Label("Age:"), ageLabel);
Button clearConfirm = new Button("Request to confirm changes");
clearConfirm.setOnAction(e -> changeConfirmed = false);
root.getChildren().addAll(nameBox, ageBox, nameTF, slider, clearConfirm);
root.setAlignment(Pos.CENTER);
Scene scene = new Scene(root, 400, 300);
stage.setScene(scene);
stage.show();
}
private void showConfirmDialog() {
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
alert.setTitle("Confirm Change");
alert.setHeaderText("A change occurred.");
alert.setContentText("Do you really want to change the value?");
Optional<ButtonType> buttonType = alert.showAndWait();
if(buttonType.isPresent() && buttonType.get().equals(ButtonType.OK)) {
changeConfirmed = true;
}
}
public static void main(String[] args) {
launch();
}
}
Update: inserted project (reproductible example) and last details
I have an application that is displaying a TableView filled with simple objects (observable list)
I want to display selected items (rows) in a TableView highlighting them.
Ex: If the user press 'Insert' i update (in the observable list) the object which is selected. A boolean in the object will do. The objects are 'marked'; the user can do something else.
I cannot use myTableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); as the user will loose the selection as soon as a key is pressed or a mouse clik happens.
With that in mind, this means i'm managing keyboard like so:
public boolean implementListenerPackage(Scene s) {
//some init then...
s.setOnKeyReleased(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent ke) {
switch (ke.getCode()) {
case INSERT:
setObservableListObjectSelect();
break;
}
}
});
}
The object in the observable list is rather simple:
public class myObject {
private boolean selected;
private String otherStuff = "";
// Then constructor , getters and setters
And i have a MouseEvent management to handle other actions as well. When i'm creating my TableView i add this:
myTableView.setRowFactory(rftv-> {
TableRow<type> rowObj = new TableRow<>();
rowObj.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
if (e.getClickCount() == 2 && (!rowObj.isEmpty())) {
SomeClass.doSomethingForDoubleClik()
} else { // Simple clic
SomeClass.doSomethingForSimpleClik()
}
}
});
return rowObj;
});
My goal is to change the CSS of a row when the myObject boolean changes. And by doing so make the user selection highlighted even though the user click on another row.
I tried :
A lot of reading on this website, but only to find simple examples. Not the one i have with several obstacles at the same time.
to implement more things in the rowFactory. But i couldn't do it. If it compiles, then it crashes with a nullpointerexception. I never been good at those tricky syntax things.
to do it directly from the keyboard management but only to find it's rather complicated. I have to get the selected object, update it, then find the selected cells and change the CSS one by one (column logic).
to implement a "binding" (An example here) between the object and the row but only to find myself wondering how to implement it as it was an answer for a different problem 'flavor'.
It's probably in front of my eyes but i can't see it.
update:
Bear in mind that
the keyboard management is centralized.
there is already a factory set on the tableView.
They are several TableView in the original application so i whish the CSS style change automatically and not in some kind of 'hardcoded' fashion.
The minimal code:
package application;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.VBox;
public class Main extends Application {
Label lbl01 = new Label("Information");
#Override
public void start(Stage primaryStage) {
try {
TableView tv1 = new TableView();
TableColumn<MyObject, String> column1 = new TableColumn<>("Col 01");
column1.setCellValueFactory(new PropertyValueFactory<>("keyboardSelected"));
TableColumn<MyObject, String> column2 = new TableColumn<>("Col 02");
column2.setCellValueFactory(new PropertyValueFactory<>("dataA"));
TableColumn<MyObject, String> column3 = new TableColumn<>("Col 03");
column3.setCellValueFactory(new PropertyValueFactory<>("dataB"));
TableColumn<MyObject, String> column4 = new TableColumn<>("Col 04");
column4.setCellValueFactory(new PropertyValueFactory<>("dataC"));
tv1.getColumns().add(column1);
tv1.getColumns().add(column2);
tv1.getColumns().add(column3);
tv1.getColumns().add(column4);
ObservableList<MyObject> olm1 = FXCollections.observableArrayList();
olm1.addAll(new MyObject(false, "Object01 A", "Object01 B", "Object01 C"),
new MyObject(false, "Object02 A", "Object02 B", "Object02 C"),
new MyObject(false, "Object03 A", "Object03 B", "Object03 C"),
new MyObject(false, "Object04 A", "Object04 B", "Object04 C"),
new MyObject(false, "Object05 A", "Object05 B", "Object05 C")
);
tv1.setItems(olm1);
tv1.setRowFactory(dc -> {
TableRow<MyObject> rowObj = new TableRow<>();
rowObj.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
if (e.getClickCount() == 2 && (!rowObj.isEmpty())) {
lbl01.setText("Double click on line " + tv1.getSelectionModel().getSelectedIndex());
} else {
lbl01.setText("Single click on line " + +tv1.getSelectionModel().getSelectedIndex());
}
}
});
return rowObj;
});
VBox root = new VBox(tv1, lbl01);
Scene scene = new Scene(root, 512, 640);
primaryStage.setScene(scene);
KeyboardManagement km = new KeyboardManagement();
km.implementListener(scene, lbl01, tv1);
primaryStage.show();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
package application;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TableView;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
public class KeyboardManagement {
public KeyboardManagement() {
}
public boolean implementListener(Scene s, Label l, TableView tv1) {
boolean retRep = false;
try {
s.setOnKeyReleased(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent ke) {
if (ke.getCode() == KeyCode.SPACE) {
MyObject m1 = (MyObject) tv1.getSelectionModel().getSelectedItem();
tv1.refresh();
m1.setKeyboardSelected(!m1.isKeyboardSelected());
l.setText("Space was pressed / TableView Line :" + tv1.getSelectionModel().getSelectedIndex()
+ ". " + m1.toString());
}
}
});
} catch (Exception e) {
}
return retRep;
}
}
package application;
public class MyObject {
private boolean keyboardSelected;
private String dataA;
private String dataB;
private String dataC;
public MyObject(boolean keyboardSelected, String dataA, String dataB, String dataC) {
super();
this.keyboardSelected = keyboardSelected;
this.dataA = dataA;
this.dataB = dataB;
this.dataC = dataC;
}
public boolean isKeyboardSelected() {
return keyboardSelected;
}
public void setKeyboardSelected(boolean keyboardSelected) {
this.keyboardSelected = keyboardSelected;
}
public String getDataA() {
return dataA;
}
public void setDataA(String dataA) {
this.dataA = dataA;
}
public String getDataB() {
return dataB;
}
public void setDataB(String dataB) {
this.dataB = dataB;
}
public String getDataC() {
return dataC;
}
public void setDataC(String dataC) {
this.dataC = dataC;
}
#Override
public String toString() {
return "MyObject [keyboardSelected=" + keyboardSelected + ", dataA=" + dataA + ", dataB=" + dataB + ", dataC="
+ dataC + "]";
}
}
Found it!
Simple and complex at the same time.
The trick is to insert at the right place a listener on the object property.
Inside the rowfactory definition.
Before the #override
Pass the row object as an argument and 'voila'.
Something like :
rowObj.itemProperty().addListener((observable, oldValue, newValue) -> updateTableRowCss(rowObj, newValue));
Notes:
You can add several listeners on different object properties.
I strongly suggest you create a method the listener will call. Sometimes the IDE syntax analyzers are making bloodshed in the screen. It doesn't help finding errors in this type of code.
From the tests i made, it seems that the modifications on the style you do will preval (like more important) on the already loaded style. Which is fine.
This way you keep a centralized keyboard management (like in this example) and the mouse event management in the factory.
The cursor still moves freely and you keep an object selection you can reuse later.
The modified example code from above:
package application;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.VBox;
public class Main extends Application {
Label lbl01 = new Label("Information");
#Override
public void start(Stage primaryStage) {
try {
TableView tv1 = new TableView();
TableColumn<MyObject, String> column1 = new TableColumn<>("Col 01");
column1.setCellValueFactory(new PropertyValueFactory<>("keyboardSelected"));
TableColumn<MyObject, String> column2 = new TableColumn<>("Col 02");
column2.setCellValueFactory(new PropertyValueFactory<>("dataA"));
TableColumn<MyObject, String> column3 = new TableColumn<>("Col 03");
column3.setCellValueFactory(new PropertyValueFactory<>("dataB"));
TableColumn<MyObject, String> column4 = new TableColumn<>("Col 04");
column4.setCellValueFactory(new PropertyValueFactory<>("dataC"));
tv1.getColumns().add(column1);
tv1.getColumns().add(column2);
tv1.getColumns().add(column3);
tv1.getColumns().add(column4);
ObservableList<MyObject> olm1 = FXCollections.observableArrayList();
olm1.addAll(new MyObject(false, "Object01 A", "Object01 B", "Object01 C"),
new MyObject(false, "Object02 A", "Object02 B", "Object02 C"),
new MyObject(false, "Object03 A", "Object03 B", "Object03 C"),
new MyObject(false, "Object04 A", "Object04 B", "Object04 C"),
new MyObject(false, "Object05 A", "Object05 B", "Object05 C")
);
tv1.setItems(olm1);
tv1.setRowFactory(dc -> {
TableRow<MyObject> rowObj = new TableRow<>();
rowObj.itemProperty().addListener((observable, oldValue, newValue) -> updateTableRowCss(rowObj, newValue));
rowObj.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
if (e.getClickCount() == 2 && (!rowObj.isEmpty())) {
lbl01.setText("Double click on line " + tv1.getSelectionModel().getSelectedIndex());
} else {
lbl01.setText("Single click on line " + +tv1.getSelectionModel().getSelectedIndex());
}
}
});
return rowObj;
});
VBox root = new VBox(tv1, lbl01);
Scene scene = new Scene(root, 512, 640);
primaryStage.setScene(scene);
KeyboardManagement km = new KeyboardManagement();
km.implementListener(scene, lbl01, tv1);
primaryStage.show();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
private void updateTableRowCss(TableRow<MyObject> rowObj, MyObject item) {
// On doit vérifier si null
if (item != null ) {
if (item.isKeyboardSelected()) {
rowObj.setStyle("-fx-background-color: #FF000080;");
} else {
rowObj.setStyle("");
}
}
}
}
Final result
i have following problem:
listView.getSelectionModel().selectedItemProperty().addListener((obs, oldV, newV) -> {
if (!selectionChanged(newV)) {
lististView.getSelectionModel().select(oldV);
}
});
selectionChanged(newV) just pop a Message Box with "You wanna select new value?" and return the result as a boolean value. When i click on cancel it returns false, so that the condition is true. But because of .select(oldV); it would be result in a loop. How can i cancel or rollback a selection in javafx listview?
Create a boolean flag and don't ask the user if it is not set. You need to wrap the change back to the original value (if the user vetoes the change) in a Platform.runLater(...) to avoid issues with conflicts in the selection model's selectedItems list (basically you cannot change a list while another list change is being processed).
private boolean checkSelectionChange = true ;
// ...
listView.getSelectionModel().selectedItemProperty().addListener((obs, oldV, newV) -> {
if (checkSelectionChange) {
checkSelectionChange = false ;
Platform.runLater(() -> {
if (!selectionChanged(newV)) {
lististView.getSelectionModel().select(oldV);
}
checkSelectionChange = true ;
});
}
});
SSCCE:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.ButtonType;
import javafx.scene.control.ListView;
import javafx.stage.Stage;
public class ListViewSelectionUserVeto extends Application {
private boolean checkSelectionChange = true ;
#Override
public void start(Stage primaryStage) {
ListView<String> listView = new ListView<>();
listView.getItems().addAll("One", "Two", "Three", "Four");
listView.getSelectionModel().selectedItemProperty().addListener((obs, oldValue, newValue) -> {
if (checkSelectionChange) {
checkSelectionChange = false ;
Platform.runLater(() -> {
if (! verifySelectionChange(newValue)) {
listView.getSelectionModel().select(oldValue);
}
checkSelectionChange = true ;
});
}
});
Scene scene = new Scene(listView);
primaryStage.setScene(scene);
primaryStage.show();
}
private boolean verifySelectionChange(String newValue) {
Alert alert = new Alert(AlertType.CONFIRMATION);
alert.setContentText("Change selection to "+newValue);
return alert.showAndWait().filter(ButtonType.OK::equals).isPresent();
}
public static void main(String[] args) {
launch(args);
}
}
I'm building a small program that's got a settings screen where the user can choose a color theme. I used a JRadioButton for every color but I can't get the program to save it for the next time I run the program. Here's my code:
private Preferences userPreferences = Preferences.userRoot();
MainWindow() {
super("Timer");
setLayout(new GridLayout(4,3,5,5));
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setSize(650,290);
setResizable(false);
userPreferences.get("COLOR_CODE", "#ededed");
//MORE CODE HERE
#Override
public void menuSelected(MenuEvent e) {
//Open settings window
if(e.getSource().equals(settings)) {
about.setEnabled(false);
exit.setEnabled(false);
settingsFrame = new SettingsWindow();
settingsFrame.setAlwaysOnTop(true);
settingsFrame.setLocationRelativeTo(null);
settingsFrame.setVisible(true);
//WindowListener for closing the settings window
settingsFrame.addWindowListener(new WindowAdapter() {
#Override
public void windowClosing(WindowEvent we) {
settings.setEnabled(true);
about.setEnabled(true);
exit.setEnabled(true);
}
});
//Adding action listeners to the radio buttons
settingsFrame.defaultColorRB.addActionListener(ee -> {
settingsFrame.setColor("#ededed");
getContentPane().setBackground(Color.decode(settingsFrame.getColorCode()));
userPreferences.put("COLOR_CODE", settingsFrame.getColorCode());
});
settingsFrame.whiteColorRB.addActionListener(ee -> {
settingsFrame.setColor("#FFFFFF");
getContentPane().setBackground(Color.decode(settingsFrame.getColorCode()));
userPreferences.put("COLOR_CODE", settingsFrame.getColorCode());
});
settingsFrame.lightGrayColorRB.addActionListener(ee -> {
settingsFrame.setColor("#D2D8DF");
getContentPane().setBackground(Color.decode(settingsFrame.getColorCode()));
userPreferences.put("COLOR_CODE", settingsFrame.getColorCode());
});
settingsFrame.darkGrayColorRB.addActionListener(ee -> {
settingsFrame.setColor("#A2A4A6");
getContentPane().setBackground(Color.decode(settingsFrame.getColorCode()));
userPreferences.put("COLOR_CODE", settingsFrame.getColorCode());
});
settingsFrame.yellowColorRB.addActionListener(ee -> {
settingsFrame.setColor("#FBFF00");
getContentPane().setBackground(Color.decode(settingsFrame.getColorCode()));
userPreferences.put("COLOR_CODE", settingsFrame.getColorCode());
});
settingsFrame.pinkColorRB.addActionListener(ee -> {
settingsFrame.setColor("#F58EB3");
getContentPane().setBackground(Color.decode(settingsFrame.getColorCode()));
userPreferences.put("COLOR_CODE", settingsFrame.getColorCode());
});
settingsFrame.cyanColorRB.addActionListener(ee -> {
settingsFrame.setColor("#32D0F7");
getContentPane().setBackground(Color.decode(settingsFrame.getColorCode()));
userPreferences.put("COLOR_CODE", settingsFrame.getColorCode());
});
}
}
Please can anyone let me know why the code above doesn't save the user's choice of color?
If saving the color code, as posted in the question, you will need to test the saved color code and select the respective button. Example, very basic, just for demonstration:
// assuming userPreferences is java.util.prefs.Preferences
String colorCode = userPreferences.get("COLOR_CODE", "#ededed");
switch (colorCode.toLowerCase()) {
case "#ededed": defaultColorRB.setSelected(true); break;
case "#ffffff": whiteColorRB.setSelected(true); break;
...
default: /* none selected - not sure what you want to do in that case */ break;
}
Hint 1: use constants for the preferences key "COLOR_CODE" and the colors (e.g. "ededed")
Hint 2: since you are using radio buttons, that is, some distinct colors, it would be easier save the selected radio button index (or some constant) instead of the color code itself (e.g. #ededed). If the user is allowed to select ANY color, not only the radio buttons ones, you sure must save the color code.
Here an example for the second hint:
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.prefs.Preferences;
import javax.swing.ButtonGroup;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.SwingUtilities;
import javax.swing.border.TitledBorder;
public class Radio {
private static final String PREF_BUTTON_INDEX = "color button";
private final Preferences prefs = Preferences.userNodeForPackage(getClass());
private static final String[] COLORS = { "#ededed", "#ffffff", "#a00000" };
private List<JRadioButton> buttons;
private JLabel output; // so we can see something, simulating usage of color
private Radio() {
buttons = new ArrayList<>();
SwingUtilities.invokeLater(this::initGUI);
}
private void initGUI() {
JPanel panel = new JPanel();
ButtonGroup group = new ButtonGroup();
for (int i = 0; i < COLORS.length; i++) {
JRadioButton button = new JRadioButton(COLORS[i]);
button.addActionListener(this::buttonSelected);
group.add(button);
panel.add(button);
buttons.add(button);
}
output = new JLabel("undefined");
output.setBorder(new TitledBorder("Color:"));
panel.add(output);
int colorIndex = prefs.getInt(PREF_BUTTON_INDEX, -1);
if (colorIndex != -1) {
buttons.get(colorIndex).setSelected(true);
output.setText(COLORS[colorIndex]);
}
JOptionPane.showMessageDialog(null, panel);
}
private void buttonSelected(ActionEvent ev) {
int index = buttons.indexOf(ev.getSource());
if (index != -1) {
output.setText(COLORS[index]);
prefs.putInt(PREF_BUTTON_INDEX, index);
}
}
public static void main(String[] args) {
new Radio();
}
}
One good usage first is to create a node and not to save the preferences directly in the user root node.
Try this:
// This will define a node in which the preferences can be stored
prefs = Preferences.userRoot().node(this.getClass().getName());
And see if this changing something.
I have a listview that uses a CheckBoxListCell to display a list with checkboxes next to the items. How do I add a listener to this checkbox to know when an item as been selected or unselected?
Solution
You don't add a listener to the checkbox. You add a listener to the observable property of the object which was associated with the checkbox by the CheckBoxListCell.forListView routine.
Setting up the association:
ListView<Task> checklist = new ListView<>(tasks);
checklist.setCellFactory(CheckBoxListCell.forListView(Task::selectedProperty));
Adding a listener for all items:
tasks.forEach(task -> task.selectedProperty().addListener((observable, wasSelected, isSelected) -> {
if (isSelected) {
// . . .
} else {
// . . .
}
}));
Documentation
The process is described in the CheckBoxListCell.forListView javadoc like so:
getSelectedProperty - A Callback that, given an object of type T
(which is a value taken out of the ListView.items list), will
return an ObservableValue that represents whether the given
item is selected or not. This ObservableValue will be bound
bidirectionally (meaning that the CheckBox in the cell will set/unset
this property based on user interactions, and the CheckBox will
reflect the state of the ObservableValue, if it changes externally).
Sample Program
A sample program which demonstrated some of the patterns which could be used with CheckBoxListCell:
import javafx.application.Application;
import javafx.beans.property.*;
import javafx.collections.*;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.scene.control.cell.CheckBoxListCell;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.StringConverter;
import java.util.*;
import java.util.stream.Collectors;
public class CheckList extends Application {
#Override
public void start(Stage stage) throws Exception{
ObservableList<Task> tasks = FXCollections.observableArrayList(
Arrays.stream(taskNames).map(Task::new).collect(Collectors.toList())
);
ListView<String> reactionLog = new ListView<>();
tasks.forEach(task -> task.selectedProperty().addListener((observable, wasSelected, isSelected) -> {
if (isSelected) {
reactionLog.getItems().add(reactionStrings.get(task.getName()));
reactionLog.scrollTo(reactionLog.getItems().size() - 1);
}
}));
ListView<Task> checklist = new ListView<>(tasks);
checklist.setCellFactory(CheckBoxListCell.forListView(Task::selectedProperty, new StringConverter<Task>() {
#Override
public String toString(Task object) {
return object.getName();
}
#Override
public Task fromString(String string) {
return null;
}
}));
HBox layout = new HBox(10, checklist, reactionLog);
layout.setPrefSize(350, 150);
layout.setPadding(new Insets(10));
Scene scene = new Scene(layout);
stage.setScene(scene);
stage.show();
}
public static class Task {
private ReadOnlyStringWrapper name = new ReadOnlyStringWrapper();
private BooleanProperty selected = new SimpleBooleanProperty(false);
public Task(String name) {
this.name.set(name);
}
public String getName() {
return name.get();
}
public ReadOnlyStringProperty nameProperty() {
return name.getReadOnlyProperty();
}
public BooleanProperty selectedProperty() {
return selected;
}
public boolean isSelected() {
return selected.get();
}
public void setSelected(boolean selected) {
this.selected.set(selected);
}
}
public static void main(String[] args) {
launch(args);
}
private static final String[] taskNames = {
"Walk the dog",
"Skin the cat",
"Feed the pig"
};
private static final Map<String, String> reactionStrings = new HashMap<>();
static {
reactionStrings.put("Walk the dog", "The dog thanks you");
reactionStrings.put("Skin the cat", "The cat hates you");
reactionStrings.put("Feed the pig", "The pig wants more");
}
}
Sample output after selecting the first item once and the third item three times.
Here is an alternative if the item does not already have a property that indicates if it has been selected or not:
public class CheckedListViewCheckObserver<T> extends SimpleObjectProperty<Pair<T, Boolean>> {
BooleanProperty getObserverForObject(T object) {
BooleanProperty value = new SimpleBooleanProperty(false);
value.addListener((observable, oldValue, newValue) -> {
CheckedListViewCheckObserver.this.set(new Pair<>(object, newValue));
});
return value;
}
}
Then to use it, you simply do:
CheckedListViewCheckObserver observer = new CheckedListViewCheckObserver<>();
checklist.setCellFactory(CheckBoxListCell.forListView(observer::getObserverForObject));
Now you can set a listener to listen for any changes:
observer.addListener((obs, old, curr) -> {
if (curr.getValue()) {
System.out.println("You have checked " + curr.getKey());
} else {
System.out.println("You have unchecked " + curr.getKey());
}
});
The advantage of this method is that it does not depend on the objects being used; Instead, since it is generic, you can simply attach it to an already existing listview and it starts working off the bat.
Hope this helps someone.