Replace a selected Label with a TextField - java

I have created ListView of Labels using :
ListView<Label> list = new ListView<Label>();
Image folder = new Image(getClass().getResourceAsStream("folder.png"));
ObservableList<Label> data = FXCollections.observableArrayList();
for (int i = 0; i < 6; i++) {
Label lbl = new Label();
lbl.setText("label" + i);
lbl.setGraphic(new ImageView(folder));
lbl.setContentDisplay(ContentDisplay.LEFT);
lbl.setGraphicTextGap(10.2);
data.add(lbl);
}
list.setItems(data);
I want the user to be able to double click on any of the Labels within the ListView, the selected Label should be replaced with a TextField so that the user can enter a new label name dynamically.
After the user presses Enter the TextField should turn back into a Label.

Don't use Label as the type of data for the ListView. Use String. Then you can just use the standard TextFieldListCell which has exactly the functionality you describe. Since you want a graphic in the standard cell display, just subclass TextFieldListCell and override the appropriate methods to include the graphic when the text field is not displayed:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.ListView;
import javafx.scene.control.cell.TextFieldListCell;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.StringConverter;
import javafx.util.converter.DefaultStringConverter;
public class EditableListViewTest extends Application {
#Override
public void start(Stage primaryStage) {
ListView<String> list = new ListView<>();
Image testImg = new Rectangle(12, 12, Color.CORNFLOWERBLUE).snapshot(null, null);
for (int i = 0; i < 6; i++) {
list.getItems().add("label "+i);
}
StringConverter<String> identityStringConverter = new DefaultStringConverter();
list.setCellFactory(lv -> new TextFieldListCell<String>(identityStringConverter) {
private ImageView imageView = new ImageView(testImg);
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (! empty && ! isEditing()) {
setStaticGraphic();
}
}
#Override
public void cancelEdit() {
super.cancelEdit();
setStaticGraphic();
}
#Override
public void commitEdit(String newValue) {
super.commitEdit(newValue);
setStaticGraphic();
}
private void setStaticGraphic() {
setGraphic(imageView);
setContentDisplay(ContentDisplay.LEFT);
setGraphicTextGap(10.2);
}
});
list.setEditable(true);
primaryStage.setScene(new Scene(new BorderPane(list), 250, 400));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

Related

JavaFX: Make Chips Editable in JFXChipView

I want to ask if it is possible to make a chip in JFXChipView editable once it has been set.
You can create your own JFXChip and implement a behavior to enable editing. First, you need to have an editable label. I looked up online and I found this post: JavaFX custom control - editable label. Then, you can extend JFXChip to use that EditableLabel:
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXChip;
import com.jfoenix.controls.JFXChipView;
import com.jfoenix.svg.SVGGlyph;
import javafx.beans.binding.Bindings;
import javafx.beans.property.Property;
import javafx.scene.layout.HBox;
public class EditableChip<T> extends JFXChip<Property<T>> {
protected final HBox root;
public EditableChip(JFXChipView<Property<T>> view, Property<T> item) {
super(view, item);
JFXButton closeButton = new JFXButton(null, new SVGGlyph());
closeButton.getStyleClass().add("close-button");
closeButton.setOnAction(event -> {
view.getChips().remove(item);
event.consume();
});
// Create the label with an initial value from the item
String initialValue = view.getConverter().toString(item);
EditableLabel label = new EditableLabel(initialValue);
label.setMaxWidth(100);
// Bind the item to the text in the label
item.bind(Bindings.createObjectBinding(() -> view.getConverter().fromString(label.getText()).getValue(), label.textProperty()));
root = new HBox(label, closeButton);
getChildren().setAll(root);
}
}
Note: I am using Property<T> instead of using the desired class T because JFXChipView stores the item the first time you add it. And in that case, you're going to get the values as you entered them the first time when calling JFXChipView#getChips().
Sample application:
import com.jfoenix.controls.JFXChipView;
import javafx.application.Application;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleStringProperty;
import javafx.scene.Scene;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.StringConverter;
public class EditableChipViewApp extends Application {
#Override
public void start(Stage primaryStage) {
JFXChipView<Property<String>> chipView = new JFXChipView<>();
chipView.setChipFactory(EditableChip::new);
chipView.setConverter(new StringConverter<Property<String>>() {
#Override
public String toString(Property<String> object) {
return object == null ? null : object.getValue();
}
#Override
public Property<String> fromString(String string) {
return new SimpleStringProperty(string);
}
});
VBox container = new VBox(chipView);
Scene scene = new Scene(container, 800, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Result:
This is how you get the actual values of the chips:
List<String> chipsValues = chipView.getChips().stream().map(Property::getValue).collect(Collectors.toList());

TableView: Get Notified When Scroll Reaches Bottom/ Top of Table

I can listen for scroll events:
tableView.addEventFilter(javafx.scene.input.ScrollEvent.SCROLL,
new EventHandler<javafx.scene.input.ScrollEvent>() {
#Override
public void handle(final javafx.scene.input.ScrollEvent scrollEvent) {
System.out.println("Scrolled.");
}
});
But How can I be notified if the bottom/ top of the table is reached?
A simple way of doing this is to retrieve the ScrollBar using a lookup:
ScrollBar tvScrollBar = (ScrollBar) tableView.lookup(".scroll-bar:vertical");
You can then add a listener to check if it's reached the bottom (the valueProperty of the ScrollBar is represented by the percentage it's been scrolled, so 0.0 is the top and 1.0 is the bottom):
tvScrollBar.valueProperty().addListener((observable, oldValue, newValue) -> {
if ((Double) newValue == 1.0) {
System.out.println("Bottom!");
}
});
Below is a simple MCVE that demonstrates how to put it all together:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.ScrollBar;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class ScrollBarNotify extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
// Simple interface
VBox root = new VBox(5);
root.setPadding(new Insets(10));
root.setAlignment(Pos.CENTER);
// Simple TableView to demonstrate
TableView<String> tableView = new TableView<>();
TableColumn<String, String> column = new TableColumn<>("Text");
column.setCellValueFactory(f -> new SimpleStringProperty(f.getValue()));
tableView.getColumns().add(column);
// Add some sample items to our TableView
for (int i = 0; i < 100; i++) {
tableView.getItems().add("Item #" + i);
}
// Now, let's add a listener to the TableView's scrollbar. We can only access the ScrollBar after the Scene is
// rendered, so we need to do schedule this to run later.
Platform.runLater(() -> {
ScrollBar tvScrollBar = (ScrollBar) tableView.lookup(".scroll-bar:vertical");
tvScrollBar.valueProperty().addListener((observable, oldValue, newValue) -> {
if ((Double) newValue == 1.0) {
System.out.println("Bottom!");
}
});
});
// Finally, add the TableViewto our layout
root.getChildren().add(tableView);
// Show the Stage
primaryStage.setWidth(300);
primaryStage.setHeight(300);
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
}
If you're using Java 10+ you can subclass TableViewSkin and get access to the VirtualFlow. The latter class has the position property which you can use to know if the top or bottom has been reached.
Here's an example using custom events:
MyEvent.java
import javafx.event.Event;
import javafx.event.EventType;
public class MyEvent extends Event {
public static final EventType<MyEvent> ANY = new EventType<>(Event.ANY, "MY_EVENT");
public static final EventType<MyEvent> TOP_REACHED = new EventType<>(ANY, "TOP_REACHED");
public static final EventType<MyEvent> BOTTOM_REACHED = new EventType<>(ANY, "BOTTOM_REACHED");
public MyEvent(EventType<? extends MyEvent> eventType) {
super(eventType);
}
}
MyTableViewSkin.java
import javafx.scene.control.TableView;
import javafx.scene.control.skin.TableViewSkin;
public class MyTableViewSkin<T> extends TableViewSkin<T> {
public MyTableViewSkin(TableView<T> control) {
super(control);
getVirtualFlow().positionProperty().addListener((obs, oldVal, newVal) -> {
if (newVal.doubleValue() == 0.0) {
control.fireEvent(new MyEvent(MyEvent.TOP_REACHED));
} else if (newVal.doubleValue() == 1.0) {
control.fireEvent(new MyEvent(MyEvent.BOTTOM_REACHED));
}
});
}
}
App.java
import javafx.application.Application;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.stage.Stage;
public class App extends Application {
#Override
public void start(Stage primaryStage) {
var table = new TableView<Integer>();
for (int i = 0; i < 250; i++) {
table.getItems().add(i);
}
var column = new TableColumn<Integer, Number>("Value");
column.setCellValueFactory(features -> new SimpleIntegerProperty(features.getValue()));
table.getColumns().add(column);
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
table.setSkin(new MyTableViewSkin<>(table));
table.addEventHandler(MyEvent.ANY, event -> System.out.printf("%s%n", event.getEventType()));
primaryStage.setScene(new Scene(table, 500, 300));
primaryStage.setTitle("Example");
primaryStage.show();
}
}
In this example I manually call table.setSkin. Another option is to subclass TableView and override createDefaultSkin which returns the skin you want to use.
tableView.addEventFilter(javafx.scene.input.ScrollEvent.SCROLL,
new EventHandler<javafx.scene.input.ScrollEvent>() {
#Override
public void handle(final javafx.scene.input.ScrollEvent scrollEvent) {
Object virtualFlow = ((javafx.scene.control.SkinBase<?>) tableView.getSkin()).getChildren().get(1);
double position = -1;
try {
position = (double) virtualFlow.getClass().getMethod("getPosition").invoke(virtualFlow);
} catch (Exception ignored) { }
if(position == 0.0) {
System.out.println("scrolled to top!");
}
else if(position == 1.0) {
System.out.println("scrolled to bottom!");
}
}
});

javaFX:listview with Radio Button

I have a list with items which should carry RadioButton with list items.
the ListView is an observable ArrayList with data I want to add radio Button with each item in list View.
Create a custom ListCell and set the graphic of the ListCell to a RadioButton. You can add more functionality inside updateItem() if required.
Output
Complete Example
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.RadioButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class RadioButtonListView extends Application {
public static final ObservableList names =
FXCollections.observableArrayList();
private ToggleGroup group = new ToggleGroup();
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("List View Sample");
final ListView listView = new ListView();
listView.setPrefSize(200, 250);
listView.setEditable(true);
names.addAll(
"Adam", "Alex", "Alfred", "Albert",
"Brenda", "Connie", "Derek", "Donny",
"Lynne", "Myrtle", "Rose", "Rudolph",
"Tony", "Trudy", "Williams", "Zach"
);
listView.setItems(names);
listView.setCellFactory(param -> new RadioListCell());
StackPane root = new StackPane();
root.getChildren().add(listView);
primaryStage.setScene(new Scene(root, 200, 250));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
private class RadioListCell extends ListCell<String> {
#Override
public void updateItem(String obj, boolean empty) {
super.updateItem(obj, empty);
if (empty) {
setText(null);
setGraphic(null);
} else {
RadioButton radioButton = new RadioButton(obj);
radioButton.setToggleGroup(group);
// Add Listeners if any
setGraphic(radioButton);
}
}
}
}

FilteredList breaks after entering a space

I have a ListView with a TextField above it. If a user enters in a search query into the textfield, the listview will update and filter itself to show relevant results.
The ListView shows items from a FilteredList, which is filled with Employee objects. Each Employee has a first and last name.
package application.ctrl;
import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.collections.transformation.FilteredList;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.geometry.Pos;
import javafx.geometry.Side;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.CustomMenuItem;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import application.Main;
import application.objects.Employee;
import application.objects.EmployeeDatabase;
public class EmployeePickerWidget extends VBox implements Initializable {
#FXML
private TextField textField;
#FXML
private Button addNewEmployee;
#FXML
private ListView<Employee> employeeList;
private FilteredList<Employee> filteredList;
private ContextMenu cm;
private CustomMenuItem item;
private ClickedEmployeeInterface parent;
public EmployeePickerWidget(ClickedEmployeeInterface parent) {
FXMLLoader loader = new FXMLLoader(this.getClass().getResource(
Main.EMPLOYEE_PICKER));
loader.setRoot(this);
loader.setController(this);
try {
loader.load();
} catch (IOException e) {
e.printStackTrace();
}
this.parent = parent;
}
#Override
public void initialize(URL location, ResourceBundle resources) {
setupEmployeeListView();
setupTextField();
}
private void setupEmployeeListView() {
filteredList = new FilteredList<Employee>(EmployeeDatabase.getInstance()
.getObservableList());
employeeList = new ListView<Employee>();
employeeList.setItems(filteredList);
employeeList.setOnMouseClicked(arg0 -> {
if (employeeList.getSelectionModel().getSelectedItem() != null) {
cm.hide();
parent.handleClickedEmployee();
}
});
}
private void setupTextField() {
textField.textProperty().addListener(
(observable, oldValue, newValue) -> {
filteredList.setPredicate(employee -> {
return filterHelper(employee, newValue);
});
});
textField.setText(" ");
textField.setText("");
textField.setOnMouseClicked(event -> cm
.show(textField, Side.BOTTOM, 0, 0));
cm = new ContextMenu();
item = new CustomMenuItem();
VBox container = new VBox();
container.setAlignment(Pos.CENTER_RIGHT);
container.getChildren().add(employeeList);
Button defineEmployeeBtn = new Button("Define New Employee");
defineEmployeeBtn.setOnAction(event -> {
FXMLLoader loader = new FXMLLoader(getClass().getResource(
Main.DEFINE_NEW_EMPLOYEE));
Parent root = null;
try {
root = loader.load();
} catch (IOException e) {
e.printStackTrace();
}
Scene newScene = new Scene(root);
Stage newStage = new Stage();
newStage.setScene(newScene);
newStage.show();
});
container.getChildren().add(defineEmployeeBtn);
item.setContent(container);
cm.getItems().add(item);
}
private boolean filterHelper(Employee employee, String query) {
String first = employee.getFirst().toLowerCase(), last = employee
.getLast().toLowerCase();
String[] querySplit = query.replace(",", "\\s").split("\\s+");
int length = querySplit.length;
for (int i = 0; i < length; i++)
querySplit[i] = querySplit[i].toLowerCase();
if (length == 1) {
if (first.contains(querySplit[0]) || last.contains(querySplit[0]))
return true;
else
return false;
} else if (length == 2) {
if (first.contains(querySplit[0]) || last.contains(querySplit[0]))
if (first.contains(querySplit[1]) || last.contains(querySplit[1]))
return true;
return false;
} else if (length == 3) {
return false;
}
return false;
}
public Employee getEmployee() {
return employeeList.getSelectionModel().getSelectedItem();
}
#FXML
public void addNewEmployee() {
}
}
interface ClickedEmployeeInterface {
void handleClickedEmployee();
}
If there were 3 employees named "Donald Trump", "Donald Smith", and "Donald Jackson" in the database, then the following needs to happen:
Typing up to the word "Donald" will show all 3 results.
Typing a space after Donald (resulting in "Donald ") will still show 3 results.
Typing a T after the previous query (resulting in "Donald T") should only show 1 result.
The problem is, after I enter in a space, the ListView breaks, and all of my Employees disappear from the ListView. When I click outside of the textfield and click back in again, it triggers this:
textField.setOnMouseClicked(event -> cm
.show(textField, Side.BOTTOM, 0, 0));
And my ListView suddenly works again, showing that one Employee.
How do I make the ListView filter properly without having to click out and back in?
I do not have the FXML file, so I wasn't able to replicate your problem. There are multiple problems with your code and this is the not the optimum solution, still, I have edited your answer to give you hints and help you understand the areas where you might have committed logical errors
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.geometry.Side;
import javafx.scene.Scene;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Label;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
public class DemoList extends Application {
#Override
public void start(Stage stage) throws Exception {
GridPane gridPane = new GridPane();
Label label = new Label("Name");
final TextField textField = new TextField();
textField.setFocusTraversable(false);
textField.setPromptText("Please Type Here");
final ContextMenu cm = new ContextMenu();
final ObservableList<String> employeeList = FXCollections
.observableArrayList();
employeeList.addAll("Donald Duck", "Donald Mouse", "Donald Goofy");
textField.textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> arg0,
String arg1, String arg2) {
// To clear the Context Menu so that same items are not added
// multiple times
cm.getItems().clear();
for (String employee : employeeList) {
if (filterHelper(employee, arg2)) {
cm.getItems().add(new MenuItem(employee));
}
}
}
});
textField.setOnMouseClicked(new EventHandler<Event>() {
#Override
public void handle(Event arg0) {
// To clear the Context Menu so that same items are not added
// multiple times
cm.getItems().clear();
//Adding the data for initial click
for (String employee : employeeList) {
if (filterHelper(employee, textField.getText())) {
cm.getItems().add(new MenuItem(employee));
}
}
cm.show(textField, Side.BOTTOM, 0, 0);
}
});
gridPane.add(label, 0, 0);
gridPane.add(textField, 0, 1);
Scene scene = new Scene(gridPane, 300, 300);
stage.setScene(scene);
stage.show();
}
private boolean filterHelper(String employee, String query) {
//Splitting Employee name to fetch first and last name
String first = employee.split(" ")[0].toLowerCase(), last = employee
.split(" ")[1].toLowerCase();
String[] querySplit = query.replace(",", "\\s").split("\\s+");
int length = querySplit.length;
for (int i = 0; i < length; i++)
querySplit[i] = querySplit[i].toLowerCase();
/**
* Avoid adding unnecessary return statement
* I have removed all the 'return false' statements
* The last return will take care of all the 'return false'
*/
//only single word
if (length == 1) {
if (first.startsWith(querySplit[0])
|| last.startsWith(querySplit[0]))
return true;
}
//two words, considering first word is first name
//and second word is last name
else if (length == 2) {
if (first.startsWith(querySplit[0])
&& last.startsWith(querySplit[1]))
return true;
}
return false;
}
public static void main(String[] args) {
launch(args);
}
}

PaginationSample Javafx cannot assign a value to final variable pagination

I'm trying to reproduce a Pagination Sample from oracle samples, but when I imported the project something strange happened that I can not build and run the project:
The complete code is:
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.VBox;
import javafx.util.Callback;
public class PaginationSample extends Application {
private final Pagination pagination;
private Image[] images = new Image[7];
private void init(Stage primaryStage) {
Group root = new Group();
primaryStage.setScene(new Scene(root));
VBox outerBox = new VBox();
outerBox.setAlignment(Pos.CENTER);
//Images for our pages
for (int i = 0; i < 7; i++) {
images[i] = new Image(PaginationSample.class.getResource("animal" + (i + 1) + ".jpg").toExternalForm(), false);
}
pagination = PaginationBuilder.create().pageCount(7).pageFactory(new Callback<Integer, Node>() {
#Override public Node call(Integer pageIndex) {
return createAnimalPage(pageIndex);
}
}).build();
//Style can be numeric page indicators or bullet indicators
Button styleButton = ButtonBuilder.create().text("Toggle pagination style").onAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent me) {
if (!pagination.getStyleClass().contains(Pagination.STYLE_CLASS_BULLET)) {
pagination.getStyleClass().add(Pagination.STYLE_CLASS_BULLET);
} else {
pagination.getStyleClass().remove(Pagination.STYLE_CLASS_BULLET);
}
}
}).build();
outerBox.getChildren().addAll(pagination, styleButton);
root.getChildren().add(outerBox);
}
//Creates the page content
private VBox createAnimalPage(int pageIndex) {
VBox box = new VBox();
ImageView iv = new ImageView(images[pageIndex]);
box.setAlignment(Pos.CENTER);
Label desc = new Label("PAGE " + (pageIndex + 1));
box.getChildren().addAll(iv, desc);
return box;
}
#Override public void start(Stage primaryStage) throws Exception {
init(primaryStage);
primaryStage.show();
}
public static void main(String[] args) { launch(args); }
}
But netbeans show me the error "cannot assign a value to final variable pagination" for the following line:
pagination = PaginationBuilder.create().pageCount(7).pageFactory(new Callback<Integer, Node>() {
Someone can explain me what is going wrong??
A final field can be initialized only once. So the best place to initialize it is when its declared
private final Pagination pagination = new Pagination(...);
or it can be done in the constructor, since the constructor is assured to be called once per instance
private final Pagination pagination;
public PaginationSample() {
pagination = new Pagination(...);
}
final field cannot be initialized in a method because a method can be called multiple times once an instance of that class gets created

Categories