【问题标题】:UTF-8 encoding for output from Console to JavaFX TextArea从控制台输出到 JavaFX TextArea 的 UTF-8 编码
【发布时间】:2020-12-22 08:18:41
【问题描述】:

我想将 Console 中的输出重定向到 JavaFX TextArea,我在这里遵循一个建议:JavaFX: Redirect console output to TextArea that is created in SceneBuilder

我尝试在 PrintStream() 中将字符集设置为 UTF-8,但 it does not look so well。将字符集设置为 UTF-16 会有所改善,但 it is still illegible

在 Eclipse IDE 中,控制台中假定的文本输出结果很好:

KHA khởi đầu phiên giao dịch sáng nay ở mức 23600 điểm, khối lượng giao dịch trong ngày đạt 765 cổ phiếu, tương đương khoảng 18054000 đồng.

Controller.java

public class Controller {
    @FXML
    private Button button;

    public Button getButton() {
        return button;
    }

    @FXML
    private TextArea textArea;

    public TextArea getTextArea() {
        return textArea;
    }

    private PrintStream printStream;

    public PrintStream getPrintStream() {
        return printStream;
    }

    public void initialize() {
        textArea.setWrapText(true);
        printStream = new PrintStream(new UITextOutput(textArea), true, StandardCharsets.UTF_8);
    } // Encoding set to UTF-8

    public class UITextOutput extends OutputStream {
        private TextArea text;

        public UITextOutput(TextArea text) {
            this.text = text;
        }

        public void appendText(String valueOf) {
            Platform.runLater(() -> text.appendText(valueOf));
        }

        public void write(int b) throws IOException {
            appendText(String.valueOf((char) b));
        }
    }
}

UI.java

public class UI extends Application {
    @Override
    public void start(Stage stage) {
        try {
            FXMLLoader loader = new FXMLLoader(getClass().getResource("Sample.fxml"));
            Parent root = loader.load();
            Controller control = loader.getController();

            stage.setTitle("Title");
            stage.setScene(new Scene(root));
            stage.show();

            control.getButton().setOnAction(new EventHandler<ActionEvent>() {
                public void handle(ActionEvent event) {
                    try {
                        System.setOut(control.getPrintStream());
                        System.setErr(control.getPrintStream());
                        System.out.println(
                                "KHA khởi đầu phiên giao dịch sáng nay ở mức 23600 điểm, khối lượng giao dịch trong ngày đạt 765 cổ phiếu, tương đương khoảng 18054000 đồng.");
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

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

Sample.fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.layout.BorderPane?>


<BorderPane prefHeight="339.0" prefWidth="468.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/11.0.1" fx:controller="main.Controller">
   <center>
      <TextArea fx:id="textArea" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER" />
   </center>
   <right>
      <Button fx:id="button" mnemonicParsing="false" onAction="#getButton" text="Button" BorderPane.alignment="CENTER" />
   </right>
</BorderPane>

我还是 Java 新手,所以我不熟悉 PrintStream 或 OutputStream 究竟是如何工作的。请原谅我的无知。

每一个建议都值得赞赏。

【问题讨论】:

  • 好吧,我尝试在 Controller 的 initialize() 中添加 textArea.setFont(new Font("TimesRoman", 14)); ,但我仍然得到相同的结果。我应该使用哪些其他字体?
  • 嗯。问题是,我对字符编码/解码的复杂性并不熟悉。但我觉得String.valueOf((char) b) 可能是个问题。 b 是单个“字节”(0 到 255 之间的值)。然而,如果我没记错的话,UTF-8 中的某些字符由多个字节表示。
  • 问题代码似乎得到了支持,因为直接使用您的字符串文字调用setText(...) 工作正常(至少在使用-encoding UTF-8 编译源文件时)。
  • 我可以使用setText(...) 将控制台输出推送到TextArea 吗?也许将控制台输出放入一个字符串(或其他东西?)然后 setText() 呢?
  • 解决问题的不是setText。当我说代码与setText 一起工作时,问题不在于TextArea,而在于如何将字节解码为字符。我用一个例子添加了一个答案。

标签: java javafx utf-8 printstream javafx-textarea


【解决方案1】:

我相信你的问题是由这段代码引起的:

public void write(int b) throws IOException {
    appendText(String.valueOf((char) b));
}

这是将每个单独的字节转换为一个字符。换句话说,它假设每个字符都由一个字节表示。这不一定是真的。某些编码such as UTF-8 可能使用多个字节来表示单个字符。如果他们希望能够表示超过 256 个字符,则必须这样做。

您需要正确解码传入的字节。与其自己尝试这样做,不如找到一种方法来使用BufferedReader 之类的东西。幸运的是,PipedInputStreamPipedOutputStream 可以做到这一点。例如:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.PrintStream;
import java.io.UncheckedIOException;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.stage.Stage;

import static java.nio.charset.StandardCharsets.UTF_8;

public class Main extends Application {

  @Override
  public void start(Stage primaryStage) {
    TextArea area = new TextArea();
    area.setWrapText(true);

    redirectStandardOut(area);

    primaryStage.setScene(new Scene(area, 800, 600));
    primaryStage.show();

    System.out.println(
        "KHA khởi đầu phiên giao dịch sáng nay ở mức 23600 điểm, khối lượng giao dịch trong ngày đạt 765 cổ phiếu, tương đương khoảng 18054000 đồng.");
  }

  private void redirectStandardOut(TextArea area) {
    try {
      PipedInputStream in = new PipedInputStream();
      System.setOut(new PrintStream(new PipedOutputStream(in), true, UTF_8));

      Thread thread = new Thread(new StreamReader(in, area));
      thread.setDaemon(true);
      thread.start();
    } catch (IOException ex) {
      throw new UncheckedIOException(ex);
    }
  }

  private static class StreamReader implements Runnable {

    private final StringBuilder buffer = new StringBuilder();
    private boolean notify = true;

    private final BufferedReader reader;
    private final TextArea textArea;

    StreamReader(InputStream input, TextArea textArea) {
      this.reader = new BufferedReader(new InputStreamReader(input, UTF_8));
      this.textArea = textArea;
    }

    @Override
    public void run() {
      try (reader) {
        int charAsInt;
        while ((charAsInt = reader.read()) != -1) {
          synchronized (buffer) {
            buffer.append((char) charAsInt);
            if (notify) {
              notify = false;
              Platform.runLater(this::appendTextToTextArea);
            }
          }
        }
      } catch (IOException ex) {
        throw new UncheckedIOException(ex);
      }
    }

    private void appendTextToTextArea() {
      synchronized (buffer) {
        textArea.appendText(buffer.toString());
        buffer.delete(0, buffer.length());
        notify = true;
      }
    }
  }
}

上面使用buffer 是为了避免JavaFX 应用程序线程 被任务淹没。

您需要考虑的其他一些事项:

  • 由于您使用的是字符串文字,请确保您既使用 UTF-8 保存源文件,又使用 -encoding UTF-8 编译代码。
  • 确保与TextArea 一起使用的字体可以代表您想要的所有字符。
  • 可能您还需要使用-Dfile.encoding=UTF-8 运行应用程序,但我不确定。我没有,它仍然对我有用。

【讨论】:

  • 效果很好,非常感谢!
【解决方案2】:

尝试将您的默认 JVM 编码设置为 UTF-8。

java -Dfile.encoding=UTF-8 -jar YourJarfile.jar

更多详情请看这个帖子:Setting the default Java character encoding

如果您不想导出文件,请进入 Eclipse Preferences > General > Workspace 并将文本文件编码设置为 UTF-8(或您想要的编码) )。

还有一些细节:How to change default text file encoding in Eclipse

【讨论】:

  • 感谢您的建议。对不起,这意味着我必须先将我的项目打包成一个可执行的 .jar 文件,不是吗?而当jar在另一台电脑上运行时,是不是还要重新设置JVM?
  • 如果其他平台上的默认编码与您的首选不同,那么您必须在执行前设置JVM参数。
  • 嗯,在 Eclipse IDE 中设置首选项只会影响代码编辑器和控制台,但不会影响到 TextArea 的输出。
猜你喜欢
  • 2010-12-12
  • 1970-01-01
  • 2015-11-26
  • 1970-01-01
  • 2016-02-03
  • 1970-01-01
  • 1970-01-01
  • 2018-06-18
  • 1970-01-01
相关资源
最近更新 更多