ListView - removeAll doesn't work? - java

(This is code from the book "JavaFX 2.0 by example" by Carl Dea - the code example is freely available at Apress so I'm sure they don't mind me using it here)
I have example code which works perfectly
package javafx2introbyexample.chapter1.recipe1_11;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.VPos;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
/**
*
* #author cdea
*/
public class CreatingAndWorkingWithObservableLists extends Application {
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Chapter 1-11 Creating and Working with ObservableLists");
Group root = new Group();
Scene scene = new Scene(root, 400, 250, Color.WHITE);
// create a grid pane
GridPane gridpane = new GridPane();
gridpane.setPadding(new Insets(5));
gridpane.setHgap(10);
gridpane.setVgap(10);
// candidates label
Label candidatesLbl = new Label("Candidates");
GridPane.setHalignment(candidatesLbl, HPos.CENTER);
gridpane.add(candidatesLbl, 0, 0);
Label heroesLbl = new Label("Heroes");
gridpane.add(heroesLbl, 2, 0);
GridPane.setHalignment(heroesLbl, HPos.CENTER);
// candidates
final ObservableList<String> candidates = FXCollections.observableArrayList("Superman",
"Spiderman",
"Wolverine",
"Police",
"Fire Rescue",
"Soldiers",
"Dad & Mom",
"Doctor",
"Politician",
"Pastor",
"Teacher");
final ListView<String> candidatesListView = new ListView<String>(candidates);
candidatesListView.setPrefWidth(150);
candidatesListView.setPrefHeight(150);
gridpane.add(candidatesListView, 0, 1);
// heros
final ObservableList<String> heroes = FXCollections.observableArrayList();
final ListView<String> heroListView = new ListView<String>(heroes);
heroListView.setPrefWidth(150);
heroListView.setPrefHeight(150);
gridpane.add(heroListView, 2, 1);
// select heroes
Button sendRightButton = new Button(">");
sendRightButton.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
String potential = candidatesListView.getSelectionModel().getSelectedItem();
if (potential != null) {
candidatesListView.getSelectionModel().clearSelection();
candidates.remove(potential);
heroes.add(potential);
}
}
});
// deselect heroes
Button sendLeftButton = new Button("<");
sendLeftButton.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
String notHero = heroListView.getSelectionModel().getSelectedItem();
if (notHero != null) {
heroListView.getSelectionModel().clearSelection();
heroes.remove(notHero);
candidates.add(notHero);
}
}
});
VBox vbox = new VBox(5);
vbox.getChildren().addAll(sendRightButton,sendLeftButton);
gridpane.add(vbox, 1, 1);
GridPane.setConstraints(vbox, 1, 1, 1, 2,HPos.CENTER, VPos.CENTER);
root.getChildren().add(gridpane);
primaryStage.setScene(scene);
primaryStage.show();
}
}
It is code for moving persons back and forth between two listviews, one at a time. What I want to do is make it possible to select and move several persons in one click.
The relevant excerpts I want to change are:
final ListView<String> candidatesListView = new ListView<String>(candidates);
candidatesListView.setPrefWidth(150);
candidatesListView.setPrefHeight(150);
gridpane.add(candidatesListView, 0, 1);
// heros
final ObservableList<String> heroes = FXCollections.observableArrayList();
final ListView<String> heroListView = new ListView<String>(heroes);
...
// select heroes
Button sendRightButton = new Button(">");
sendRightButton.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
String potential = candidatesListView.getSelectionModel().getSelectedItem();
if (potential != null) {
candidatesListView.getSelectionModel().clearSelection();
candidates.remove(potential);
heroes.add(potential);
}
}
});
// deselect heroes
Button sendLeftButton = new Button("<");
sendLeftButton.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
String notHero = heroListView.getSelectionModel().getSelectedItem();
if (notHero != null) {
heroListView.getSelectionModel().clearSelection();
heroes.remove(notHero);
candidates.add(notHero);
}
}
});
What I have tried changing:
First I add the following import:
import javafx.scene.control.SelectionMode;
Then I add the lines
candidatesListView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
heroListView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
beneath the respective declarations of the two lists.
Lastly I change the code of the handling of the eventbutton to
public void handle(ActionEvent event) {
ObservableList<String> potential = candidatesListView.getSelectionModel().getSelectedItems();
if (potential != null) {
System.out.println(potential);
candidates.removeAll(potential);
heroes.addAll(potential);
candidatesListView.getSelectionModel().clearSelection();
}
}
That is- I change it to getSelectedItem_s_, then I addAll and removeAll instead of merely adding/removing one person. This just leaves the listView blank when I try to move several people over. What gives?
Ps. I also tried just adding/removing several people one at a time by iterating over the list "potential", but that also gave the wrong result.

Unfortunately you've met a bug: http://javafx-jira.kenai.com/browse/RT-24367
The original problem is next: ListView.getSelectionMode() returns part of it's observable list but not the copy. So removing from that list leads to various issues.
Use next code which copies list before removing items from it:
sendRightButton.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
ObservableList<String> potential =
FXCollections.observableArrayList( //copy
candidatesListView.getSelectionModel().getSelectedItems());
if (potential != null) {
heroes.addAll(potential);
candidates.removeAll(potential);
candidatesListView.getSelectionModel().clearSelection();
}
}
});

Related

JavaFX Slider Tick Marks Initialized via CSS Disappear

I am using CSS to configure my JavaFX Sliders, then applying the style in code with:
cssSlider.getStyleClass().add("slider-style");
When I first open my window, the tick marks are present on the CSS configured Slider(s). When I close and reopen the window, the tick marks are no longer present.
This following example demonstrates the anomaly using 2 Sliders, one configured directly, the other via CSS. Click the button to hide the window for 2 seconds. Notice that the Slider in which I directly configure the attributes works fine after hiding and re-showing, but the CSS configured Slider loses its tick marks after hiding and re-showing.
Does anyone have any ideas why showing, hiding, and re-showing the window causes the tick marks to vanish from the CSS configured Slider? Am I doing something wrong, or is this a JavaFX bug?
sample.css:
.slider-style {
-fx-show-tick-marks: true;
-fx-snap-to-ticks: true;
-fx-major-tick-unit: 5;
-fx-minor-tick-count: 5;
}
CssExample.java:
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
import javafx.scene.layout.GridPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import java.io.IOException;
import java.net.URL;
/**
* This simple example demonstrates that JavaFX Sliders configured with CSS only show their tick marks the first time
* they are shown. If the Slider is hidden, then shown again, the tick marks are gone forever.
*/
public class CssExample extends Application {
#Override
public void start(Stage stage) throws InterruptedException, IOException {
Group root = new Group();
Scene scene = new Scene(root, 400, 200);
stage.setScene(scene);
stage.setTitle("Slider Sample");
scene.setFill(Color.BLACK);
GridPane grid = new GridPane();
grid.setPadding(new Insets(10, 10, 10, 10));
grid.setVgap(10);
grid.setHgap(70);
scene.setRoot(grid);
int rowNumber = 1;
Label directLabel = new Label("Slider from attribute assignment");
GridPane.setConstraints(directLabel, 1, rowNumber++);
grid.getChildren().add(directLabel);
Slider directSlider = new Slider();
GridPane.setConstraints(directSlider, 1, rowNumber++);
grid.getChildren().add(directSlider);
directSlider.setShowTickMarks(true);
directSlider.setSnapToTicks(true);
directSlider.setMajorTickUnit(5);
directSlider.setMinorTickCount(5);
Label cssLabel = new Label("Slider from CSS (tick marks disappear after hidden)");
GridPane.setConstraints(cssLabel, 1, rowNumber++);
grid.getChildren().add(cssLabel);
Slider cssSlider = new Slider();
GridPane.setConstraints(cssSlider, 1, rowNumber++);
grid.getChildren().add(cssSlider);
URL url = getClass().getResource("sample.css");
String cssString = url.toExternalForm();
scene.getStylesheets().add(cssString);
cssSlider.getStyleClass().add("slider-style");
Button button = new Button("Hide for 2 Seconds");
GridPane.setConstraints(button, 1, rowNumber++);
grid.getChildren().add(button);
button.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent e) {
stage.hide();
stage.show();
}
});
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
is this a JavaFX bug?
Yes.
See: https://github.com/openjdk/jfx/blob/fdc88341f1df8fb9c99356ada54b25124b77ea6e/modules/javafx.controls/src/main/java/javafx/scene/control/skin/SliderSkin.java#L398
It is a bug in the internal implementation of the setShowTickMarks method of SliderSkin (verified in JavaFX 18.0.1).
Test case:
import javafx.animation.PauseTransition;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Slider;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.io.IOException;
public class CssExample extends Application {
private static final String CSS = // language=CSS
"""
.slider-style {
-fx-show-tick-marks: true;
-fx-snap-to-ticks: true;
-fx-major-tick-unit: 5;
-fx-minor-tick-count: 5;
}
""";
private static final String CSS_INLINE = "data:text/css," + CSS;
#Override
public void start(Stage stage) throws InterruptedException, IOException {
Platform.setImplicitExit(false);
Slider cssSlider = new Slider();
cssSlider.showTickMarksProperty().addListener((observable, oldValue, newValue) ->
System.out.println(cssSlider.showTickMarksProperty())
);
cssSlider.getStyleClass().add("slider-style");
PauseTransition hideAnimation = new PauseTransition(Duration.seconds(2));
hideAnimation.setOnFinished(e -> stage.show());
Button hideWindow = new Button("Hide for 2 Seconds");
hideWindow.setOnAction(e -> {
stage.hide();
hideAnimation.play();
});
Button closeApp = new Button("Close app");
closeApp.setOnAction(e -> Platform.exit());
VBox layout = new VBox(
10,
cssSlider, hideWindow, closeApp
);
layout.setPadding(new Insets(10));
layout.setPrefSize(400, 120);
Scene scene = new Scene(layout);
scene.getStylesheets().add(CSS_INLINE);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Test output:
BooleanProperty [bean: Slider#132908b9[styleClass=slider slider-style], name: showTickMarks, value: true]
BooleanProperty [bean: Slider#132908b9[styleClass=slider slider-style], name: showTickMarks, value: false]
BooleanProperty [bean: Slider#132908b9[styleClass=slider slider-style], name: showTickMarks, value: true]
It switches showTicks from true to false, and back to true, which triggers the bug.
In the current implementation for the setShowTicks method:
private void setShowTickMarks(boolean ticksVisible, boolean labelsVisible) {
showTickMarks = (ticksVisible || labelsVisible);
Slider slider = getSkinnable();
if (showTickMarks) {
if (tickLine == null) {
tickLine = new NumberAxis();
tickLine.setAutoRanging(false);
tickLine.setSide(slider.getOrientation() == Orientation.VERTICAL ? Side.RIGHT : (slider.getOrientation() == null) ? Side.RIGHT: Side.BOTTOM);
tickLine.setUpperBound(slider.getMax());
tickLine.setLowerBound(slider.getMin());
tickLine.setTickUnit(slider.getMajorTickUnit());
tickLine.setTickMarkVisible(ticksVisible);
tickLine.setTickLabelsVisible(labelsVisible);
tickLine.setMinorTickVisible(ticksVisible);
// add 1 to the slider minor tick count since the axis draws one
// less minor ticks than the number given.
tickLine.setMinorTickCount(Math.max(slider.getMinorTickCount(),0) + 1);
if (slider.getLabelFormatter() != null) {
tickLine.setTickLabelFormatter(stringConverterWrapper);
}
getChildren().clear();
getChildren().addAll(tickLine, track, thumb);
} else {
tickLine.setTickLabelsVisible(labelsVisible);
tickLine.setTickMarkVisible(ticksVisible);
tickLine.setMinorTickVisible(ticksVisible);
}
}
else {
getChildren().clear();
getChildren().addAll(track, thumb);
// tickLine = null;
}
getSkinnable().requestLayout();
}
The first time it shows the ticks it will do this:
getChildren().clear();
getChildren().addAll(tickLine, track, thumb);
Then, when the ticks are hidden, it will do this:
getChildren().clear();
getChildren().addAll(track, thumb);
Then, when the ticks are supposed to be shown again, the tickLine is not added back to the children, so it never shows the ticks again.

Items replacing but not asked

I'm working on a little game. So I create the game interface (countains the gun, the canvas which I use as gamespace and an interface for the user to control is gun). I place the different elements in the window and there is my problem. When I execute my code, all is well placed but once I use one of the buttons (buttons in both codes) or the slider (second code), the slider and the fire button replace themselves. And I don't understand why because I never asked this rellocation in my code. Also, when the items rellocate, I can't use any other items excepted the fire button and the slider.
Here are screenshots of what I have before using a button (first screenshot) (it's also how I want the interface to be) and the second screenshot shows the rellocation I have.
How it looks when I use nothing.
How it looks when I use a button or the slider.
Main.java :
package application;
import bureaux.Bureau;
import canons.Canon;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.control.ToolBar;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class Main extends Application{
private StackPane root, rootBureau;
private Scene scene;
private Stage stage;
private Text joueur;
private Button menu, musique, ajoutJoueur;
private FlowPane rootJeu;
private Bureau bureauJoueur;
private ToolBar toolBar;
private Canon canonJoueur;
#Override
public void start(Stage primaryStage) throws IOException {
getRoot();
getScene();
stage = primaryStage;
creerInterface();
stage.setTitle("Mad Java Guns");
stage.setResizable(false);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
public void creerInterface(String mode) { // creating the gamespace and the objects that the user need to play
getToolBar().getItems().addAll(getMusique(), getAjoutJoueur());
getRootBureau().getChildren().add(getBureauJoueur());
getRootJeu().getChildren().add(getCanonJoueur());
getRoot().getChildren().addAll(getToolBar(), getJoueur(), getRootBureau(), getRootJeu());
}
// Getters
public StackPane getRoot() {
if(root == null) {
root = new StackPane();
}
return root;
}
public Scene getScene() {
if(scene == null) {
scene = new Scene(root,1000,800);
}
return scene;
}
public Text getJoueur() { // gamespace
if(joueur == null) {
joueur = new Text("Espace de jeu");
joueur.setFont(Font.font("Arial", 20));
joueur.setTranslateY(120);
}
return joueur;
}
public ToolBar getToolBar() {
if(toolBar == null) {
toolBar = new ToolBar();
toolBar.setTranslateY(122);
toolBar.setTranslateX(3);
toolBar.setStyle("-fx-background-color: transparent");
}
return toolBar;
}
public Button getMusique() { // button too change the music in game
if (musique == null) {
musique = new Button("Musique");
musique.setOnMouseClicked(e -> {
System.out.println("musique"); // not coded yet
});
musique.setFocusTraversable(false);
}
return musique;
}
public Button getAjoutJoueur() { // add players in the game
if(ajoutJoueur == null) {
ajoutJoueur = new Button("Ajouter un joueur");
ajoutJoueur.setOnMouseClicked(e -> {
System.out.println("ajoutJoueur"); //not coded yet
});
ajoutJoueur.setFocusTraversable(false);
}
return ajoutJoueur;
}
public StackPane getRootBureau() { // pane where the user's interface will be placed
if(rootBureau == null) {
rootBureau = new StackPane();
rootBureau.setStyle("-fx-background-color: lightgrey");
rootBureau.setMaxSize(990, 250);
rootBureau.setTranslateY(270);
}
return rootBureau;
}
public Bureau getBureauJoueur() { // user's interface
if(bureauJoueur == null) {
bureauJoueur = new Bureau("Billy", getCanonJoueur());
}
return bureauJoueur;
}
}
Class Bureau.java :
package bureaux;
import canons.Canon;
import javafx.geometry.Orientation;
import javafx.scene.Parent;
import javafx.scene.control.Button;
import javafx.scene.control.Slider;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
public class Bureau extends Parent {
private Slider sliderCanon;
private HBox boxPrincipale;
private VBox boxControlesCanon;
private Button feu;
public Bureau(String nom, Canon canon) {
getBoxControlesCanon().getChildren().addAll(getSliderCanon(), getFeu());
getBoxPrincipale().getChildren().add(getBoxControlesCanon());
this.setTranslateX(-480); // placing the boxes
this.setTranslateY(-95);
this.getChildren().add(getBoxPrincipale());
}
//Getteurs
public HBox getBoxPrincipale() {
if(boxPrincipale == null) { // return a HBox which countains the VBox (next function)
// and other elements which aren't created yet.
boxPrincipale = new HBox();
}
return boxPrincipale;
}
public VBox getBoxControlesCanon() { // return a VBox which countains the controls of the gun
//(gun not showed in the code, doesn't concern the problem)
if(boxControlesCanon == null) {
boxControlesCanon = new VBox();
boxControlesCanon.setSpacing(20);
}
return boxControlesCanon;
}
public Slider getSliderCanon() { //slider to orient the gun (gun not showed in the code, doesn't concern the problem)
if(sliderCanon == null) {
sliderCanon = new Slider(0, 360, 0);
sliderCanon.setOrientation(Orientation.VERTICAL);
sliderCanon.valueProperty().addListener(e -> {
System.out.println(sliderCanon.getValue());
});
sliderCanon.setShowTickMarks(true);
sliderCanon.setShowTickLabels(true);
sliderCanon.setMajorTickUnit(90f);
}
return sliderCanon;
}
public Button getFeu() { // fire button
if(feu == null) {
feu = new Button("Feu");
feu.setOnMouseClicked(e -> {
System.out.println("Feu");
});
feu.setFocusTraversable(false);
}
return feu;
}
}
Please ask for more informations if necessary. Thanks for your help.
EDIT : sorry for the unpoliteness on top of this text, I used to edit it and add "Hello" but it just don't want to show it :/
You are using Bureau extends Parent. You will have to be more specific and used the nodes that will produce the outcome you need.
Try something like Bureau extends HBox. Then
getBoxControlesCanon().getChildren().add(new VBox(getSliderCanon(), getFeu()));
To get the left alignment, you may need to do something like
getBoxControlesCanon().getChildren().addAll(new VBox(getSliderCanon(), getFeu()), someOtherNode);
HBox.setHGrow(someOtherNode, Priority.ALWAYS);
If you look at Parent compared to VBox, you will see that Parent does not describe how children nodes will be laid out. A lot of nodes that are a subclass of Parent do describe how their children nodes will be laid out.

How to make a ListView selectable but not editable

so I'm writing a javafx app and I need to be able to select the cells from the list view (for copy paste purposes) but I don't want to make it editable, I mean, the content cannot be changed unless I want to (allowing it through a button, for example).
So I have the following code:
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.scene.control.cell.TextFieldListCell;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
List<String> contacts = new ArrayList<>(Arrays.asList("968787522","3424234234","2343234324"));
ListView<String> contactsList = new ListView();
contactsList.setItems(FXCollections.observableArrayList(contacts));
//this gives me the ability to edit the row as text field but I want this text field to not be editable
contactsList.setCellFactory(TextFieldListCell.forListView());
StackPane pane = new StackPane();
pane.getChildren().add(contactsList);
primaryStage.setScene(new Scene(pane, 300, 275));
primaryStage.show(); }
public static void main(String[] args) {
launch(args);
}
}
and if I set 'contactsList' as not editable, I'm not able to edit, neither select.
As you can see (image bellow),I'm editing the cell, but I want to be able to select the text(not the item), but I don't want to be able to delete characters (text selectable but not editable).
so after breaking my head off, lots of research and API reading, I came up with a solution. This does EXACTLY what I wanted to do. Here is the demo if someone needs it ;)
So the idea is, each time we want to select the content of a row we need to select the row, get the textField and set the editing to true or false, (every time).
So in the demo that I made, I placed a button so you can toggle the editing to true or false to be sure that's is working, and how is working.
Cheers.
I commented some of the code for better understanding, if you have any questions about this just let me know.
package sample;
import com.sun.javafx.scene.control.skin.VirtualFlow;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.TextFieldListCell;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Main extends Application {
private boolean editable = false;
public static IndexedCell getCell(final Control control, final int index) {
return getVirtualFlow(control).getCell(index);
}
public static VirtualFlow<?> getVirtualFlow(Control control) {
Group group = new Group();
Scene scene = new Scene(group);
Stage stage = new Stage();
if(control.getScene() == null) {
group.getChildren().setAll(control);
stage.setScene(scene);
stage.show();
}
VirtualFlow<?>flow = (VirtualFlow<?>) control.lookup("#virtual-flow");
return flow;
}
public void setEditable(ListView contactsList){
//this needs to be done since we need to run our code after the text field was rendered
//so we need to invoke our code after this happens, if not it will throw a null pointer...
Platform.runLater(() -> {
//this is one of the most important guys because javafx api says that
//TextFieldListCell.forListView() allows editing of the cell content when the cell is double-clicked,
// or when {#link ListView#edit(int)} is called.
int rowIndex = contactsList.getSelectionModel().getSelectedIndex();
contactsList.edit(rowIndex);
ListCell rootCell = (ListCell) getCell(contactsList, rowIndex);
TextField textField = (TextField) rootCell.getGraphic();
textField.setEditable(editable);
});
}
#Override
public void start(Stage primaryStage) throws Exception{
List<String> contacts = new ArrayList<>(Arrays.asList("968787522","3424234234","2343234324"));
ListView<String> contactsList = new ListView();
contactsList.setItems(FXCollections.observableArrayList(contacts));
contactsList.setEditable(true);
//this gives me the ability to edit the row as text field but I want this text field to not be editable
contactsList.setCellFactory(TextFieldListCell.forListView());
contactsList.setOnEditStart(e -> {
setEditable(contactsList);
});
StackPane pane = new StackPane();
Button editBtn = new Button("Toggle edit");
editBtn.setOnAction(event -> {
editable = !editable;
editBtn.setText("Editing = " + editable);
//to cancel any editing that might be occuring
contactsList.getSelectionModel().clearSelection();
});
pane.getChildren().addAll(contactsList,editBtn);
primaryStage.setScene(new Scene(pane, 300, 275));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
If I understand you correctly, it is not necessary to set the listview to 'not editable', as the default behaviour should suffice for your purpose. Take a look at this code, for example:
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class NewFXMain extends Application {
#Override
public void start(Stage primaryStage) {
ListView listView = new ListView();
listView.getItems().addAll("one","two","three","four");
listView.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
System.out.println(listView.getSelectionModel().getSelectedItem());
}
});
StackPane root = new StackPane();
root.getChildren().add(listView);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("ListView Example");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I changed nothing about the editable-property of the ListView, but I can select every item, without being able to edit it (in the sense of changing its value). You can easily add an EventHandler to the ListView to perform whatever operation you want to perform. You could also add an EventHandler to every cell of the ListView by manipulating the CellFactory, as shown in this answer: How to handle ListView item clicked action?
Here's what works for me:
TableView<DataBean> table = new TableView<>();
table.setItems(...); // list of some DataBean objects with dataBeanField proprty
table.setEditable(true);
TableColumn<DataBean, String> column = new TableColumn<>("SomeData");
column.setCellValueFactory(new PropertyValueFactory<DataBean, String>("dataBeanField"));
column.setCellFactory(new Callback<TableColumn<DataBean, String>, TableCell<DataBean, String>>() {
#Override
public TableCell<DataBean, String> call(TableColumn<DataBean, String> param) {
return new TextFieldTableCell<>(new DefaultStringConverter() {
private String defaultValue = "";
#Override
public String fromString(String newValue) {
return super.fromString(defaultValue);
}
#Override
public String toString(String value) {
return defaultValue = super.toString(value);
}
});
}
});

In javafx, how does one make methods done in event handlers affect the rest of the code?

Basically my code is like this:
fileOpener.setOnAction(
new EventHandler<ActionEvent>() {
#Override
public void handle(final ActionEvent e) {
myFileList.add(openMusicTracks.showOpenDialog(window));
System.out.println(myFileList.getName(0)); //prints file name so I know this works
}
});
I want the add method (that's inside of the EventHandler) to actually edit the arraylist for everywhere else so that later when I reference it in
ObservableList<String> playList = FXCollections.observableArrayList ();
for(int i = 0; i < myFileList.size(); i++) {
playList.add(i, myFileList.get(i).getName());
System.out.println(myFileList.getName(0)); //doesn't print the file name, so I know this doesn't work.
}
the arraylist won't be empty. How do I do this? I'm sorry if there's a more elegant way to word this, but I have honestly no idea how to research this, I've tried. Thanks.
A simple example which shows how can an ArrayList be shared between methods.
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.util.ArrayList;
import java.util.List;
public class Main extends Application {
private List<String> list = new ArrayList<>();
#Override
public void start(Stage primaryStage) {
Button add = new Button("Add");
Button display = new Button("Show");
// Add Items
add.setOnAction(event -> list.add("Item"));
// Display Items
display.setOnAction(e -> {
printAndClear();
});
VBox root = new VBox(10, add, display);
root.setAlignment(Pos.CENTER);
Scene scene = new Scene(root, 200, 200);
primaryStage.setScene(scene);
primaryStage.show();
}
private void printAndClear() {
list.forEach(System.out::println);
list.clear();
}
public static void main(String[] args) {
launch(args);
}
}

JavaFX handlers not triggered

I've written the following code.
import java.util.ArrayList;
import java.util.List;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.stage.Stage;
import javafx.util.Callback;
public class App extends Application {
private ListView<String> listView;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
List<String> friendList = new ArrayList<String>();
friendList.add("Alice");
friendList.add("Bob");
listView = new ListView<>(FXCollections.observableArrayList(friendList));
listView.setCellFactory(new Callback<ListView<String>, ListCell<String>>() {
#Override
public ListCell<String> call(ListView<String> p) {
ListCell<String> cell = new ListCell<String>() {
#Override
protected void updateItem(String t, boolean empty) {
super.updateItem(t, empty);
if (t != null) {
Label usernameLabel = new Label(t);
usernameLabel.setFont(Font.font("Arial", FontWeight.BOLD, 12));
Button callButton = new Button("Call");
callButton.setOnAction(e -> System.out.println("action")); // not working
callButton.addEventHandler(MouseEvent.MOUSE_ENTERED, e -> System.out.println("entered"));
callButton.addEventHandler(MouseEvent.MOUSE_CLICKED, e -> System.out.println("clicked")); // not working
HBox usernameBox = new HBox(5);
usernameBox.setAlignment(Pos.CENTER_LEFT);
usernameBox.getChildren().addAll(usernameLabel);
BorderPane borderPane = new BorderPane();
borderPane.setLeft(usernameBox);
borderPane.setRight(callButton);
VBox vbox = new VBox(3);
vbox.getChildren().addAll(borderPane);
setGraphic(vbox);
}
}
};
return cell;
}
});
stage.setScene(new Scene(listView));
stage.show();
}
}
If you look at the callButton, you see that it gets three different handlers. However, only the MOUSE_ENTERED event handler is really triggered. The other ones are completely ignored. What can be the problem?
EDIT: Added and removed some code, in order to make it runnable.
This is a known bug in JavaFX 8, which is fixed in the latest ea release (1.8.0_20).
As a workaround, create the controls once and register handlers with them, then just update their state in the updateItem(...) method:
listView.setCellFactory(new Callback<ListView<String>, ListCell<String>>() {
#Override
public ListCell<String> call(ListView<String> p) {
Label usernameLabel = new Label();
usernameLabel.setFont(Font.font("Arial", FontWeight.BOLD, 12));
Button callButton = new Button("Call");
HBox usernameBox = new HBox(5);
usernameBox.setAlignment(Pos.CENTER_LEFT);
usernameBox.getChildren().addAll(usernameLabel);
BorderPane borderPane = new BorderPane();
borderPane.setLeft(usernameBox);
borderPane.setRight(callButton);
VBox vbox = new VBox(3);
vbox.getChildren().addAll(borderPane);
ListCell<String> cell = new ListCell<String>() {
#Override
protected void updateItem(String t, boolean empty) {
super.updateItem(t, empty);
if (t != null) {
usernameLabel.setText(t);
setGraphic(vbox);
} else {
setGraphic(null); // you will have weird bugs without this: don't omit it
}
}
};
callButton.setOnAction(e -> System.out.println("action: "+cell.getItem()));
callButton.addEventHandler(MouseEvent.MOUSE_ENTERED, e -> System.out.println("entered "+ cell.getItem()));
callButton.addEventHandler(MouseEvent.MOUSE_CLICKED, e -> System.out.println("clicked "+ cell.getItem()));
return cell;
}
});
Note that this "workaround" is really the preferred approach anyway, and the one that was intended by the designers of the "virtualized" controls like ListView, TableView, etc. The point is that updateItem(...) is potentially called very frequently by the application, whereas cells are created very rarely. By creating new controls in the updateItem(...) method you potentially introduce performance issues. Create them once for the cell, and then just configure them in updateItem(...). Note also how I just registered the event handlers once, and had the handlers refer to cell.getItem() to see which item is currently represented by the cell.
One last thing: you have a bug in your code (which I fixed). Since cells can be reused, including for the case where a cell displaying an item is reused for an empty cell, it's important that you always handle the case where the item is null (typically by setting text and/or graphic to null).
Could you add the code of getIconAndResizeTo16( String s ). I guess the node you return there consumes mouse clicks.
Here is a runnable example that demonstrates the issue. It is just a guess though.
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class App extends Application {
public static void main(String[] args) { launch(args); }
#Override
public void start(Stage stage) throws Exception {
Button callButton = new Button("", getIconAndResizeTo16("Phone"));
callButton.setOnAction(e -> System.out.println("clicked1")); // not working
callButton.addEventHandler(MouseEvent.MOUSE_ENTERED, e -> System.out.println("entered"));
callButton.addEventHandler(MouseEvent.MOUSE_CLICKED, e -> System.out.println("clicked")); // not working
Button chatButton = new Button("", getIconAndResizeTo16("Chat") );
chatButton.setOnAction(e -> System.out.println("clicked2")); // not working
HBox callIconBox = new HBox(3);
callIconBox.setAlignment(Pos.CENTER_RIGHT);
callIconBox.getChildren().addAll(callButton, chatButton);
stage.setScene(new Scene(callIconBox));
stage.show();
}
private Node getIconAndResizeTo16(String s) {
Label l = new Label("Consumes " + s + " Events");
l.addEventHandler(MouseEvent.MOUSE_PRESSED, e -> { e.consume(); });
l.addEventHandler(MouseEvent.MOUSE_CLICKED, e -> { e.consume(); });
return l;
}
}

Categories

Resources