拆分模块
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
package com.ecep.contract.util;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import com.ecep.contract.Desktop;
|
||||
import com.ecep.contract.SpringApp;
|
||||
import com.ecep.contract.ds.other.service.SysConfService;
|
||||
import com.ecep.contract.model.SysConf;
|
||||
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleDoubleProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.scene.control.Control;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
public abstract class AbstractConfigBounder<T> implements ConfigBounder {
|
||||
@Setter
|
||||
private SysConfService confService;
|
||||
String key;
|
||||
SysConf conf;
|
||||
@Getter
|
||||
@Setter
|
||||
Control control;
|
||||
@Getter
|
||||
@Setter
|
||||
javafx.util.StringConverter<T> converter;
|
||||
@Setter
|
||||
Property<T> property;
|
||||
ObjectProperty<LocalDateTime> modified = new SimpleObjectProperty<>(this, "modified");
|
||||
ObjectProperty<LocalDateTime> created = new SimpleObjectProperty<>(this, "created");
|
||||
|
||||
public AbstractConfigBounder(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> runAsync(Runnable runnable) {
|
||||
return CompletableFuture.runAsync(runnable, Desktop.instance.getExecutorService()).exceptionally(ex -> {
|
||||
ex.printStackTrace();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public SysConfService getConfService() {
|
||||
if (confService == null) {
|
||||
confService = SpringApp.getBean(SysConfService.class);
|
||||
}
|
||||
return confService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
getControl().setDisable(true);
|
||||
runAsync(this::asyncHandle);
|
||||
}
|
||||
|
||||
protected void asyncHandle() {
|
||||
conf = getConfService().findById(key);
|
||||
if (conf == null) {
|
||||
conf = new SysConf();
|
||||
conf.setId(key);
|
||||
conf.setCreated(LocalDateTime.now());
|
||||
conf.setModified(LocalDateTime.now());
|
||||
conf = getConfService().save(conf);
|
||||
}
|
||||
Property<T> property = getProperty();
|
||||
property.addListener(this::propertyChanged);
|
||||
bindBidirectional(property);
|
||||
modified.set(conf.getModified());
|
||||
created.set(conf.getCreated());
|
||||
getControl().setDisable(false);
|
||||
}
|
||||
|
||||
public Property<T> getProperty() {
|
||||
if (property == null) {
|
||||
T value = getConverter().fromString(conf.getValue());
|
||||
property = createProperty(value);
|
||||
setProperty(property);
|
||||
}
|
||||
return property;
|
||||
}
|
||||
|
||||
public ObjectProperty<LocalDateTime> getModifiedProperty() {
|
||||
return modified;
|
||||
}
|
||||
|
||||
public ObjectProperty<LocalDateTime> getCreatedProperty() {
|
||||
return created;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Property<T> createProperty(T value) {
|
||||
if (value instanceof Boolean b) {
|
||||
return (Property<T>) new SimpleBooleanProperty(b);
|
||||
} else if (value instanceof Integer i) {
|
||||
return (Property<T>) new SimpleIntegerProperty(i);
|
||||
} else if (value instanceof Double d) {
|
||||
return (Property<T>) new SimpleDoubleProperty(d);
|
||||
} else if (value instanceof String s) {
|
||||
return (Property<T>) new SimpleStringProperty(s);
|
||||
}
|
||||
return new SimpleObjectProperty<>(value);
|
||||
}
|
||||
|
||||
abstract void bindBidirectional(Property<T> property);
|
||||
|
||||
void propertyChanged(ObservableValue<? extends T> observable, T oldValue,
|
||||
T newValue) {
|
||||
conf.setValue(getConverter().toString(newValue));
|
||||
conf.setModified(LocalDateTime.now());
|
||||
save();
|
||||
}
|
||||
|
||||
public void save() {
|
||||
SysConf saved = getConfService().save(conf);
|
||||
modified.set(saved.getModified());
|
||||
created.set(saved.getCreated());
|
||||
}
|
||||
|
||||
}
|
||||
122
client/src/main/java/com/ecep/contract/util/AsyncUtils.java
Normal file
122
client/src/main/java/com/ecep/contract/util/AsyncUtils.java
Normal file
@@ -0,0 +1,122 @@
|
||||
package com.ecep.contract.util;
|
||||
|
||||
import java.util.concurrent.*;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import com.ecep.contract.Desktop;
|
||||
|
||||
/**
|
||||
* 异步任务工具类 - 提供超时处理等增强功能
|
||||
*/
|
||||
public class AsyncUtils {
|
||||
/**
|
||||
* 执行带超时的CompletableFuture任务
|
||||
*/
|
||||
public static <T> CompletableFuture<T> supplyAsyncWithTimeout(Supplier<T> supplier, long timeout, TimeUnit unit) {
|
||||
CompletableFuture<T> future = CompletableFuture.supplyAsync(supplier);
|
||||
return withTimeout(future, timeout, unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行带超时的CompletableFuture任务,并指定线程池
|
||||
*/
|
||||
public static <T> CompletableFuture<T> supplyAsyncWithTimeout(Supplier<T> supplier, Executor executor, long timeout,
|
||||
TimeUnit unit) {
|
||||
CompletableFuture<T> future = CompletableFuture.supplyAsync(supplier, executor);
|
||||
return withTimeout(future, timeout, unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为已有的CompletableFuture添加超时处理
|
||||
*/
|
||||
public static <T> CompletableFuture<T> withTimeout(CompletableFuture<T> future, long timeout, TimeUnit unit) {
|
||||
CompletableFuture<T> timeoutFuture = new CompletableFuture<>();
|
||||
|
||||
ScheduledExecutorService executor = Desktop.instance.getExecutorService();
|
||||
|
||||
ScheduledFuture<?> schedule = executor.schedule(() -> {
|
||||
TimeoutException timeoutException = new TimeoutException(
|
||||
"Task timed out after " + timeout + " " + unit.name());
|
||||
timeoutFuture.completeExceptionally(timeoutException);
|
||||
}, timeout, unit);
|
||||
;
|
||||
|
||||
return future.applyToEither(timeoutFuture, t -> t)
|
||||
.whenComplete((t, ex) -> schedule.cancel(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* 带重试机制的CompletableFuture任务
|
||||
*/
|
||||
public static <T> CompletableFuture<T> supplyAsyncWithRetry(Supplier<T> supplier, int maxRetries,
|
||||
long delayBetweenRetries, TimeUnit unit) {
|
||||
return supplyAsyncWithRetry(supplier, maxRetries, delayBetweenRetries, unit, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 带重试机制的CompletableFuture任务,并指定哪些异常需要重试
|
||||
*/
|
||||
public static <T> CompletableFuture<T> supplyAsyncWithRetry(
|
||||
Supplier<T> supplier,
|
||||
int maxRetries,
|
||||
long delayBetweenRetries,
|
||||
TimeUnit unit,
|
||||
Class<? extends Exception>[] retryableExceptions) {
|
||||
|
||||
CompletableFuture<T> resultFuture = new CompletableFuture<>();
|
||||
|
||||
// 执行任务的方法
|
||||
executeWithRetry(supplier, resultFuture, 0, maxRetries, delayBetweenRetries, unit, retryableExceptions);
|
||||
|
||||
return resultFuture;
|
||||
}
|
||||
|
||||
private static <T> void executeWithRetry(
|
||||
Supplier<T> supplier,
|
||||
CompletableFuture<T> resultFuture,
|
||||
int currentAttempt,
|
||||
int maxRetries,
|
||||
long delayBetweenRetries,
|
||||
TimeUnit unit,
|
||||
Class<? extends Exception>[] retryableExceptions) {
|
||||
|
||||
CompletableFuture.supplyAsync(supplier)
|
||||
.thenAccept(resultFuture::complete)
|
||||
.exceptionally(ex -> {
|
||||
if (currentAttempt < maxRetries && shouldRetry(ex, retryableExceptions)) {
|
||||
// 延迟后重试
|
||||
try {
|
||||
Thread.sleep(unit.toMillis(delayBetweenRetries));
|
||||
} catch (InterruptedException ie) {
|
||||
Thread.currentThread().interrupt();
|
||||
resultFuture.completeExceptionally(ie);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 递归调用,增加尝试次数
|
||||
executeWithRetry(supplier, resultFuture, currentAttempt + 1,
|
||||
maxRetries, delayBetweenRetries, unit, retryableExceptions);
|
||||
} else {
|
||||
// 达到最大重试次数或异常不可重试,完成Future
|
||||
resultFuture.completeExceptionally(ex);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private static boolean shouldRetry(Throwable ex, Class<? extends Exception>[] retryableExceptions) {
|
||||
// 如果未指定重试异常类型,则所有异常都重试
|
||||
if (retryableExceptions == null || retryableExceptions.length == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 检查异常是否属于可重试类型
|
||||
for (Class<? extends Exception> exceptionClass : retryableExceptions) {
|
||||
if (exceptionClass.isInstance(ex.getCause())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.ecep.contract.util;
|
||||
|
||||
import org.controlsfx.control.ToggleSwitch;
|
||||
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.util.StringConverter;
|
||||
import javafx.util.converter.BooleanStringConverter;
|
||||
|
||||
public class BooleanConfig extends AbstractConfigBounder<Boolean> {
|
||||
public BooleanConfig(String key) {
|
||||
super(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ToggleSwitch getControl() {
|
||||
return (ToggleSwitch) super.getControl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public StringConverter<Boolean> getConverter() {
|
||||
StringConverter<Boolean> converter = super.getConverter();
|
||||
if (converter == null) {
|
||||
converter = new BooleanStringConverter();
|
||||
setConverter(converter);
|
||||
}
|
||||
return converter;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Property<Boolean> createProperty(Boolean value) {
|
||||
// fixbug when value is null
|
||||
value = value == null ? false : value;
|
||||
return super.createProperty(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
void bindBidirectional(Property<Boolean> property) {
|
||||
getControl().selectedProperty().bindBidirectional(property);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.ecep.contract.util;
|
||||
|
||||
import com.ecep.contract.service.SysConfService;
|
||||
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
|
||||
public class BooleanConfigProperty extends SimpleBooleanProperty {
|
||||
|
||||
private final String key;
|
||||
private final SysConfService service;
|
||||
|
||||
public BooleanConfigProperty(String key, SysConfService service) {
|
||||
this(key, service, service.getBoolean(key));
|
||||
}
|
||||
|
||||
public BooleanConfigProperty(String key, SysConfService service, boolean initialValue) {
|
||||
super(initialValue);
|
||||
System.out.println("key = " + key + ", initialValue = " + initialValue);
|
||||
this.key = key;
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void invalidated() {
|
||||
super.invalidated();
|
||||
System.out.println("key = " + key + ", set = " + get());
|
||||
service.set(key, Boolean.toString(get()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.ecep.contract.util;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import com.ecep.contract.service.SysConfService;
|
||||
|
||||
public interface ConfigBounder {
|
||||
|
||||
void setConfService(SysConfService confService);
|
||||
|
||||
void initialize();
|
||||
|
||||
CompletableFuture<Void> runAsync(Runnable runnable);
|
||||
|
||||
SysConfService getConfService();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package com.ecep.contract.util;
|
||||
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* 延时执行器
|
||||
*/
|
||||
public class DelayOnceExecutor {
|
||||
private final Runnable runnable;
|
||||
private final Executor executor;
|
||||
private CompletableFuture<Void> future;
|
||||
/**
|
||||
* 定义一个Consumer类型的异常处理器
|
||||
*/
|
||||
@Setter
|
||||
private Consumer<Exception> exceptionHandler;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*/
|
||||
public DelayOnceExecutor(Runnable runnable, long delay, TimeUnit unit) {
|
||||
this.runnable = runnable;
|
||||
executor = CompletableFuture.delayedExecutor(delay, unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行 runnable,如果future不为空,则取消之前的任务
|
||||
*/
|
||||
public void tick() {
|
||||
if (future != null) {
|
||||
future.cancel(true);
|
||||
}
|
||||
future = CompletableFuture.runAsync(this::run, executor);
|
||||
}
|
||||
|
||||
public void tickNow() {
|
||||
if (future != null) {
|
||||
future.cancel(true);
|
||||
}
|
||||
future = CompletableFuture.runAsync(this::run);
|
||||
}
|
||||
|
||||
void run() {
|
||||
try {
|
||||
runnable.run();
|
||||
} catch (Exception e) {
|
||||
if (exceptionHandler != null) {
|
||||
exceptionHandler.accept(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消任务
|
||||
*/
|
||||
public void cancel() {
|
||||
if (future != null) {
|
||||
future.cancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听属性变化,当属性变化时,执行 runnable
|
||||
*
|
||||
* @param properties 要监听的属性
|
||||
*/
|
||||
public DelayOnceExecutor listen(ObservableValue<?>... properties) {
|
||||
for (ObservableValue<?> property : properties) {
|
||||
property.addListener((observable, oldValue, newValue) -> this.tick());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Consumer<Exception> exceptionHandler;
|
||||
*
|
||||
* @param exceptionHandler 异常处理
|
||||
*/
|
||||
public DelayOnceExecutor exception(Consumer<Exception> exceptionHandler) {
|
||||
this.exceptionHandler = exceptionHandler;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
172
client/src/main/java/com/ecep/contract/util/ExcelUtils.java
Normal file
172
client/src/main/java/com/ecep/contract/util/ExcelUtils.java
Normal file
@@ -0,0 +1,172 @@
|
||||
package com.ecep.contract.util;
|
||||
|
||||
import org.apache.poi.ss.usermodel.*;
|
||||
import org.apache.poi.ss.util.CellAddress;
|
||||
import org.apache.poi.ss.util.CellRangeAddress;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class ExcelUtils {
|
||||
public static Row getRow(Sheet sheet, int rowIndex, boolean autoCreate) {
|
||||
Row row = sheet.getRow(rowIndex);
|
||||
if (row == null) {
|
||||
if (autoCreate) {
|
||||
return sheet.createRow(rowIndex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return row;
|
||||
}
|
||||
|
||||
public static Cell getCell(Sheet sheet, String cellAddress) {
|
||||
return getCell(sheet, cellAddress, false);
|
||||
}
|
||||
|
||||
public static Cell getCell(Sheet sheet, String cellAddress, boolean autoCreate) {
|
||||
CellAddress address = new CellAddress(cellAddress);
|
||||
Row row = getRow(sheet, address.getRow(), autoCreate);
|
||||
if (row == null) {
|
||||
return null;
|
||||
}
|
||||
return getCell(row, address.getColumn(), autoCreate);
|
||||
}
|
||||
|
||||
public static Cell getCell(Sheet sheet, int rowIndex, int columnIndex, boolean autoCreate) {
|
||||
Row row = getRow(sheet, rowIndex, autoCreate);
|
||||
if (row == null) {
|
||||
return null;
|
||||
}
|
||||
return getCell(row, columnIndex, autoCreate);
|
||||
}
|
||||
|
||||
public static Cell getCell(Row row, int columnIndex, boolean autoCreate) {
|
||||
Cell cell = row.getCell(columnIndex);
|
||||
if (cell == null) {
|
||||
if (autoCreate) {
|
||||
return row.createCell(columnIndex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return cell;
|
||||
}
|
||||
|
||||
public static String getCellValue(Sheet sheet, String cellAddress) {
|
||||
Cell cell = getCell(sheet, cellAddress);
|
||||
if (cell == null) {
|
||||
return null;
|
||||
}
|
||||
return cell.getStringCellValue();
|
||||
}
|
||||
|
||||
|
||||
public static Cell setCellValue(Sheet sheet, String cellAddress, String text) {
|
||||
CellAddress address = new CellAddress(cellAddress);
|
||||
return setCellValue(sheet, address.getRow(), address.getColumn(), text);
|
||||
}
|
||||
|
||||
public static Cell setCellValue(Sheet sheet, String cellAddress, double value) {
|
||||
CellAddress address = new CellAddress(cellAddress);
|
||||
return setCellValue(sheet, address.getRow(), address.getColumn(), value);
|
||||
}
|
||||
|
||||
public static Cell setCellValue(Sheet sheet, String cellAddress, LocalDate date) {
|
||||
CellAddress address = new CellAddress(cellAddress);
|
||||
return setCellValue(sheet, address.getRow(), address.getColumn(), date);
|
||||
}
|
||||
|
||||
|
||||
public static Cell setCellValue(Sheet sheet, int rowIndex, int columnIndex, String text) {
|
||||
Row row = getRow(sheet, rowIndex, true);
|
||||
return setCellValue(row, columnIndex, text);
|
||||
}
|
||||
|
||||
public static Cell setCellValue(Row row, int columnIndex, String text) {
|
||||
assert row != null;
|
||||
Cell cell = getCell(row, columnIndex, true);
|
||||
assert cell != null;
|
||||
cell.setCellValue(text);
|
||||
return cell;
|
||||
}
|
||||
|
||||
public static Cell setCellValue(Sheet sheet, int rowIndex, int columnIndex, double value) {
|
||||
Row row = getRow(sheet, rowIndex, true);
|
||||
return setCellValue(row, columnIndex, value);
|
||||
}
|
||||
|
||||
public static Cell setCellValue(Row row, int columnIndex, double value) {
|
||||
assert row != null;
|
||||
Cell cell = getCell(row, columnIndex, true);
|
||||
assert cell != null;
|
||||
cell.setCellValue(value);
|
||||
return cell;
|
||||
}
|
||||
|
||||
public static Cell setCellValue(Sheet sheet, int rowIndex, int columnIndex, LocalDate date) {
|
||||
Row row = getRow(sheet, rowIndex, true);
|
||||
assert row != null;
|
||||
return setCellValue(row, columnIndex, date);
|
||||
}
|
||||
|
||||
public static Cell setCellValue(Row row, int columnIndex, LocalDate date) {
|
||||
assert row != null;
|
||||
Cell cell = getCell(row, columnIndex, true);
|
||||
assert cell != null;
|
||||
cell.setCellValue(date);
|
||||
return cell;
|
||||
}
|
||||
|
||||
public static Cell setCellValue(Sheet sheet, int rowIndex, int columnIndex, Double value) {
|
||||
Row row = getRow(sheet, rowIndex, true);
|
||||
assert row != null;
|
||||
Cell cell = getCell(row, columnIndex, true);
|
||||
assert cell != null;
|
||||
if (value == null) {
|
||||
cell.setBlank();
|
||||
} else {
|
||||
cell.setCellValue(value);
|
||||
}
|
||||
return cell;
|
||||
}
|
||||
|
||||
public static void setBorder(CellStyle cellStyle, BorderStyle borderStyle) {
|
||||
cellStyle.setBorderTop(borderStyle);
|
||||
cellStyle.setBorderBottom(borderStyle);
|
||||
cellStyle.setBorderLeft(borderStyle);
|
||||
cellStyle.setBorderRight(borderStyle);
|
||||
}
|
||||
|
||||
public static void mergedCells(Sheet sheet, String address) {
|
||||
CellRangeAddress cellAddresses = CellRangeAddress.valueOf(address);
|
||||
sheet.addMergedRegion(cellAddresses);
|
||||
}
|
||||
|
||||
public static void mergedCells(Cell cell, int rows, int cols) {
|
||||
Sheet sheet = cell.getSheet();
|
||||
CellRangeAddress region = new CellRangeAddress(cell.getRowIndex(), cell.getRowIndex() + rows, cell.getColumnIndex(), cell.getColumnIndex() + cols);
|
||||
sheet.addMergedRegion(region);
|
||||
}
|
||||
|
||||
public static void copyStyle(Sheet source, String sourceCellAddress, Sheet target, String targetCellAddress) {
|
||||
Cell sourceCell = getCell(source, sourceCellAddress);
|
||||
Cell targetCell = getCell(target, targetCellAddress);
|
||||
copyStyle(sourceCell, targetCell);
|
||||
}
|
||||
|
||||
public static void copyStyle(Sheet source, String sourceCellAddress, Cell targetCell) {
|
||||
Cell sourceCell = getCell(source, sourceCellAddress);
|
||||
copyStyle(sourceCell, targetCell);
|
||||
}
|
||||
|
||||
public static void copyStyle(Cell source, Cell target) {
|
||||
if (source == null) {
|
||||
return;
|
||||
}
|
||||
if (target == null) {
|
||||
return;
|
||||
}
|
||||
target.setCellStyle(source.getCellStyle());
|
||||
}
|
||||
}
|
||||
10
client/src/main/java/com/ecep/contract/util/FxmlPath.java
Normal file
10
client/src/main/java/com/ecep/contract/util/FxmlPath.java
Normal file
@@ -0,0 +1,10 @@
|
||||
package com.ecep.contract.util;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Target({ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface FxmlPath {
|
||||
String value() default "";
|
||||
}
|
||||
88
client/src/main/java/com/ecep/contract/util/FxmlUtils.java
Normal file
88
client/src/main/java/com/ecep/contract/util/FxmlUtils.java
Normal file
@@ -0,0 +1,88 @@
|
||||
package com.ecep.contract.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import com.ecep.contract.AppV2;
|
||||
import com.ecep.contract.Desktop;
|
||||
import com.ecep.contract.SpringApp;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
|
||||
public class FxmlUtils {
|
||||
|
||||
public static FXMLLoader newLoader(String path) {
|
||||
FXMLLoader loader = new FXMLLoader();
|
||||
URL location = AppV2.class.getResource(path);
|
||||
if (location == null) {
|
||||
throw new RuntimeException("无法找到窗口资源文件 " + path);
|
||||
}
|
||||
loader.setLocation(location);
|
||||
loader.setControllerFactory(SpringApp::getBean);
|
||||
return loader;
|
||||
}
|
||||
|
||||
public static CompletableFuture<FXMLLoader> newLoaderAsync(String path,
|
||||
java.util.function.Consumer<FXMLLoader> consumer) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
FXMLLoader loader = newLoader(path);
|
||||
try {
|
||||
loader.load();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Unable open " + path, e);
|
||||
}
|
||||
consumer.accept(loader);
|
||||
return loader;
|
||||
}).whenComplete((v, ex) -> {
|
||||
if (ex != null) {
|
||||
UITools.showExceptionAndWait("无法打开 " + path + " 界面", ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步载入显示界面
|
||||
*
|
||||
* @param path fxml文件路径,类地址 / 开头 根路径
|
||||
* @param initializeLoader fxml 文件加载完毕后,回调函数
|
||||
* @param runLater 在JavaFx线程中执行的回调函数
|
||||
* @return CompletableFuture
|
||||
*/
|
||||
public static CompletableFuture<Void> newLoaderAsyncWithRunLater(
|
||||
String path,
|
||||
java.util.function.Consumer<FXMLLoader> initializeLoader,
|
||||
java.util.function.Consumer<FXMLLoader> runLater) {
|
||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
FXMLLoader loader = newLoader(path);
|
||||
if (initializeLoader != null) {
|
||||
initializeLoader.accept(loader);
|
||||
}
|
||||
loader.load();
|
||||
Platform.runLater(() -> {
|
||||
try {
|
||||
runLater.accept(loader);
|
||||
future.complete(null);
|
||||
} catch (Exception e) {
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
});
|
||||
} catch (IOException e) {
|
||||
future.completeExceptionally(new RuntimeException("Unable open " + path, e));
|
||||
}
|
||||
}, Desktop.instance.getExecutorService()).whenComplete((v, ex) -> {
|
||||
if (ex != null) {
|
||||
future.completeExceptionally(ex);
|
||||
}
|
||||
});
|
||||
future.whenComplete((v, ex) -> {
|
||||
if (ex != null) {
|
||||
UITools.showExceptionAndWait("无法打开 " + path + " 界面", ex);
|
||||
}
|
||||
});
|
||||
return future;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.ecep.contract.util;
|
||||
|
||||
import com.ecep.contract.service.SysConfService;
|
||||
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
|
||||
public class IntegerConfigProperty extends SimpleIntegerProperty
|
||||
// implements Property<Integer>
|
||||
{
|
||||
private final String key;
|
||||
private final SysConfService service;
|
||||
|
||||
public IntegerConfigProperty(String key, SysConfService service) {
|
||||
super(service.getInt(key));
|
||||
this.key = key;
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void invalidated() {
|
||||
super.invalidated();
|
||||
service.set(key, Integer.toString(get()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.ecep.contract.util;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.scene.control.DatePicker;
|
||||
import javafx.util.StringConverter;
|
||||
import javafx.util.converter.LocalDateStringConverter;
|
||||
|
||||
public class LocalDateConfig extends AbstractConfigBounder<LocalDate> {
|
||||
|
||||
public LocalDateConfig(String string) {
|
||||
super(string);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DatePicker getControl() {
|
||||
return (DatePicker) super.getControl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public StringConverter<LocalDate> getConverter() {
|
||||
StringConverter<LocalDate> converter = super.getConverter();
|
||||
if (converter == null) {
|
||||
converter = new LocalDateStringConverter(DateTimeFormatter.ISO_LOCAL_DATE, null);
|
||||
setConverter(converter);
|
||||
}
|
||||
return converter;
|
||||
}
|
||||
|
||||
@Override
|
||||
void bindBidirectional(Property<LocalDate> property) {
|
||||
getControl().valueProperty().bindBidirectional(property);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.ecep.contract.util;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.util.StringConverter;
|
||||
import javafx.util.converter.LocalDateTimeStringConverter;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
public class LocalDateTimeConfig extends AbstractConfigBounder<LocalDateTime> {
|
||||
@Getter
|
||||
@Setter
|
||||
private javafx.util.StringConverter<LocalDateTime> controlConverter;
|
||||
|
||||
public LocalDateTimeConfig(String string) {
|
||||
super(string);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextField getControl() {
|
||||
return (TextField) super.getControl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public javafx.util.StringConverter<LocalDateTime> getConverter() {
|
||||
javafx.util.StringConverter<LocalDateTime> converter = super.getConverter();
|
||||
if (converter == null) {
|
||||
converter = new LocalDateTimeStringConverter(DateTimeFormatter.ISO_LOCAL_DATE_TIME, null);
|
||||
setConverter(converter);
|
||||
}
|
||||
return converter;
|
||||
}
|
||||
|
||||
@Override
|
||||
void bindBidirectional(Property<LocalDateTime> property) {
|
||||
StringConverter<LocalDateTime> converter = getControlConverter();
|
||||
if (converter == null) {
|
||||
converter = getConverter();
|
||||
}
|
||||
getControl().textProperty().bindBidirectional(property, converter);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.ecep.contract.util;
|
||||
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
|
||||
public class MyDateTimePropertyUtils {
|
||||
/**
|
||||
* 本地日期时间转换为时间戳
|
||||
*
|
||||
* @param property
|
||||
* @return
|
||||
*/
|
||||
public static Instant localDateTimeToInstant(SimpleObjectProperty<LocalDateTime> property) {
|
||||
LocalDateTime dateTime = property.get();
|
||||
if (dateTime != null) {
|
||||
return dateTime.toInstant(ZoneOffset.ofHours(8));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.ecep.contract.util;
|
||||
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.scene.control.Control;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.util.StringConverter;
|
||||
|
||||
public class StringConfig extends AbstractConfigBounder<String> {
|
||||
|
||||
public StringConfig(String string) {
|
||||
super(string);
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public TextField getControl() {
|
||||
// return (TextField) super.getControl();
|
||||
// }
|
||||
|
||||
@Override
|
||||
public StringConverter<String> getConverter() {
|
||||
StringConverter<String> converter = super.getConverter();
|
||||
if (converter == null) {
|
||||
converter = new StringConverter<String>() {
|
||||
@Override
|
||||
public String toString(String object) {
|
||||
return object;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String fromString(String string) {
|
||||
return string;
|
||||
}
|
||||
};
|
||||
setConverter(converter);
|
||||
}
|
||||
return converter;
|
||||
}
|
||||
|
||||
@Override
|
||||
void bindBidirectional(Property<String> property) {
|
||||
Control ctrl = getControl();
|
||||
if (ctrl == null) {
|
||||
return;
|
||||
}
|
||||
if (ctrl instanceof TextField textField) {
|
||||
textField.textProperty().bindBidirectional(property);
|
||||
} else if (ctrl instanceof Label label) {
|
||||
label.textProperty().bindBidirectional(property);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package com.ecep.contract.util;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.data.domain.Sort;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TableRow;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.input.PickResult;
|
||||
|
||||
public class TableViewUtils {
|
||||
|
||||
/**
|
||||
* 获取 Table 的排序
|
||||
*/
|
||||
public static <T> List<Sort.Order> getOrders(TableView<T> table) {
|
||||
return table.getSortOrder().stream().filter(c -> {
|
||||
if (!c.isSortable()) {
|
||||
return false;
|
||||
}
|
||||
String id = c.getId();
|
||||
return id.endsWith("Column");
|
||||
}).map(c -> {
|
||||
String id = c.getId();
|
||||
id = id.replace(table.getId() + "_", "");
|
||||
id = id.replace("Column", "");
|
||||
Sort.Direction dir = c.getSortType() == TableColumn.SortType.ASCENDING ? Sort.Direction.ASC : Sort.Direction.DESC;
|
||||
return new Sort.Order(dir, id);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
public static <T> void bindDoubleClicked(TableView<T> table, Consumer<T> consumer) {
|
||||
table.setOnMouseClicked(event -> {
|
||||
if (event.getClickCount() == 2 && event.getButton() == MouseButton.PRIMARY) {
|
||||
PickResult pickResult = event.getPickResult();
|
||||
Node intersectedNode = pickResult.getIntersectedNode();
|
||||
if (intersectedNode instanceof TableRow) {
|
||||
// 鼠标点击的地方没有定义列,只返回了行,跳过不做处理
|
||||
TableRow<T> row = (TableRow<T>) intersectedNode;
|
||||
row.getItem();
|
||||
return;
|
||||
}
|
||||
|
||||
T selectedItem = table.getSelectionModel().getSelectedItem();
|
||||
if (selectedItem != null) {
|
||||
try {
|
||||
consumer.accept(selectedItem);
|
||||
} catch (UnsupportedOperationException e) {
|
||||
UITools.showExceptionAndWait("当前操作不支持", e);
|
||||
} catch (Exception e) {
|
||||
UITools.showExceptionAndWait(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static <T> void bindEnterPressed(TableView<T> table, Consumer<T> consumer) {
|
||||
table.setOnKeyPressed(event -> {
|
||||
if (event.getCode() == KeyCode.ENTER) {
|
||||
T selectedItem = table.getSelectionModel().getSelectedItem();
|
||||
if (selectedItem != null) {
|
||||
consumer.accept(selectedItem);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算表格的可视行数
|
||||
*
|
||||
* @param table 表格视图
|
||||
* @param <T> T
|
||||
* @return 可视行数
|
||||
*/
|
||||
public static <T> int getTableViewVisibleRows(TableView<T> table) {
|
||||
int rows = 0;
|
||||
Set<Node> tableRow = table.lookupAll("TableRow");
|
||||
for (Node node : tableRow) {
|
||||
if (node instanceof TableRow) {
|
||||
if (!node.isVisible()) {
|
||||
continue;
|
||||
}
|
||||
rows++;
|
||||
}
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
}
|
||||
322
client/src/main/java/com/ecep/contract/util/UITools.java
Normal file
322
client/src/main/java/com/ecep/contract/util/UITools.java
Normal file
@@ -0,0 +1,322 @@
|
||||
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.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
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 = null;
|
||||
if (e instanceof org.springframework.orm.ObjectOptimisticLockingFailureException) {
|
||||
message = "锁冲突, 数据可能已经被修改, 请重新加载, 再次重试.";
|
||||
} else {
|
||||
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<ButtonType> showAndWait(Alert.AlertType alertType, String title, String header,
|
||||
String message, Node content) {
|
||||
CompletableFuture<ButtonType> 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<ButtonType> showConfirmation(String title, String message) {
|
||||
return showAndWait(Alert.AlertType.CONFIRMATION, "请选择", title, message, null);
|
||||
}
|
||||
|
||||
public static <T> Dialog<T> createDialog() {
|
||||
Dialog<T> dialog = new Dialog<>();
|
||||
VBox box = new VBox();
|
||||
Label headerLabel = new Label();
|
||||
headerLabel.setId("header");
|
||||
|
||||
VBox.setMargin(headerLabel, new Insets(5, 0, 8, 0));
|
||||
|
||||
ListView<T> listView = new ListView<>();
|
||||
listView.setMinHeight(50);
|
||||
listView.setId("list-view");
|
||||
ObservableList<T> 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示一个对话框, 并等待用户关闭对话框
|
||||
* <p>
|
||||
* 对话框内嵌一个 ListView,可在 consumer 中向 ListView 添加内容
|
||||
*
|
||||
* @param title 对话框标题
|
||||
* @param header 对话框头
|
||||
* @param consumer 对话框内容生成器
|
||||
*/
|
||||
public static void showDialogAndWait(String title, String header, Consumer<ObservableList<String>> consumer) {
|
||||
Dialog<String> dialog = createDialog();
|
||||
dialog.setTitle(title);
|
||||
|
||||
DialogPane dialogPane = dialog.getDialogPane();
|
||||
Node content = dialogPane.getContent();
|
||||
|
||||
Label headerLabel = (Label) content.lookup("#header");
|
||||
ListView<String> listView = (ListView<String>) 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<Consumer<Message>> init) {
|
||||
Dialog<Message> 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<Message> listView = (ListView<Message>) box.lookup("#list-view");
|
||||
listView.setCellFactory(param -> new Tasker.MessageListCell());
|
||||
|
||||
headerLabel.textProperty().bind(task.titleProperty());
|
||||
|
||||
java.util.function.Predicate<Message> 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());
|
||||
|
||||
if (task instanceof Tasker<?> tasker) {
|
||||
// 提交任务
|
||||
Desktop.instance.getExecutorService().submit(tasker);
|
||||
}
|
||||
if (init != null) {
|
||||
init.accept(msg -> consumer.test(msg));
|
||||
}
|
||||
dialog.showAndWait();
|
||||
if (task.isRunning()) {
|
||||
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 <T> AutoCompletionBinding<T> autoCompletion(
|
||||
TextField textField,
|
||||
SimpleObjectProperty<T> property,
|
||||
EntityStringConverter<T> converter) {
|
||||
return autoCompletion(textField, property, converter::suggest, converter);
|
||||
}
|
||||
|
||||
/**
|
||||
* 给 TextField 增加一个 自动补全功能
|
||||
*
|
||||
* @param textField 要补全的文本框
|
||||
* @param property 绑定的属性
|
||||
* @param func 获取补全数据的方法
|
||||
* @param converter 对象转换器
|
||||
* @param <T> 对象类型
|
||||
* @return AutoCompletionBinding
|
||||
*/
|
||||
public static <T> AutoCompletionBinding<T> autoCompletion(
|
||||
TextField textField,
|
||||
SimpleObjectProperty<T> property,
|
||||
Function<AutoCompletionBinding.ISuggestionRequest, List<T>> func,
|
||||
StringConverter<T> converter) {
|
||||
// 先赋值
|
||||
textField.textProperty().set(converter.toString(property.get()));
|
||||
// 监听 property 变化
|
||||
property.addListener((observable, oldValue, newValue) -> {
|
||||
textField.textProperty().set(converter.toString(newValue));
|
||||
});
|
||||
|
||||
// 关联一个 自动完成
|
||||
AutoCompletionBinding<T> 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user