为什么我的 JTable 在 OS X 上使用 VoiceOver 时总是报告为空?
Why is my JTable always reported as empty using VoiceOver on OS X?
VoiceOver OSX 10.10.4 (Yosemite),使用 JRE 1.7.0_75 和 JRE 1.8.0_45,报告以下 table 作为 "empty".
package Whosebug.examples.jtable;
import javax.swing.JFrame;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
public class TableDemo extends JFrame
{
private static final long serialVersionUID = 1L;
public TableDemo()
{
super("Accessible JTable?");
final String[] columnNames =
{
"First Name",
"Last Name",
"Sport",
"# of Years",
"Vegetarian"
};
final Object[][] data =
{
{"Kathy", "Smith", "Snowboarding", new Integer(5), new Boolean(false)},
{"John", "Doe", "Rowing", new Integer(3), new Boolean(true)},
};
final JTable jTable = new JTable(data, columnNames);
jTable.getAccessibleContext().setAccessibleName("data table");
System.out.println("rows: " + jTable.getAccessibleContext().getAccessibleTable().getAccessibleRowCount());
System.out.println("cols: " + jTable.getAccessibleContext().getAccessibleTable().getAccessibleColumnCount());
System.out.println("java: " + System.getProperty("java.version"));
jTable.setOpaque(true);
setContentPane(jTable);
}
private static void createAndShowGUI()
{
final TableDemo frame = new TableDemo();
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
@Override
public void run()
{
createAndShowGUI();
}
});
}
}
除了 VoiceOver 说 table 是空的,其他一切似乎都正常:
- VoiceOver 使用 setAccessibleName()
拾取我给 table 取的名字
- JTable correctly returns an instance of AccessibleTable。
- 调用 getAccessibleRowCount() and getAccessibleColumnCount() 打印预期值。
我错过了什么?
更多信息:
- 我试过使用 JRE 1.6。0_65 这是 Apple 提供的 JRE,但我遇到了同样的问题。我试过这个,以防在移动到 Oracle 提供的 JRE 时它发生了变化。
实际上 Java 辅助功能随 Java 7 和 Java 8 软件包一起提供,对于更早的 Java 版本,您需要手动安装它。
而如果您查看 java 文档,您会发现他们推荐的软件很少,例如 JAWS、NonVisual Desktop Access (NVDA)、SuperNova、Window-Eyes 等 OS 默认解说软件。但这仅限于 Windows。
http://docs.oracle.com/javase/7/docs/technotes/guides/access/enable_and_test.html
Since you are running onto OSX 10.10.4 (Yosemite) OS, you are not required to alter anything. But you may still try one of the softwares listed.
VoiceOver 说 "empty" 的原因是因为 accessibility hierarchy is not being exposed correctly. You can use the Accessibility Inspector tool (one of the Xcode developer tools) to examine the accessibility hierarchy. In this case, with the Accessibility Inspector tool running, hovering the mouse pointer over the JTable
shows that there is an AXTable element with 10 AXStaticText children (one for each of the cells). Tables should be exposed as AXTable > AXRow > AXCell > … . Also, according to the Roles reference,一个 AXTable 元素在其他必需属性中应该有一个 Rows 属性,但是 JRE 没有向可访问性层次结构公开这些属性.
我已经在 Windows 8.1 Pro 上使用 Java 1.8.0_51 试用了您的程序,我看到了类似的问题。与辅助功能检查器工具类似,Windows SDK 附带一个 Inspect tool,可用于检查辅助功能数据。当 运行 您的测试用例时, JTable
似乎根本没有暴露。启用 Windows 讲述人,我无法导航到 table 或其单元格。
因此,JRE 似乎不完全支持 table 可访问性。
在 source of javax.accessibility.AccessibleRole
中,您可以看到定义 ROW
常量的代码与记录为 "under consideration for potential future use".
的其他常量一起被注释掉了
在source of JTable
中,可以看到定义了AccessibleJTable
、AccessibleTableHeader
、AccessibleJTableCell
、AccessibleJTableHeaderCell
helper classes ,但是 table.
的行没有 Accessible
实现
理论上,您可以编写一个自定义 AccessibleContext
实现,该实现将公开 JTable
到 OS 的更完整的可访问性层次结构。但是,我不确定是否可以完全解决 Java 明显缺乏对 table 可访问性的支持。
这是否可能取决于平台。例如,检查 source code of src/macosx/native/sun/awt/JavaAccessibilityUtilities.m
, you can see how Java's accessibility roles are mapped to the NSAccessibility*Role constants. You can see that the ROW_HEADER
accessible role 是否映射到 AXRow。因此,您可以使用 ROW_HEADER
可访问角色公开 AXTable 的 AXRow 子级。这是一些成功执行此操作的代码:
public static class MyJTable extends JTable {
public MyJTable(TableModel tm) {
super(tm);
}
@Override
public MyAccessibleJTable getAccessibleContext() {
if (accessibleContext == null) {
accessibleContext = new MyAccessibleJTable();
}
return (MyAccessibleJTable)accessibleContext;
}
protected class MyAccessibleJTable extends AccessibleJTable {
@Override
public int getAccessibleChildrenCount() {
if (MyJTable.this.getColumnCount() <= 0) {
return 0;
}
return MyJTable.this.getRowCount();
}
@Override
public Accessible getAccessibleChild(int i) {
if (i < 0 || getAccessibleChildrenCount() <= i) {
return null;
}
TableColumn firstColumn = getColumnModel().getColumn(0);
TableCellRenderer renderer = firstColumn.getCellRenderer();
if (renderer == null) {
Class<?> columnClass = getColumnClass(0);
renderer = getDefaultRenderer(columnClass);
}
Component component = renderer.getTableCellRendererComponent(MyJTable.this, null, false, false, i, 0);
return new MyAccessibleRow(MyJTable.this, i, component);
}
}
protected static class MyAccessibleRow extends AccessibleContext implements Accessible {
private MyJTable table;
private int row;
private Component rendererComponent;
protected MyAccessibleRow(MyJTable table, int row, Component renderComponent) {
this.table = table;
this.row = row;
this.rendererComponent = rendererComponent;
}
@Override
public AccessibleRole getAccessibleRole() {
// ROW_HEADER is used because it maps to NSAccessibilityRowRole
// on Mac.
return AccessibleRole.ROW_HEADER;
}
@Override
public Locale getLocale() {
AccessibleContext ac = rendererComponent.getAccessibleContext();
if (ac != null) {
return ac.getLocale();
} else {
return null;
}
}
@Override
public int getAccessibleChildrenCount() {
return 0; // TODO return the number of columns in this row
}
@Override
public Accessible getAccessibleChild(int i) {
return null; // TODO return a MyAccessibleJTableCell
}
@Override
public int getAccessibleIndexInParent() {
return row;
}
@Override
public AccessibleStateSet getAccessibleStateSet() {
return null; // TODO
}
@Override
public AccessibleContext getAccessibleContext() {
return this; // TODO
}
@Override
public AccessibleComponent getAccessibleComponent() {
return table.getAccessibleContext(); // TODO
}
}
}
从这张截图可以看出:
.. AXTable 上现在有两个 AXRow 子项。但是,VoiceOver 仍将 table 宣布为 "empty"。我不确定这是因为行没有 AXCell 子行,还是因为 AXTable 缺少其必需的属性,还是其他原因。
如果您要走自定义 AccessibleContext
路线,最好完全避免尝试公开 table 层次结构。相反,您可以将 table 建模为列表,其中每一行对应一个列表项,并且每个列表项包含每个单元格的一个组。这类似于 Firefox(已测试版本 39.0)使用的方法。目前在 Mac 上,Firefox 在公开 HTML table 时不使用 table 角色。不过,这应该在 Firefox 41 中得到修复。参见 Bug 744790 - [Mac] HTML table semantics are not communicated to VoiceOver at all。
我也在使用 Mac OS 10.10.4 和 Java 1.8.0_51.
编辑: "empty" table 问题已被报告为 OpenJDK Bug JDK-7124284 [macosx] Nothing heard from VoiceOver when navigating in a table。根据评论,Mac Swing 可访问性存在几个已知问题,目前已推迟到 JDK 9.
另一种可能的解决方法是使用 JavaFX TableView
class。尝试在 Java 8u40.
中实现的 http://docs.oracle.com/javafx/2/ui_controls/table-view.htm I am seeing that VoiceOver is properly announcing the table. JavaFX accessibility was implemented as part of JEP 204 中的 TableView
示例
VoiceOver OSX 10.10.4 (Yosemite),使用 JRE 1.7.0_75 和 JRE 1.8.0_45,报告以下 table 作为 "empty".
package Whosebug.examples.jtable;
import javax.swing.JFrame;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
public class TableDemo extends JFrame
{
private static final long serialVersionUID = 1L;
public TableDemo()
{
super("Accessible JTable?");
final String[] columnNames =
{
"First Name",
"Last Name",
"Sport",
"# of Years",
"Vegetarian"
};
final Object[][] data =
{
{"Kathy", "Smith", "Snowboarding", new Integer(5), new Boolean(false)},
{"John", "Doe", "Rowing", new Integer(3), new Boolean(true)},
};
final JTable jTable = new JTable(data, columnNames);
jTable.getAccessibleContext().setAccessibleName("data table");
System.out.println("rows: " + jTable.getAccessibleContext().getAccessibleTable().getAccessibleRowCount());
System.out.println("cols: " + jTable.getAccessibleContext().getAccessibleTable().getAccessibleColumnCount());
System.out.println("java: " + System.getProperty("java.version"));
jTable.setOpaque(true);
setContentPane(jTable);
}
private static void createAndShowGUI()
{
final TableDemo frame = new TableDemo();
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
@Override
public void run()
{
createAndShowGUI();
}
});
}
}
除了 VoiceOver 说 table 是空的,其他一切似乎都正常:
- VoiceOver 使用 setAccessibleName() 拾取我给 table 取的名字
- JTable correctly returns an instance of AccessibleTable。
- 调用 getAccessibleRowCount() and getAccessibleColumnCount() 打印预期值。
我错过了什么?
更多信息:
- 我试过使用 JRE 1.6。0_65 这是 Apple 提供的 JRE,但我遇到了同样的问题。我试过这个,以防在移动到 Oracle 提供的 JRE 时它发生了变化。
实际上 Java 辅助功能随 Java 7 和 Java 8 软件包一起提供,对于更早的 Java 版本,您需要手动安装它。
而如果您查看 java 文档,您会发现他们推荐的软件很少,例如 JAWS、NonVisual Desktop Access (NVDA)、SuperNova、Window-Eyes 等 OS 默认解说软件。但这仅限于 Windows。 http://docs.oracle.com/javase/7/docs/technotes/guides/access/enable_and_test.html
Since you are running onto OSX 10.10.4 (Yosemite) OS, you are not required to alter anything. But you may still try one of the softwares listed.
VoiceOver 说 "empty" 的原因是因为 accessibility hierarchy is not being exposed correctly. You can use the Accessibility Inspector tool (one of the Xcode developer tools) to examine the accessibility hierarchy. In this case, with the Accessibility Inspector tool running, hovering the mouse pointer over the JTable
shows that there is an AXTable element with 10 AXStaticText children (one for each of the cells). Tables should be exposed as AXTable > AXRow > AXCell > … . Also, according to the Roles reference,一个 AXTable 元素在其他必需属性中应该有一个 Rows 属性,但是 JRE 没有向可访问性层次结构公开这些属性.
我已经在 Windows 8.1 Pro 上使用 Java 1.8.0_51 试用了您的程序,我看到了类似的问题。与辅助功能检查器工具类似,Windows SDK 附带一个 Inspect tool,可用于检查辅助功能数据。当 运行 您的测试用例时, JTable
似乎根本没有暴露。启用 Windows 讲述人,我无法导航到 table 或其单元格。
因此,JRE 似乎不完全支持 table 可访问性。
在 source of javax.accessibility.AccessibleRole
中,您可以看到定义 ROW
常量的代码与记录为 "under consideration for potential future use".
在source of JTable
中,可以看到定义了AccessibleJTable
、AccessibleTableHeader
、AccessibleJTableCell
、AccessibleJTableHeaderCell
helper classes ,但是 table.
Accessible
实现
理论上,您可以编写一个自定义 AccessibleContext
实现,该实现将公开 JTable
到 OS 的更完整的可访问性层次结构。但是,我不确定是否可以完全解决 Java 明显缺乏对 table 可访问性的支持。
这是否可能取决于平台。例如,检查 source code of src/macosx/native/sun/awt/JavaAccessibilityUtilities.m
, you can see how Java's accessibility roles are mapped to the NSAccessibility*Role constants. You can see that the ROW_HEADER
accessible role 是否映射到 AXRow。因此,您可以使用 ROW_HEADER
可访问角色公开 AXTable 的 AXRow 子级。这是一些成功执行此操作的代码:
public static class MyJTable extends JTable {
public MyJTable(TableModel tm) {
super(tm);
}
@Override
public MyAccessibleJTable getAccessibleContext() {
if (accessibleContext == null) {
accessibleContext = new MyAccessibleJTable();
}
return (MyAccessibleJTable)accessibleContext;
}
protected class MyAccessibleJTable extends AccessibleJTable {
@Override
public int getAccessibleChildrenCount() {
if (MyJTable.this.getColumnCount() <= 0) {
return 0;
}
return MyJTable.this.getRowCount();
}
@Override
public Accessible getAccessibleChild(int i) {
if (i < 0 || getAccessibleChildrenCount() <= i) {
return null;
}
TableColumn firstColumn = getColumnModel().getColumn(0);
TableCellRenderer renderer = firstColumn.getCellRenderer();
if (renderer == null) {
Class<?> columnClass = getColumnClass(0);
renderer = getDefaultRenderer(columnClass);
}
Component component = renderer.getTableCellRendererComponent(MyJTable.this, null, false, false, i, 0);
return new MyAccessibleRow(MyJTable.this, i, component);
}
}
protected static class MyAccessibleRow extends AccessibleContext implements Accessible {
private MyJTable table;
private int row;
private Component rendererComponent;
protected MyAccessibleRow(MyJTable table, int row, Component renderComponent) {
this.table = table;
this.row = row;
this.rendererComponent = rendererComponent;
}
@Override
public AccessibleRole getAccessibleRole() {
// ROW_HEADER is used because it maps to NSAccessibilityRowRole
// on Mac.
return AccessibleRole.ROW_HEADER;
}
@Override
public Locale getLocale() {
AccessibleContext ac = rendererComponent.getAccessibleContext();
if (ac != null) {
return ac.getLocale();
} else {
return null;
}
}
@Override
public int getAccessibleChildrenCount() {
return 0; // TODO return the number of columns in this row
}
@Override
public Accessible getAccessibleChild(int i) {
return null; // TODO return a MyAccessibleJTableCell
}
@Override
public int getAccessibleIndexInParent() {
return row;
}
@Override
public AccessibleStateSet getAccessibleStateSet() {
return null; // TODO
}
@Override
public AccessibleContext getAccessibleContext() {
return this; // TODO
}
@Override
public AccessibleComponent getAccessibleComponent() {
return table.getAccessibleContext(); // TODO
}
}
}
从这张截图可以看出:
.. AXTable 上现在有两个 AXRow 子项。但是,VoiceOver 仍将 table 宣布为 "empty"。我不确定这是因为行没有 AXCell 子行,还是因为 AXTable 缺少其必需的属性,还是其他原因。
如果您要走自定义 AccessibleContext
路线,最好完全避免尝试公开 table 层次结构。相反,您可以将 table 建模为列表,其中每一行对应一个列表项,并且每个列表项包含每个单元格的一个组。这类似于 Firefox(已测试版本 39.0)使用的方法。目前在 Mac 上,Firefox 在公开 HTML table 时不使用 table 角色。不过,这应该在 Firefox 41 中得到修复。参见 Bug 744790 - [Mac] HTML table semantics are not communicated to VoiceOver at all。
我也在使用 Mac OS 10.10.4 和 Java 1.8.0_51.
编辑: "empty" table 问题已被报告为 OpenJDK Bug JDK-7124284 [macosx] Nothing heard from VoiceOver when navigating in a table。根据评论,Mac Swing 可访问性存在几个已知问题,目前已推迟到 JDK 9.
另一种可能的解决方法是使用 JavaFX TableView
class。尝试在 Java 8u40.
TableView
示例