Java FX的多种select与鼠标像在Excel中

我是JavaFX的新手,我有一个GridPane与TextElements在其中。 我想在第一个元素上用MousePressedselect多个元素,比如在Excel中,然后将鼠标拖到另一个元素上,然后松开鼠标。 在这之后,鼠标被移动的所有元素应该被select并以某种方式被改变(在我下面的例子中,将textcolor改为蓝色)。 我在互联网上search了很多东西,但是我没有真正发现如何做到这一点,除了setOnDragDetected方法和startFullDrag()。 但是它只改变了鼠标被按下的第一个元素的textcolor。 有人可以告诉我我失踪了吗?

public class Test extends Application { @Override public void start(Stage primaryStage) { primaryStage.setTitle("Test"); Group root = new Group(); Scene scene = new Scene(root, 600, 600); GridPane mainTable = new GridPane(); mainTable.setGridLinesVisible(true); mainTable.prefHeightProperty().bind(scene.heightProperty()); mainTable.prefWidthProperty().bind(scene.widthProperty()); for (int i = 0; i < 6; i++){ for (int j = 0; j < 7; j++){ StackPane entry = new StackPane(); mainTable.add(entry, i, j); Text tEntry = new Text(); tEntry.setText("empty"); entry.getChildren().add(tEntry); } } ObservableList <Node> entries = mainTable.getChildren(); for (Node elem : entries){ elem.setOnDragDetected(new EventHandler<MouseEvent>() { @Override public void handle(MouseEvent event) { elem.startFullDrag(); StackPane entry = (StackPane) elem; Text tEntry = (Text) entry.getChildren().get(0); tEntry.setFill(Color.BLUE); } }); } int numCol = 6; for (int col = 0; col < numCol; col++){ ColumnConstraints column1 = new ColumnConstraints(); column1.setPercentWidth(100d/numCol); mainTable.getColumnConstraints().add(column1); } int numRow = 7; for (int row = 0; row < numRow; row++){ RowConstraints row1 = new RowConstraints(); row1.setPercentHeight(100d/numRow); mainTable.getRowConstraints().add(row1); } root.getChildren().add(mainTable); primaryStage.setScene(scene); primaryStage.show(); } /** * @param args the command line arguments */ public static void main(String[] args) { launch(args); } 

}

我会这样做,如下所示:

  • 保留所选文本的可观察集合(我在下面的例子中使用了Label )。 这可以很容易地find选定的(我认为你需要在某些时候)
  • 观察选定的文本并更新伪类状态。 使用外部CSS文件来更改颜色。
  • 使用onDragDetected()处理程序将select设置为该单元格
  • 使用onMouseDragEntered扩展select。 您将需要跟踪select开始的位置。

另外,通过使用GridPane的属性并将其应用到文本元素,可以简化您的布局。 另外:当gridLinesVisible设置为true时,请谨慎使用GridPane上的getChildren()GridPane的子节点包含表示网格线的节点,因此您可以获得意想不到的结果。

以下是网格窗格中可选标签的示例:

 import java.util.HashSet; import java.util.Set; import javafx.application.Application; import javafx.collections.FXCollections; import javafx.collections.ObservableSet; import javafx.collections.SetChangeListener.Change; import javafx.css.PseudoClass; import javafx.geometry.Pos; import javafx.scene.Scene; import javafx.scene.control.Label; import javafx.scene.layout.GridPane; import javafx.scene.layout.Priority; import javafx.stage.Stage; public class SelectableGridPane extends Application { private final ObservableSet<Label> selectedLabels = FXCollections.observableSet(); private final int ROWS = 10 ; private final int COLS = 10 ; private final Label[][] labels = new Label[COLS][ROWS]; private final PseudoClass SELECTED = PseudoClass.getPseudoClass("selected"); @Override public void start(Stage primaryStage) { GridLocation mouseDownLoc = new GridLocation(); GridPane grid = new GridPane(); grid.setHgap(5); grid.setVgap(5); grid.setGridLinesVisible(true); for (int i = 0; i < COLS; i++) { for (int j = 0; j < ROWS; j++) { addLabel(grid, i, j, mouseDownLoc); } } selectedLabels.addListener((Change<? extends Label> change) -> { if (change.wasAdded()) { Label label = change.getElementAdded(); label.pseudoClassStateChanged(SELECTED, true); } else if (change.wasRemoved()) { Label label = change.getElementRemoved(); label.pseudoClassStateChanged(SELECTED, false); } }); Scene scene = new Scene(grid, 800, 800); scene.getStylesheets().add("dragging-grid-pane.css"); primaryStage.setScene(scene); primaryStage.show(); } private void addLabel(GridPane grid, int col, int row, GridLocation mouseDownLoc) { Label label = new Label("Cell ["+(col+1)+", "+(row+1)+"]"); labels[col][row] = label ; grid.add(label, col, row); GridPane.setFillWidth(label, true); GridPane.setHgrow(label, Priority.ALWAYS); GridPane.setVgrow(label, Priority.ALWAYS); label.setAlignment(Pos.CENTER); label.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); label.setOnDragDetected(event -> { mouseDownLoc.x = col ; mouseDownLoc.y = row ; selectedLabels.clear(); selectedLabels.add(label); label.startFullDrag(); }); label.setOnMouseDragEntered(event -> recomputeSelection(mouseDownLoc, col, row)); } private void recomputeSelection(GridLocation mouseDown, int x, int y) { Set<Label> newSelection = new HashSet<>(); int startX = Math.min(x, mouseDown.x); int endX = Math.max(x, mouseDown.x); int startY = Math.min(y, mouseDown.y); int endY = Math.max(y, mouseDown.y); for (int j = startY; j <= endY; j++) { for (int i = startX; i <= endX; i++) { newSelection.add(labels[i][j]); } } // remove anything in selectedLabels // that is not in newSelection: selectedLabels.retainAll(newSelection); // add everything from newSelection // to selectedLabels (will not duplicate): selectedLabels.addAll(newSelection); } private static class GridLocation { int x, y ; } public static void main(String[] args) { launch(args); } } 

拖动并网pane.css:

 .label:selected { -fx-background-color: -fx-background ; -fx-background: -fx-selection-bar ; } 

这是Java 8代码:它使用CSS伪类 。 如果你被迫使用Java 7,你将需要使用普通的css类,这很难pipe理:

  selectedLabels.addListener(new SetChangeListener() { @Override public void onChanged(Change<? extends Label> change) { if (change.wasAdded()) { Label label = change.getElementAdded(); if (! label.getStyleClass().contains("selected")) { label.getStyleClass().add("selected"); } } else if (change.wasRemoved()) { Label label = change.getElementRemoved() ; label.getStyleClass().removeAll(Collections.singleton("selected")); } } }); 

该CSS文件成为

 .label.selected { -fx-background-color: -fx-background ; -fx-background: -fx-selection-bar ; } 

(注意:labelselected之间被改变为:在select器中没有空格。)

您还需要将其他事件处理程序从lambdaexpression式更改为匿名内部类(或其他类实现)。

您的拖放监听器应用于每个单元格,当事件启动时,它只会影响该单元格,所以这就是为什么只改变一个单元格的颜色。

另外,对于拖动工作,你需要创build一个带有一些内容的Dragboard实例。

当你还在拖动鼠标进入其他单元格时,你需要为这些单元格添加另一个监听器,以便它们可能成为拖放目标。

像这样的东西:

 ObservableList <Node> entries = mainTable.getChildren(); for (Node elem : entries){ elem.setOnDragDetected(new EventHandler<MouseEvent>() { @Override public void handle(MouseEvent e) { StackPane entry = (StackPane) elem; Dragboard db = entry.startDragAndDrop(TransferMode.ANY); ClipboardContent cb = new ClipboardContent(); cb.put(DataFormat.PLAIN_TEXT, ""); db.setContent(cb); Text tEntry = (Text) entry.getChildren().get(0); tEntry.setFill(Color.BLUE); } }); elem.setOnDragOver(new EventHandler<DragEvent>() { @Override public void handle(DragEvent e) { e.acceptTransferModes(TransferMode.ANY); StackPane entry = (StackPane) elem; Text tEntry = (Text) entry.getChildren().get(0); tEntry.setFill(Color.BLUE); } }); } 

也看看这篇文章 ,它使用TableViewGrigPane ,而不是一个GrigPane来允许范围select。