如何将 jscrollPane 设置为所需的值?

How to set the scrollPane to the desired vValue?

我在滚动窗格中有一个瓷砖窗格。 tilepane 充满了具有删除或编辑等操作的按钮。在我删除或编辑按钮后,滚动窗格会自动回到顶部,这是我不想要的。我希望它保持在当前位置(用户滚动到的位置)。

我尝试获取和设置滚动窗格的 vValue。 getvValue 获取值并 setter 设置它,但滚动窗格不响应它并在操作(删除/编辑)后返回顶部。我尝试了这个问题的解决方案:,但是 layout() 方法也没有做任何事情。我在这里做错了什么?如何让滚动窗格保持原样?

编辑:我发现了问题,但仍然不知道如何解决。显然,在 fxml 中设置和更改 hBoxes 的可见性会使滚动窗格回到顶部。如果我删除 setVisible 方法并 运行 它,我可以删除项目并且滚动窗格保持在原位。如何解决这个问题?

根据要求,我创建了一个最小的、完整的、可验证的代码示例。 运行 它,向下滚动一点,右键单击上面有名称和价格的按钮,然后单击删除。它会向上滚动到顶部。

这是我的代码:

public class Controller {
private static LinkedHashMap<String, BigDecimal> mProductMap = new LinkedHashMap<>();

@FXML private TilePane fieldContainer = new TilePane();
@FXML private ScrollPane scroll;
@FXML private Button deletebtn;
@FXML private Button editbtn;
@FXML private HBox homeBar;
@FXML private HBox actionBar;

@FXML
private void initialize() {
    addProduct("Coffee", new BigDecimal("2.00"));
    addProduct("Tea", new BigDecimal("2.00"));
    addProduct("Cappuccino", new BigDecimal("2.00"));
    addProduct("Espresso", new BigDecimal("2.00"));
    addProduct("Cooky", new BigDecimal("2.00"));
    addProduct("Candy", new BigDecimal("2.00"));
    addProduct("Chocobar", new BigDecimal("2.00"));
    addProduct("Cola", new BigDecimal("2.00"));
    addProduct("Fanta", new BigDecimal("2.00"));
    addProduct("Beer", new BigDecimal("2.00"));
    addProduct("Salad", new BigDecimal("2.00"));
    addProduct("Sandwich", new BigDecimal("2.00"));
    addProduct("Water", new BigDecimal("2.00"));
    addProduct("Cassis", new BigDecimal("2.00"));
}

// makes the delete and edit buttons appear after selecting a button from the tilepane
private void select(String selectedProduct) {

    /*
    the setVisible method for the hBoxes in fxml cause the scrollbar to go back to the top
    without them, the scrollpane stays where it is.
    I tried changing visibility with both setVisible and CSS, but they both cause the problem
    I need the actionbar to appear when you select a button (right click on it)
    */
    homeBar.setVisible(false);
    actionBar.setVisible(true);

    EventHandler<ActionEvent> delete = event -> {
        deleteProduct(selectedProduct);  // deletes an item from a LinkedHashMap
        homeBar.setVisible(true);
        actionBar.setVisible(false);
    };
    deletebtn.setOnAction(delete);

    // I want the same to happen when the edit handler is used, scrollpane needs to remain its position
    EventHandler<ActionEvent> edit = event -> {
        editProduct(selectedProduct);  // edits an item from a LinkedHashMap
        homeBar.setVisible(true);
        actionBar.setVisible(false);
    };
    editbtn.setOnAction(edit);
}

/*
Code below does not cause the problem, but I added it as a reference
*/

private void deleteProduct(String product) {
    if (mProductMap.containsKey(product)) {
        mProductMap.remove(product);
        System.out.printf("%s has been deleted!%n", product);
    } else {
        System.out.printf("%s does not exist. Please try again.%n", product);
    }
    addButtons();
}

private void editProduct(String product) {
    List<String> indexKeys = new ArrayList<>(mProductMap.keySet());
    List<BigDecimal> indexValues = new ArrayList<>(mProductMap.values());
    BigDecimal price = mProductMap.get(product); // gets the product's value (the price)
    int indexKey = indexKeys.indexOf(product);
    int indexValue = indexValues.indexOf(price);

    if (mProductMap.containsKey(product)) {
        int sizeBefore = mProductMap.size();
        addingProduct();
        int sizeAfter = mProductMap.size();

        if (sizeAfter > sizeBefore) {
            indexKeys.remove(product);
            indexValues.remove(price);
            mProductMap.remove(product);

            // Make a new list to get the new entry at the end
            List<Map.Entry<String,BigDecimal>> entryList = new ArrayList<>(mProductMap.entrySet());
            Map.Entry<String, BigDecimal> lastEntry = entryList.get(entryList.size()-1);

            String key = lastEntry.getKey();
            BigDecimal value = lastEntry.getValue();
            indexKeys.add(indexKey, key);
            indexValues.add(indexValue, value);
            mProductMap.clear();

            // Put the keys and values from the two lists back to the map
            for (int i=0; i<indexKeys.size(); i++) {
                addProduct(indexKeys.get(i), indexValues.get(i));
            }
        }
    } else {
        System.out.printf("%s does not exist. Please try again.%n", product);
    }
}

void addProduct(String product, BigDecimal price) {
    mProductMap.put(product, price);
    addButtons();
}

// Adding buttons to the TilePane fieldContainer in center of BorderPane
// One button per key-value pair of mProductMap
private void addButtons() {
    // clears the TilePane to prevent duplicate buttons
    fieldContainer.getChildren().clear();

    for (Map.Entry<String, BigDecimal> entry : mProductMap.entrySet()) {
        StackPane newField = new StackPane();
        Button main = new Button();
        main.setOnMousePressed(me -> {
            if (me.getButton() == MouseButton.SECONDARY) {  // = right click
                select(entry.getKey());
            }
        });
        main.setText(entry.getKey() + "\n" + entry.getValue());
        newField.getChildren().add(main);
        fieldContainer.setAlignment(Pos.TOP_LEFT);
        fieldContainer.getChildren().add(newField);
    }
}

// Popup for adding products to the Map with the + button
@FXML
private void addingProduct(){
    Stage newStage = new Stage();
    VBox popup = new VBox();
    final BooleanProperty firstTime = new SimpleBooleanProperty(true); // Variable to store the focus on stage load
    TextField product = new TextField("");
    product.setId("product");
    product.setPromptText("Enter the item name...");
    // code to remove the focus from first textfield on stage load
    product.focusedProperty().addListener((observable,  oldValue,  newValue) -> {
        if(newValue && firstTime.get()){
            popup.requestFocus(); // Delegate the focus to container
            firstTime.setValue(false); // Variable value changed for future references
        }
    });

    TextField price = new TextField("");
    price.setId("price");
    price.setPromptText("Enter the item price...");
    Button submit = new Button("Submit");
    Label label = new Label();
    label.setId("label");
    submit.setOnAction(e -> {
        if ( (product.getText() != null && !product.getText().isEmpty() &&
                price.getText() != null && !price.getText().isEmpty() ) ) {
            addProduct(product.getText(), new BigDecimal(price.getText()) );
            newStage.close();
        } else {
            label.setText("Fill in both fields");
        }
    });

    popup.getChildren().add(product);
    popup.getChildren().add(price);
    popup.getChildren().add(submit);
    popup.getChildren().add(label);
    Scene stageScene = new Scene(popup, 300, 200);
    newStage.setScene(stageScene);
    newStage.showAndWait();
  }
}

FXML:

<BorderPane xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.Controller">
<top>
    <StackPane>
        <HBox fx:id="homeBar" styleClass="main-bar" visible="true">
            <Button StackPane.alignment="BOTTOM_LEFT">Home</Button>
            <Button onAction="#addingProduct" StackPane.alignment="BOTTOM_RIGHT">Add a new product</Button>
        </HBox>
        <HBox fx:id="actionBar" styleClass="main-bar" visible="false">
            <Button fx:id="deletebtn" StackPane.alignment="BOTTOM_CENTER">Delete</Button>
            <Button fx:id="editbtn" StackPane.alignment="BOTTOM_CENTER">Edit</Button>
            <Button onAction="#addingProduct" StackPane.alignment="BOTTOM_RIGHT">Add a new product</Button>
        </HBox>
    </StackPane>
</top>

<center>
    <ScrollPane fx:id="scroll" hbarPolicy="NEVER">
        <TilePane fx:id="fieldContainer" prefColumns="2" prefTileHeight="100.0" prefTileWidth="144.0">
        </TilePane>
    </ScrollPane>
</center>

<bottom>
</bottom>
</BorderPane>

主要:

public class Main extends Application {

@Override
public void start(Stage primaryStage) throws Exception{
    Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
    primaryStage.setScene(new Scene(root, 300, 275));
    primaryStage.show();
}
public static void main(String[] args) {
    launch(args);
  }
}

我在这个 post 的答案的帮助下自己修复了它:

从 TilePane 中删除一个按钮后,ScrollPane 会找到下一个要关注的节点,默认情况下它是窗格中的第一个节点。通过请求将焦点放在 TilePane (fieldContainer) 上,ScrollPane 保持原位。

我将此代码添加到删除和编辑方法中:

fieldContainer.requestFocus();