質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
JavaFX

JavaFXとは、Java仮想マシン上で動作するリッチインターネットアプリケーション (RIA) のGUIライブラリです。Swingとは異なり、FXMLと呼ばれる XMLとCSSを併用してデザインを記述します。

Kotlin

Kotlinは、ジェットブレインズ社のアンドリー・ブレスラフ、ドミトリー・ジェメロフが開発した、 静的型付けのオブジェクト指向プログラミング言語です。

Q&A

解決済

4回答

972閲覧

TextAreaで非同期にテキストが表示されない

Hinyari_Gohan

総合スコア4

JavaFX

JavaFXとは、Java仮想マシン上で動作するリッチインターネットアプリケーション (RIA) のGUIライブラリです。Swingとは異なり、FXMLと呼ばれる XMLとCSSを併用してデザインを記述します。

Kotlin

Kotlinは、ジェットブレインズ社のアンドリー・ブレスラフ、ドミトリー・ジェメロフが開発した、 静的型付けのオブジェクト指向プログラミング言語です。

0グッド

0クリップ

投稿2020/01/25 05:32

編集2020/01/25 05:33

前提・実現したいこと

マークシート読取りソフトを支援するプログラムをKotlinとJavaFXを用いて作成しています。

実装したい内容は

  • PDFファイルをjpeg画像に変換する機能
  • CSVファイルを解析し,データの加工をする機能

の2つであり,これらの実装は問題なく動いています。

その中で,進捗状況(ログ)を非同期にTextAreaに表示したいですのですが
ここで期待通りの動作をしません。

発生している問題・エラーメッセージ

コンソールには正常にログが流れており,実装内容も正常に動作しています。
しかし,TextAreaには何も表示されず,エラーログも出ていません。

該当のソースコード

######Main.kt

Kotlin

1import javafx.concurrent.Task 2import java.util.concurrent.Executors 3() 4 5 6fun main(args: Array<String>) { 7 Application.launch(MarkAnalyzer::class.java, *args) 8} 9 10class MarkAnalyzer : Application() { 11 12 override fun start(primaryStage: Stage) { 13 14 val root = FXMLLoader.load<Parent>(javaClass.classLoader.getResource("markanalyzer.fxml")) 15 16 primaryStage.title = "MarkAnalyzer" 17 18 val scene = Scene(root) 19 20 primaryStage.scene = scene 21 primaryStage.setOnCloseRequest { exitProcess(0) } 22 23 // アイコンを指定する 24 val iconURI = javaClass.classLoader.getResource() 25 if (iconURI != null) { 26 val iconImage = Image(iconURI.toString()) 27 primaryStage.icons.add(iconImage) 28 } 29 30 primaryStage.show() 31 } 32} 33 34data class Team( 35 val id: String, 36 val grade : String, 37 val classNumber : String, 38 val game : String, 39 val team : String, 40 val player : List<String> 41) 42 43class Controller : Initializable { 44 45 @FXML 46 lateinit var convertButton : Button 47 48 @FXML 49 lateinit var analyzeButton : Button 50  (略) 51 52 @FXML 53 lateinit var logTextArea : TextArea 54 55 private var csvFile = File("") 56 private var exportDirectory = File("") 57 private var pdffiles : List<File> = listOf() 58 59 companion object { 60 lateinit var instance : Controller 61 } 62 63 override fun initialize(location: URL?, resources: ResourceBundle?) { 64 instance = this 65 66 ファイル選択ダイアログに関する実装() 67 68 logTextArea.textProperty().addListener { _, _, _ -> 69 logTextArea.scrollTop = Double.MAX_VALUE 70 } 71 } 72 73 private val service = Executors.newSingleThreadScheduledExecutor() 74 75 @FXML 76 fun convertPdf() { 77 if (pdffiles.isEmpty()) { 78 return 79 } 80 81 val task: Task<Long> = object : Task<Long>() { 82 override fun call(): Long { 83 for (file in pdffiles) { 84 val filename = file.name 85 val pdf = PDDocument.load(file) 86 val pdfRenderer = PDFRenderer(pdf) 87 88 log("$filename を読み込みました") 89 log("$filename${pdf.pages.count}ページあります") 90 91 for ((index, page) in pdf.pages.withIndex()) { 92 log("$filename${index + 1} ページ目を処理中です") 93 val bim = pdfRenderer.renderImageWithDPI(index, 300f, ImageType.BINARY) 94 95 if (removalNoiseCheckBox.isSelected) { 96 97 } 98 99 ImageIOUtil.writeImage(bim, "${exportFolderLabel.text}\$filename-${index + 1}.jpg", 300) 100 } 101 102 log("$filename の処理が完了しました") 103 pdf.close() 104 } 105 106 return 1 107 } 108 } 109 110 task.onFailed = EventHandler { event -> event.source.exception.printStackTrace() } 111 task.onSucceeded = EventHandler { println("Done.") } 112 113 logTextArea.textProperty().bind(task.messageProperty()) 114 115 service.execute(task) 116 } 117 118 @FXML 119 fun analyzeCSV() { 120 if (!csvFile.exists()) { 121 log("エラー CSVファイルの取得に失敗しました") 122 return 123 } 124 125 val task : Task<Long> = object : Task<Long>() { 126 override fun call(): Long { 127 val csvAnalyzer = CSVAnalyze(csvFile) 128 csvAnalyzer.analyze() 129 130 return 1 131 } 132 } 133 134 task.onFailed = EventHandler { event -> event.source.exception.printStackTrace() } 135 task.onSucceeded = EventHandler { println("Done.") } 136 137 logTextArea.textProperty().bind(task.messageProperty()) 138 139 service.execute(task) 140 } 141 142 private fun applyButtonStatus() { 143 convertButton.isDisable = (pdffiles.isEmpty() || !exportDirectory.exists()) 144 analyzeButton.isDisable = !csvFile.exists() 145 } 146 147 148 fun log(txt: String) { 149 // このprintlnは正常に動作 150 println("log called with $txt") 151 152 val presentDateTime = LocalDateTime.now() 153 val logtext = "[" + presentDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) + "] $txt" 154 155 // この内容が反映されない 156 logTextArea.appendText(logtext + System.getProperty("line.separator")) 157 } 158 159}
CSVAnalyzer.kt

Kotlin

1import2 3class CSVAnalyze(file: File) { 4 5 private val controller = Controller.instance 6 7 private var records : List<List<String>> 8 9 private var inputCsvFile : File = file 10 11 init { 12 controller.log("CSVAnalyze インスタンスが生成されました。") 13 14 // 読み込み行のリストを作成する 15 val lines = arrayListOf<List<String>>() 16 17 controller.log("CSVファイルの読み込みを開始します") 18 // CSVファイルを読み込む(Windowsで扱うためShift_JIS) 19 inputCsvFile.forEachLine(charset("Shift_JIS")) { 20 if (it.isNotBlank()) { 21 lines.add(it.split(',')) 22 } 23 } 24 controller.log("${file.name}を読み込みました。") 25 26 // 先頭行はタイトル行なので削除する 27 // TODO: トグル? 28 lines.removeAt(0) 29 30 // 変数へ代入 31 records = lines 32 33 // デバッグ用 34 //println(lines) 35 } 36 37 fun analyze() { 38 val teamlist = mutableListOf<Team>() 39 40 // 一行ずつ回していく 41 for ((recordindex, line) in records.withIndex()) { 42 controller.log("[rowdata: $recordindex]" + line.joinToString(", ")) 43 val players = arrayListOf<String>() 44 45 val id = line[3] 46 val grade = line[4].replace("0", "") 47 val classNumber = classnumber(line[5]) 48 val game = line[6] 49 val team = line[7] 50 51 52 // 列を参照し選手の4ケタ番号を取り出している 53 for ((index, value) in line.withIndex()) { 54 if (index in 8..25) { 55 if (value.isBlank()) { 56 continue 57 } 58 val onesplace = if (line[index + 18].toInt() == 10) 9 else line[index + 18].toInt() - 1 59 val tensplace = if (value.toInt() == 10) 9 else value.toInt() - 1 60 61 if (onesplace == 0 && tensplace == 0) continue 62 63 val fourdigit = "$grade$classNumber$tensplace$onesplace" 64 players.add(fourdigit) 65 } 66 players.sort() 67 } 68 69 controller.log("[${recordindex+1}行目] ${grade}${classNumber}組 ゲーム($game) チーム($team) プレイヤー${players}") 70 71 teamlist.add(Team(id, grade, classNumber, game, team, players)) 72 } 73 74 controller.log("CSVファイルの解析が終了しました。") 75 76 val sortedList = teamlist.sortedWith(compareBy({it.game}, {it.grade}, {it.classNumber}, {it.team})) 77 78 controller.log("項目を昇順に並び替えました。") 79 controller.log("出力ファイルの生成を開始します。") 80 81 val nowtime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("MMdd-hhmmss")) 82 val writeCsvFile = File(inputCsvFile.parent + "/加工後_$nowtime.csv") 83 val osw = OutputStreamWriter(FileOutputStream(writeCsvFile), "Shift_JIS") 84 val bw = BufferedWriter(osw) 85 86 bw.write("連番,学年,クラス,競技,チーム,選手") 87 bw.newLine() 88 var i = 1 89 for (team in sortedList) { 90 val sb = StringBuilder() 91 92 sb.append(i).append(",").append(team.grade).append(",").append(team.classNumber).append(",").append(team.game).append(",") 93 .append(team.team).append(",") 94 i++ 95 team.player.forEach { 96 sb.append(it).append(",") 97 } 98 bw.write(sb.toString()) 99 bw.newLine() 100 } 101 bw.close() 102 103 controller.log("ファイルを正常に出力しました。") 104 } 105} 106

試したこと

サイトをいくつか参照する中で**~property().bind(他のproperty)**と,他プロパティのバインドを行わなければならないことを知り,
logTextArea.textProperty().bind(task.messageProperty())という実装をしました。

補足情報(FW/ツールのバージョンなど)

開発言語:Kotlin(1.3.50), Java 8

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答4

0

無事解決されたようですが、内容を単純にしたサンプルを作ってみました。参考まで。

kotlin

1import javafx.application.Application 2import javafx.application.Platform 3import javafx.scene.Scene 4import javafx.scene.control.Button 5import javafx.scene.control.TextArea 6import javafx.scene.layout.GridPane 7import javafx.stage.Stage 8import java.time.LocalDateTime 9import kotlin.concurrent.thread 10 11object xxMessage { 12 @JvmStatic 13 fun main(args: Array<String>) { 14 Application.launch(App::class.java, *args) 15 } 16 17 class App : Application() { 18 override fun start(stage: Stage) { 19 val msgA = TextArea() 20 val buttonA = Button("ボタンイベントで処理").also { node -> 21 node.setOnAction { ev -> 22 node.isDisable = true 23 msgA.text = "" 24 // 時間のかかる処理 25 repeat(5) { index -> 26 val msg = "(${index}): ${LocalDateTime.now()}" 27 msgA.text += "${msg}\n" 28 println("${node.text} // ${msg}") 29 Thread.sleep(1000) 30 } 31 node.isDisable = false 32 } 33 } 34 35 // 36 val msgB = TextArea() 37 val buttonB = Button("別スレッドで処理").also { node -> 38 node.setOnAction { ev -> 39 thread { 40 Platform.runLater { 41 node.isDisable = true 42 msgB.text = "" 43 } 44 // 時間のかかる処理 45 repeat(5) { index -> 46 Platform.runLater { 47 val msg = "(${index}): ${LocalDateTime.now()}" 48 msgB.text += "${msg}\n" 49 println("${node.text} // ${msg}") 50 } 51 Thread.sleep(1000) 52 } 53 Platform.runLater { node.isDisable = false } 54 } 55 } 56 } 57 58 // 59 val gridPane = GridPane().apply { 60 add(msgA, 0, 0) 61 add(buttonA, 0, 1) 62 add(msgB, 1, 0) 63 add(buttonB, 1, 1) 64 } 65 66 stage.scene = Scene(gridPane) 67 stage.title = this.javaClass.name 68 stage.show() 69 } 70 } 71}

投稿2020/01/25 11:20

編集2020/01/25 11:23
shiketa

総合スコア3971

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

失礼しました。ちょっと外しましたかもしれません。
処理は別スレッド(Task)で実行されていますね。

logTextArea.textProperty().bind(task.messageProperty())logTetAreaとバインドしているmessageProperty()はどこで更新されていますか?その近辺を見直す必要があるのかもしれません。

投稿2020/01/25 06:07

shiketa

総合スコア3971

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Hinyari_Gohan

2020/01/25 08:32 編集

回答ありがとうございます。 省略をしすぎてしまい,さらに読み辛いコードにしてしまいごめんなさい(汗) ご指摘をいただいたProperty部分の実装を見直し, https://stackoverflow.com/questions/36250015/javafx-execute-operation-every-x-ms-while-window-is-open/36250491#36250491 のサイトを発見しました。これを参考に実装をしたところ正常に動作しました。 ご指摘,本当にありがとうございました!
guest

0

analyexx()を実行するタイミングが読み取れないので確かなことは言えませんが、xxボタンのイベントハンドラ内で処理しているのではないでしょうか?

イベントハンドラで重い、時間のかかるような処理は実行せずに、別スレッドで処理しましょう。その別スレッドからlog()メソッド呼び出し、log()メソッドはPlatform#runLator()を使って、TextAreaを更新しましょう。

kotlin

1 fun log(txt: String) { 2 Platform.runLator{ 3 // このprintlnは正常に動作 4 println("log called with $txt") 5 val presentDateTime = LocalDateTime.now() 6 val logtext = "[" + presentDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) + "] $txt" 7 // この内容が反映されない 8 logTextArea.appendText(logtext + System.getProperty("line.separator")) 9 } 10 }

see: JavaFXでスレッドを使って描画するときの注意

投稿2020/01/25 05:59

shiketa

総合スコア3971

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

自己解決

shiketa さんにご指摘をいただいた,messageProperty について検索を進めるうちに
https://stackoverflow.com/questions/36250015/javafx-execute-operation-every-x-ms-while-window-is-open/36250491#36250491
を発見し,実装したところ正常に動作しました。

class ProgressLog { private val logText = ReadOnlyStringWrapper() fun textProperty() : ReadOnlyStringProperty { return logText } private fun getText() : String { return if(textProperty().get() == null) "" else textProperty().get() } fun log(text: String) { val presentDateTime = LocalDateTime.now() val formatted = "[" + presentDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) + "] $text" println("ProgressLog_log with $text") logText.set(getText() + "\n" + formatted) }

よって

logTextArea.textProperty().bind(task.messageProperty())

はコメントアウトし,ProgressLogクラスのインスタンスを生成,logメソッドを呼ぶことで解決しました。

投稿2020/01/25 08:32

Hinyari_Gohan

総合スコア4

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問