Pasting text data in the TableView using JAVAFX - javafx

I have a TableView which accepts data by individual cell editing. Now I also want to enter data by pasting from a file as shown below.
11.12 23.32 15.43
22.23 24.45 26.65
I want to paste data using say CTRL+V. I have already seen posts like post in StackOverflow or the GIT repo. I could not paste data for more than one row.
Here I am giving the main code.
package testmatrix;
import javafx.application.Application;
import javafx.collections.*;
import javafx.event.*;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn.CellEditEvent;
import javafx.scene.control.*;
import javafx.scene.control.cell.*;
import javafx.scene.layout.*;
import javafx.stage.Stage;
public class TestMATRIX extends Application {
TableView<CommonDataClass> matrixData = new TableView<CommonDataClass>();
final ObservableList<CommonDataClass> matData = FXCollections.
observableArrayList();
TableColumn[] matrixDataColumns = new TableColumn[6];
int numColVal = 0;
int theNDX = 0;
int maxRowNum = 0;
TextField TC1 = new TextField();
TextField TC2 = new TextField();
TextField TC3 = new TextField();
TextField TC4 = new TextField();
TextField TC5 = new TextField();
TextField TC6 = new TextField();
boolean numColStatus = false;
int oldRowVal = 0;
int oldColVal = 0;
boolean newRow = false;
boolean newCol = false;
#Override
public void start(Stage primaryStage) {
final BorderPane root = new BorderPane();
TextField myTextField = new TextField();
Label colL = new Label("Column Number->");
TextField colNumT = new TextField();
Button getNum = new Button("SET");
colNumT.setMaxWidth(40);
TableUtils.doCopyPasteHandler (matrixData, matData);
matrixData.setVisible(true);
matrixData.setEditable(true);
matrixData.getSelectionModel().setCellSelectionEnabled(true);
matrixData.getSelectionModel ().setSelectionMode (SelectionMode.MULTIPLE);
HBox hb1 = new HBox();
HBox hb2 = new HBox();
hb1.getChildren().add(matrixData);
hb2.getChildren().addAll(colL, colNumT, getNum);
root.setCenter(hb1);
root.setRight(hb2);
getNum.addEventHandler(ActionEvent.ACTION, e -> {
numColStatus = (colNumT.getText().isEmpty()) ? false : true;
numColVal = (numColStatus) ? Integer.parseInt(colNumT.getText()): 0;
if (numColStatus) {
addRowBelow();
matrixData.getColumns ().clear ();
for (int ii = 0; ii < numColVal; ii++) {
matrixDataColumns[ii] = createCol(ii);
editCommit(ii);
}
matrixData.setItems(matData);
if (numColStatus) {
for (int ii = 0; ii < numColVal; ii++) {
matrixData.getColumns().add(matrixDataColumns[ii]);
}
}
matrixData.refresh();
}
});
Scene scene = new Scene(root, 1200, 400);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
TableColumn<CommonDataClass, String> createCol(int icol) {
TableColumn<CommonDataClass, String> column = new TableColumn<>();
column.setMinWidth(30);
column.setStyle(
"-fx-alignment: CENTER-RIGHT;-fx-font-family: monospace; -fx-font-size: 10px; ");
String nameC = "myD" + (icol + 1);
System.out.println("colName->" + nameC);
column.setCellValueFactory(
new PropertyValueFactory<CommonDataClass, String>(nameC));
column.setCellFactory(
new DragSelectionCellFactory<CommonDataClass, String>(TextFieldTableCell.forTableColumn()
)
);
column.setMinWidth(120);
return column;
}
private void editCommit(int ii) {
System.out.println("Command came here");
matrixDataColumns[ii].setOnEditCommit(
new EventHandler<CellEditEvent<CommonDataClass, String>>() {
#Override
public void handle(CellEditEvent<CommonDataClass, String> event) {
int colNum = event.getTablePosition().getColumn();
switch (colNum) {
case 0:
event.getTableView().getItems().get(event.getTablePosition().getRow()).setMyD1(event.getNewValue());
break;
case 1:
event.getTableView().getItems().get(event.getTablePosition().getRow()).setMyD2(event.getNewValue());
break;
case 2:
event.getTableView().getItems().get(event.getTablePosition().getRow()).setMyD3(event.getNewValue());
break;
case 3:
event.getTableView().getItems().get(event.getTablePosition().getRow()).setMyD4(event.getNewValue());
break;
case 4:
event.getTableView().getItems().get(event.getTablePosition().getRow()).setMyD5(event.getNewValue());
break;
case 5:
event.getTableView().getItems().get(event.getTablePosition().getRow()).setMyD6(event.getNewValue());
break;
}
matrixData.setItems(matData);
matrixData.refresh();
if (!event.getNewValue().isEmpty()) {
addRowBelow();
}
}
});
}
void addRowBelow() {
matData.add(new CommonDataClass(
TC1.getText(), TC2.getText(), TC3.getText(),
TC4.getText(), TC5.getText(), TC6.getText()
));
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
Here I am showing the data structure for the TableView.
package testmatrix;
import javafx.beans.property.*;
public class CommonDataClass {
private final SimpleStringProperty myD1;
private final SimpleStringProperty myD2;
private final SimpleStringProperty myD3;
private final SimpleStringProperty myD4;
private final SimpleStringProperty myD5;
private final SimpleStringProperty myD6;
public CommonDataClass(String myStr1, String myStr2, String myStr3, String myStr4, String myStr5, String myStr6) {
this.myD1 = new SimpleStringProperty(myStr1);
this.myD2 = new SimpleStringProperty(myStr2);
this.myD3 = new SimpleStringProperty(myStr3);
this.myD4 = new SimpleStringProperty(myStr4);
this.myD5 = new SimpleStringProperty(myStr5);
this.myD6 = new SimpleStringProperty(myStr6);
}
public String getMyD1(){
return myD1.get();
}
public String getMyD2(){
return myD2.get();
}
public String getMyD3(){
return myD3.get();
}
public String getMyD4(){
return myD4.get();
}
public String getMyD5(){
return myD5.get();
}
public String getMyD6(){
return myD6.get();
}
public void setMyD1(String myStr){
myD1.set(myStr);
}
public void setMyD2(String myStr){
myD2.set(myStr);
}
public void setMyD3(String myStr){
myD3.set(myStr);
}
public void setMyD4(String myStr){
myD4.set(myStr);
}
public void setMyD5(String myStr){
myD5.set(myStr);
}
public void setMyD6(String myStr){
myD6.set(myStr);
}
public StringProperty dataNameProperty(int index) {
StringProperty strProp = null;
switch (index) {
case 0:
strProp = myD1;
break;
case 1:
strProp = myD2;
break;
case 2:
strProp = myD3;
break;
case 3:
strProp = myD4;
break;
case 4:
strProp = myD5;
break;
case 5:
strProp = myD6;
break;
}
return strProp;
}
}
Here I am giving the code for the DrageSelection for cell.
package testmatrix;
import javafx.scene.control.TableCell;
import javafx.scene.input.*;
public class DragSelectionCell extends TableCell<CommonDataClass, String> {
public DragSelectionCell() {
setOnDragDetected ((MouseEvent event) -> {
startFullDrag ();
getTableColumn ().getTableView ().getSelectionModel ().select (
getIndex (), getTableColumn ());
});
setOnMouseDragEntered ((MouseDragEvent event) -> {
getTableColumn ().getTableView ().getSelectionModel ().select (
getIndex (), getTableColumn ());
});
}
#Override
public void updateItem(String item, boolean empty) {
super.updateItem (item, empty);
if ( empty ) {
setText (null);
} else {
setText (item);
}
}
}
Here I am giving the DragSelectionCellfactory code.
package testmatrix;
import javafx.scene.control.*;
import javafx.util.Callback;
public class DragSelectionCellFactory<CommonDataClass, String> implements
Callback<TableColumn<CommonDataClass, String>, TableCell<CommonDataClass, String>> {
private final Callback<TableColumn<CommonDataClass, String>, TableCell<CommonDataClass, String>> factory;
public DragSelectionCellFactory(
Callback<TableColumn<CommonDataClass, String>, TableCell<CommonDataClass, String>> factory) {
this.factory = factory;
}
public DragSelectionCellFactory() {
this (col -> new TableCell<CommonDataClass, String> () {
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem (item, empty);
if ( empty || item == null ) {
setText (null);
} else {
setText (item.toString ());
}
}
});
}
#Override
public TableCell<CommonDataClass, String> call(
final TableColumn<CommonDataClass, String> col) {
TableCell<CommonDataClass, String> cell = factory.call (col);
cell.setOnDragDetected (event -> {
cell.startFullDrag ();
col.getTableView ().getSelectionModel ().select (cell.getIndex (),
col);
});
cell.setOnMouseDragEntered (event -> {
col.getTableView ().getSelectionModel ().select (cell.getIndex (),
col);
});
cell.setOnMouseEntered (e -> {
String item = cell.getItem ();
if ( item != null ) {
}
});
return cell;
}
}
The following code blocks describe similar to the Table Utility for copy/paste routine as mentioned in the beginning. However, paste part was not working and so I tried to modify. Now I am totally confused. The copy part was copying, while paste part as per code shown in the reference above could not paste with all the rows and columns.
package testmatrix;
import java.util.StringTokenizer;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.*;
import javafx.event.EventHandler;
import javafx.scene.control.*;
import javafx.scene.input.*;
public class TableUtils extends TableCell<CommonDataClass, String> {
ObservableList<CommonDataClass> datTab = FXCollections.
observableArrayList ();
public static void doCopyPasteHandler(TableView<?> table,
ObservableList<CommonDataClass> myData) {
table.setOnKeyPressed (new TableKeyEventHandler ());
}
public static class TableKeyEventHandler implements EventHandler<KeyEvent> {
KeyCodeCombination copyCode = new KeyCodeCombination (KeyCode.C,
KeyCodeCombination.CONTROL_ANY);
KeyCodeCombination pasteCode = new KeyCodeCombination (KeyCode.V,
KeyCodeCombination.CONTROL_ANY);
//#Override
public void handle(KeyEvent event) {
if ( copyCode.match (event) ) {
if ( event.getSource () instanceof TableView ) {
copySelection2Clipboard ((TableView<?>) event.getSource ()); // this will copy to clipboard
event.consume (); // After using event please consume
}
} else if ( pasteCode.match (event) ) {
if ( event.getSource () instanceof TableView ) {
pasteFromClipboard ((TableView<?>) event.getSource ());
event.consume ();
}
}
}
}
public static void copySelection2Clipboard(TableView<?> table) {
StringBuilder clipboardString = new StringBuilder ();
ObservableList<TablePosition> positionList = table.getSelectionModel ().
getSelectedCells ();
int prevRow = -1;
for ( TablePosition pos : positionList ) {
int row = pos.getRow ();
int col = pos.getColumn ();
if ( prevRow == row ) { // determine whether we advance in a row or col (newline)
clipboardString.append ('\t');
} else if ( prevRow != -1 ) {
clipboardString.append ('\n');
}
String text = "";
Object obsValue = (Object) table.getColumns ().get (col).
getCellObservableValue (row);
if ( obsValue == null ) {
text = "";
} else if ( obsValue instanceof StringProperty ) {
text = ((StringProperty) obsValue).get ();
} else {
System.out.println ("Unsupported observable value: " + obsValue);
}
clipboardString.append (text);
prevRow = row;
}
ClipboardContent clipboardContent = new ClipboardContent ();
clipboardContent.putString (clipboardString.toString ());
}
public static void pasteFromClipboard(TableView<?> table) {
if ( table.getSelectionModel ().getSelectedCells ().size () == 0 ) {
return;
}
TablePosition pasteCellPosition = table.getSelectionModel ().
getSelectedCells ().get (0); // get cell position at start
String pasteString = Clipboard.getSystemClipboard ().getString ();
StringTokenizer rowTokenizer = new StringTokenizer (pasteString, "\n");
int rowNum = rowTokenizer.countTokens () + 1;
int rowCB = -1;
while (rowTokenizer.hasMoreTokens ()) {
rowCB++;
String rowString = rowTokenizer.nextToken ();
StringTokenizer colTokenizer = new StringTokenizer (rowString, "\t");
int colCB = -1;
while (colTokenizer.hasMoreTokens ()) {
colCB++;
String clpCellCont = colTokenizer.nextToken ();
int rowTable = pasteCellPosition.getRow () + rowCB;
int colTable = pasteCellPosition.getColumn () + colCB;
if ( rowTable >= table.getItems ().size () ) {
continue;
}
if ( colTable >= table.getColumns ().size () ) {
continue;
}
TableColumn tabCol = table.getVisibleLeafColumn (colTable);
ObservableValue obsVal = tabCol.
getCellObservableValue (rowTable);
}
}
}
}
It would be of great help, if I get any help. Thanks and Regards

You couldn't insert a value since there is no code in your event handler, that assigns a value.
Furthermore you do not use the properties, which makes things more complicated.
If you do use the properties
TableColumn<CommonDataClass, String> createCol(int icol) {
...
column.setCellValueFactory(
cd -> cd.getValue().dataNameProperty(icol));
you can use the properties to assign the new values:
public static <T> void pasteFromClipboard(TableView<T> table) {
if (table.getSelectionModel().getSelectedCells().isEmpty()) {
return;
}
TablePosition pasteCellPosition = table.getSelectionModel().
getSelectedCells().get(0); // get cell position at start
Clipboard cb = Clipboard.getSystemClipboard();
// check, if clipboard contains a string
if (!cb.hasString()) {
return;
}
String pasteString = cb.getString();
String[][] values = Stream.of(pasteString.split("\r?\n"))
.map(line -> line.split("\t")).toArray(String[][]::new);
final int offsetY = pasteCellPosition.getRow();
final int offsetX = pasteCellPosition.getColumn();
final int maxY = Math.min(table.getItems().size() - offsetY, values.length);
final int colMax = table.getColumns().size() - offsetX;
for (int y = 0; y < maxY; y++) {
String[] r = values[y];
final int maxX = Math.min(colMax, r.length);
T rowObject = table.getItems().get(y+offsetY);
for (int x = 0; x < maxX; x++) {
Object property = table.getColumns().get(x + offsetX).getCellObservableValue(rowObject);
if (property instanceof StringProperty) {
// write value using the property
((StringProperty) property).set(r[x]);
}
}
}
}
This only inserts values into existing rows...
Note though, that you should really use the naming conventions in your DragSelectionCellFactory class, i.e. use a single letter as name for type parameters. In your case the type parameters are named CommonDataClass and String, i.e. the names of existing classes, which will almost surely lead to confusion...
Probably you should remove the type parameters though:
public class DragSelectionCellFactory implements
Callback<TableColumn<CommonDataClass, String>, TableCell<CommonDataClass, String>> {
Furthermore copying the content will not work, since you forgot to set the clipboard content:
ClipboardContent clipboardContent = new ClipboardContent();
clipboardContent.putString(clipboardString.toString());
Clipboard.getSystemClipboard().setContent(clipboardContent);
Furthermore there is no guarantee that the selected cells are in a rectangular area. Cells can be selected arbitrarily by holding down Ctrl.
Also
getNum.addEventHandler(ActionEvent.ACTION, e -> {...});
could (and in this case IMHO should) be replaced with
getNum.setOnAction(e -> {...});

Related

JavaFX: Fit Columns of TableView to content to avoid cut the content

I have following problem: When i create a TableView and put data in the columns, the columns fit to content automatically. But if there many rows (more than 30) JavaFX optimize the columnwidth to the average length of all content.
In the first example i put the long strings first in the table and everything is fine.
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class Example extends Application {
#Override
public void start(Stage stage) {
// Init data
ObservableList<Person> persons = FXCollections.observableArrayList();
persons.add(new Person("Maximus-Superman", "Power", "Maximus-Superman.Power#test.com"));
persons.add(new Person("Max", "Powerstrongsupercool", "Max.Powerstrongsupercool#test.com"));
persons.add(new Person("Maximus-Superman", "Powerstrongsupercool", "Maximus-Superman.Powerstrongsupercool#test.com"));
for (int i = 0; i < 100; i++) {
persons.add(new Person("Max", "Power", "Max.Power#test.com"));
}
// Init table
TableView<Person> table = new TableView<Person>();
table.setMaxWidth(Double.MAX_VALUE);
table.setMaxHeight(Double.MAX_VALUE);
table.setItems(persons);
// Init columns
TableColumn<Person, String> firstname = new TableColumn<Person, String>("Firstname");
firstname.setCellValueFactory(new PropertyValueFactory<Person, String>("firstname"));
table.getColumns().add(firstname);
TableColumn<Person, String> lastname = new TableColumn<Person, String>("Lastname");
lastname.setCellValueFactory(new PropertyValueFactory<Person, String>("lastname"));
table.getColumns().add(lastname);
TableColumn<Person, String> email = new TableColumn<Person, String>("E-Mail");
email.setCellValueFactory(new PropertyValueFactory<Person, String>("email"));
table.getColumns().add(email);
// Init Stage
Scene scene = new Scene(table, 400, 150);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
public class Person {
private String firstname;
private String lastname;
private String email;
public Person(String firstname, String lastname, String email) {
this.firstname = firstname;
this.lastname = lastname;
this.email = email;
}
// Getters and Setters
}
}
Looks good...
In the second example i put short strings first in the table and at least the long strings. In this example JavaFX choose columnwidth to small and the content gets cut.
public class Example extends Application {
#Override
public void start(Stage stage) {
// Init data
ObservableList<Person> persons = FXCollections.observableArrayList();
for (int i = 0; i < 100; i++) {
persons.add(new Person("Max", "Power", "Max.Power#test.com"));
}
persons.add(new Person("Maximus-Superman", "Power", "Maximus-Superman.Power#test.com"));
persons.add(new Person("Max", "Powerstrongsupercool", "Max.Powerstrongsupercool#test.com"));
persons.add(new Person("Maximus-Superman", "Powerstrongsupercool", "Maximus-Superman.Powerstrongsupercool#test.com"));
[...]
Looks bad...
How can i avoid this?​
EDIT 19.05.2018
The links in the comments didn`t work.
So, i have found the problem in the source of JavaFx:
In the updateScene() method the maxRows are set to 30. There is no way to change the value because every methods that are involved are protected or private.
The comment is right. It can be take much time to create a table if there many rows in the table. But sometimes a developer knows the much possible rows of a table or it´s okay to risk higher loadingtime.
One solution is to contact Oracle to create a setter() for the value of max. rows. So the developer can choose the max. rows for each column individually.
public class TableColumnHeader extends Region {
[...]
private void updateScene() {
// RT-17684: If the TableColumn widths are all currently the default,
// we attempt to 'auto-size' based on the preferred width of the first
// n rows (we can't do all rows, as that could conceivably be an unlimited
// number of rows retrieved from a very slow (e.g. remote) data source.
// Obviously, the bigger the value of n, the more likely the default
// width will be suitable for most values in the column
final int n = 30; // ------------------------------------------> This is the problem!
if (! autoSizeComplete) {
if (getTableColumn() == null || getTableColumn().getWidth() != DEFAULT_COLUMN_WIDTH || getScene() == null) {
return;
}
doColumnAutoSize(getTableColumn(), n);
autoSizeComplete = true;
}
}
[...]
private void doColumnAutoSize(TableColumnBase<?,?> column, int cellsToMeasure) {
double prefWidth = column.getPrefWidth();
// if the prefWidth has been set, we do _not_ autosize columns
if (prefWidth == DEFAULT_COLUMN_WIDTH) {
getTableViewSkin().resizeColumnToFitContent(column, cellsToMeasure);
}
}
[...]
}
public class TableViewSkin<T> extends TableViewSkinBase<T, T, TableView<T>, TableViewBehavior<T>, TableRow<T>, TableColumn<T, ?>> {
[...]
#Override protected void resizeColumnToFitContent(TableColumn<T, ?> tc, int maxRows) {
if (!tc.isResizable()) return;
// final TableColumn<T, ?> col = tc;
List<?> items = itemsProperty().get();
if (items == null || items.isEmpty()) return;
Callback/*<TableColumn<T, ?>, TableCell<T,?>>*/ cellFactory = tc.getCellFactory();
if (cellFactory == null) return;
TableCell<T,?> cell = (TableCell<T, ?>) cellFactory.call(tc);
if (cell == null) return;
// set this property to tell the TableCell we want to know its actual
// preferred width, not the width of the associated TableColumnBase
cell.getProperties().put(TableCellSkin.DEFER_TO_PARENT_PREF_WIDTH, Boolean.TRUE);
// determine cell padding
double padding = 10;
Node n = cell.getSkin() == null ? null : cell.getSkin().getNode();
if (n instanceof Region) {
Region r = (Region) n;
padding = r.snappedLeftInset() + r.snappedRightInset();
}
int rows = maxRows == -1 ? items.size() : Math.min(items.size(), maxRows); // ------------------> if maxRows equals -1 every item will be checked
double maxWidth = 0;
for (int row = 0; row < rows; row++) {
cell.updateTableColumn(tc);
cell.updateTableView(tableView);
cell.updateIndex(row);
if ((cell.getText() != null && !cell.getText().isEmpty()) || cell.getGraphic() != null) {
getChildren().add(cell);
cell.applyCss();
maxWidth = Math.max(maxWidth, cell.prefWidth(-1));
getChildren().remove(cell);
}
}
// dispose of the cell to prevent it retaining listeners (see RT-31015)
cell.updateIndex(-1);
// RT-36855 - take into account the column header text / graphic widths.
// Magic 10 is to allow for sort arrow to appear without text truncation.
TableColumnHeader header = getTableHeaderRow().getColumnHeaderFor(tc);
double headerTextWidth = Utils.computeTextWidth(header.label.getFont(), tc.getText(), -1);
Node graphic = header.label.getGraphic();
double headerGraphicWidth = graphic == null ? 0 : graphic.prefWidth(-1) + header.label.getGraphicTextGap();
double headerWidth = headerTextWidth + headerGraphicWidth + 10 + header.snappedLeftInset() + header.snappedRightInset();
maxWidth = Math.max(maxWidth, headerWidth);
// RT-23486
maxWidth += padding;
if(tableView.getColumnResizePolicy() == TableView.CONSTRAINED_RESIZE_POLICY) {
maxWidth = Math.max(maxWidth, tc.getWidth());
}
tc.impl_setWidth(maxWidth);
}
[...]
}
Another solution is to fire a MouseEvent on the header of the rect of the TableColumn.
If there a MouseEvent with a ClickCount equals 2 and the PrimaryButton is down the resizeColumnToFitContent() method is called with a value for maxRows of -1.
int rows = maxRows == -1 ? items.size() : Math.min(items.size(), maxRows);
-1 means all rows that are in the TableView.
public class NestedTableColumnHeader extends TableColumnHeader {
[...]
private static final EventHandler<MouseEvent> rectMousePressed = new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent me) {
Rectangle rect = (Rectangle) me.getSource();
TableColumnBase column = (TableColumnBase) rect.getProperties().get(TABLE_COLUMN_KEY);
NestedTableColumnHeader header = (NestedTableColumnHeader) rect.getProperties().get(TABLE_COLUMN_HEADER_KEY);
if (! header.isColumnResizingEnabled()) return;
if (me.getClickCount() == 2 && me.isPrimaryButtonDown()) {
// the user wants to resize the column such that its
// width is equal to the widest element in the column
header.getTableViewSkin().resizeColumnToFitContent(column, -1); // -----------------------> this method should be call and everything is fine
} else {
// rather than refer to the rect variable, we just grab
// it from the source to prevent a small memory leak.
Rectangle innerRect = (Rectangle) me.getSource();
double startX = header.getTableHeaderRow().sceneToLocal(innerRect.localToScene(innerRect.getBoundsInLocal())).getMinX() + 2;
header.dragAnchorX = me.getSceneX();
header.columnResizingStarted(startX);
}
me.consume();
}
};
[...]
}
So, is it possible to create a new MouseEvent with a ClickCount of 2 and the PrimaryButtonDown-boolean is true and fire this to the TableColumn?
And: How can i contact Oracle to please them to create a setter() for the maxRows in the next release?

When i run my program my thread is working but images are not setting for the grid can someone give a solution for this.

ISymbol interface
package main;
import javafx.scene.image.Image;
public interface ISymbol {
void setImage(String location,String name);
Image getImage();
void setValue(int value);
int getValue();
}
Symbol class
package main;
import javafx.scene.image.Image;
import java.io.File;
public class Symbol implements ISymbol {
Image image;
int value;
#Override
public void setImage(String location,String name) {
File file = new File(location);
image = new Image(file.toURI().toString(),100,100,true,true);
}
#Override
public Image getImage() {
return image;
}
#Override
public void setValue(int value) {
this.value = value;
}
#Override
public int getValue() {
return value;
}
}
In here i'm trying to add images randomly to a array and i'm using that array in my main class to add those images to my reels
Reel class
package main;
import java.util.Random;
public class Reel {
public Symbol[] spin(){
Symbol cherry = new Symbol();
Symbol redSeven = new Symbol();
Symbol watermelon = new Symbol();
Symbol bell = new Symbol();
Symbol lemon = new Symbol();
Symbol plum = new Symbol();
Random random = new Random();
Symbol[] symbolArray = new Symbol[6];
for (int i = 0; i < symbolArray.length; i++) {
int randomNumber = random.nextInt(6);
System.out.println(randomNumber);
switch (randomNumber) {
case 0:
cherry.setValue(2);
cherry.setImage("/images/cherry.png","cherry");
symbolArray[i] = cherry;
break;
case 1:
lemon.setValue(3);
lemon.setImage("/images/lemon.png","lemon");
symbolArray[i] = lemon;
break;
case 2:
plum.setValue(4);
plum.setImage("/images/plum.png","plum");
symbolArray[i] = plum;
break;
case 3:
watermelon.setValue(5);
watermelon.setImage("/images/watermelon.png", "watermelon");
symbolArray[i] = watermelon;
break;
case 4:
bell.setValue(6);
bell.setImage("/images/bell.png", "bell");
symbolArray[i] = bell;
break;
case 5:
redSeven.setValue(7);
redSeven.setImage("images/redseven.png","seven");
symbolArray[i] = redSeven;
break;
default:
break;
}
}
return symbolArray;
}
}
This is my main class that include all methods. In the btnSpin method i'm calling my thread and for setting images for the reels i have used a reel method
I have debug my program and checked whether the image is coming the image was on there but when i set my image to the image view it wont work while i'm running my thread those imageviews are disappeared can someone give me a solution waiting for a reply thank you :)
SlotMachine class
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.effect.Reflection;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import main.Reel;
import main.Symbol;
public class SlotMachine extends Application implements Runnable {
//creating a thread
Thread thread1 = new Thread(){
#Override public void run(){
reel1();
}
};
//default image for reel
private Image image = new Image("/images/icon.png");
//UI variables
private Text title;
private Label lblStatus,lblInformationArea, lblBetAmount, lblCreditArea;
private ImageView image1, image2, image3;
private Button btnSpin, btnAddCoin, btnBetOne, btnBetMax, btnReset, btnStatistics;
//calculation variables
private int remainingCoins = 10;
private int betAmount, wins, lost, reel1value, reel2value, reel3value;
#Override
public void start(Stage primaryStage) {
GridPane grid = new GridPane();
grid.setPadding(new Insets(10, 0, 10, 0));
grid.setHgap(20);
grid.setVgap(20);
grid.setGridLinesVisible(true);
// Title in row 0 column 3 with styling
title = new Text();
title.setCache(true);
title.setText("REEL RUSH");
title.setFill(Color.YELLOW);
title.setFont(Font.font("Arial", FontWeight.BOLD, 60));
Reflection r = new Reflection();
title.setEffect(r);
GridPane.setConstraints(title, 3, 1);
GridPane.setHalignment(title, HPos.CENTER);
// Reel1 in row 4 column 2
image1 = new ImageView(image);
GridPane.setConstraints(image1, 2, 4);
GridPane.setHalignment(image1, HPos.CENTER);
// Reel2 in row 4 column 3
image2 = new ImageView(image);
GridPane.setConstraints(image2, 3, 4);
GridPane.setHalignment(image2, HPos.CENTER);
// Reel3 in row 4 column 4
image3 = new ImageView(image);
GridPane.setConstraints(image3, 4, 4);
GridPane.setHalignment(image3, HPos.CENTER);
// adding mouse click event for image views
image1.setOnMouseClicked(new EventHandler<javafx.scene.input.MouseEvent>() {
#Override
public void handle(javafx.scene.input.MouseEvent event) {
symbolClicked(event);
System.out.println("REEL 1 IS CLICKED");
}
});
image2.setOnMouseClicked(new EventHandler<javafx.scene.input.MouseEvent>() {
#Override
public void handle(javafx.scene.input.MouseEvent event) {
symbolClicked(event);
System.out.println("REEL 2 IS CLICKED");
}
});
image3.setOnMouseClicked(new EventHandler<javafx.scene.input.MouseEvent>() {
#Override
public void handle(javafx.scene.input.MouseEvent event) {
symbolClicked(event);
System.out.println("REEL 3 IS CLICKED");
}
});
// Status label row 8 column 4
lblStatus = new Label("YOU LOOSE");
lblStatus.setId("label-lblStatus");
GridPane.setConstraints(lblStatus, 3, 8);
GridPane.setHalignment(lblStatus, HPos.CENTER);
//information area label row 9 column 3
lblInformationArea = new Label("INFORMATION AREA ");
lblInformationArea.setId("label-lbl");
GridPane.setConstraints(lblInformationArea, 3, 9);
GridPane.setHalignment(lblInformationArea, HPos.CENTER);
// Credit area label row 5 column 2
lblCreditArea = new Label("CREDIT AREA: " + remainingCoins);
lblCreditArea.setId("label-lbl");
GridPane.setConstraints(lblCreditArea, 2, 9);
GridPane.setHalignment(lblCreditArea, HPos.CENTER);
// Bet amount label row 5 column 4
lblBetAmount = new Label("BET AMOUNT: " +betAmount);
lblBetAmount.setId("label-lbl");
GridPane.setConstraints(lblBetAmount, 4, 9);
GridPane.setHalignment(lblBetAmount, HPos.CENTER);
// Add coin button row 6 column 3
btnSpin = new Button("SPIN");
btnSpin.setId("button-btnSpin");
GridPane.setConstraints(btnSpin, 3, 10);
GridPane.setHalignment(btnSpin, HPos.CENTER);
// Add coin button row 8 column 1
btnAddCoin = new Button("ADD COIN");
GridPane.setConstraints(btnAddCoin, 2, 12);
GridPane.setHalignment(btnAddCoin, HPos.CENTER);
// Add coin button row 8 column 2
btnBetOne = new Button("BET ONE");
btnBetOne.setFont(Font.font("Arial", 20));
GridPane.setConstraints(btnBetOne, 1, 12);
GridPane.setHalignment(btnBetOne, HPos.CENTER);
// Add coin button row 8 column 3
btnBetMax = new Button("BET MAX");
GridPane.setConstraints(btnBetMax, 4, 12);
GridPane.setHalignment(btnBetMax, HPos.CENTER);
// Add coin button row 8 column 4
btnReset = new Button("RESET");
GridPane.setConstraints(btnReset, 6, 12);
GridPane.setHalignment(btnReset, HPos.CENTER);
// Add coin button row 8 column 5
btnStatistics = new Button("STATISTICS");
GridPane.setConstraints(btnStatistics, 3, 12);
GridPane.setHalignment(btnStatistics, HPos.CENTER);
// ------------------- Adding mouse events for each button ---------------------------
btnAddCoin.setOnAction(new EventHandler<javafx.event.ActionEvent>() {
#Override
public void handle(javafx.event.ActionEvent event) {
remainingCoins++;
lblCreditArea.setText("CREDIT AREA: "+remainingCoins);
}
});
btnBetOne.setOnAction(new EventHandler<javafx.event.ActionEvent>() {
#Override
public void handle(javafx.event.ActionEvent event) {
if (remainingCoins > 0) {
remainingCoins--;
betAmount++;
lblBetAmount.setText("BET AMOUNT: " + betAmount);
lblCreditArea.setText("CREDIT AREA: " + remainingCoins);
} else {
lblInformationArea.setText("No Credits Left!!!! Please Insert A Coin");
}
}
});
btnSpin.setOnAction(new EventHandler<javafx.event.ActionEvent>() {
#Override
public void handle(javafx.event.ActionEvent event) {
if (betAmount > 0) {
System.out.println("SPIN BUTTON CLICKED");
thread1.start();
} else {
lblInformationArea.setText("You did not bet!!!! Please Bet");
}
}
});
btnReset.setOnAction(new EventHandler<javafx.event.ActionEvent>() {
#Override
public void handle(javafx.event.ActionEvent event) {
remainingCoins = 10;
betAmount = 0;
lblBetAmount.setText("BET AMOUNT: " + betAmount);
lblCreditArea.setText("CREDIT AREA: " + remainingCoins);
lblInformationArea.setText("Status");
image1.setImage(image);
image2.setImage(image);
image3.setImage(image);
}
});
btnBetMax.setOnAction(new EventHandler<javafx.event.ActionEvent>() {
#Override
public void handle(javafx.event.ActionEvent event) {
if (remainingCoins >= 3) {
remainingCoins = remainingCoins - 3;
betAmount = betAmount + 3;
lblBetAmount.setText("BET AMOUNT: " + betAmount);
lblCreditArea.setText("CREDIT AREA: " + remainingCoins);
} else {
lblInformationArea.setText("No Credits Left!!!! Please Insert A Coin");
}
}
});
btnStatistics.setOnAction(new EventHandler<javafx.event.ActionEvent>() {
#Override
public void handle(javafx.event.ActionEvent event) {
//statistic();
lblInformationArea.setText("Spin the Reel First");
}
});
// adding all to the scene
grid.getChildren().addAll(title, lblStatus, lblInformationArea, lblCreditArea, lblBetAmount, btnAddCoin, btnBetMax, btnBetOne, btnReset, btnSpin, btnStatistics, image1, image3 , image2);
grid.setAlignment(Pos.TOP_CENTER);
Scene scene = new Scene(grid, 1450, 920);
scene.getStylesheets().add("/css/main.css");
primaryStage.setTitle("REEL RUSH");
primaryStage.setScene(scene);
primaryStage.show();
}
public void reel1() {
while (true) {
//creating reel objects for each reel
Reel firstReel = new Reel();
Reel secondReel = new Reel();
Reel thirdReel = new Reel();
Symbol[] firstReelSymbols = firstReel.spin();
Symbol[] secondReelSymbols = secondReel.spin();
Symbol[] thirdReelSymbols = thirdReel.spin();
for (Symbol item : firstReelSymbols) {
Image img1 = item.getImage();
image1.setImage(img1);
reel1value = item.getValue();
}
for (Symbol item : secondReelSymbols) {
Image img1 = item.getImage();
image2.setImage(img1);
reel1value = item.getValue();
}
for (Symbol item : thirdReelSymbols) {
Image img1 = item.getImage();
image3.setImage(img1);
reel1value = item.getValue();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void symbolClicked(javafx.scene.input.MouseEvent event) {
//TODO stop thread when image clicked
if((reel1value==reel3value)&&(reel2value==reel3value)){
//check if all 3 numbers are same
lblInformationArea.setText("You Win");
remainingCoins+=(betAmount*reel1value);
lblCreditArea.setText("Credits Area: "+remainingCoins);
wins++;
}else{
lblInformationArea.setText("You Loose");
lost++;
}
betAmount=0;
lblBetAmount.setText("Bet Amount: "+betAmount);
}
#Override
public void run() {
}
public static void main(String[] args){
launch(args);
}
}
Technically, while you can "run" an Application, we would never implement Runnable in an Application. JavaFX will do its own application management, and there is no way you can "run" an application in another thread.
Now, back to your question. Your reel1() is badly written. If you know a particular method is going to run in non-UI thread (i.e. JavaFX Application thread), you must take note not to directly set any kind of value that changes the UI within it.
So, this:
for (Symbol item : firstReelSymbols) {
Image img1 = item.getImage();
image1.setImage(img1);
reel1value = item.getValue();
}
should becomes something like:
for (Symbol item : firstReelSymbols) {
Image img1 = item.getImage();
Platform.runLater(() -> image1.setImage(img1)); // Run this run on UI thread
reel1value = item.getValue(); // Not sure what this value is for, may need to be wrapped inside Platform.runLater() if it affects UI
}
Other than this, it is weird that you are looping through a list of Symbol objects, and inside the loop you are setting the same image1 field.

Gluon Mobile Cardpane UI Enhancements: Cardcell Generation/Deletion & Cardpane Styling

I'm trying to create a cardpane with custom HBox CardCells.
Issue #1
How do I set the background of this CardPane? I want it to be transparent, but it won't change from this grey color. I have tried adding styling to the node directly as well as add a custom stylesheet. I have also tried the setBackground method:
Issue #2
Taken from this SO post, I was able to add an animation for cell generation in which it fades in upwards. However, in random card inserts, different cells lose the node that I have embedded in that cell. I don't know if this is because of the recycling concept of these cards (based on Gluon docs) or what:
Issue #3
I created functionality such that the user can delete the cards by swiping left. However, the same issue from Issue #2 arises, but to an even greater extent in which the entire cell is missing but still taking space. If I have only one cell and swipe left, it works all the time. However when I have more than one cell (for example I have 3 cells and I delete the 2nd cell), things get broken, event handlers for cells get removed, swiping left on one cell starts the animation on a cell below it, etc. Is there a way I can perform this functionality or is my best bet to just get rid of the CardPane and use a combination of VBox and HBox elements?
private void addToCardPane(CustomCard newCard) {
ObservableList<Node> items = cardpane.getItems();
boolean override = false;
for (int i = 0; i < cardpane.getItems().size(); i++) {
CustomCard box = (CustomCard) items.get(i);
if (box.checkEquality(newCard)) {
box.increaseNumber(newCard);
override = true;
break;
}
}
if (override == false) {
cardpane.getItems().add(newCard);
cardpane.layout();
VirtualFlow vf = (VirtualFlow) cardpane.lookup(".virtual-flow");
Node cell = vf.getCell(cardpane.getItems().size() - 1);
cell.setTranslateX(0);
cell.setOpacity(1.0);
if (!cardpane.lookup(".scroll-bar").isVisible()) {
FadeInUpTransition f = new FadeInUpTransition(cell);
f.setRate(2);
f.play();
} else {
PauseTransition p = new PauseTransition(Duration.millis(20));
p.setOnFinished(e -> {
vf.getCell(cardpane.getItems().size() - 1).setOpacity(0);
vf.show(cardpane.getItems().size() - 1);
FadeTransition f = new FadeTransition();
f.setDuration(Duration.seconds(1));
f.setFromValue(0);
f.setToValue(1);
f.setNode(vf.getCell(cardpane.getItems().size() - 1));
f.setOnFinished(t -> {
});
f.play();
});
p.play();
}
}
initializeDeletionLogic();
}
private void initializeDeletionLogic() {
VirtualFlow vf = (VirtualFlow) cardpane.lookup(".virtual-flow");
for (int i = 0; i < cardpane.getItems().size(); i++) {
CustomCard card = (CustomCard ) cardpane.getItems().get(i);
Node cell2 = vf.getCell(i);
addRemovalLogicForCell(card, cell2);
}
}
private static double initX = 0;
private void addRemovalLogicForCell(OpioidCard card, Node cell) {
card.setOnMousePressed(e -> {
initX = e.getX();
});
card.setOnMouseDragged(e -> {
double current = e.getX();
if (current < initX) {
if ((current - initX) < 0 && (current - initX) > -50) {
cell.setTranslateX(current - initX);
}
}
});
card.setOnMouseReleased(e -> {
double current = e.getX();
double delta = current - initX;
System.out.println(delta);
if (delta > -50) {
int originalMillis = 500;
double ratio = (50 - delta) / 50;
int newMillis = (int) (500 * ratio);
TranslateTransition translate = new TranslateTransition(Duration.millis(newMillis));
translate.setToX(0);
translate.setNode(cell);
translate.play();
} else {
FadeTransition ft = new FadeTransition(Duration.millis(300), cell);
ft.setFromValue(1.0);
ft.setToValue(0);
TranslateTransition translateTransition
= new TranslateTransition(Duration.millis(300), cell);
translateTransition.setFromX(cell.getTranslateX());
translateTransition.setToX(-400);
ParallelTransition parallel = new ParallelTransition();
parallel.getChildren().addAll(ft, translateTransition);
parallel.setOnFinished(evt -> {
removeCard(card);
ObservableList<CustomCard > cells = FXCollections.observableArrayList();
for(int i = 0; i < this.cardpane.getItems().size(); i++){
cells.add((CustomCard )this.cardpane.getItems().get(i));
}
this.cardpane.getItems().clear();
for(int i = 0; i < cells.size(); i++){
this.cardpane.getItems().add(cells.get(i));
}
initializeDeletionLogic();
initX = 0;
});
parallel.play();
}
});
}
private void removeCard(OpioidCard card) {
for (int i = 0; i < cardpane.getItems().size(); i++) {
if (cardpane.getItems().get(i) == card) {
cardpane.getItems().remove(i);
updateNumber(this.totalNumber);
break;
}
}
for (int i = 0; i < dataList.size(); i++) {
if (dataList.get(i).getName().equalsIgnoreCase(card.getName())) {
dataList.remove(i);
}
}
this.cardpane.layout();
initializeDeletionLogic();
}
WORKING DEMO OF ISSUE:
package com.mobiletestapp;
import com.gluonhq.charm.glisten.animation.FadeInUpTransition;
import com.gluonhq.charm.glisten.control.AppBar;
import com.gluonhq.charm.glisten.control.CardCell;
import com.gluonhq.charm.glisten.control.CardPane;
import com.gluonhq.charm.glisten.mvc.View;
import com.gluonhq.charm.glisten.visual.MaterialDesignIcon;
import com.sun.javafx.scene.control.skin.VirtualFlow;
import javafx.animation.FadeTransition;
import javafx.animation.ParallelTransition;
import javafx.animation.PauseTransition;
import javafx.animation.TranslateTransition;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.util.Duration;
public class BasicView extends View {
class CustomCard extends StackPane{
public CustomCard(String text){
this.getChildren().add(new Label(text));
}
}
private static double initX = 0;
private static void addRemovalLogicForCell(CustomCard card, Node cell) {
card.setOnMousePressed(e -> {
initX = e.getX();
});
card.setOnMouseDragged(e -> {
double current = e.getX();
if (current < initX) {
if ((current - initX) < 0 && (current - initX) > -50) {
cell.setTranslateX(current - initX);
}
}
});
card.setOnMouseReleased(e -> {
double current = e.getX();
double delta = current - initX;
System.out.println(delta);
if (delta > -50) {
int originalMillis = 500;
double ratio = (50 - delta) / 50;
int newMillis = (int) (500 * ratio);
TranslateTransition translate = new TranslateTransition(Duration.millis(newMillis));
translate.setToX(0);
translate.setNode(cell);
translate.play();
} else {
FadeTransition ft = new FadeTransition(Duration.millis(300), cell);
ft.setFromValue(1.0);
ft.setToValue(0);
TranslateTransition translateTransition
= new TranslateTransition(Duration.millis(300), cell);
translateTransition.setFromX(cell.getTranslateX());
translateTransition.setToX(-400);
ParallelTransition parallel = new ParallelTransition();
parallel.getChildren().addAll(ft, translateTransition);
parallel.setOnFinished(evt -> {
for(int i = 0; i < cardPane.getItems().size(); i++){
if(cardPane.getItems().get(i) == card){
cardPane.getItems().remove(i);
}
}
initX = 0;
});
parallel.play();
}
});
}
private static CardPane cardPane = null;
public BasicView(String name) {
super(name);
cardPane = new CardPane();
cardPane.setCellFactory(p -> new CardCell<CustomCard>() {
#Override
public void updateItem(CustomCard item, boolean empty) {
super.updateItem(item, empty);
if (!empty) {
setText(null);
setGraphic(item);
} else {
setText(null);
setGraphic(null);
}
}
});
setCenter(cardPane);
}
private static void addCard(CustomCard newCard){
cardPane.getItems().add(newCard);
cardPane.layout();
VirtualFlow vf = (VirtualFlow) cardPane.lookup(".virtual-flow");
Node cell = vf.getCell(cardPane.getItems().size() - 1);
cell.setTranslateX(0);
cell.setOpacity(1.0);
if (!cardPane.lookup(".scroll-bar").isVisible()) {
FadeInUpTransition f = new FadeInUpTransition(cell);
f.setRate(2);
f.play();
} else {
PauseTransition p = new PauseTransition(Duration.millis(20));
p.setOnFinished(e -> {
vf.getCell(cardPane.getItems().size() - 1).setOpacity(0);
vf.show(cardPane.getItems().size() - 1);
FadeTransition f = new FadeTransition();
f.setDuration(Duration.seconds(1));
f.setFromValue(0);
f.setToValue(1);
f.setNode(vf.getCell(cardPane.getItems().size() - 1));
f.setOnFinished(t -> {
});
f.play();
});
p.play();
}
addRemovalLogicForCell(newCard, cell);
}
#Override
protected void updateAppBar(AppBar appBar) {
appBar.setNavIcon(MaterialDesignIcon.MENU.button(e -> System.out.println("Menu")));
appBar.setTitleText("Basic View");
appBar.getActionItems().add(MaterialDesignIcon.ADD.button(e -> addCard(new CustomCard("Hello"))));
}
}
This leads to the following output when adding and swiping left for deletion:
If you check with ScenicView, you will notice that the CardPane holds a CharmListView control, which in terms uses an inner ListView that takes the size of its parent.
So this should work:
.card-pane > .charm-list-view > .list-view {
-fx-background-color: transparent;
}
As I mentioned, the control is based on a ListView, so the way to provide cells is using the cell factory. As you can read in the control's JavaDoc:
The CardPane is prepared for a big number of items by reusing its cards.
A developer may personalize cell creation by specifying a cell factory through cellFactoryProperty(). The default cell factory is prepared to accept objects from classes that extend Node or other classes that don't extend from Node, in the latter case the card text will be given by the Object.toString() implementation of the object.
If you are not using it yet, consider using something like this:
cardPane.setCellFactory(p -> new CardCell<T>() {
#Override
public void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
if (!empty) {
setText(null);
setGraphic(createContent(item));
} else {
setText(null);
setGraphic(null);
}
}
});
This should manage for you the cards layout, avoiding blank cells or wrong reuse of them.
As for the animation, there shouldn't be a problem in using it.
For swipe animations, the Comments2.0 sample provides a similar use case: A ListView where each cell uses a SlidingListTile. Have a look at its implementation.
You should be able to reuse it with the CardPane.
Try it out, and if you still have issues, post a working sample here (or provide a link), so we can reproduce them.
EDIT
Based on the posted code, a comment related to how the factory cell should be set:
All the JavaFX controls using cells (like ListView or TableView), and also the Gluon CardPane, follow the MVC pattern:
Model. The control is bound to a model, using an observable list of items of that model. In the case of the sample, a String, or any regular POJO, or, as the preferred choice, a JavaFX bean (with observable properties).
So in this case, you should have:
CardPane<String> cardPane = new CardPane<>();
View. The control has a method to set how the cell renders the model, the cellFactory. This factory can define just text, or any graphic node, like your CustomCard.
In this case, you should have:
cardPane.setCellFactory(p -> new CardCell<String>() {
private final CustomCard card;
{
card = new CustomCard();
}
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
card.setText(item);
setGraphic(card);
setText(null);
} else {
setGraphic(null);
setText(null);
}
}
});
where:
class CustomCard extends StackPane {
private final Label label;
public CustomCard(){
label = new Label();
getChildren().add(label);
}
public void setText(String text) {
label.setText(text);
}
}
Internally, the control uses a VirtualFlow that manages to reuse cells, and only modify the content (the model) when scrolling.
As you can see in the cell factory, now you'll iterate over the model (String), while the CustomCard remains the same, and only the content its updated.
Using this approach doesn't present any of the issues you have described, at least when adding cells.
EDIT 2
I've come up with a solution that works fine for me and should solve all the issues mentioned. Besides what was mentioned before, it is also required restoring the transformations applied to the CustomCard in the updateItem callbacks.
public class BasicView extends View {
private final CardPane<String> cardPane;
public BasicView(String name) {
super(name);
cardPane = new CardPane<>();
cardPane.setCellFactory(p -> new CardCell<String>() {
private final CustomCard card;
private final HBox box;
{
card = new CustomCard();
card.setMaxWidth(Double.MAX_VALUE);
card.prefWidthProperty().bind(widthProperty());
box = new HBox(card);
box.setAlignment(Pos.CENTER);
box.setStyle("-fx-background-color: grey");
addRemovalLogicForCell(card);
}
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
card.setText(item);
card.setTranslateX(0);
card.setOpacity(1.0);
setGraphic(box);
setText(null);
} else {
setGraphic(null);
setText(null);
}
}
});
setCenter(cardPane);
}
class CustomCard extends StackPane {
private final Label label;
public CustomCard(){
label = new Label();
label.setStyle("-fx-font-size: 20;");
getChildren().add(label);
setStyle("-fx-padding: 20; -fx-background-color: white");
setPrefHeight(100);
}
public void setText(String text) {
label.setText(text);
}
public String getText() {
return label.getText();
}
}
private double initX = 0;
private void addRemovalLogicForCell(CustomCard card) {
card.setOnMousePressed(e -> {
initX = e.getX();
});
card.setOnMouseDragged(e -> {
double current = e.getX();
if ((current - initX) < 0 && (current - initX) > -50) {
card.setTranslateX(current - initX);
}
});
card.setOnMouseReleased(e -> {
double current = e.getX();
double delta = current - initX;
if (delta < 50) {
if (delta > -50) {
int originalMillis = 500;
double ratio = (50 - delta) / 50;
int newMillis = (int) (500 * ratio);
TranslateTransition translate = new TranslateTransition(Duration.millis(newMillis));
translate.setToX(0);
translate.setNode(card);
translate.play();
} else {
FadeTransition ft = new FadeTransition(Duration.millis(300), card);
ft.setFromValue(1.0);
ft.setToValue(0);
TranslateTransition translateTransition
= new TranslateTransition(Duration.millis(300), card);
translateTransition.setFromX(card.getTranslateX());
translateTransition.setToX(-400);
ParallelTransition parallel = new ParallelTransition();
parallel.getChildren().addAll(ft, translateTransition);
parallel.setOnFinished(evt -> {
cardPane.getItems().remove(card.getText());
initX = 0;
});
parallel.play();
}
}
});
}
private void addCard(String newCard){
cardPane.getItems().add(newCard);
cardPane.layout();
VirtualFlow vf = (VirtualFlow) cardPane.lookup(".virtual-flow");
IndexedCell cell = vf.getCell(cardPane.getItems().size() - 1);
cell.setTranslateX(0);
cell.setOpacity(0);
if (! cardPane.lookup(".scroll-bar").isVisible()) {
FadeInUpTransition f = new FadeInUpTransition(cell, true);
f.setRate(2);
f.play();
} else {
PauseTransition p = new PauseTransition(Duration.millis(20));
p.setOnFinished(e -> {
vf.show(cardPane.getItems().size() - 1);
FadeInTransition f = new FadeInTransition(cell);
f.setRate(2);
f.play();
});
p.play();
}
}
#Override
protected void updateAppBar(AppBar appBar) {
appBar.setNavIcon(MaterialDesignIcon.MENU.button(e -> System.out.println("Menu")));
appBar.setTitleText("Basic View");
appBar.getActionItems().add(MaterialDesignIcon.ADD.button(e -> addCard("Hello #" + new Random().nextInt(100))));
}
}

ScrollBar within custom Alert box not working JavaFX

Is there an easy way to put a scroll bar in an Alert box and have it actually scroll? I'm adding a grid pane to the scroll pane. While the Alert box is active, I'm adding and removing content as the user requests. Unfortunately, there is a limit to ho much I can add because the Alert box grows beyond the screen height. Hence the reason for a scroll pane. The problem is, it doesn't seem to think it needs to scroll even though the content is below the screen
I've tried a bunch of things, but each time the scroll bars grow with the scroll pane. I even tried a custom ScrollPane as suggested by James_D. Still not luck.
Any help would be awesome!
Here is the code for the custom Alert box
import java.util.ArrayList;
import java.util.List;
import com.sun.xml.internal.bind.v2.runtime.unmarshaller.XmlVisitor.TextPredictor;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Bounds;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.DialogPane;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.ScrollPane.ScrollBarPolicy;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
public class SettingsChangeWindow extends Alert {
public enum SETTING_TYPE {SINGLE, MULTIPLE};
private SETTING_TYPE type = null;
private IndexedGridPane parentGrid;
private SettingBean bean;
private DialogPane parentPane;
public SettingsChangeWindow(SettingBean bean) {
super(AlertType.CONFIRMATION);
this.bean = bean;
this.type = bean.getType();
SizeableScrollPane scroll = new SizeableScrollPane();
scroll.setHbarPolicy(ScrollBarPolicy.ALWAYS);
scroll.setVbarPolicy(ScrollBarPolicy.ALWAYS);
scroll.setFitToHeight(true);
scroll.setFitToWidth(true);
parentGrid = new IndexedGridPane();
scroll.setContent(parentGrid);
this.parentPane = getDialogPane();
setResizable(true);
if(type == SETTING_TYPE.SINGLE){
FriendlyVBox vbox = new FriendlyVBox();
setTitle("Change " + bean.getName());
setHeaderText("Change the " + bean.getName() + " value by changing the value in the box");
parentGrid.setPadding(new Insets(20, 150, 0, 10));
vbox.getChildren().addAll(new Label(bean.getName()), new AutoTextBox(bean.getValue()));
parentGrid.add(vbox, 0, 0);
}else{
setTitle("Change " + bean.getName());
String header = "Change the " + bean.getName() + " value by changing the value in the box\n";
header += "You may add and delete value sets (may require resizing)";
setHeaderText(header);
parentGrid.add(new AddButton(), 1, 0);
parentGrid.add(new RemoveButton(), 2, 0);
addMultipleValues();
}
this.parentPane.setContent(scroll);
// parentPane.setMinHeight(GridPane.USE_PREF_SIZE);
// parentPane.setMinWidth(GridPane.USE_PREF_SIZE);
// getDialogPane().getChildren().stream().forEach(node -> ((Label)node).setMinHeight(Region.USE_PREF_SIZE));
}
public SettingBean getValue(){
return bean;
}
private void addMultipleValues(){
List<Object> values = bean.getChildren();
if(bean.getName().equals("TSPAddressPostal") || bean.getName().equals("SchemeOperatorAddressPostal")){
for(Object addr : values){
PhysicalAddressBean address = (PhysicalAddressBean)addr;
addPhysicalAddress(address);
}
}else{
for(Object uri : values){
addURI((String)uri);
}
}
}
public void saveValue(){
if(type == SETTING_TYPE.SINGLE){
List<Node> children = parentGrid.getChildren();
for(Node child : children){
if(child instanceof FriendlyVBox){
// cast to FriendlyVBox
String value = ((FriendlyVBox)child).getTextField().getText();
this.bean.setValue(value);
}
}
}else{
saveMultipleValues();
}
}
/**
* For values in XML that can have multiple child nodes
*/
private void saveMultipleValues(){
switch(bean.getName()){
case "TSPAddressPostal" :
savePostalAddress();
break;
case "SchemeOperatorAddressPostal":
savePostalAddress();
break;
default:
saveURI();
break;
}
}
/**
* If the setting bean is encapsulating a list of physical address
* (when the name is: PostalAddress) populate via predefined structure
*/
private void savePostalAddress(){
List<Object> addresses = new ArrayList<>();
List<Node> children = parentGrid.getChildren();
for(Node child : children){
if(child instanceof IndexedGridPane){
IndexedGridPane pane = (IndexedGridPane) child;
PhysicalAddressBean add = new PhysicalAddressBean();
// each address attribute in the order listed in Trust List XML
add.setStreetAddress(((FriendlyVBox)pane.get(0, 1)).getTextField().getText());
add.setLocality(((FriendlyVBox)pane.get(0, 2)).getTextField().getText());
add.setPostalCode(((FriendlyVBox)pane.get(0, 3)).getTextField().getText());
add.setCountryName(((FriendlyVBox)pane.get(0, 4)).getTextField().getText());
// add address bean to list
addresses.add(add);
}
}
bean.setChildren(addresses);
}
/**
* used to store any values in the XML that can have multiple child URI values
*/
private void saveURI(){
List<Object> uris = new ArrayList<>();
List<Node> children = parentGrid.getChildren();
for(Node child : children){
if(child instanceof FriendlyVBox){
FriendlyVBox vBox = (FriendlyVBox) child;
uris.add(vBox.getTextField().getText());
}
}
bean.setChildren(uris);
}
private void addURI(String uri){
int newSlot = parentGrid.getRowCount();
FriendlyVBox vBox = new FriendlyVBox();
vBox.getChildren().addAll(new Label("\n" + bean.getName()), new AutoTextBox(""));
parentGrid.add(vBox, 0, newSlot);
}
private void addPhysicalAddress(){
int newSlot = parentGrid.getRowCount();
IndexedGridPane pane = new IndexedGridPane();
Label label = new Label("\nPostal Address");
pane.add(label, 0, 0);
label.setFont(Font.font("system", FontWeight.BOLD, 12));
FriendlyVBox postal = new FriendlyVBox();
postal.getChildren().addAll(new Label("Street Address"), new AutoTextBox(""));
FriendlyVBox local = new FriendlyVBox();
local.getChildren().addAll(new Label("Locale"), new AutoTextBox(""));
FriendlyVBox postalCode = new FriendlyVBox();
postalCode.getChildren().addAll(new Label("Postal Code"), new AutoTextBox(""));
FriendlyVBox country = new FriendlyVBox();
country.getChildren().addAll(new Label("Country Name"), new AutoTextBox(""));
pane.add(postal, 0, 1);
pane.add(local, 0, 2);
pane.add(postalCode, 0, 3);
pane.add(country, 0, 4);
parentGrid.add(pane, 0, newSlot);
}
private void addPhysicalAddress(PhysicalAddressBean address){
int newSlot = parentGrid.getRowCount();
IndexedGridPane pane = new IndexedGridPane();
Label label = new Label("\nPostal Address");
pane.add(label, 0, 0);
label.setFont(Font.font("system", FontWeight.BOLD, 12));
FriendlyVBox street = new FriendlyVBox();
street.getChildren().addAll(new Label("Street Address"), new AutoTextBox(address.getStreetAddress()));
FriendlyVBox local = new FriendlyVBox();
local.getChildren().addAll(new Label("Locale"), new AutoTextBox(address.getLocality()));
FriendlyVBox postalCode = new FriendlyVBox();
postalCode.getChildren().addAll(new Label("Postal Code"), new AutoTextBox(address.getPostalCode()));
FriendlyVBox country = new FriendlyVBox();
country.getChildren().addAll(new Label("Country Name"), new AutoTextBox(address.getCountryName()));
pane.add(street, 0, 1);
pane.add(local, 0, 2);
pane.add(postalCode, 0, 3);
pane.add(country, 0, 4);
parentGrid.add(pane, 0, newSlot);
}
private class IndexedGridPane extends GridPane{
public Node get(final int row, final int column) {
Node result = null;
ObservableList<Node> childrens = super.getChildren();
for (Node node : childrens) {
if(super.getRowIndex(node) == row && super.getColumnIndex(node) == column) {
result = node;
break;
}
}
return result;
}
public int getRowCount() {
int numRows = getRowConstraints().size();
for (int i = 0; i < getChildren().size(); i++) {
Node child = getChildren().get(i);
if (child.isManaged()) {
Integer rowIndex = GridPane.getRowIndex(child);
if(rowIndex != null){
numRows = Math.max(numRows,rowIndex+1);
}
}
}
return numRows;
}
}
private class AutoTextBox extends TextField{
public AutoTextBox(String contents){
setMinWidth(Region.USE_PREF_SIZE);
setMaxWidth(Region.USE_PREF_SIZE);
textProperty().addListener(new AutoAdjustText());
setText(contents);
}
private class AutoAdjustText implements ChangeListener<String>{
#Override
public void changed(ObservableValue<? extends String> ov,
String prevText, String currText) {
Platform.runLater(() -> {
Text text = new Text(currText);
text.setFont(getFont()); // Set the same font, so the size is the same
double width = text.getLayoutBounds().getWidth() // This big is the Text in the TextField
+ getPadding().getLeft() + getPadding().getRight() // Add the padding of the TextField
+ 2d; // Add some spacing
setPrefWidth(width); // Set the width
positionCaret(getCaretPosition()); // If you remove this line, it flashes a little bit
});
}
}
}
private class FriendlyVBox extends VBox{
public TextField getTextField(){
List<Node> children = getChildren();
for(Node child : children){
if(child instanceof TextField){
return (TextField)child;
}
}
return null;
}
}
private class AddButton extends Button{
public AddButton(){
setText("Add+");
onActionProperty().set(new AddValue());
}
private class AddValue implements EventHandler<ActionEvent>{
#Override
public void handle(ActionEvent event) {
if(bean.getName().equals("TSPAddressPostal") || bean.getName().equals("SchemeOperatorAddressPostal")){
addPhysicalAddress();
}else{
addURI("Add URI here");
// parentPane.setContent(parentGrid);
}
parentPane.getScene().getWindow().sizeToScene();
}
}
}
private class RemoveButton extends Button{
public RemoveButton(){
setText("Remove");
onActionProperty().set(new RemoveValue());
}
private class RemoveValue implements EventHandler<ActionEvent>{
#Override
public void handle(ActionEvent event) {
int rowCount = parentGrid.getRowCount();
parentGrid.getChildren().remove(rowCount);
parentPane.getScene().getWindow().sizeToScene();
}
}
}
private class SizeableScrollPane extends ScrollPane{
public SizeableScrollPane() {
viewportBoundsProperty().addListener(new Resizer());
hvalueProperty().addListener(new Resizer());
vvalueProperty().addListener(new Resizer());
}
private class Resizer implements ChangeListener<Object> {
#Override
public void changed(ObservableValue<? extends Object> observable, Object oldValue, Object newValue) {
double hmin = getHmin();
double hmax = getHmax();
double hvalue = getHvalue();
double contentWidth = getContent().getLayoutBounds().getWidth();
double viewportWidth = getViewportBounds().getWidth();
double hoffset =
Math.max(0, contentWidth - viewportWidth) * (hvalue - hmin) / (hmax - hmin);
double vmin = getVmin();
double vmax = getVmax();
double vvalue = getVvalue();
double contentHeight = getContent().getLayoutBounds().getHeight();
double viewportHeight = getViewportBounds().getHeight();
double voffset =
Math.max(0, contentHeight - viewportHeight) * (vvalue - vmin) / (vmax - vmin);
System.out.printf("Offset: [%.1f, %.1f] width: %.1f height: %.1f %n",
hoffset, voffset, viewportWidth, viewportHeight);
}
}
}
}
I feel stupid.
if you comment out the lines in the constructor:
scroll.setHbarPolicy(ScrollBarPolicy.ALWAYS);
scroll.setVbarPolicy(ScrollBarPolicy.ALWAYS);
scroll.setFitToHeight(true);
scroll.setFitToWidth(true);
And you comment out the lines in the button listeners:
parentPane.getScene().getWindow().sizeToScene();
All is good in the world. You don't even need a custom ScrollPane, it works as expected. I hope this helps someone else

How to detect mouse movement over node while button is pressed?

Problem
You can add an event listener to a node which detects mouse movement over it. This doesn't work if a mouse button was pressed before you moved over the node.
Question
Does anyone know how to detect mouse movement while the button is pressed? So far I've only found a solution by using the MOUSE_DRAGGED event and then instead of using getSource() using getPickResult() and evaluating the PickResult data.
Here's the code including Uluk's solution. The old and new solution are switchable via the useNewVersion (Uluk's version) boolean:
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.PickResult;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class Main extends Application {
boolean useNewVersion= true;
int rows = 10;
int columns = 20;
double width = 1024;
double height = 768;
#Override
public void start(Stage primaryStage) {
try {
BorderPane root = new BorderPane();
// create grid
Grid grid = new Grid( columns, rows, width, height);
MouseGestures mg = new MouseGestures();
// fill grid
for (int row = 0; row < rows; row++) {
for (int column = 0; column < columns; column++) {
Cell cell = new Cell(column, row);
mg.makePaintable(cell);
grid.add(cell, column, row);
}
}
root.setCenter(grid);
// create scene and stage
Scene scene = new Scene(root, width, height);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
private class Grid extends Pane {
int rows;
int columns;
double width;
double height;
Cell[][] cells;
public Grid( int columns, int rows, double width, double height) {
this.columns = columns;
this.rows = rows;
this.width = width;
this.height = height;
cells = new Cell[rows][columns];
}
/**
* Add cell to array and to the UI.
*/
public void add(Cell cell, int column, int row) {
cells[row][column] = cell;
double w = width / columns;
double h = height / rows;
double x = w * column;
double y = h * row;
cell.setLayoutX(x);
cell.setLayoutY(y);
cell.setPrefWidth(w);
cell.setPrefHeight(h);
getChildren().add(cell);
}
}
private class Cell extends StackPane {
int column;
int row;
public Cell(int column, int row) {
this.column = column;
this.row = row;
getStyleClass().add("cell");
Label label = new Label(this.toString());
getChildren().add(label);
}
public void highlight() {
getStyleClass().add("cell-highlight");
}
public void unhighlight() {
getStyleClass().remove("cell-highlight");
}
public String toString() {
return this.column + "/" + this.row;
}
}
public class MouseGestures {
public void makePaintable( Node node) {
if( useNewVersion) {
node.setOnMousePressed( onMousePressedEventHandler);
node.setOnDragDetected( onDragDetectedEventHandler);
node.setOnMouseDragEntered( onMouseDragEnteredEventHandler);
} else {
node.setOnMousePressed( onMousePressedEventHandler);
node.setOnMouseDragged( onMouseDraggedEventHandler);
node.setOnMouseReleased( onMouseReleasedEventHandler);
}
}
/* old version */
EventHandler<MouseEvent> onMousePressedEventHandler = event -> {
Cell cell = (Cell) event.getSource();
if( event.isPrimaryButtonDown()) {
cell.highlight();
} else if( event.isSecondaryButtonDown()) {
cell.unhighlight();
}
};
EventHandler<MouseEvent> onMouseDraggedEventHandler = event -> {
PickResult pickResult = event.getPickResult();
Node node = pickResult.getIntersectedNode();
if( node instanceof Cell) {
Cell cell = (Cell) node;
if( event.isPrimaryButtonDown()) {
cell.highlight();
} else if( event.isSecondaryButtonDown()) {
cell.unhighlight();
}
}
};
EventHandler<MouseEvent> onMouseReleasedEventHandler = event -> {
};
EventHandler<MouseEvent> onDragDetectedEventHandler = event -> {
Cell cell = (Cell) event.getSource();
cell.startFullDrag();
};
EventHandler<MouseEvent> onMouseDragEnteredEventHandler = event -> {
Cell cell = (Cell) event.getSource();
if( event.isPrimaryButtonDown()) {
cell.highlight();
} else if( event.isSecondaryButtonDown()) {
cell.unhighlight();
}
};
}
}
In the end you should be able to paint via primary mouse button and erase the paint via secondary mouse button:
One solution is to add an event filter to the scene which enables the sourceNode.startFullDrag(). This will work even if you start dragging the mouse outside of your canvas (if you want any space without nodes in your application).
Like this:
scene.addEventFilter(MouseEvent.DRAG_DETECTED , new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
scene.startFullDrag();
}
});
And then you could:
node.setOnMouseDragEntered(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
led.setOn(true);
}
});
The (source) node which handles the initial DRAG_DETECTED event should invoke sourceNode.startFullDrag(), then the target node will able to handle one of MouseDragEvents, for instance MOUSE_DRAG_OVER or MOUSE_DRAG_ENTERED event with respective targetNode.setOn<MouseDragEvent>() method.

Resources