Codificação UTF-8 para saída do Console para JavaFX TextArea

Dec 22 2020

Desejo redirecionar a saída do Console para JavaFX TextArea e sigo uma sugestão aqui: JavaFX: Redirecionar a saída do console para TextArea criada no SceneBuilder

Tentei definir o conjunto de caracteres como UTF-8 em PrintStream (), mas não parece muito bem . Definir o conjunto de caracteres como UTF-16 melhora um pouco, mas ainda é ilegível .

No Eclipse IDE, a suposta saída de texto no Console resulta bem:

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>

Ainda sou novo em Java, então não estou familiarizado como exatamente o PrintStream ou o OutputStream funcionam. Por favor, desculpe minha ignorância.

Cada sugestão é apreciada.

Respostas

1 Slaw Dec 23 2020 at 03:29

Acredito que seu problema seja causado por este código:

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

Isso converte cada byte individual em um caractere. Em outras palavras, presume-se que cada caractere seja representado por um único byte. Isso não é necessariamente verdade. Algumas codificações, como UTF-8 , podem usar vários bytes para representar um único caractere. Eles têm que fazê-lo se quiserem representar mais de 256 caracteres.

Você precisará decodificar corretamente os bytes de entrada. Em vez de tentar fazer isso sozinho, seria melhor encontrar uma maneira de usar algo como BufferedReader. Felizmente, isso é possível com PipedInputStreame PipedOutputStream. Por exemplo:

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;
      }
    }
  }
}

O uso do bufferacima é uma tentativa de evitar sobrecarregar o JavaFX Application Thread com tarefas.

Algumas outras coisas que você precisa levar em consideração:

  • Como você está usando uma string literal, certifique-se de salvar o arquivo de origem com UTF-8 e compilar o código com -encoding UTF-8.
  • Certifique-se de que a fonte usada com o TextAreapode representar todos os caracteres que você deseja.
  • É possível que você também precise executar o aplicativo, -Dfile.encoding=UTF-8mas não tenho certeza. Eu não fiz e ainda funcionou para mim.
Nickitiki Dec 22 2020 at 15:47

Tente definir sua codificação JVM padrão para UTF-8.

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

Para obter mais detalhes, consulte este tópico: Configurando a codificação de caracteres Java padrão

Se você não quiser exportar seu arquivo, vá para Preferências do Eclipse > Geral> Espaço de trabalho e defina a codificação do arquivo de texto para UTF-8 (ou a codificação que você gostaria de ter).

Existem mais alguns detalhes: Como alterar a codificação do arquivo de texto padrão no Eclipse