-2

I have a JavaFX scene with 4 buttons that are used to make the user choose between 4 different wizards (application is a game). In the controller, I initialize the available Wizards attribute, and when another player makes his choice, the setAvailableWizards method is called: at that time I would like to remove from the scene the buttons corresponding to the wizard not available anymore:

Wizard Enum:

public enum Wizard {
    KING, PIXIE, SORCERER, WIZARD;
}

JavaFX controller:

public class WizardController extends ViewObservable implements Initializable {
    public HBox wizardsHBox;
    private List<Wizard> availableWizards;

    @Override
    public void initialize(URL url, ResourceBundle resourceBundle) {
        availableWizards = Arrays.stream(values()).toList();
    }

    public void setAvailableWizards(List<Wizard> availableWizardsUpdate) {
        List<Wizard> removed = new ArrayList<>(availableWizards);
        removed.removeAll(availableWizardsUpdate);

        availableWizards = availableWizardsUpdate;

        System.out.println(wizardsHBox.getChildren());

        removed.forEach(r -> {
            Button toRemove = (Button) Gui.getStage().getScene().lookup("#" + r.toString().toLowerCase() + "Button");
            wizardsHBox.getChildren().remove(toRemove);
            System.out.println(wizardsHBox.getChildren());
        });
    }

    public void handleKingButton(ActionEvent actionEvent) {
        String chosenId = ((Button) actionEvent.getSource()).getId();
        String chosenWizard = chosenId.substring(0, chosenId.indexOf("Button")).toUpperCase();

        // notify to the server
    }
}

FXML

<HBox fx:id="wizardsHBox" alignment="CENTER" layoutX="10.0" layoutY="144.0" prefHeight="332.0" prefWidth="599.0" spacing="20.0">
         <children>
            <Button fx:id="kingButton" mnemonicParsing="false" onAction="#handleKingButton" styleClass="wizard-btn">
               <graphic>
                  <VBox alignment="CENTER" prefHeight="322.0" prefWidth="143.0" spacing="20.0">
                     <children>
                        <Text strokeType="OUTSIDE" strokeWidth="0.0" text="King" />
                        <ImageView fitHeight="193.0" fitWidth="117.0" pickOnBounds="true" preserveRatio="true">
                           <image>
                              <Image url="@../images/wizards/king.png" />
                           </image>
                        </ImageView>
                     </children>
                  </VBox>
               </graphic>
            </Button>
            <Button fx:id="sorcererButton" layoutX="229.0" layoutY="10.0" mnemonicParsing="false" onAction="#handleKingButton" styleClass="wizard-btn">
               <graphic>
                  <VBox alignment="CENTER" prefHeight="322.0" prefWidth="143.0" spacing="20.0">
                     <children>
                        <Text strokeType="OUTSIDE" strokeWidth="0.0" text="Sorcerer" />
                        <ImageView fitHeight="193.0" fitWidth="117.0" pickOnBounds="true" preserveRatio="true">
                           <image>
                              <Image url="@../images/wizards/sorcerer.png" />
                           </image>
                        </ImageView>
                     </children>
                  </VBox>
               </graphic>
            </Button>
            <Button fx:id="pixieButton" layoutX="320.0" layoutY="10.0" mnemonicParsing="false" onAction="#handleKingButton" styleClass="wizard-btn">
               <graphic>
                  <VBox alignment="CENTER" prefHeight="322.0" prefWidth="143.0" spacing="20.0">
                     <children>
                        <Text strokeType="OUTSIDE" strokeWidth="0.0" text="Pixie" />
                        <ImageView fitHeight="193.0" fitWidth="117.0" pickOnBounds="true" preserveRatio="true">
                           <image>
                              <Image url="@../images/wizards/pixie.png" />
                           </image>
                        </ImageView>
                     </children>
                  </VBox>
               </graphic>
            </Button>
            <Button fx:id="wizardButton" layoutX="410.0" layoutY="10.0" mnemonicParsing="false" onAction="#handleKingButton" styleClass="wizard-btn">
               <graphic>
                  <VBox alignment="CENTER" prefHeight="322.0" prefWidth="143.0" spacing="20.0">
                     <children>
                        <Text strokeType="OUTSIDE" strokeWidth="0.0" text="Wizard" />
                        <ImageView fitHeight="193.0" fitWidth="117.0" pickOnBounds="true" preserveRatio="true">
                           <image>
                              <Image url="@../images/wizards/wizard.png" />
                           </image>
                        </ImageView>
                     </children>
                  </VBox>
               </graphic>
            </Button>
         </children>
         <padding>
            <Insets left="20.0" right="20.0" />
         </padding>
      </HBox>
Abra
  • 19,142
  • 7
  • 29
  • 41
  • So what exactly is your problem? Your code does not remove any buttons? In other words, are you saying that method `setAvailableWizards` does not remove any of the children of `wizardsHBox` ? – Abra Jun 10 '22 at 10:02
  • exactly, when I call `wizardsHBox.getChildren().remove(toRemove);` nothing happens, although `toRemove` seems to contain the correct reference to the button. – Pietro Lodi Rizzini Jun 10 '22 at 10:27
  • You're doing a lot of looking-up of components via string manipulation, instead of normal Java approaches. In your `removed.forEach(...)` loop, log the id you're looking for, and the result of the lookup, to ensure your looking for, and finding, the correct things. – James_D Jun 10 '22 at 12:59
  • as I said, the result of the lookup is correct, I've checked that, the probelm is in the `remove` method – Pietro Lodi Rizzini Jun 10 '22 at 13:14
  • 4
    [mcve] please.. mind the __M__ and make sure it's runnable as-is – kleopatra Jun 10 '22 at 13:52
  • If you call `wizardsHBox.getChildren().contains(toRemove)` before the `remove` call, does it return `true`? And afterwards, does it return `false`? – Slaw Jun 11 '22 at 00:51

1 Answers1

1

Given your FXML file, I'm assuming you have those 4 wizard buttons already created when your application starts. By the looks of it they have their own respective fx:ids.

Add them at the beginning of your class the same way you've added your wizardsHBox and then for each button create a setOnMouseClicked() event handler to call wizardsHBox.getChildren().remove(clickedButton). Of course, just this, will not keep track of all of the available wizards left. So, we need to change how the setAvailableWizards() method works.

You end up with something like this:

public class WizardController extends ViewObservable implements Initializable {
    // Make sure they're 'private'.
    @FXML
    private Button kingButton, sorcererButton, pixieButton, wizardButton;
    @FXML
    private HBox wizardsHBox;
    private List<Wizard> availableWizards;

    @Override
    public void initialize(URL url, ResourceBundle resourceBundle) {
        availableWizards = Arrays.stream(values()).toList();

        kingButton.setOnMouseClicked(new EventHandler<MouseEvent>() {
            @Override
            public void handle(MouseEvent event) {
                // wizardsHBox.getChildren().remove(kingButton);
                setAvailableWizards(kingButton, Wizard.KING);
            }
        });

        sorcererButton.setOnMouseClicked(new EventHandler<MouseEvent>() {
            @Override
            public void handle(MouseEvent event) {
                // wizardsHBox.getChildren().remove(sorcererButton);
                setAvailableWizards(sorcererButton, Wizard.SORCERER);
            }
        });

        pixieButton.setOnMouseClicked(new EventHandler<MouseEvent>() {
            @Override
            public void handle(MouseEvent event) {
                // wizardsHBox.getChildren().remove(pixieButton);
                setAvailableWizards(pixieButton, Wizard.PIXIE);
            }
        });
        
        wizardButton.setOnMouseClicked(new EventHandler<MouseEvent>() {
            @Override
            public void handle(MouseEvent event) {
                // wizardsHBox.getChildren().remove(wizardButton);
                setAvailableWizards(wizardButton, Wizard.WIZARD);
            }
        });
    }

    public void setAvailableWizards(Button selectedWizard, Wizard wizardType) {
        // Go over the 'availableWizards' list and check if any of the items match the provided 'wizardType' argument.
        // If any of them does, remove it from the 'availableWizards' list and remove the button from the UI
        for (Wizard wizard : availableWizards) {
            if (wizard == wizardType) {
                wizardsHBox.getChildren().remove(selectedWizard);
                availableWizards.remove(wizardType);
                wizardNotification(selectedWizard);
            }
        }

        System.out.println(wizardsHBox.getChildren());
    }

    // I'm not sure what this is, but I'll assume it sends to the server which wizard was selected whenever the corresponding button is clicked.
    // So I'll changed it up a bit. 
    // (Renamed from 'handleKingButton' to 'wizardNotification')
    // Also, from the original name, I'm assuming you have 'handlePixieButton', 'handleWizardButton', etc. - now you don't need them. This one is being reused regardless of which button you click.
    public void wizardNotification(Button selectedWizard) {
        // chosenId = button fx:id
        String chosenId = selectedWizard.getId();
        String chosenWizard = chosenId.substring(0, chosenId.indexOf("Button")).toUpperCase();

        // notify to the server
    }
}

P.S. There might be some typos, I wrote the code straight up here.

P.S.2. Make sure you're using a Task for the server notification otherwise the UI might freeze if it takes too long for a response.

Doombringer
  • 596
  • 4
  • 19
  • `Scene#lookup(String)` **does not** create a new instance. Returning an object from any method does not create a new instance. Assigning an object to a new variable does not create a new instance. Java is _pass-by-value_, but the "value" is the "reference". So, the _reference_ is copied, but the two references still point to the same object instance. – Slaw Jun 11 '22 at 19:43
  • @Slaw I stand corrected. I've removed those statements from my answer. The rest should still be fine, though, as it does what the OP is trying to do (i.e. remove buttons from the `wizardsHBox` and the `availableWizards` list based on which button's being clicked). – Doombringer Jun 11 '22 at 21:02