完善公共组件无障碍交互
This commit is contained in:
@@ -25,6 +25,10 @@ public final class EmptyStatePane extends VBox {
|
||||
Objects.requireNonNull(content, "content");
|
||||
setAlignment(Pos.CENTER);
|
||||
getStyleClass().add("empty-state");
|
||||
setAccessibleText(content.description().isEmpty()
|
||||
? content.title()
|
||||
: content.title() + "。" + content.description());
|
||||
setFocusTraversable(false);
|
||||
|
||||
Label title = new Label(content.title());
|
||||
title.setFont(Theme.heading(fontSize));
|
||||
@@ -43,6 +47,8 @@ public final class EmptyStatePane extends VBox {
|
||||
}
|
||||
Button button = StyledButton.secondary(
|
||||
content.actionLabel(), Theme.base(fontSize - 1));
|
||||
button.setAccessibleText(content.actionLabel());
|
||||
button.setFocusTraversable(true);
|
||||
button.setOnAction(event -> action.run());
|
||||
getChildren().add(button);
|
||||
}
|
||||
|
||||
@@ -27,15 +27,21 @@ public final class OperationStatusPane extends HBox {
|
||||
getStyleClass().add("operation-status");
|
||||
|
||||
progressBar.setPrefWidth(240);
|
||||
progressBar.setAccessibleText("任务进度");
|
||||
messageLabel.setFont(Theme.base(fontSize));
|
||||
messageLabel.setTextFill(Theme.TEXT_SECONDARY);
|
||||
messageLabel.setAccessibleText("任务状态");
|
||||
HBox.setHgrow(messageLabel, Priority.ALWAYS);
|
||||
|
||||
cancelButton = com.example.workutils.system.ui.StyledButton.danger(
|
||||
"停止", Theme.base(fontSize));
|
||||
cancelButton.setAccessibleText("停止当前任务");
|
||||
cancelButton.setOnAction(event -> controller.cancel());
|
||||
|
||||
getChildren().addAll(progressBar, messageLabel, cancelButton);
|
||||
getChildren().addAll(progressBar, messageLabel);
|
||||
if (cancellable) {
|
||||
getChildren().add(cancelButton);
|
||||
}
|
||||
controller.snapshotProperty().addListener(
|
||||
(observable, oldValue, value) -> render(value));
|
||||
render(controller.snapshot());
|
||||
@@ -45,6 +51,10 @@ public final class OperationStatusPane extends HBox {
|
||||
progressBar.setProgress(snapshot.progress());
|
||||
messageLabel.setText(snapshot.message().isBlank() ? defaultMessage(snapshot.state())
|
||||
: snapshot.message());
|
||||
String stateDescription = "任务状态:" + messageLabel.getText();
|
||||
setAccessibleText(stateDescription);
|
||||
messageLabel.setAccessibleText(stateDescription);
|
||||
progressBar.setAccessibleText("任务进度:" + stateDescription);
|
||||
cancelButton.setVisible(cancellable
|
||||
&& snapshot.state() == OperationState.RUNNING);
|
||||
cancelButton.setManaged(cancelButton.isVisible());
|
||||
|
||||
@@ -13,6 +13,7 @@ import javafx.scene.control.TableCell;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
@@ -45,6 +46,17 @@ public final class ExportHistoryPane extends VBox {
|
||||
Objects.requireNonNull(commands, "commands");
|
||||
|
||||
table.setItems(items);
|
||||
table.setAccessibleText("导出历史记录");
|
||||
table.setFocusTraversable(true);
|
||||
table.setOnKeyPressed(event -> {
|
||||
if (event.getCode() == KeyCode.ENTER) {
|
||||
HistoryItem selected = table.getSelectionModel().getSelectedItem();
|
||||
if (selected != null) {
|
||||
commands.openFile(selected.filePath());
|
||||
event.consume();
|
||||
}
|
||||
}
|
||||
});
|
||||
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
|
||||
table.setPlaceholder(new EmptyStatePane(
|
||||
new EmptyStatePane.Content(
|
||||
@@ -85,6 +97,7 @@ public final class ExportHistoryPane extends VBox {
|
||||
setText(empty ? null : value);
|
||||
HistoryItem row = empty ? null : getTableRow().getItem();
|
||||
setTooltip(row == null ? null : new Tooltip(row.filePath()));
|
||||
setAccessibleText(row == null ? null : "生成文件:" + row.filePath());
|
||||
}
|
||||
});
|
||||
return column;
|
||||
@@ -106,6 +119,8 @@ public final class ExportHistoryPane extends VBox {
|
||||
|
||||
{
|
||||
actions.setAlignment(Pos.CENTER);
|
||||
open.setAccessibleText("打开生成文件");
|
||||
folder.setAccessibleText("打开生成文件所在目录");
|
||||
folder.setTooltip(new Tooltip("打开文件所在目录"));
|
||||
MenuItem edit = new MenuItem("用系统编辑器打开");
|
||||
MenuItem copy = new MenuItem("复制文件路径");
|
||||
@@ -123,6 +138,7 @@ public final class ExportHistoryPane extends VBox {
|
||||
super.updateItem(value, empty);
|
||||
row = empty ? null : getTableRow().getItem();
|
||||
setGraphic(row == null ? null : actions);
|
||||
setAccessibleText(row == null ? null : "历史记录操作:" + row.filePath());
|
||||
}
|
||||
});
|
||||
return column;
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
package com.example.workutils.system.ui;
|
||||
|
||||
import com.example.workutils.system.ui.async.AsyncOperationController;
|
||||
import com.example.workutils.system.ui.async.OperationStatusPane;
|
||||
import com.example.workutils.system.ui.history.ExportHistoryPane;
|
||||
import com.example.workutils.system.ui.history.HistoryFileCommands;
|
||||
import com.example.workutils.system.ui.history.HistoryItem;
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.FutureTask;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
|
||||
class SharedPaneAccessibilityTest {
|
||||
|
||||
@BeforeAll
|
||||
static void startJavaFx() throws Exception {
|
||||
CountDownLatch started = new CountDownLatch(1);
|
||||
Platform.startup(started::countDown);
|
||||
started.await();
|
||||
}
|
||||
|
||||
@Test
|
||||
void emptyStateExposesCombinedDescriptionWithoutTakingFocus() throws Exception {
|
||||
runFx(() -> {
|
||||
EmptyStatePane pane = new EmptyStatePane(
|
||||
new EmptyStatePane.Content("暂无结果", "请先执行任务", ""),
|
||||
null,
|
||||
14);
|
||||
|
||||
assertEquals("暂无结果。请先执行任务", pane.getAccessibleText());
|
||||
assertFalse(pane.isFocusTraversable());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void nonCancellableStatusHasNoHiddenButton() throws Exception {
|
||||
runFx(() -> {
|
||||
AsyncOperationController<Void> controller =
|
||||
new AsyncOperationController<>(Runnable::run, Runnable::run);
|
||||
OperationStatusPane pane = new OperationStatusPane(controller, 14, false);
|
||||
|
||||
long buttons = pane.getChildren().stream()
|
||||
.filter(Button.class::isInstance)
|
||||
.count();
|
||||
|
||||
assertEquals(0, buttons);
|
||||
assertEquals("任务状态:就绪", pane.getAccessibleText());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void enterOpensSelectedHistoryResult() throws Exception {
|
||||
runFx(() -> {
|
||||
List<String> opened = new ArrayList<>();
|
||||
HistoryFileCommands commands = new HistoryFileCommands(
|
||||
opened::add, path -> { }, path -> { }, path -> { });
|
||||
HistoryItem item = new HistoryItem(
|
||||
"1", "全部", "D:/export/result.csv", "2", "2026-07-31 19:00:00");
|
||||
ExportHistoryPane pane = new ExportHistoryPane(
|
||||
"数量", FXCollections.observableArrayList(item), commands, 14);
|
||||
pane.table().getSelectionModel().select(item);
|
||||
|
||||
pane.table().fireEvent(new KeyEvent(
|
||||
KeyEvent.KEY_PRESSED, "", "", KeyCode.ENTER,
|
||||
false, false, false, false));
|
||||
|
||||
assertEquals(List.of("D:/export/result.csv"), opened);
|
||||
assertEquals("导出历史记录", pane.table().getAccessibleText());
|
||||
});
|
||||
}
|
||||
|
||||
private static void runFx(ThrowingRunnable action) throws Exception {
|
||||
FutureTask<Void> task = new FutureTask<>(() -> {
|
||||
action.run();
|
||||
return null;
|
||||
});
|
||||
Platform.runLater(task);
|
||||
task.get();
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface ThrowingRunnable {
|
||||
void run() throws Exception;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user