JavaFX MVC架构下多路径更新的ListView如何实现动态过滤?
核心问题说明
- Controller中创建的
FilteredList是initialize方法内的局部变量,绑定的是从ListView items里复制出来的独立列表,和后续剪贴板新增的内容完全没有关联,自然无法生效。 - 剪贴板内容变更的回调线程不是JavaFX应用线程,直接修改UI会有线程安全问题,大概率会抛异常。
- 你直接操作ListView的
items属性,而没有把ListView和FilteredList绑定,过滤规则自然不会作用到展示内容上。
具体修改步骤
1. Controller 代码修改
把数据源、FilteredList提为全局成员,绑定ListView到FilteredList,新增公共方法用来添加剪贴板内容:
package minimalsandbox; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.collections.transformation.FilteredList; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.scene.control.Button; import javafx.scene.control.ListView; import javafx.scene.control.ToggleButton; import javafx.scene.input.*; import javafx.scene.layout.AnchorPane; import javafx.stage.Stage; import javafx.scene.control.TextField; import java.net.URL; import java.util.ResourceBundle; public class Controller implements Initializable { @FXML private Button myButton01; @FXML public ListView<String> mylistviewvar; @FXML private ToggleButton myToggleBut; @FXML private AnchorPane myAnchorPane; @FXML private TextField searchFieldvar; // 全局数据源,所有新增内容都往这里加 private final ObservableList<String> dataSource = FXCollections.observableArrayList(); // 全局过滤列表 private FilteredList<String> filteredData; // 给Main调用的新增剪贴板内容的方法 public void addClipboardContent(String content) { dataSource.add(0, content); } @FXML void myButton01myEventClicked(MouseEvent event) { System.out.println("hi there !"); // to put string INTO clipboard Clipboard clipboard = Clipboard.getSystemClipboard(); ClipboardContent content = new ClipboardContent(); content.putString("Some text"); clipboard.setContent(content); // to copy from clipboard String fromclipboardstr = Clipboard.getSystemClipboard().getString(); System.out.println(fromclipboardstr); } @FXML void myToggleButEventSB(MouseEvent event) { if (myToggleBut.isSelected()) { System.out.println("is ON"); Stage stage = (Stage) myAnchorPane.getScene().getWindow(); stage.setAlwaysOnTop(true); }else { System.out.println("is OFF"); Stage stage = (Stage) myAnchorPane.getScene().getWindow(); stage.setAlwaysOnTop(false); } } @FXML void myonKeyTyped(KeyEvent event) { System.out.println("input changed"); // 这里不用加额外逻辑,textProperty的监听已经处理过滤了 } @Override public void initialize(URL url, ResourceBundle resourceBundle) { // 绑定过滤列表到数据源 filteredData = new FilteredList<>(dataSource, s -> true); // 把ListView的items设为过滤后的列表 mylistviewvar.setItems(filteredData); // 搜索框文本变更监听,动态改过滤规则 searchFieldvar.textProperty().addListener(obs->{ String filter = searchFieldvar.getText(); if(filter == null || filter.trim().isEmpty()) { filteredData.setPredicate(s -> true); } else { filteredData.setPredicate(s -> s.contains(filter)); } }); } }
2. Main 代码修改
新增Platform.runLater包裹UI更新逻辑,调用Controller的公共方法添加内容,删掉没用的局部ObservableList创建:
package minimalsandbox; import javafx.application.Application; import javafx.application.Platform; import javafx.fxml.FXMLLoader; import javafx.geometry.Rectangle2D; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.scene.input.Clipboard; import javafx.stage.Screen; import javafx.stage.Stage; public class Main extends Application { @Override public void start(Stage primaryStage) throws Exception { FXMLLoader loader =new FXMLLoader(); loader.setLocation(Main.class.getResource("/minimalsandbox/View.fxml")); Parent root = loader.load(); Scene scene = new Scene(root); primaryStage.setScene(scene); primaryStage.setX(200); primaryStage.setY(500); Rectangle2D bounds = Screen.getPrimary().getVisualBounds(); double x = bounds.getMinX() + (bounds.getWidth() - scene.getWidth()) * 0.2; double y = bounds.getMinY() + (bounds.getHeight() - scene.getHeight()) * 0.55; primaryStage.setX(x); primaryStage.setY(y); final Clipboard systemClipboard = Clipboard.getSystemClipboard(); Controller controller = loader.getController(); new com.sun.glass.ui.ClipboardAssistance(com.sun.glass.ui.Clipboard.SYSTEM) { @Override public void contentChanged() { System.out.print("System clipboard content changed: "); if ( systemClipboard.hasImage() ) { System.out.println("image"); } else if ( systemClipboard.hasString() ) { System.out.println("string"); String fromclipboard = Clipboard.getSystemClipboard().getString(); // 必须包在Platform.runLater里,否则非FX线程改UI会报错 Platform.runLater(() -> { controller.addClipboardContent(fromclipboard); }); } else if ( systemClipboard.hasFiles() ) { System.out.println("files"); } } }; primaryStage.show(); System.out.println("current thread: " + Thread.currentThread().getName()); } public static void main(String[] args) { launch(args); } }
3. View.fxml 代码不需要修改,直接用原来的即可。
疑问解答
- 动态填充的列表怎么传入FilteredList:不需要手动传,只要FilteredList绑定的是同一个
ObservableList数据源,你往数据源里新增/删除内容的时候,FilteredList会自动感知变化,触发过滤规则更新展示内容。 - 不需要在
myonKeyTyped事件里写过滤逻辑,你原来用的textProperty().addListener更可靠,能覆盖所有文本变更场景,包括粘贴输入、中文输入法输入、右键剪切/删除等keyTyped捕获不到的情况。 - 不要直接操作ListView的
items属性,所有数据变更都操作底层的数据源dataSource即可,视图层会自动同步。
内容的提问来源于stack exchange,提问作者progLearn00
相关产品推荐
相关产品推荐

