草庐IT

java - JavaFX "console"输出的可访问屏幕阅读

coder 2024-03-08 原文

我正在尝试创建一个可供屏幕阅读器访问的简单 UI。我基本上是成功的,但我无法以让屏幕阅读器读取新文本输出的方式设计 UI。

目前,我有一个 TextArea 显示由 System.setOut 创建和设置的匿名 PrintStream 的输出。有时我会打开一个 TextField 用于字符串输入,但我一直只使用 TextArea 来测试文本的阅读(目前它只是监听要显示的击键更多文本用于测试目的)。

问题是这样的:当新文本通过 System.out 添加到 TextArea 时,屏幕阅读器不会读取它。我仍然可以使用箭头键向上导航以阅读添加的内容,但在首次添加时不会阅读。有什么方法可以让屏幕阅读器更像一个标准控制台(它会自动读取所有新文本)来处理我的 TextArea 吗?我正在使用 NVDA。

我尝试过的事情:
- 使用 TextArea.notifyAccessibleAttributeChanged(AccessibleAttribute.TEXT)
- 使用 TextArea.requestFocus()TextArea.notifyAccessibleAttributeChanged(AccessibleAttribute.FOCUS_NODE)
- 在刷新期间使用 TextArea.setAccessibleText(theNewText) 时禁用 PrintStream 上的自动刷新
- 使用隐藏的 Label 设置为新文本并专注于它(我还在摆弄这个;屏幕阅读器无法阅读实际的“隐藏”文本,所以我正在尝试找到一种方法绘制它但也是“不可见的”,也许在 TextArea 后面)
- 将焦点切换到另一个 Node 并返回,这并不像我喜欢的那样工作,因为它读取其他 Node 的可访问内容,然后读取 的整个主体>文本区域
- 这些的各种组合

我似乎无法让它工作。我觉得我在这里遗漏了一些简单而明显的东西,但 JavaFX Accessibility API 仍然相对较新,我找不到针对此类特定问题的解决方案。

这是我的应用程序的相关代码,如果有帮助的话:

@Override
public void start(Stage primaryStage) {
    try {
        primaryStage.setTitle("Test");
        root = new BorderPane();
        root.setFocusTraversable(false);
        Scene scene = new Scene(root,800,600);
        scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
        primaryStage.setScene(scene);
        //Create middle
        output = new TextArea();
        output.setEditable(false);
        output.setFocusTraversable(false); //I've tried true also, just to test
        output.setAccessibleRole(AccessibleRole.TEXT_AREA);
        root.setCenter(output);
        ...
        //Begin
        primaryStage.show();
        Thread th = new Thread(new AppMain());
        th.start();
    } catch(Exception e) {
        e.printStackTrace();
    }
}

@Override
public void init() {
    //Set output to TextArea
    System.setOut(new PrintStream(new OutputStream() {
        @Override
        public void write(int b) throws IOException {
            appendTextArea(String.valueOf((char) b));
        }
    }, true)); //I've also overriden flush while this is false, see above
}

public void appendTextArea(String str) {
    Platform.runLater(() -> {
        output.appendText(str);
    });
}

非常感谢您提供的任何帮助或建议。这个小问题困扰我太久了,我对 JavaFX 还是个新手。谢谢!

最佳答案

这是一个基于您的代码的完整工作示例。

披露:对于屏幕阅读器,我在我的 Mac 上使用“Voice Over Utility”,但希望这与您的环境没有太大差异。

关键是利用 Control#executeAccessibleAction方法。

示例:

TextArea.executeAccessibleAction(AccessibleAction.SET_TEXT_SELECTION, start, finish);

应用类

package application;

import java.io.PrintStream;
import java.io.IOException;
import java.io.OutputStream;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.stage.Stage;
import javafx.scene.AccessibleAction;
import javafx.scene.AccessibleRole;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.layout.BorderPane;

public class Main extends Application
{
    private TextArea    output;
    private BorderPane  root;

    private final StringBuilder STR_BUFFER = new StringBuilder();
    private static final String NEW_LINE   = System.lineSeparator();

    @Override
    public void start(Stage primaryStage)
    {
        try
        {
            primaryStage.setTitle("Test");
            root = new BorderPane();
            root.setFocusTraversable(false);
            Scene scene = new Scene(root, 800, 600);
            scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
            primaryStage.setScene(scene);

            // Create middle
            output = new TextArea();
            output.setEditable(false);
            output.setFocusTraversable(true); // I've tried true also, just to test 

            // ----------------------------------------------
            // Tell the Screen Reader what it needs to access
            // ----------------------------------------------
            output.setAccessibleRole(AccessibleRole.TEXT_AREA);

            root.setCenter(output);
            // ...

            // Begin
            primaryStage.show();

            // start the thread
            Thread th = new Thread(new AppMain());
            th.start();
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }

    @Override
    public void init()
    {
        // Set output to TextArea when we have a full string
        System.setOut(new PrintStream(new OutputStream()
        {
            @Override
            public void write(int b) throws IOException
            {
                if (b == '\r')
                {
                    return;
                }
                if (b == '\n')
                {
                    final String text = STR_BUFFER.toString() + NEW_LINE;
                    appendTextArea(text);
                    STR_BUFFER.setLength(0);
                }
                else
                {
                    STR_BUFFER.append((char) b);
                }
            }
        }, true));
    }

    public void appendTextArea(String str)
    {
        Platform.runLater(() ->
        {
            int anchor = output.getText().length();

            output.appendText(str);

            // just to clear it
            output.positionCaret(0);

            // ----------------------------------------------
            // Tell the Screen Reader what it needs to do
            // ----------------------------------------------
            output.executeAccessibleAction(AccessibleAction.SET_TEXT_SELECTION, anchor, anchor + str.length());
        });
    }

    public static void main(String[] args)
    {
        launch(args);
    }
}

线程类

/*
 * Just to simulate a feed to the console (textArea).
 * This will die after 1 minute.
 */
package application;

public class AppMain implements Runnable
{
    @Override
    public void run()
    {
        int i = 0;
        long start = System.currentTimeMillis();

        while (System.currentTimeMillis() - start < 60000)
        {
            try
            {
                Thread.sleep(3000);
            }
            catch (InterruptedException e)
            {}
            System.out.println("This is line number " + ++i);
        }
    }
}

关于java - JavaFX "console"输出的可访问屏幕阅读,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36575952/

有关java - JavaFX "console"输出的可访问屏幕阅读的更多相关文章

  1. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  2. ruby-on-rails - rails : "missing partial" when calling 'render' in RSpec test - 2

    我正在尝试测试是否存在表单。我是Rails新手。我的new.html.erb_spec.rb文件的内容是:require'spec_helper'describe"messages/new.html.erb"doit"shouldrendertheform"dorender'/messages/new.html.erb'reponse.shouldhave_form_putting_to(@message)with_submit_buttonendendView本身,new.html.erb,有代码:当我运行rspec时,它失败了:1)messages/new.html.erbshou

  3. ruby-on-rails - 由于 "wkhtmltopdf",PDFKIT 显然无法正常工作 - 2

    我在从html页面生成PDF时遇到问题。我正在使用PDFkit。在安装它的过程中,我注意到我需要wkhtmltopdf。所以我也安装了它。我做了PDFkit的文档所说的一切......现在我在尝试加载PDF时遇到了这个错误。这里是错误:commandfailed:"/usr/local/bin/wkhtmltopdf""--margin-right""0.75in""--page-size""Letter""--margin-top""0.75in""--margin-bottom""0.75in""--encoding""UTF-8""--margin-left""0.75in""-

  4. ruby - 检查 "command"的输出应该包含 NilClass 的意外崩溃 - 2

    为了将Cucumber用于命令行脚本,我按照提供的说明安装了arubagem。它在我的Gemfile中,我可以验证是否安装了正确的版本并且我已经包含了require'aruba/cucumber'在'features/env.rb'中为了确保它能正常工作,我写了以下场景:@announceScenario:Testingcucumber/arubaGivenablankslateThentheoutputfrom"ls-la"shouldcontain"drw"假设事情应该失败。它确实失败了,但失败的原因是错误的:@announceScenario:Testingcucumber/ar

  5. ruby-on-rails - 在混合/模块中覆盖模型的属性访问器 - 2

    我有一个包含模块的模型。我想在模块中覆盖模型的访问器方法。例如:classBlah这显然行不通。有什么想法可以实现吗? 最佳答案 您的代码看起来是正确的。我们正在毫无困难地使用这个确切的模式。如果我没记错的话,Rails使用#method_missing作为属性setter,因此您的模块将优先,阻止ActiveRecord的setter。如果您正在使用ActiveSupport::Concern(参见thisblogpost),那么您的实例方法需要进入一个特殊的模块:classBlah

  6. ruby - 通过 erb 模板输出 ruby​​ 数组 - 2

    我正在使用puppet为ruby​​程序提供一组常量。我需要提供一组主机名,我的程序将对其进行迭代。在我之前使用的bash脚本中,我只是将它作为一个puppet变量hosts=>"host1,host2"我将其提供给bash脚本作为HOSTS=显然这对ruby​​不太适用——我需要它的格式hosts=["host1","host2"]自从phosts和putsmy_array.inspect提供输出["host1","host2"]我希望使用其中之一。不幸的是,我终其一生都无法弄清楚如何让它发挥作用。我尝试了以下各项:我发现某处他们指出我需要在函数调用前放置“function_”……这

  7. ruby - 续集在添加关联时访问many_to_many连接表 - 2

    我正在使用Sequel构建一个愿望list系统。我有一个wishlists和itemstable和一个items_wishlists连接表(该名称是续集选择的名称)。items_wishlists表还有一个用于facebookid的额外列(因此我可以存储opengraph操作),这是一个NOTNULL列。我还有Wishlist和Item具有续集many_to_many关联的模型已建立。Wishlist类也有:selectmany_to_many关联的选项设置为select:[:items.*,:items_wishlists__facebook_action_id].有没有一种方法可以

  8. java - 等价于 Java 中的 Ruby Hash - 2

    我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/

  9. ruby - 如何进行排列以有效地定制输出 - 2

    这是一道面试题,我没有答对,但还是很好奇怎么解。你有N个人的大家庭,分别是1,2,3,...,N岁。你想给你的大家庭拍张照片。所有的家庭成员都排成一排。“我是家里的friend,建议家庭成员安排如下:”1岁的家庭成员坐在这一排的最左边。每两个坐在一起的家庭成员的年龄相差不得超过2岁。输入:整数N,1≤N≤55。输出:摄影师可以拍摄的照片数量。示例->输入:4,输出:4符合条件的数组:[1,2,3,4][1,2,4,3][1,3,2,4][1,3,4,2]另一个例子:输入:5输出:6符合条件的数组:[1,2,3,4,5][1,2,3,5,4][1,2,4,3,5][1,2,4,5,3][

  10. ruby-on-rails - 迷你测试错误 : "NameError: uninitialized constant" - 2

    我遵循MichaelHartl的“RubyonRails教程:学习Web开发”,并创建了检查用户名和电子邮件长度有效性的测试(名称最多50个字符,电子邮件最多255个字符)。test/helpers/application_helper_test.rb的内容是:require'test_helper'classApplicationHelperTest在运行bundleexecraketest时,所有测试都通过了,但我看到以下消息在最后被标记为错误:ERROR["test_full_title_helper",ApplicationHelperTest,1.820016791]test

随机推荐