package com.ecep.contract.util; import java.io.PrintWriter; import java.io.StringWriter; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.function.Consumer; import java.util.function.Function; import com.ecep.contract.model.IdentityEntity; import com.ecep.contract.service.QueryService; import com.ecep.contract.vm.IdentityViewModel; import javafx.beans.property.ObjectProperty; import org.controlsfx.control.textfield.AutoCompletionBinding; import org.controlsfx.control.textfield.TextFields; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.StringUtils; import com.ecep.contract.Desktop; import com.ecep.contract.Message; import com.ecep.contract.converter.EntityStringConverter; import com.ecep.contract.task.Tasker; import javafx.application.Platform; import javafx.beans.property.SimpleObjectProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.geometry.Insets; import javafx.geometry.Rectangle2D; import javafx.scene.Node; import javafx.scene.control.Alert; import javafx.scene.control.ButtonType; import javafx.scene.control.Dialog; import javafx.scene.control.DialogPane; import javafx.scene.control.Label; import javafx.scene.control.ListView; import javafx.scene.control.ProgressBar; import javafx.scene.control.TextArea; import javafx.scene.control.TextField; import javafx.scene.layout.GridPane; import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; import javafx.stage.Screen; import javafx.util.StringConverter; public class UITools { private static final Logger logger = LoggerFactory.getLogger(UITools.class); public static void showExceptionAndWait(String title, Throwable e) { String stackTrace = printStackTrace(e); Label label = new Label("异常堆栈信息如下:"); TextArea textArea = new TextArea(stackTrace); textArea.setEditable(false); textArea.setWrapText(true); GridPane.setVgrow(textArea, Priority.ALWAYS); GridPane.setHgrow(textArea, Priority.ALWAYS); GridPane expContent = new GridPane(); expContent.setMaxWidth(Double.MAX_VALUE); expContent.add(label, 0, 0); expContent.add(textArea, 0, 1); String message = e.getMessage(); showAndWait(Alert.AlertType.ERROR, "程序异常", title, message, expContent); } /** * 显示 Alert 窗口 * * @param alertType alert 类型 * @param title 标题 * @param header 头 * @param message 消息内容 * @param content 扩张节点 */ public static CompletableFuture showAndWait(Alert.AlertType alertType, String title, String header, String message, Node content) { CompletableFuture future = new CompletableFuture<>(); Platform.runLater(() -> { Alert alert = new Alert(alertType); if (content != null) { alert.getDialogPane().setExpandableContent(content); alert.getDialogPane().setExpanded(true); } alert.setTitle(title); if (header != null) alert.setHeaderText(header); if (message != null) alert.setContentText(message); Rectangle2D visualBounds = Screen.getPrimary().getVisualBounds(); alert.getDialogPane().setMaxWidth(visualBounds.getWidth() * 0.9); alert.showAndWait(); future.complete(alert.getResult()); }); return future; } public static CompletableFuture showConfirmation(String title, String message) { return showAndWait(Alert.AlertType.CONFIRMATION, "请选择", title, message, null); } public static Dialog createDialog() { Dialog dialog = new Dialog<>(); VBox box = new VBox(); Label headerLabel = new Label(); headerLabel.setId("header"); VBox.setMargin(headerLabel, new Insets(5, 0, 8, 0)); ListView listView = new ListView<>(); listView.setMinHeight(50); listView.setId("list-view"); ObservableList listViewDataSet = FXCollections.observableArrayList(); listView.setItems(listViewDataSet); VBox.setVgrow(listView, Priority.ALWAYS); box.getChildren().setAll(headerLabel, listView); dialog.getDialogPane().setContent(box); dialog.getDialogPane().getButtonTypes().add(ButtonType.OK); dialog.setResizable(true); dialog.setWidth(600); dialog.setHeight(500); return dialog; } /** * 显示一个对话框, 并等待用户关闭对话框 *

* 对话框内嵌一个 ListView,可在 consumer 中向 ListView 添加内容 * * @param title 对话框标题 * @param header 对话框头 * @param consumer 对话框内容生成器 */ public static void showDialogAndWait(String title, String header, Consumer> consumer) { Dialog dialog = createDialog(); dialog.setTitle(title); DialogPane dialogPane = dialog.getDialogPane(); Node content = dialogPane.getContent(); Label headerLabel = (Label) content.lookup("#header"); ListView listView = (ListView) content.lookup("#list-view"); headerLabel.setText(header); CompletableFuture.runAsync(() -> { consumer.accept(listView.getItems()); }).exceptionally(e -> { Platform.runLater(() -> { listView.getItems().add(e.getMessage()); }); if (logger.isErrorEnabled()) { logger.error(e.getMessage(), e); } return null; }); dialog.showAndWait(); } /** * 显示一个对话框, 并等待用户关闭对话框 * * @param title 对话框标题 * @param task 任务 * @param init 初始化 */ public static void showTaskDialogAndWait(String title, javafx.concurrent.Task task, Consumer> init) { Dialog dialog = createDialog(); dialog.getDialogPane().getScene().getStylesheets().add("/ui/dialog.css"); dialog.setTitle(title); DialogPane dialogPane = dialog.getDialogPane(); VBox box = (VBox) dialogPane.getContent(); Label headerLabel = (Label) box.lookup("#header"); ListView listView = (ListView) box.lookup("#list-view"); listView.setCellFactory(param -> new Tasker.MessageListCell()); headerLabel.textProperty().bind(task.titleProperty()); java.util.function.Predicate consumer = message -> { if (Platform.isFxApplicationThread()) { listView.getItems().add(message); } else { Platform.runLater(() -> listView.getItems().add(message)); } return false; }; if (task instanceof Tasker tasker) { tasker.setMessageHandler(consumer); } else { task.messageProperty().addListener((observable, oldValue, newValue) -> { consumer.test(Message.info(newValue)); }); } // 加一个进度条 ProgressBar progressBar = new ProgressBar(0); progressBar.setPrefHeight(12); progressBar.setPrefWidth(200); progressBar.prefWidthProperty().bind(box.widthProperty().subtract(10)); progressBar.setMinHeight(12); progressBar.setVisible(true); VBox.setMargin(progressBar, new Insets(6, 0, 6, 0)); Platform.runLater(() -> { box.getChildren().add(progressBar); }); // progressBar.disabledProperty().bind(task.runningProperty()); progressBar.visibleProperty().bind(task.runningProperty()); progressBar.progressProperty().bind(task.progressProperty()); task.progressProperty().addListener((observable, oldValue, newValue) -> { System.out.println("progress = " + newValue); }); if (task instanceof Tasker tasker) { // 提交任务 new Thread(tasker).start(); // try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) { // executor.submit(tasker); // System.out.println("executor = " + executor); // } } if (init != null) { init.accept(consumer::test); } dialog.showAndWait(); task.cancel(); } private static String printStackTrace(Throwable e) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); e.printStackTrace(pw); return sw.toString(); } public static void showAlertAndWait(String message) { Alert alert = new Alert(Alert.AlertType.INFORMATION); alert.setContentText(message); alert.showAndWait(); } public static AutoCompletionBinding autoCompletion( TextField textField, SimpleObjectProperty property, EntityStringConverter converter) { return autoCompletion(textField, property, converter::suggest, converter); } public static > AutoCompletionBinding autoCompletion( TextField textField, ObjectProperty idProperty, QueryService queryService) { StringConverter converter = queryService.getStringConverter(); return autoCompletion(textField, idProperty, queryService, converter, queryService::search); } public static > AutoCompletionBinding autoCompletion( TextField textField, ObjectProperty idProperty, QueryService service, StringConverter converter, Function> searcher) { Integer id = idProperty.get(); if (id != null) { T entity = service.findById(id); // 先赋值 textField.textProperty().set(converter.toString(entity)); } // 监听 property 变化 idProperty.addListener((observable, oldValue, newValue) -> { if (newValue == null) { textField.textProperty().set(null); return; } T newEntity = service.findById(newValue); textField.textProperty().set(converter.toString(newEntity)); }); // 关联一个 自动完成 AutoCompletionBinding completionBinding = TextFields.bindAutoCompletion(textField, p -> { if (p.isCancelled()) { return null; } try { return searcher.apply(p.getUserText()); } catch (Exception e) { textField.setText(e.getMessage()); throw new RuntimeException(e); } }, converter); // 设置自动补全选中值到 property completionBinding.setOnAutoCompleted(event -> { T completion = event.getCompletion(); idProperty.set(completion.getId()); }); // fixed 当输入框丢失焦点时,输入框内容为空时,设置property 为null textField.focusedProperty().addListener((observable, oldValue, newValue) -> { if (!newValue) { if (!StringUtils.hasText(textField.getText())) { idProperty.set(null); } } }); return completionBinding; } /** * 给 TextField 增加一个 自动补全功能 * * @param textField 要补全的文本框 * @param property 绑定的属性 * @param func 获取补全数据的方法 * @param converter 对象转换器 * @param 对象类型 * @return AutoCompletionBinding */ public static AutoCompletionBinding autoCompletion( TextField textField, SimpleObjectProperty property, Function> func, StringConverter converter) { // 先赋值 textField.textProperty().set(converter.toString(property.get())); // 监听 property 变化 property.addListener((observable, oldValue, newValue) -> { textField.textProperty().set(converter.toString(newValue)); }); // 关联一个 自动完成 AutoCompletionBinding completionBinding = TextFields.bindAutoCompletion(textField, p -> { if (p.isCancelled()) { return null; } try { return func.apply(p); } catch (Exception e) { textField.setText(e.getMessage()); throw new RuntimeException(e); } }, converter); // 设置自动补全选中值到 property completionBinding.setOnAutoCompleted(event -> { property.set(event.getCompletion()); }); // fixed 当输入框丢失焦点时,输入框内容为空时,设置property 为null textField.focusedProperty().addListener((observable, oldValue, newValue) -> { if (!newValue) { if (!StringUtils.hasText(textField.getText())) { property.set(null); } } }); return completionBinding; } /** * 确认对话框 * * @param title * @param text * @return */ public static boolean showConfirmDialog(String title, String text) { // TODO Alert alert = new Alert(Alert.AlertType.CONFIRMATION); alert.setTitle(title); alert.setHeaderText(text); if (alert.showAndWait().get() == ButtonType.OK) { return true; } return false; } }