1

I have 14 pairs of Buttons associated with two groups of textFields that increment and decrement their associated textFields by 2. Each text field displays how many pairs of plates of specific weights are to be loaded onto a barbell. They are stored in two 2-dimensional arrays, one for pounds and the other for kilograms. [increment or decrement][buttons]

final Button[][] poundIncrementDecrementButton = new Button[2][7];
final Button[][] kilogramIncrementDecrementButton = new Button[2][7];

I'm using Scene Builder and JavaFX, and I'm trying to avoid writing 28 @FXML event handlers. This is the method I've come up with so far to iterate through each of the arrays, but I'm not sure how I can replace the lambda expressions to make this work.

// assigns event handlers to increment and decrement buttons.
private void incrementDrecimentButtonEventHandlers() {

    // p=1 increment buttons, p=2 decrement buttons
    for (int p = 0; p < poundIncrementDecrementButton.length; p++) {
        // loops through buttons
        for (int j = 0; j < poundIncrementDecrementButton[p].length; j++) {
            Button incrementButton = poundIncrementDecrementButton[p][j];
            final int incrementDecriment = p;
            final int textField = j;
            incrementButton.setOnAction((ActionEvent event) -> {
                incrementDecrementPlate("pounds", incrementDecriment, textField);
            });
        }

        // k=1 increment buttons, k=2 decrement buttons
        for (int k = 0; k < kilogramIncrementDecrementButton.length; k++) {
            // loops through buttons
            for (int j = 0; j < kilogramIncrementDecrementButton[k].length; j++) {
                Button incrementButton = kilogramIncrementDecrementButton[k][j];
                final int incrementDecriment = k;
                final int textField = j;
                incrementButton.setOnAction((ActionEvent event) -> {
                    incrementDecrementPlate("kilograms", incrementDecriment, textField);
                });
            }

        }
    }
}

I then have the event handlers call this method with the relevant indices.

// increments or decrements the plates
private void incrementDecrementPlate(String unit, int incrementDecrement, int textField) {

    Double oldValue = Double.parseDouble(poundTextFieldList.get(textField).getText());
    String incremented;
    String decremented;

    if (oldValue % 2 != 0) {
        incremented = Double.toString(oldValue + 1);
    } else {
        incremented = Double.toString(oldValue + 2);
    }

    if (oldValue % 2 != 0) {
        decremented = Double.toString(oldValue - 1);
    } else if (oldValue != 0) {
        decremented = Double.toString(oldValue - 2);
    } else {
        decremented = Double.toString(oldValue);
    }

    switch (unit) {
        case "pounds":
            if (incrementDecrement == 0) {
                poundTextFieldList.get(textField).setText(incremented);
            } else {
                poundTextFieldList.get(textField).setText(decremented);
            }
            break;
        case "kilograms":
            if (incrementDecrement == 0) {
                kilogramTextFieldList.get(textField).setText(incremented);
            } else {
                kilogramTextFieldList.get(textField).setText(decremented);
            }
            break;
    }
}
CompEng
  • 55
  • 1
  • 8
  • This seems like a lot of code to do something that could probably be written in less code. – SedJ601 Apr 19 '18 at 05:29
  • It seems like it would be much easier to do this part of the UI layout and event handling entirely in Java - it's very little additional code to the code you've already posted, than to have an FXML file with 28 (or 56?) ` – James_D Apr 19 '18 at 11:11
  • It appears you are doing it manually which is possible but the JavaFX approach would be best for the long term. Create UI controls in code, use Controller, Buttons to set properties, TextFields to observe the properties. Basic stuff - check the Oracle JavaFX tutorial – Siraj K Apr 19 '18 at 13:04
  • @Siraj That's exactly what I'm doing. All of this code is located in my controller class. I used Scene Builder to design the UI. I'm trying to stick to MVC as much as possible. – CompEng Apr 19 '18 at 19:46

2 Answers2

1

After some experimentation, I came up with a solution. Keep in mind, this is my attempt at sticking to MVC architecture using Scene Builder, FXML, and JavaFX.

There are 14 different plates, 7 in lbs and 7 in kgs. Each plate has a text field to display how many of each are needed to be loaded on a barbell for a given weight. These textFields are stored in ObservableLists and are given listeners elsewhere in the controller.

// textFields associated with 45, 35, 25, 15, 10, 5, 2.5lb plates
ObservableList<TextField> poundTextFieldList = FXCollections.observableArrayList();
// textFields associated with 25, 20, 15, 10, 5, 2.5, 1.25kg plates
ObservableList<TextField> kilogramTextFieldList = FXCollections.observableArrayList();

These arrays store the number of plates.

int[] poundPlatesToLoad = calculator.getPoundPlatesToLoad();
int[] kilogramPlatesToLoad = calculator.getKilogramPlatesToLoad();

These ArrayLists store the increment buttons

private final ArrayList<Button> poundIncrementButtons = new ArrayList();
private final ArrayList<Button> poundDecrementButtons = new ArrayList();
private final ArrayList<Button> kilogramIncrementButtons = new ArrayList();
private final ArrayList<Button> kilogramDecrementButtons = new ArrayList();

Since I used Scene Builder to design the GUI, each button requires @FXML tags to identify them. I don't think there is a way around this, so there are 28 of these in the controller.

@FXML
private Button poundIncrement45Button;
@FXML
private Button poundDecrement45Button;

Then, to avoid writing 28 handlers, I wrote 4 handlers associated with each ArrayList.

public void poundIncrementButtonPressed(ActionEvent event) {
    System.out.println("pound increment button pressed");
    Button button = (Button) event.getSource();
    // identifies which plate is to be incremented
    int plate = poundIncrementButtons.indexOf(button);
    incrementDecrementPlate(0, 0, plate);
}

And one more method to increment or decrement the correct textField.

private void incrementDecrementPlate(int unit, int incrementDecrement, int textField) {

    int oldValue;
    if(unit == 0){
        oldValue = (int) poundPlatesToLoad[textField];
    } else {
        oldValue = (int) kilogramPlatesToLoad[textField];
    }

    String incremented;
    String decremented;

    if (oldValue % 2 != 0) {
        incremented = Integer.toString(oldValue + 1);
    } else {
        incremented = Integer.toString(oldValue + 2);
    }

    if (oldValue % 2 != 0) {
        decremented = Integer.toString(oldValue - 1);
    } else if (oldValue != 0) {
        decremented = Integer.toString(oldValue - 2);
    } else {
        decremented = Integer.toString(oldValue);
    }

    switch (unit) {
        case 0:
            if (incrementDecrement == 0) {
                poundTextFieldList.get(textField).setText(incremented);
            } else {
                poundTextFieldList.get(textField).setText(decremented);
            }
            break;
        case 1:
            if (incrementDecrement == 0) {
                kilogramTextFieldList.get(textField).setText(incremented);
            } else {
                kilogramTextFieldList.get(textField).setText(decremented);
            }
            break;
    }
}

Here's an image of the program so far. Each text field dynamically responds to changes. The barbell on the right displays kilogram plates in response to changes in the textFields.

Barbell Math

CompEng
  • 55
  • 1
  • 8
  • SceneBuilder is just a tool to create FXML files similar to writing HTML or using a visual editor. You can set the controller in it so it will automatically add the @FXML fields for you. BTW, your new code can be compacted but as long as it works... – Siraj K Apr 19 '18 at 22:11
  • Scene Builder writes to the FXML file, but from my understanding, in order to link elements from the FXML file to the contoller class, you need to use @FXML tags in the controller class in order to work with them (buttons, textFields) programatically. And yes I agree, I will work on compacting it. – CompEng Apr 19 '18 at 22:58
  • I'm also aware that Scene Builder can generate a skeleton controller class, but it just generates 28 individual handlers for the buttons, which is exactly what I was trying to avoid. – CompEng Apr 19 '18 at 23:04
0

The following code demonstrates how to have group of (2) buttons incrementing the value of a text field:

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;

public class FxMain extends Application {

    private Button[] increment, decrement;
    private TextField[] txt;
    private static final int numberOfGroups = 3;

    @Override
    public void start(Stage stage) {

        initComponents(numberOfGroups);
        GridPane root = new GridPane();
        for(int row =0; row < numberOfGroups; row++){ //add all components 
            root.addRow(row, decrement[row], txt[row],increment[row]);
        }
        Scene scene = new Scene(root);
        stage.setScene(scene);
        stage.show();
    }

    //create and initialize components 
    private void initComponents(int size) {
        increment = new Button[size]; decrement = new Button[size];
        txt = new TextField[size];

        for(int group = 0; group < size; group++) {
            final int i = group;
            txt[i] = new TextField(String.valueOf(0));
            increment[i] = new Button("+");
            increment[i].setOnAction(a-> updateTxtField(txt[i], 1));//assign handle 
            decrement[i] = new Button("-");
            decrement[i].setOnAction(a-> updateTxtField(txt[i],-1));
        }
    }
    //increment text filed value
    private void updateTxtField(TextField textField, int i) {

        int newValue = Integer.valueOf(textField.getText()) + i;
        textField.setText(String.valueOf(newValue));
    }

    public static void main(String[] args) { launch(args);}
}

Do not hesitate to ask for clarifications as needed. For more help, post MCVE

c0der
  • 18,467
  • 6
  • 33
  • 65
  • Thanks for the response. Since I used Scene Builder to create the GUI, I'm not sure how to link the event handlers to the buttons properly without @FXML tags in my controller class? – CompEng Apr 19 '18 at 09:17
  • For more help, post [MCVE] – c0der Apr 19 '18 at 10:02