How do I sort through an array of buttons to see if only one is left enabled? - javafx

How do I sort through an array of buttons to see if only one is left enabled? I am creating a Sudoku solver and need to check if there is only one button left unselected in each row, column, or box and if it is to highlight the last remaining option. I can't find a command that will allow me to check each individual cell in the row to see if ONLY one is left. This is what I have so far
public class SudoHelper extends Application
{
public boolean [][][] DisabledCell = new boolean[3][9][9]; // Creates our array
Scene scene;
Pane pane;
Pane main;
BookMark bane;
#Override
public void start (Stage primaryStage)
{
for(int a = 0;a<3;a++)
{
for(int b=0;b<9;b++)
{
for(int c=0; c<9;c++)
{
DisabledCell[a][b][c]=false;
}
}
}
mouseClicks = new MouseEvent[0];
SmartCell[] currentGame = new SmartCell[81];
pane = new Pane();
pane.setPrefSize(684, 702);
int x,y;
x=y=0;
main = new Pane();
for(int i=0; i<81; i++)
{
currentGame[i]= new SmartCell(i);
currentGame[i].setLayoutX(x);
currentGame[i].setLayoutY(y);
pane.getChildren().add(currentGame[i]);
x+=76; // Sets the layout for out array of SmartCells
if(x==684) // and puts our additional buttons on the screen
{ // With our scene and stage
x=0;
y+=78;
}
}
main.setPrefSize(1100, 702);
main.getChildren().add(pane);
bane= new BookMark();
scene = new Scene(main);
main.getChildren().add(bane);
bane.setPrefSize(416, 702);
bane.setLayoutX(685);
bane.setLayoutY(0);
primaryStage.setScene(scene);
primaryStage.setTitle("Sudoku Helper");
primaryStage.show();
}
public static void main(String[] args)
{
Application.launch(args); // Starts the game
}
class BookMark extends Pane
{
class List
{
String[] MyList=new String[0];
public void Add(String Add)
{
if(this.MyList.length>0)
{
String[] Resize = new String[this.MyList.length+1];
for(int i=0;i<this.MyList.length;i++)
{
Resize[i]=this.MyList[i];
}
Resize[this.MyList.length+1]=Add;
}
else
{
this.MyList = new String[1];
this.MyList[0]=Add;
}
}
public void Clear()
{
this.MyList = new String[0];
}
}
Button lkm = new Button("Load Bookmark"); // Creates our load bookmark button
Button bkm = new Button("Save Bookmark"); // Creates out save bookmark button'
final ToggleGroup group1 = new ToggleGroup();
final ToggleGroup group2 = new ToggleGroup();
RadioButton rl1a = new RadioButton("Rule One All"); // Creates rule one radiobutton
RadioButton rl1s = new RadioButton("Rule One Click"); // Creates Rule one click radiobutton
RadioButton rl2s = new RadioButton("Rule Two Click"); // Creates rule two click radiobutton
RadioButton rl2a = new RadioButton("Rule Two All"); // Create rule two radiobutton
void ruleTwo()
{
}
void ruleOne()
{
int _x,_y;
int B=10;
String[]Getloc = name.split("~");
int loc = Integer.parseInt(Getloc[1]);
_y = loc%9;
if(loc<8) // checking which row we are looking at
{
_x=0;
for(_x=0; loc<8;)
{
if()
{
}
}
}
else if(loc<18)
{
_x=1;
}
else if(loc<27)
{
_x=2;
}
else if(loc<36)
{
_x=3;
}
else if(loc<45)
{
_x=4;
}
else if(loc<54)
{
_x=5;
}
else if(loc<63)
{
_x=6;
}
else if(loc<72)
{
_x=7;
}
else
{
_x=8; // checks blocks were looking at
}
if(_y>=0&&_y<=2) // checks which block we are in
{
if(_x>=0&&_x<=2)
{
B=0;
}
else if(_x>=3&&_x<=5)
{
B=1;
}
else if(_x>=6&&_x<=8)
{
B=2;
}
}
else if(_y>=3&&_y<=5)
{
if(_x>=0&&_x<=2)
{
B=3;
}
else if(_x>=3&&_x<=5)
{
B=4;
}
else if(_x>=6&&_x<=8)
{
B=5;
}
}
else if(_y>=6&&_y<=8)
{
if(_x>=0&&_x<=2)
{
B=6;
}
else if(_x>=3&&_x<=5)
{
B=7;
}
else if(_x>=6&&_x<=8)
{
B=8;
}
}
}
BookMark() // Our buttons specifications (Location font ect)
{
this.bkm = new Button("Save Bookmark");
this.lkm = new Button("Load Bookmark");
this.bkm.setFont(Font.font("Ariel", FontWeight.BOLD, FontPosture.REGULAR, 12));
this.bkm.setLayoutX(10);
this.bkm.setLayoutY(10);
this.lkm.setFont(Font.font("Ariel", FontWeight.BOLD, FontPosture.REGULAR, 12));
this.lkm.setLayoutX(150);
this.lkm.setLayoutY(10);
this.rl1a.setLayoutX(10);
this.rl1a.setLayoutY(250);
this.rl2a.setLayoutX(10);
this.rl2a.setLayoutY(500);
this.rl2s.setLayoutX(250);
this.rl1s.setLayoutY(250);
this.rl1s.setLayoutX(250);
this.rl2s.setLayoutY(500);
this.rl1a.setFont(Font.font("Ariel", FontWeight.BOLD, FontPosture.REGULAR, 12));
this.rl1s.setFont(Font.font("Ariel", FontWeight.BOLD, FontPosture.REGULAR, 12));
this.rl2a.setFont(Font.font("Ariel", FontWeight.BOLD, FontPosture.REGULAR, 12));
this.rl2s.setFont(Font.font("Ariel", FontWeight.BOLD, FontPosture.REGULAR, 12));
this.group1.getToggles().add(this.rl1a);
this.group1.getToggles().add(this.rl1s);
this.group2.getToggles().add(this.rl2a);
this.group2.getToggles().add(this.rl2s);
BookMark.this.getChildren().add(this.lkm);
BookMark.this.getChildren().add(this.bkm);
BookMark.this.getChildren().add(this.rl1a);
BookMark.this.getChildren().add(this.rl1s);
BookMark.this.getChildren().add(this.rl2a);
BookMark.this.getChildren().add(this.rl2s);
// bkm.setOnMouseClicked(e -> Save(e));
}
}
private MouseEvent[] mouseClicks;
class SmartCell extends StackPane
{
GridPane buttonPane;
Pane valPane;
Text textVal;
Button [] btn;
String name;
SmartCell(int nameint)
{
buttonPane = new GridPane();
btn = new Button[10];
for(int i = 1; i <= 9; i++)
{
btn[i] = new Button(i+""); // Turns i into a String
btn[i].setFont(Font.font("Ariel", FontWeight.BOLD, FontPosture.REGULAR, 12));
btn[i].setOnMouseClicked(e -> mouseHandler(e));
buttonPane.add(btn[i], (i-1)%3, (i-1)/3);
}
// When the user clicks one of the 9 buttons, we want to take the number of the button
// they clicked, and set the text on the text pane to that number, hide the 9 buttons,
// and show the text pane.
name = "SmartCell~"+String.valueOf(nameint);
textVal = new Text(25, 55, "");
textVal.setFont(Font.font("Arial", 48));
textVal.setTextAlignment(TextAlignment.CENTER);
valPane = new Pane();
valPane.setStyle("-fx-border-color:black; -fx-border-stroke-width:1");
valPane.getChildren().add(textVal);
getChildren().add(buttonPane); // Add the pane with the 9 buttons to the cell
getChildren().add(valPane); // Add the pane with the one piece of text to the cell
buttonPane.setVisible(true); // We start out showing the 9 buttons
valPane.setVisible(false); // ...NOT showing the pane with the single text
} // end constructor
void disqual(MouseEvent e)
{
MouseEvent[] ResizeMouse = new MouseEvent[SudoHelper.this.mouseClicks.length+1];
ResizeMouse[ResizeMouse.length-1]=e;
SudoHelper.this.mouseClicks = ResizeMouse;
int _x,_y;
int B=10;
String[]Getloc = name.split("~");
int loc = Integer.parseInt(Getloc[1]);
_y = loc%9;
if(loc<8) // checking which row we are looking at
{
_x=0;
}
else if(loc<18)
{
_x=1;
}
else if(loc<27)
{
_x=2;
}
else if(loc<36)
{
_x=3;
}
else if(loc<45)
{
_x=4;
}
else if(loc<54)
{
_x=5;
}
else if(loc<63)
{
_x=6;
}
else if(loc<72)
{
_x=7;
}
else
{
_x=8; // checks blocks were looking at
}
if(_y>=0&&_y<=2) // checks which block we are in
{
if(_x>=0&&_x<=2)
{
B=0;
}
else if(_x>=3&&_x<=5)
{
B=1;
}
else if(_x>=6&&_x<=8)
{
B=2;
}
}
else if(_y>=3&&_y<=5)
{
if(_x>=0&&_x<=2)
{
B=3;
}
else if(_x>=3&&_x<=5)
{
B=4;
}
else if(_x>=6&&_x<=8)
{
B=5;
}
}
else if(_y>=6&&_y<=8)
{
if(_x>=0&&_x<=2)
{
B=6;
}
else if(_x>=3&&_x<=5)
{
B=7;
}
else if(_x>=6&&_x<=8)
{
B=8;
}
};
int CellNum =Integer.parseInt(((Button)e.getSource()).getText());
if(DisabledCell[0][_x][CellNum-1]==true||DisabledCell[1][_y][CellNum-1]==true||DisabledCell[2][B][CellNum-1]==true)
{
disqualify(Integer.parseInt(((Button)e.getSource()).getText()));
return;
}
DisabledCell[0][_x][CellNum-1]=true;
DisabledCell[1][_y][CellNum-1]=true;
DisabledCell[2][B][CellNum-1]=true;
textVal.setText(((Button)e.getSource()).getText());
buttonPane.setVisible(false);
valPane.setVisible(true);
for(int i = 1; i <= 9; i++)disqualify(i); // Since we have locked in, all others are out of play
// in this cell
int c = 0;
int z = 0;
switch(B)
{ // Figures out which block our selected number is in.
case 0: z=0;
break; // ^
case 1: z=27;
break; // ^
case 2: z=54;
break; // ^
case 3: z=3;
break; // ^
case 4: z=30;
break; // ^
case 5: z=57;
break; // ^
case 6: z=6;
break; // ^
case 7: z=33;
break; // ^
case 8: z=60;
break; // ^
}
for(int w = 0; w<9; w++)
{
Object xob =SudoHelper.this.pane.getChildren().get((_x*9)+w);
SmartCell d =SmartCell.class.cast(xob); // Disqualifies x cells
d.disqualify(CellNum);
xob=d;
Object yob =SudoHelper.this.pane.getChildren().get(_y+(9*w));
SmartCell b =SmartCell.class.cast(yob); // Disqualifies Y Cells
b.disqualify(CellNum);
yob=b;
Object zob =SudoHelper.this.pane.getChildren().get(z+c);
SmartCell a =SmartCell.class.cast(zob);
a.disqualify(CellNum); // Disqualifies boxes
zob=a;
c++;
if(c==3)
{
z+=9;
c=0;
}
}
}
void mouseHandler(MouseEvent e)
{
// When any button gets clicked, we take the text from the button, put it on the Text
// shape, hide the pane with the 9 buttons, and show the text pane, making it look like
// the 9 buttons have "gone", and the new value that we have "locked in" has taken their place.
//
if(e.getSource() instanceof Button)
{
// If this was a right click, then just disable this button; If it was a left click then lock
// in the value this button represents.
if(e.getButton() == MouseButton.SECONDARY)
{
disqualify(Integer.parseInt(((Button)e.getSource()).getText()));
// disables button after clicked
return;
}
// System.out.print("A button was clicked"); // for debugging
disqual(e);
} // end if source was a button
} // end mouseHandler
void disqualify(int buttonNo)
{
// When we are called, we disable button #buttonNo in this cell
btn[buttonNo].setDisable(true);
btn[buttonNo].setStyle("-fx-base:black; -fx-text-fill:black; -fx-opacity:1.0"); // Sets color of cells and numbers after disabled.
}
public String toString()
{
// The toString representation of a cell is a string containing a list of
// the values "still in play" --- the remaining candidate values --- for the cell
//
// Start with an empty string. Visit all 9 buttons, and if a given button is
// not disabled (i.e still in play), then add its number (from the text on the
// button) to our string
//
String result = "";
for(int i = 1; i <= 9; i++)
if(!btn[i].isDisabled())
result += i;
return result;
}
}
private boolean[] InitalizeTile() // Initalizes our board of 81 cells
{
boolean[] newbool = new boolean[81];
for(int i=0; i<81; i++)
{
newbool[i] = false;
}
Random R = new Random();
for(int j=0; j<0; j++)
{
while(true)
{
int W = R.nextInt(81);
if(newbool[W]==false)
{
newbool[W]=true;
break;
}
}
}
return newbool;
}
}

All I really need to know is how to look at an array of buttons and see if only one is left enabled.
Arrays.stream(buttons).filter(button -> !button.isDisabled()).count() == 1
Sample application:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.control.Button;
import javafx.stage.Stage;
import java.util.Arrays;
public class DisabledButtonCount extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
Button[] buttons = {
new Button("1"),
new Button("2"),
new Button("3")
};
buttons[1].setDisable(true);
buttons[2].setDisable(true);
System.out.println(
"Only one button enabled? " +
(Arrays.stream(buttons).filter(button -> !button.isDisabled()).count() == 1)
);
Platform.exit();
}
}

Related

JavaFX 11 TableView Cell navigation by TAB key pressed without custom editableCell class

The problem:
I want to navigate through a TableView from one cell to the next right neighbor cell in JavaFX by using the TAB key.
Notice: The TableView is set to editable. And CellSelection is enabled too.
tableReceipt.getSelectionModel().setCellSelectionEnabled(true);
The handling of the KeyPressedEvent seemingly is not my problem, but to request the focus of the single cell on the right of the current cell.
I can focus one cell but when i press the TAB key the focus goes out of the table on other form elements.
The TableView contains some editable TextFieldTableCells and one editable ComboBoxTableCell.
I don't use a custom class for the editable Cells but Code like this:
Callback<TableColumn<Receipt, int>, TableCell<Receipt, int>> tfCallBack = TextFieldTableCell.forTableColumn();
columnAmount.setCellFactory(tfCallBack);
for a TableCell with editable TextField nature.
My question:
How can I implement a solution to solve my problem? A theoretical solution would help too. I allready searched for this topic but only found an example that's using a custom EditableCell class. I think there must be a solution using the callback method like I do.
Solution approach:
tableReceipt.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent t) {
if (t.getCode() == KeyCode.TAB) {
tableReceipt.getFocusModel().focusRightCell();
Robot r = new Robot();
r.keyPress(KeyCode.ENTER);
}
}
});
With this code I can get focus of the right cell next to the current one. And I need the ENTER KeyPress to enable the editable mode of the Cell. But when I press TAB on keyboard the new value is not committed. For example I press '2' the default value is '0' and after pressing TAB the value is again '0'.
Question No.2:
How can I combine the code above with a changeListener/onEditCommitListener, that the new value is stored in the cell after pressing TAB?
Thank you.
This may be helpful. You have to manually call it after you complete a cell edit.
import javafx.scene.control.TableColumn;
import javafx.scene.control.TablePosition;
import javafx.scene.control.TableView;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Brian Boutin on 5/14/2019
*/
class CellNav<T> {
private final TableView<T> tableView;
private KeyEvent lastKeyEvent;
private TablePosition editingPosition;
CellNav(TableView<T> tableView) {
this.tableView = tableView;
this.editingPosition = null;
tableView.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
if (event.getCode() == KeyCode.TAB || event.getCode() == KeyCode.ENTER) {
editingPosition = tableView.getEditingCell();
lastKeyEvent = event;
}
});
tableView.setOnMouseReleased(e -> editingPosition = null);
for (TableColumn col : tableView.getColumns()) {
col.setOnEditCancel(event -> editingPosition = null);
}
}
void doCellNav() {
if (editingPosition == null) {
return;
}
if (lastKeyEvent.getCode() == KeyCode.ENTER) {
int selectIdx;
if (lastKeyEvent.isShiftDown()) {
selectIdx = editingPosition.getRow() - 1;
} else {
selectIdx = editingPosition.getRow() + 1;
}
if (selectIdx < 0 ) {
selectIdx = tableView.getItems().size() - 1;
} else if (selectIdx > tableView.getItems().size() - 1) {
selectIdx = 0;
}
tableView.layout();
tableView.scrollTo(selectIdx == 0 ? selectIdx : selectIdx - 1);
tableView.getSelectionModel().clearAndSelect(selectIdx);
tableView.edit(selectIdx, editingPosition.getTableColumn());
} else if (lastKeyEvent.getCode() == KeyCode.TAB) {
TableColumn colToEdit = getNextColumn(!lastKeyEvent.isShiftDown(), editingPosition.getTableColumn());
if (colToEdit != null) {
tableView.layout();
tableView.scrollToColumn(colToEdit);
tableView.edit(editingPosition.getRow(), colToEdit);
}
}
editingPosition = null;
}
boolean isNavigating() {
return editingPosition != null;
}
private TableColumn getNextColumn(boolean forward, TableColumn currentCol) {
List<TableColumn> columns = new ArrayList<>();
for (TableColumn col : tableView.getColumns()) {
if (col.isEditable() && col.isVisible() && (col.getStyleClass().contains("editable-col") || col.getStyleClass().contains("result-col"))) {
columns.add(col);
}
}
if (columns.size() < 2) {
return null;
}
int currentIndex = -1;
for (int i = 0; i < columns.size(); i++) {
if (columns.get(i) == currentCol) {
currentIndex = i;
break;
}
}
int nextIndex = currentIndex;
if (forward) {
nextIndex++;
if (nextIndex > columns.size() - 1) {
nextIndex = 0;
}
} else {
nextIndex--;
if (nextIndex < 0) {
nextIndex = columns.size() - 1;
}
}
return columns.get(nextIndex);
}
}
public class ExampleClass{
CellNav cellNav;
public ExampleClass() {
TableView yourTableView = new TableView();
cellNav = new CellNav(yourTableView);
TableColumn someCol = new TableColumn();
someCol.setOnEditCommit(e -> {
//perform a save of your table data here
//pick up cell nav again
if (cellNav.isNavigating()) {
cellNav.doCellNav();
}
});
}
}

search a word by key enter

i have a problem with my searching method.
With this method, I can enter a word in the textfield and display the word in the textarea. However, this only happens once if i let it run. I need to expand it so, that every time I click on "enter," the program should continue with searching in the textarea. How can i do this?
And please give me code examples. i have only 2 days left for my presentation.
Thanks a lot for the helps
textfield.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
if (event.getCode() == KeyCode.ENTER) {
String text = textarea.getText();
Labeled errorText = null;
if (textfield.getText() != null && !textfield.getText().isEmpty()) {
index = textarea.getText().indexOf(textfield.getText());
textarea.getText();
if (index == -1) {
errorText.setText("Search key Not in the text");
} else {
// errorText.setText("Found");
textarea.selectRange(index, index + textfield.getLength());
}
}
}
}
});
There's an overloaded version of the indexOf method allowing you to search starting at a specific index. Keep track of the index of your last find and start searching from this position:
#Override
public void start(Stage primaryStage) throws Exception {
TextField textField = new TextField("foo");
TextArea textarea = new TextArea();
for (int i = 0; i < 10; i++) {
textarea.appendText("foo\nbarfoobarfoofoo\n");
}
textField.setOnAction(evt -> {
String searchText = textField.getText();
if (searchText.isEmpty()) {
return; // searching for empty text doesn't make sense
}
int index = textarea.getSelection().getEnd();
// in case of the first search, start at the beginning
// TODO: adjust condition/starting index according to needs
if (textarea.getSelection().getLength() == 0) {
index = 0;
}
// find next occurrence
int newStartIndex = textarea.getText().indexOf(searchText, index);
// mark occurrence
if (newStartIndex >= 0) {
textarea.selectRange(newStartIndex, newStartIndex + searchText.length());
}
});
Scene scene = new Scene(new VBox(textField, textarea));
primaryStage.setScene(scene);
primaryStage.show();
}
Edit
If you are not satisfied with searching the element after the selection ( or after the cursor, if there is no range selected), you could save the data of the end of the last match:
#Override
public void start(Stage primaryStage) throws Exception {
TextField textField = new TextField("foo");
TextArea textarea = new TextArea();
for (int i = 0; i < 10; i++) {
textarea.appendText("foo\nbarfoobarfoofoo\n");
}
class SearchHandler implements EventHandler<ActionEvent> {
int index = 0;
#Override
public void handle(ActionEvent event) {
String searchText = textField.getText();
String fullText = textarea.getText();
if (index + searchText.length() > fullText.length()) {
// no more matches possible
// TODO: notify user
return;
}
// find next occurrence
int newStartIndex = textarea.getText().indexOf(searchText, index);
// mark occurrence
if (newStartIndex >= 0) {
index = newStartIndex + searchText.length();
textarea.selectRange(newStartIndex, index);
} else {
index = fullText.length();
// TODO: notify user
}
}
}
SearchHandler handler = new SearchHandler();
textField.setOnAction(handler);
// reset index to search from start when changing the text of the TextField
textField.textProperty().addListener((o, oldValue, newValue) -> handler.index = 0);
Scene scene = new Scene(new VBox(textField, textarea));
primaryStage.setScene(scene);
primaryStage.show();
}

Wait until any button is pressed?

I am writing a TicTacToe game in JavaFX. I've decided to make a board as 9 (3x3) buttons with changing text: "" (if empty) or "X" or "O". Everything is going ok beside one thing... I got stuck here:
public void game() {
while(keepPlaying) {
if(computerTurn) {;
computerMove();
}else {
while(waitForUser) {
//wait until any of 9 buttons is pushed!
}
}
if (checkResult()) {
keepPlaying = false;
}
computerTurn = !computerTurn;
}
}
How to wait for user pushing any of those 9 buttons and then continue with computer turn??
I need something like waiting for scanner input in console application, but this input must be one of 9 buttons...
I know that there are few "possible duplicates", but in fact those problems were solved using methods I can't use here, for example timer. Correct me if I am wrong.
Blocking the application thread in JavaFX should not be done since it freezes the UI. For this reason a loop like this is not well suited for a JavaFX application. Instead you should react to user input:
public void game() {
if (keepPlaying && computerTurn) {
computerMove();
if (checkResult()) {
keepPlaying = false;
}
computerTurn = false;
}
}
// button event handler
private void button(ActionEvent event) {
if (keepPlaying) {
Button source = (Button) event.getSource();
// probably the following 2 coordinates are computed from GridPane indices
int x = getX(source);
int y = getY(source);
// TODO: update state according to button pressed
if (checkResult()) {
keepPlaying = false;
} else {
computerMove();
if (checkResult()) {
keepPlaying = false;
}
}
}
}
Starting with javafx 9 there is a public API for "pausing" on the application thread however:
private static class GridCoordinates {
int x,y;
GridCoordinates (int x, int y) {
this.x = x;
this.y = y;
}
}
private final Object loopKey = new Object();
public void game() {
while(keepPlaying) {
if(computerTurn) {
computerMove();
} else {
// wait for call of Platform.exitNestedEventLoop​(loopKey, *)
GridCoordinates coord = (GridCoordinates) Platform.enterNestedEventLoop​(loopKey);
// TODO: update state
}
if (checkResult()) {
keepPlaying = false;
}
computerTurn = !computerTurn;
}
}
private void button(ActionEvent event) {
if (keepPlaying) {
Button source = (Button) event.getSource();
// probably the following 2 coordinates are computed from GridPane indices
int x = getX(source);
int y = getY(source);
Platform.exitNestedEventLoop​(loopKey, new GridCoordinates(x, y));
}
}

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))));
}
}

JavaFX Auto Scroll Table Up or Down When Dragging Rows Outside Of Viewport

I've got a table view which you can drag rows to re-position the data. The issue is getting the table view to auto scroll up or down when dragging the row above or below the records within the view port.
Any ideas how this can be achieved within JavaFX?
categoryProductsTable.setRowFactory(tv -> {
TableRow<EasyCatalogueRow> row = new TableRow<EasyCatalogueRow>();
row.setOnDragDetected(event -> {
if (!row.isEmpty()) {
Dragboard db = row.startDragAndDrop(TransferMode.MOVE);
db.setDragView(row.snapshot(null, null));
ClipboardContent cc = new ClipboardContent();
cc.put(SERIALIZED_MIME_TYPE, new ArrayList<Integer>(categoryProductsTable.getSelectionModel().getSelectedIndices()));
db.setContent(cc);
event.consume();
}
});
row.setOnDragOver(event -> {
Dragboard db = event.getDragboard();
if (db.hasContent(SERIALIZED_MIME_TYPE)) {
event.acceptTransferModes(TransferMode.COPY_OR_MOVE);
event.consume();
}
});
row.setOnDragDropped(event -> {
Dragboard db = event.getDragboard();
if (db.hasContent(SERIALIZED_MIME_TYPE)) {
int dropIndex;
if (row.isEmpty()) {
dropIndex = categoryProductsTable.getItems().size();
} else {
dropIndex = row.getIndex();
}
ArrayList<Integer> indexes = (ArrayList<Integer>) db.getContent(SERIALIZED_MIME_TYPE);
for (int index : indexes) {
EasyCatalogueRow draggedProduct = categoryProductsTable.getItems().remove(index);
categoryProductsTable.getItems().add(dropIndex, draggedProduct);
dropIndex++;
}
event.setDropCompleted(true);
categoryProductsTable.getSelectionModel().select(null);
event.consume();
updateSortIndicies();
}
});
return row;
});
Ok, so I figured it out. Not sure it's the best way to do it but it works. Basically I added an event listener to the table view which handles the DragOver event. This event is fired whilst dragging the rows within the table view.
Essentially, whilst the drag is being performed, I work out if we need to scroll up or down or not scroll at all. This is done by working out if the items being dragged are within either the upper or lower proximity areas of the table view.
A separate thread controlled by the DragOver event listener then handles the scrolling.
public class CategoryProductsReportController extends ReportController implements Initializable {
#FXML
private TableView<EasyCatalogueRow> categoryProductsTable;
private ObservableList<EasyCatalogueRow> categoryProducts = FXCollections.observableArrayList();
public enum ScrollMode {
UP, DOWN, NONE
}
private AutoScrollableTableThread autoScrollThread = null;
/**
* Initializes the controller class.
*/
#Override
public void initialize(URL url, ResourceBundle rb) {
initProductTable();
}
private void initProductTable() {
categoryProductsTable.setItems(categoryProducts);
...
...
// Multi Row Drag And Drop To Allow Items To Be Re-Positioned Within
// Table
categoryProductsTable.setRowFactory(tv -> {
TableRow<EasyCatalogueRow> row = new TableRow<EasyCatalogueRow>();
row.setOnDragDetected(event -> {
if (!row.isEmpty()) {
Dragboard db = row.startDragAndDrop(TransferMode.MOVE);
db.setDragView(row.snapshot(null, null));
ClipboardContent cc = new ClipboardContent();
cc.put(SERIALIZED_MIME_TYPE, new ArrayList<Integer>(categoryProductsTable.getSelectionModel().getSelectedIndices()));
db.setContent(cc);
event.consume();
}
});
row.setOnDragOver(event -> {
Dragboard db = event.getDragboard();
if (db.hasContent(SERIALIZED_MIME_TYPE)) {
event.acceptTransferModes(TransferMode.COPY_OR_MOVE);
event.consume();
}
});
row.setOnDragDropped(event -> {
Dragboard db = event.getDragboard();
if (db.hasContent(SERIALIZED_MIME_TYPE)) {
int dropIndex;
if (row.isEmpty()) {
dropIndex = categoryProductsTable.getItems().size();
} else {
dropIndex = row.getIndex();
}
ArrayList<Integer> indexes = (ArrayList<Integer>) db.getContent(SERIALIZED_MIME_TYPE);
for (int index : indexes) {
EasyCatalogueRow draggedProduct = categoryProductsTable.getItems().remove(index);
categoryProductsTable.getItems().add(dropIndex, draggedProduct);
dropIndex++;
}
event.setDropCompleted(true);
categoryProductsTable.getSelectionModel().select(null);
event.consume();
updateSortIndicies();
}
});
return row;
});
categoryProductsTable.addEventFilter(DragEvent.DRAG_DROPPED, event -> {
if (autoScrollThread != null) {
autoScrollThread.stopScrolling();
autoScrollThread = null;
}
});
categoryProductsTable.addEventFilter(DragEvent.DRAG_OVER, event -> {
double proximity = 100;
Bounds tableBounds = categoryProductsTable.getLayoutBounds();
double dragY = event.getY();
//System.out.println(tableBounds.getMinY() + " --> " + tableBounds.getMaxY() + " --> " + dragY);
// Area At Top Of Table View. i.e Initiate Upwards Auto Scroll If
// We Detect Anything Being Dragged Above This Line.
double topYProximity = tableBounds.getMinY() + proximity;
// Area At Bottom Of Table View. i.e Initiate Downwards Auto Scroll If
// We Detect Anything Being Dragged Below This Line.
double bottomYProximity = tableBounds.getMaxY() - proximity;
// We Now Make Use Of A Thread To Scroll The Table Up Or Down If
// The Objects Being Dragged Are Within The Upper Or Lower
// Proximity Areas
if (dragY < topYProximity) {
// We Need To Scroll Up
if (autoScrollThread == null) {
autoScrollThread = new AutoScrollableTableThread(categoryProductsTable);
autoScrollThread.scrollUp();
autoScrollThread.start();
}
} else if (dragY > bottomYProximity) {
// We Need To Scroll Down
if (autoScrollThread == null) {
autoScrollThread = new AutoScrollableTableThread(categoryProductsTable);
autoScrollThread.scrollDown();
autoScrollThread.start();
}
} else {
// No Auto Scroll Required We Are Within Bounds
if (autoScrollThread != null) {
autoScrollThread.stopScrolling();
autoScrollThread = null;
}
}
});
}
}
class AutoScrollableTableThread extends Thread {
private boolean running = true;
private ScrollMode scrollMode = ScrollMode.NONE;
private ScrollBar verticalScrollBar = null;
public AutoScrollableTableThread(TableView tableView) {
super();
setDaemon(true);
verticalScrollBar = (ScrollBar) tableView.lookup(".scroll-bar:vertical");
}
#Override
public void run() {
try {
Thread.sleep(300);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
while (running) {
Platform.runLater(() -> {
if (verticalScrollBar != null && scrollMode == ScrollMode.UP) {
verticalScrollBar.setValue(verticalScrollBar.getValue() - 0.01);
} else if (verticalScrollBar != null && scrollMode == ScrollMode.DOWN) {
verticalScrollBar.setValue(verticalScrollBar.getValue() + 0.01);
}
});
try {
sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void scrollUp() {
System.out.println("Start To Scroll Up");
scrollMode = ScrollMode.UP;
running = true;
}
public void scrollDown() {
System.out.println("Start To Scroll Down");
scrollMode = ScrollMode.DOWN;
running = true;
}
public void stopScrolling() {
System.out.println("Stop Scrolling");
running = false;
scrollMode = ScrollMode.NONE;
}
}

Resources