ScrollBar within custom Alert box not working JavaFX - 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

Related

Detecting button click of button added to javafx listview

I am very new to Java so please be patient with me. I have successfully added buttons, labels and even a progress bar to a listview cell. I need to be able to detect when one of the buttons has been clicked. Adding controls to listview content I managed to get from a couple of posts here the code i am using is shown below
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.ProgressBar;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.*;
import javafx.stage.Stage;
public class ListViewDemo extends Application {
public static class lvCell extends VBox {
Label labelName = new Label();
Label labelPath = new Label();
Label labelElapse = new Label();
Button buttonPlayPause = new Button();
Button buttonStop = new Button();
ImageView ivStop = new ImageView();
ImageView ivPlay = new ImageView();
Pane buttonSpacer = new Pane();
Pane progressBarSpacer = new Pane();
HBox hbDetail = new HBox();
HBox hbProgress = new HBox();
ProgressBar pbProgress = new ProgressBar();
File filePlay;
File fileStop;
double prefWidth = 10;
double prefHeight = 10;
lvCell(String labelText) {
super();
labelName.setText(labelText);
labelName.setMaxWidth(Double.MAX_VALUE);
labelPath.setMaxWidth(0);
labelPath.setText("Path");
pbProgress.setMaxWidth(Double.MAX_VALUE);
HBox.setHgrow(labelName, Priority.ALWAYS);
HBox.setHgrow(pbProgress, Priority.ALWAYS);
HBox.setMargin(labelName, new Insets(5, 0, 0, 0));
HBox.setMargin(pbProgress, new Insets(0, 0, 0, 0));
labelPath.setVisible(false);
buttonSpacer.setPrefSize(prefWidth, prefHeight);
labelElapse.setPrefSize(50, prefHeight);
labelElapse.setText("Time");;
progressBarSpacer.setPrefSize(prefWidth * 6, prefHeight);
filePlay = new File("src/image/play.png");
fileStop = new File("src/image/stop.png");
Image imagePlay = new Image(filePlay.toURI().toString());
Image imageStop = new Image(fileStop.toURI().toString());
ivPlay.setImage(imagePlay);
ivStop.setImage(imageStop);
ivPlay.setFitHeight(prefHeight);
ivPlay.setFitWidth(prefWidth);
ivStop.setFitHeight(prefHeight);
ivStop.setFitWidth(prefWidth);
buttonPlayPause.setGraphic(ivPlay);
buttonStop.setGraphic(ivStop);
buttonPlayPause.setMaxSize(prefWidth, prefHeight);
buttonStop.setMaxSize(prefWidth, prefHeight);
pbProgress.setMaxHeight(2);
pbProgress.setPrefHeight(2);
hbDetail.getChildren().addAll(buttonPlayPause, buttonStop, buttonSpacer, labelName, labelPath);
hbProgress.getChildren().addAll(progressBarSpacer, pbProgress, labelElapse);
this.getChildren().addAll(hbDetail, hbProgress);
}
}
public Parent createContent() {
BorderPane layout = new BorderPane();
List < lvCell > list = new ArrayList < > ();
for (int i = 0; i < 10; i++) {
list.add(new lvCell("Item " + i));
}
ListView < lvCell > listView = new ListView < lvCell > ();
ObservableList < lvCell > myObservableList = FXCollections.observableList(list);
listView.setItems(myObservableList);
layout.setCenter(listView);
return layout;
}
#Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent()));
stage.setWidth(300);
stage.setHeight(200);
stage.show();
}
public static void main(String args[]) {
launch(args);
}
}
The screen looks like this:
Any help will be greatly appreciated.
Thanks in advance, and wishing you a peaceful journey.
Yas
This is not a class well designed to put into a ListView. An object used as item in a ListView should contain data; the ListCell implementation produced by the cellFactory is responsible for determining the visual representation of the data in the ListView. This way you avoid the creation of nodes for every object reducing the memory footprint, which is exactly what ListView is designed for.
Simplified example
The data here contains just the progress and some text; it's displayed in a ProgressBar and the text of the cell; an additional button in the cell allows increasing the progress by 0.25 for each click and removing any items reaching a progress of 1.
Data class
public class Data {
private final DoubleProperty progress = new SimpleDoubleProperty();
private final String text;
public Data(String text) {
this.text = text;
}
public double getProgress() {
return progress.get();
}
public void setProgress(double value) {
progress.set(value);
}
public String getText() {
return text;
}
public ObservableValue<? extends Number> progressProperty() {
return progress;
}
}
ListView code
ListView<Data> listView = new ListView<>(someData);
listView.setCellFactory(l -> new ListCell<Data>() {
private final ProgressBar progressBar = new ProgressBar();
private final Button button = new Button("increase");
private final HBox content = new HBox(progressBar, button);
{
button.setOnAction(evt -> {
Data item = getItem();
int index = getIndex();
double progress = item.getProgress() + 0.25;
item.setProgress(progress);
if (progress >= 1) {
getListView().getItems().remove(index);
}
});
}
#Override
protected void updateItem(Data item, boolean empty) {
super.updateItem(item, empty);
progressBar.progressProperty().unbind();
if (item == null) {
setGraphic(null);
setText("");
} else {
setGraphic(content);
setText(item.getText());
progressBar.progressProperty().bind(item.progressProperty());
}
}
});

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.

Pasting text data in the TableView using 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 -> {...});

UndoFX: Undo/Redo recording every pixel of drag

I'm using UndoFX & ReactFX for implementing the Undo/Redo function for my 2D shape application.
The problem is when i move my shape the EventStream records every X/Y pixel of movement. I just want to record the last position (when the user releases the drag).
What i have tried so far:
Instead of using changesOf(rect.xProperty()).map(c -> new xChange(c)); and
changesOf(rect.yProperty()).map(c -> new yChange(c));
I created a DoubleProperty x,y, and saved the shape x,y Property to these variables when the user mouse is released.
Lastly i change the changesOf to: changesOf(this.x).map(c -> new xChange(c)); and changesOf(this.y).map(c -> new yChange(c));
But that did not work, it behaved just like before.
....
private class xChange extends RectangleChange<Double> {
public xChange(Double oldValue, Double newValue) {
super(oldValue, newValue);
}
public xChange(Change<Number> c) {
super(c.getOldValue().doubleValue(), c.getNewValue().doubleValue());
}
#Override void redo() { rect.setX(newValue); }
#Override xChange invert() { return new xChange(newValue, oldValue); }
#Override Optional<RectangleChange<?>> mergeWith(RectangleChange<?> other) {
if(other instanceof xChange) {
return Optional.of(new xChange(oldValue, ((xChange) other).newValue));
} else {
return Optional.empty();
}
}
#Override
public boolean equals(Object other) {
if(other instanceof xChange) {
xChange that = (xChange) other;
return Objects.equals(this.oldValue, that.oldValue)
&& Objects.equals(this.newValue, that.newValue);
} else {
return false;
}
}
}
...
EventStream<xChange> xChanges = changesOf(rect.xProperty()).map(c -> new xChange(c));
EventStream<yChange> yChanges = changesOf(rect.yProperty()).map(c -> new yChange(c));
changes = merge(widthChanges, heightChanges, xChanges, yChanges);
undoManager = UndoManagerFactory.unlimitedHistoryUndoManager(
changes, // stream of changes to observe
c -> c.invert(), // function to invert a change
c -> c.redo(), // function to undo a change
(c1, c2) -> c1.mergeWith(c2)); // function to merge two changes
You need to merge the changes in x with the changes in y. At present, a change in x followed by a change in y cannot be merged, so if you move the shape so that it alternates x and y changes (e.g. moving it diagonally), then each individual change will not merge with the previous one.
One way to do this is to generate changes whose old and new values are the locations, e.g. represented by Point2D objects. Here's a quick example:
import java.util.Objects;
import java.util.Optional;
import org.fxmisc.undo.UndoManager;
import org.fxmisc.undo.UndoManagerFactory;
import org.reactfx.EventStream;
import org.reactfx.EventStreams;
import org.reactfx.SuspendableEventStream;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.geometry.Insets;
import javafx.geometry.Point2D;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class UndoRectangle extends Application {
#Override
public void start(Stage primaryStage) {
Rectangle rect = new Rectangle(50, 50, 150, 100);
rect.setFill(Color.CORNFLOWERBLUE);
EventStream<PositionChange> xChanges = EventStreams.changesOf(rect.xProperty()).map(c -> {
double oldX = c.getOldValue().doubleValue();
double newX = c.getNewValue().doubleValue();
double y = rect.getY();
return new PositionChange(new Point2D(oldX, y), new Point2D(newX, y));
});
EventStream<PositionChange> yChanges = EventStreams.changesOf(rect.yProperty()).map(c -> {
double oldY = c.getOldValue().doubleValue();
double newY = c.getNewValue().doubleValue();
double x = rect.getX();
return new PositionChange(new Point2D(x, oldY), new Point2D(x, newY));
});
SuspendableEventStream<PositionChange> posChanges = EventStreams.merge(xChanges, yChanges)
.reducible(PositionChange::merge);
UndoManager undoManager = UndoManagerFactory.unlimitedHistoryUndoManager(posChanges,
PositionChange::invert,
c -> posChanges.suspendWhile(() -> {
rect.setX(c.getNewPosition().getX());
rect.setY(c.getNewPosition().getY());
}),
(c1, c2) -> Optional.of(c1.merge(c2))
);
class MouseLoc { double x, y ; }
MouseLoc mouseLoc = new MouseLoc();
rect.setOnMousePressed(e -> {
mouseLoc.x = e.getSceneX();
mouseLoc.y = e.getSceneY();
});
rect.setOnMouseDragged(e -> {
rect.setX(rect.getX() + e.getSceneX() - mouseLoc.x);
rect.setY(rect.getY() + e.getSceneY() - mouseLoc.y);
mouseLoc.x = e.getSceneX();
mouseLoc.y = e.getSceneY();
});
rect.setOnMouseReleased(e -> undoManager.preventMerge());
Pane pane = new Pane(rect);
Button undo = new Button("Undo");
undo.disableProperty().bind(Bindings.not(undoManager.undoAvailableProperty()));
undo.setOnAction(e -> undoManager.undo());
Button redo = new Button("Redo");
redo.disableProperty().bind(Bindings.not(undoManager.redoAvailableProperty()));
redo.setOnAction(e -> undoManager.redo());
HBox buttons = new HBox(5, undo, redo);
buttons.setAlignment(Pos.CENTER);
BorderPane.setMargin(buttons, new Insets(5));
BorderPane root = new BorderPane(pane, null, null, buttons, null);
Scene scene = new Scene(root, 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
public static class PositionChange {
private final Point2D oldPosition ;
private final Point2D newPosition ;
public PositionChange(Point2D oldPos, Point2D newPos) {
this.oldPosition = oldPos ;
this.newPosition = newPos ;
}
public Point2D getOldPosition() {
return oldPosition;
}
public Point2D getNewPosition() {
return newPosition;
}
public PositionChange merge(PositionChange other) {
return new PositionChange(oldPosition, other.newPosition);
}
public PositionChange invert() {
return new PositionChange(newPosition, oldPosition);
}
#Override
public boolean equals(Object o) {
if (o instanceof PositionChange) {
PositionChange other = (PositionChange) o ;
return Objects.equals(oldPosition, other.oldPosition)
&& Objects.equals(newPosition, other.newPosition);
} else return false ;
}
#Override
public int hashCode() {
return Objects.hash(oldPosition, newPosition);
}
}
public static void main(String[] args) {
launch(args);
}
}
Note that it's important the "undo" is implemented as an "atomic" change, so the undo manager sees (and ignores) a single change when you implement the undo. This can be achieved by suspending the event stream during the undo.

Array size not setting correctly

I asked a previous question here, and got excellent feedback. I am back with one more issue in the same program. My output file is always blank, and I figured out why, but I don't know how to correct it. In getArray, I set totalEmployees to the value entered in the stage2 textField. This is supposed to be the array size declaration, but when i test the code, the array size is always set to 0, therefore giving me a blank text doc. This is because the array size is set to totalEmployees before the value is saved to totalEmployees. I am not quite sure how to go about correcting this. Here is the code that I have:
import java.io.*;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.layout.GridPane;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.geometry.HPos;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
public class companyParoll extends Application
{
private TextField tfNumOfEmployees = new TextField();
private TextField tfEmpFirstName = new TextField();
private TextField tfEmpLastName = new TextField();
private TextField tfEmpPayRate = new TextField();
private TextField tfEmpHoursWorked = new TextField();
private Button btEnterNum = new Button("Submit");
private Button btNextEmp = new Button("Add Employee");
private Button btRunReport = new Button("Run Report");
private Button btQuit = new Button("Quit");
//Declare Variables
int totalEmployees;
int index = 0;
String[] firstName = new String[totalEmployees];
String[] lastName = new String[totalEmployees];
double[] payRate = new double[totalEmployees];
double[] hoursWorked = new double[totalEmployees];
Stage stage2 = new Stage();
#Override // Override the start method in the Application class
public void start(Stage primaryStage)
{
//Create the UI
GridPane gridPane =new GridPane();
GridPane gridPane2 = new GridPane();
gridPane.setHgap(75);
gridPane.setVgap(15);
gridPane.add(new Label("Employee's First Name:"), 0, 0);
gridPane.add(tfEmpFirstName, 1, 0);
gridPane.add(new Label("Employee's Last Name:"), 0, 1);
gridPane.add(tfEmpLastName, 1, 1);
gridPane.add(new Label("Employee's Hourly Pay Rate:"), 0, 2);
gridPane.add(tfEmpPayRate, 1, 2);
gridPane.add(new Label("Hours Worked by Employee"), 0, 3);
gridPane.add(tfEmpHoursWorked, 1, 3);
gridPane.add(btNextEmp, 1, 4);
gridPane.add(btQuit, 0, 6);
gridPane.add(btRunReport, 1, 6);
//Set properties
gridPane.setAlignment(Pos.CENTER);
tfEmpFirstName.setAlignment(Pos.BOTTOM_RIGHT);
tfEmpLastName.setAlignment(Pos.BOTTOM_RIGHT);
tfEmpPayRate.setAlignment(Pos.BOTTOM_RIGHT);
tfEmpHoursWorked.setAlignment(Pos.BOTTOM_RIGHT);
GridPane.setHalignment(btQuit, HPos.LEFT);
GridPane.setHalignment(btRunReport, HPos.RIGHT);
GridPane.setHalignment(btNextEmp, HPos.RIGHT);
gridPane2.setHgap(75);
gridPane2.setVgap(15);
gridPane2.add(new Label("Enter the Number of Employees:"), 0, 0);
gridPane2.add(tfNumOfEmployees,0 ,1);
gridPane2.add(btEnterNum, 0, 2);
gridPane2.setAlignment(Pos.CENTER);
tfNumOfEmployees.setAlignment(Pos.BOTTOM_RIGHT);
GridPane.setHalignment(btEnterNum, HPos.CENTER);
btEnterNum.setOnAction(e -> getArraySize());
btRunReport.setOnAction(e -> outputReport());
btNextEmp.setOnAction(e -> addEmployeeData());
btQuit.setOnAction(e -> quitApplication());
// Create a scene and place it in the stage
Scene scene= new Scene(gridPane, 400, 250) ;
primaryStage.setTitle("Payroll Calculator"); // Set title
primaryStage.setScene(scene); // Place the scene in t he stage
primaryStage.show(); // Display the stage
//Create new window to get number of employees
Scene scene2 = new Scene(gridPane2, 200, 150);
stage2.setTitle("Number of Employees");
stage2.setScene(scene2);
stage2.show();
}
public void getArraySize()
{
totalEmployees = Integer.parseInt(tfNumOfEmployees.getText());
stage2.close();
}
public void addEmployeeData()
{
while (index < firstName.length)
{
firstName[index] = tfEmpFirstName.getText();
lastName[index] = tfEmpLastName.getText();
payRate[index] = Double.parseDouble(tfEmpPayRate.getText());
hoursWorked[index] = Integer.parseInt(tfEmpHoursWorked.getText());
index ++;
break;
}
tfEmpFirstName.clear();
tfEmpLastName.clear();
tfEmpPayRate.clear();
tfEmpHoursWorked.clear();
}
public void outputReport()
{
try
{
PrintWriter empPayroll = new PrintWriter("Payroll.txt");
double regularHours = 0;
double overtimeHours = 0;
double regularPay = 0;
double overtimePay = 0;
double totalPay = 0;
for (index = 0; index < firstName.length; index++)
{
if (hoursWorked[index] >= 40)
regularHours = 40;
else
regularHours = hoursWorked[index];
if (hoursWorked[index] > 40)
overtimeHours = hoursWorked[index] - 40;
else
overtimeHours = 0;
regularPay = (payRate[index] * regularHours);
overtimePay = ((payRate[index] * 1.5) * overtimeHours);
totalPay = regularPay + overtimePay;
empPayroll.println("\nName: " + firstName[index] + " " + lastName[index]);
empPayroll.println("Pay Rate: " + payRate[index]);
empPayroll.println("Regular Hours Worked: " + regularHours);
empPayroll.println("Overtime Hours Worked: " + overtimeHours);
empPayroll.println("Regular Pay: " + regularPay);
empPayroll.println("Overtime Pay: " + overtimePay);
empPayroll.println("Total Gross Pay: " + totalPay);
}
empPayroll.close();
}
catch (IOException exp)
{
exp.printStackTrace();
}
System.out.println(firstName.length);
}
public void quitApplication()
{
Platform.exit(); //Close application
}
public static void main(String[] args)
{
Application.launch();
}
}
It looks like you're only using "totalEmployees" to instantiate those arrays.
Assuming that those arrays are not used anytime before the user clicks btEnterNum (Which is important because trying to do so would result in a Null Pointer Exception),
you could just declare the arrays, and instantiate them in your getArraySize() method.
private String[] firstName;
private String[] lastName;
private double[] payRate;
private double[] hoursWorked;
public void getArraySize() {
totalEmployees = Integer.parseInt(tfNumOfEmployees.getText());
firstName = new String[totalEmployees];
lastName = new String[totalEmployees];
payRate = new double[totalEmployees];
hoursWorked = new double[totalEmployees];
stage2.close();
}
On the other hand, however, if you do need to use those arrays first, then you could try an ArrayList instead. HTH

Resources