我是JavaFX的新手,我似乎找不到如何做到这一点。
我在Vbox中有一个ListView,其中装有一个ObservableList of Strings。我已将ListView的SelectionMode设置为MULTIPLE,这使我可以在按住Ctrl或Shift键的同时选择多个项目。
我希望能够单击一行并将鼠标向下拖动并选择多行,但是我不知道该怎么做。我尝试了几次搜索,似乎只找到拖放,而这不是我所需要的。
@FXML private ListView availableColumnList;
private ObservableList<String> availableColumns = FXCollections.<String>observableArrayList("One","Two","Three","Four");
availableColumnList.getItems().addAll(availableColumns);
availableColumnList.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
Run Code Online (Sandbox Code Playgroud)
如果您使用的是 JavaFX 10+,那么您可以ListViewSkin在那里扩展和添加功能。您需要 JavaFX 10 或更高版本的原因是,那时扩展的VirtualContainerBase类添加了方法。然后,您可以使用动画 API(例如 )通过该方法滚动。ListViewSkingetVirtualFlow()AnimationTimerListViewVirtualFlow#scrollPixels(double)
下面是一个概念验证。它所做的只是ListView当鼠标靠近顶部(或左侧)或靠近底部(或右侧)时自动滚动ListView。当鼠标进入一个单元格时,该项目被选中(粗略地)。如果您想在开始向相反方向拖动鼠标时取消选择项目,那么您需要自己实现。您可能想要实现的另一件事是,AnimationTimer如果ListView隐藏或从场景中删除,则停止。
注意:下面使用“全按-拖动-释放”手势。换句话说,存在着MouseEvent处理程序和MouseDragEvent处理程序的混合体。使用 s 的原因MouseDragEvent是因为它们可以传递到其他节点,而不仅仅是原始节点(与“简单的按下-拖动-释放”手势不同)。查看此文档以获取更多信息。
主程序.java
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.scene.control.SelectionMode;
import javafx.stage.Stage;
public final class Main extends Application {
@Override
public void start(Stage primaryStage) {
var listView = IntStream.range(0, 1000)
.mapToObj(Integer::toString)
.collect(Collectors.collectingAndThen(
Collectors.toCollection(FXCollections::observableArrayList),
ListView::new
));
// Sets the custom skin. Can also be set via CSS.
listView.setSkin(new CustomListViewSkin<>(listView));
listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
primaryStage.setScene(new Scene(listView, 600, 400));
primaryStage.show();
}
}
Run Code Online (Sandbox Code Playgroud)
自定义ListViewSkin.java
import javafx.animation.AnimationTimer;
import javafx.geometry.Rectangle2D;
import javafx.scene.control.ListView;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.skin.ListViewSkin;
import javafx.scene.input.MouseDragEvent;
import javafx.scene.input.MouseEvent;
public class CustomListViewSkin<T> extends ListViewSkin<T> {
private static final double DISTANCE = 10;
private static final double PERCENTAGE = 0.05;
private AnimationTimer scrollAnimation = new AnimationTimer() {
@Override
public void handle(long now) {
if (direction == -1) {
getVirtualFlow().scrollPixels(-DISTANCE);
} else if (direction == 1) {
getVirtualFlow().scrollPixels(DISTANCE);
}
}
};
private Rectangle2D leftUpArea;
private Rectangle2D rightDownArea;
private int direction = 0;
private int anchorIndex = -1;
public CustomListViewSkin(final ListView<T> control) {
super(control);
final var flow = getVirtualFlow();
final var factory = flow.getCellFactory();
// decorate the actual cell factory
flow.setCellFactory(vf -> {
final var cell = factory.call(flow);
// handle drag start
cell.addEventHandler(MouseEvent.DRAG_DETECTED, event -> {
if (control.getSelectionModel().getSelectionMode() == SelectionMode.MULTIPLE) {
event.consume();
cell.startFullDrag();
anchorIndex = cell.getIndex();
}
});
// handle selecting items when the mouse-drag enters the cell
cell.addEventHandler(MouseDragEvent.MOUSE_DRAG_ENTERED, event -> {
event.consume();
if (event.getGestureSource() != cell) {
final var model = control.getSelectionModel();
if (anchorIndex < cell.getIndex()) {
model.selectRange(anchorIndex, cell.getIndex() + 1);
} else {
model.selectRange(cell.getIndex(), anchorIndex + 1);
}
}
});
return cell;
});
// handle the auto-scroll functionality
flow.addEventHandler(MouseDragEvent.MOUSE_DRAG_OVER, event -> {
event.consume();
if (leftUpArea.contains(event.getX(), event.getY())) {
direction = -1;
scrollAnimation.start();
} else if (rightDownArea.contains(event.getX(), event.getY())) {
direction = 1;
scrollAnimation.start();
} else {
direction = 0;
scrollAnimation.stop();
}
});
// stop the animation when the mouse exits the flow/list (desired?)
flow.addEventHandler(MouseDragEvent.MOUSE_DRAG_EXITED, event -> {
event.consume();
scrollAnimation.stop();
});
// handle stopping the animation and reset the state when the mouse
// is released. Added to VirtualFlow because it doesn't matter
// which cell receives the event.
flow.addEventHandler(MouseEvent.MOUSE_RELEASED, event -> {
if (anchorIndex != -1) {
event.consume();
anchorIndex = -1;
scrollAnimation.stop();
}
});
updateAutoScrollAreas();
registerChangeListener(control.orientationProperty(), obs -> updateAutoScrollAreas());
registerChangeListener(flow.widthProperty(), obs -> updateAutoScrollAreas());
registerChangeListener(flow.heightProperty(), obs -> updateAutoScrollAreas());
}
// computes the regions where the mouse needs to be
// in order to start auto-scrolling. The regions depend
// on the orientation of the ListView.
private void updateAutoScrollAreas() {
final var flow = getVirtualFlow();
switch (getSkinnable().getOrientation()) {
case HORIZONTAL:
final double width = flow.getWidth() * PERCENTAGE;
leftUpArea = new Rectangle2D(0, 0, width, flow.getHeight());
rightDownArea = new Rectangle2D(flow.getWidth() - width, 0, width, flow.getHeight());
break;
case VERTICAL:
final double height = flow.getHeight() * PERCENTAGE;
leftUpArea = new Rectangle2D(0, 0, flow.getWidth(), height);
rightDownArea = new Rectangle2D(0, flow.getHeight() - height, flow.getWidth(), height);
break;
default:
throw new AssertionError();
}
}
@Override
public void dispose() {
unregisterChangeListeners(getSkinnable().orientationProperty());
unregisterChangeListeners(getVirtualFlow().widthProperty());
unregisterChangeListeners(getVirtualFlow().heightProperty());
super.dispose();
scrollAnimation.stop();
scrollAnimation = null;
}
}
Run Code Online (Sandbox Code Playgroud)
注意:正如kleopatra 提到的,至少其中一些功能更适合行为类。然而,为了简单起见,我决定仅使用现有的公共皮肤类(通过扩展它)。再次强调,以上只是概念验证。
| 归档时间: |
|
| 查看次数: |
151 次 |
| 最近记录: |