[Java] 45 Swing 01 HTMLファイルの表示

JavaFXでWebページの表示はできたのですが、ローカルHTMLファイルの方がどうしてもできません。

JavaFXよりもさらに古いSwingというツールを使ってみたところ、あっけなく解決しました。

JavaFXで色々試しても打開できず、やはりmacOSではJavaよりもSwiftなのかと半ば諦めていました。

SwingはJava8以前でしか使えませんが、JavaFXが私の手に余るので仕方がないです。

あとは競争馬名を入力するテキストフィールドやボタンを配置して完成です。

2021/8/28追記:SwingはJava9以降でも使えます。勘違いしていました。

package swing;

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.io.File;
import java.io.IOException;

import javax.swing.JEditorPane;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;

public class Sample extends JFrame {

	private JPanel contentPane;

	public static void main(String[] args) {
		EventQueue.invokeLater(new Runnable() {
			public void run() {
				try {
					Sample frame = new Sample();
					frame.setVisible(true);
				}
				catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}

	public Sample() {
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setBounds(100, 100, 450, 300);
		contentPane = new JPanel();
		contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
		contentPane.setLayout(new BorderLayout(0, 0));
		setContentPane(contentPane);
		
		JEditorPane editorPane = new JEditorPane();
		contentPane.add(editorPane, BorderLayout.CENTER);
		editorPane.setContentType("text/html");
		try {
			editorPane.setPage(new File("シャフリヤール.html").toURI().toURL());
        }
		catch (IOException e) {
			e.printStackTrace();
		}
	}
}

[Java] 44 JavaFX 06 WebViewはJava11では動かず

前回記事でJavaFXにあるWebViewの導入に苦労していることを書きましたが、原因は開発環境にありました。

Catalina起因の文字化けトラブルに巻き込まれた経緯からmacOSに疑いの目を向けていました。かといってWindowsに宗旨替えするわけにもいかず、Javaの学習を中断しSwiftへの移行を真剣に検討していたのですが、どう見ても瑕疵のないネット公開のコードも動かないため、開発環境に問題があるのではと考えました。

検証の手間のかからない順に、Javaのバージョン、OS(Windows機は持っているので)、macOSのバージョンとなるところ、まず始めにJava11からJava8に変えてみたらあっさり解決しました。

macOSを取るか、Java(Windows)を取るかという選択を迫られずに済みましたが、かなり後ろ向きな解決方法なので、将来的には他のGUI作成ツールを検討することになるでしょう。

[Java] 43 JavaFX 05 2画面の表示

同じ画面に検索結果のHTMLを埋め込もうとしましたが、どうにもうまくいかないのでまずは別画面に反映させることにします。

空の別画面を作成してみました。ここから色々試してみます。

なおEclipseのエディタではカッコの色分けができないので閉じカッコは文末から通常の位置に戻しました。波カッコにもだいぶ慣れて違和感がなくなってきました。

package horse_search;
	
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;

public class Main extends Application {
	@Override
	public void start(Stage stage) {
		try {
			stage.setScene(new Scene(new AnchorPane(), 400, 200));
			Parent root = FXMLLoader.load(getClass().getResource("App.fxml"));
			Scene scene = new Scene(root);
			scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
			stage.setScene(scene);
			stage.setTitle("Horse Search");
		
			Stage stage2 = new Stage();
			stage2.initOwner(stage);
			stage2.setScene(new Scene(new AnchorPane(), 800, 600));
			Parent root2 = FXMLLoader.load(getClass().getResource("App2.fxml"));
			Scene scene2 = new Scene(root2);
			scene2.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
			stage2.setScene(scene2);
			stage2.setTitle("検索結果");
			
			stage.show();
			stage2.show();
			
			stage2.setX(stage.getX() + 100);
		  stage2.setY(stage.getY() + 100);
		}
		
		catch(Exception e) {
			e.printStackTrace();
		}
	}

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

[Java] 42 JavaFX 04 Controllerファイルの作成

Javaで初めて作成するGUIアプリの進捗です。

Controllerファイルを作成し、競走馬名から成績を検索してHTMLファイルにまとめるという最低限の機能を持たせました。mySQLの操作については別ファイルに分割して書いています。

Scene Builderでコンポーネントをドラッグ&ドロップで並べられるのは本当に便利です。Pythonのtkinterではコンポーネントの指定にウィジェットinfoを使うのに対し、こちらはIDだけでいいのでかなり楽です。

テキストエリアに自動的にスクロールバーが付くのもいいですね。デザインソフトはみな同じなのかもしれませんが、Adobe XDに操作性が似ていてすぐ馴染みました。

次はHTMLファイルをテキストエリアに貼り付けたような感じにしたいので、これから調査します。

package horse_search;

import java.util.ArrayList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;

public class SampleController {
    @FXML
    private TextArea area1;
    @FXML
    private Label label1;
    @FXML
    private Button button1;
    @FXML
    private Button button2;
    @FXML
    private TextField textField1;
    @FXML
    private TextField textField2;
    
    @FXML
    void onButton1Action(ActionEvent event) {
    	String name = textField1.getText();
      
      // Horseクラスの戻り値を変数nameAndIDConに設定
    	ArrayList<String> nameAndIDCon = Horse.IDSearch(name);
    	
      // 変数nameAndIDConは競走馬名とIDのリストになっているのでID数は要素数から1を引いて算出
      Integer count = nameAndIDCon.size()-1;
      
      // 競走馬名とIDを結合させたプレ文字列を作成	    	
    	StringBuilder result = new StringBuilder();
    	for (String ele:nameAndIDCon) {
    		result.append(ele);}
    	
    // area1の先頭に変換した文字列を挿入
    	String resultStr = result.toString();
      area1.insertText(0,resultStr + "\n");
		
      // IDが2つ以上の場合は1つを選ばせる
      if (count >= 2) {
          // 末尾に文字列を挿入するため位置place1を算出
          String text1 = area1.getText();
          Integer place1 = text1.length() - 1;
          area1.insertText(place1,"\n" + "該当する馬が複数います。番号を入力してください。");

          // リストから先頭の競走馬名を削除
       nameAndIDCon.subList(0,1).clear();

          // 末尾に文字列を挿入するため位置place2を算出
          String text2 = area1.getText();
          Integer place2 = text2.length() - 1;
          area1.insertText(place2, "\n" + nameAndIDCon + "\n");
			  
          // 選択肢の作成
          int i = 1;
          for (String s : nameAndIDCon) {
              String text3 = area1.getText();
              Integer place3 = text3.length() - 1;
              area1.insertText(place3,i + "  " + s + "\n");
              i = i + 1;}}
        
        else if (count == 0){
            String text4 = area1.getText();
            Integer place4 = text4.length() - 1;
            area1.insertText(place4,"該当する馬はいません"+ "\n");}
        
        else {
            String name2 = nameAndIDCon.get(0);
            String ID = nameAndIDCon.get(1);
            
            String text5 = area1.getText();
            Integer place5 = text5.length() - 1;
            area1.insertText(place5,name2 + "のIDは" + ID + "です"+ "\n");

            ArrayList<ArrayList<String>> raceList = Horse.raceSearch(nameAndIDCon);
            ArrayList<ArrayList<String>> raceListCon = Horse.raceSearch2(raceList);
            Horse.toHTML(raceListCon,nameAndIDCon);}}
		
    // 該当馬が複数の場合は右のテキストフィールドに番号を入力して確定ボタンを押す。
    @FXML
    void onButton2Action(ActionEvent event) {
    	String text6 = textField2.getText();
    	System.out.println(text6);
    	
      // 文字列を整数に変換
    	int numID = Integer.parseInt(text6);
    	System.out.println(numID);
    	
    	String name3 = textField1.getText();
    	System.out.println(name3);
    	
    	ArrayList<String> nameAndIDCon = Horse.IDSearch(name3);
    	String ID =  nameAndIDCon.get(numID);
    	ArrayList<String> nameAndIDCon2 = new ArrayList<String>();
    	nameAndIDCon2.add(name3);
    	nameAndIDCon2.add(ID);
    	
    	ArrayList<ArrayList<String>> raceList = Horse.raceSearch(nameAndIDCon2);
      ArrayList<ArrayList<String>> raceListCon = Horse.raceSearch2(raceList);
      Horse.toHTML(raceListCon,nameAndIDCon2);}}

[Java] 41 JavaFX 03 Scene Builderの導入 Eclipse編

VScodeでのGUIアプリ製作は一旦見合わせ、Eclipseで進めることにしました。色々試行錯誤しているうちに大分UIにも慣れました。

Scene BuilderでGUI画面を作り、SampleControllerSkeletonの内容をコントローラのjavaファイルに写し、GUIの動作を追記するといった流れを学びました。

GUIアプリとしてはTextFieldに競走馬名を入力し、それを先日作成した競馬DB検索コードに取り込んで成績CSVファイルを作成させました。

とりあえず自分用の手本としてアップしておきます。なんでもない内容ですがここまで結構時間が掛かっています。

時間的イニシャルコストは消費したものの、Pythonのtkinterでの苦労は何だったんだと感じる位、操作性が優れている印象です。

一つのプログラミング言語にこだわるよりも適材適所で使い分けるのがいいみたいです。

import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;

public class Main extends Application {
	@Override
	public void start(Stage primaryStage) {
		try {
			AnchorPane root = (AnchorPane)FXMLLoader.load(getClass().getResource("Sample.fxml"));
			Scene scene = new Scene(root);
			scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
			primaryStage.setScene(scene);
			primaryStage.show();}
		catch(Exception e) {
			e.printStackTrace();}}
	
	public static void main(String[] args) {
		launch(args);}}
package horse_search;

import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;

public class SampleController {
  @FXML
    private TextArea area1;

  @FXML
    private Label label1;

  @FXML
    private Button button1;

  @FXML
    private TextField textField1;

  @FXML
    protected void onButton1Action(ActionEvent event) {
        String name = textField1.getText();
        System.out.println(name);
        Horse.main(null,name);}} 	
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.paint.*?>
<?import javafx.scene.effect.*?>
<?import java.lang.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.AnchorPane?>

<AnchorPane prefHeight="200.0" prefWidth="300.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="horse_search.SampleController">
   <children>
      <Label fx:id="label1" layoutX="14.0" layoutY="22.0" text="競走馬名" />
      <TextField fx:id="textField1" layoutX="71.0" layoutY="17.0" prefHeight="27.0" prefWidth="153.0" />
      <Button fx:id="button1" layoutX="231.0" layoutY="17.0" mnemonicParsing="false" onAction="#onButton1Action" prefHeight="27.0" prefWidth="52.0" text="検索" />
      <TextArea fx:id="area1" layoutX="20.0" layoutY="56.0" prefHeight="130.0" prefWidth="260.0" />
   </children>
</AnchorPane>
Scene BuilderのUI

[Java] 40 JavaFX 02 文字化け対策 Catalina起因

JavaFXを使ったコードをせっせと書いていると以下の警告に遭遇しました。

CoreText note: Client requested name ".SFNS-Regular", it will get
Times-Roman rather than the intended font. All system UI font access should
be through proper APIs such as CTFontCreateUIFontForLanguage() or
+[NSFont systemFontOfSize:].

調べてみるとどうやらmacOS Catalina 10.15のシステムフォント設定に問題があるようです。

コード内でフォントの種類を指定し解決しました。

<修正前>
lblMsg.setFont(new Font(14));

<修正後>
lblMsg.setFont(new Font("Arial",14));

[Java] 39 JavaFX 01 導入の試み VScode編

GUIアプリについてはEclipseで作成しようかと思いましたが、Xcodeと同様に多機能なUIを覚えるのが面倒で結局VScodeでの開発を模索することにしました

最初は普通にJavaプロジェクトを作成してサンプルコードを走らせたものの、launch.jsonにJavaFXのjarファイルを設定するなどしても、うまくいきませんでした。

次にいつものMavenプロジェクトを作成し、pomファイルにjarファイルの依存関係を追記。そしてサンプルコードとメインコードを同一フォルダ内に並べて走らせ、ようやく成功しました。

mainメソッドの書き方に一工夫必要でしたが、これが初級者にはキツい。そこも含めて、なんだかんだで半日かかりました。

2021/6/18追記 その後Catalinaのシステムフォント関連等で警告やエラー、英語フォントの文字化けが頻発するようになり仕方なくEclipseに移行しました。修正したOpenJFXを導入すれば解決するようですが、ビルドに手間が掛かりそうなので見送りです。

package javafx_test;

import javafx.application.Application;

public class AppTest {

	public static void main(String[] args) {
		Application.launch(sample.class);}}

[Java] 38 現在日時の文字列化

ファイル名作成のため現在日時を文字列化しました。Pythonより楽でした。

これで自製競馬データベースの検索結果をHTMLファイル化するコード作成が終わりました。ようやくJavaFXを使ったGUIアプリ作成に取り掛かることができます。

同じ内容のPythonコードは約100行、Javaで書くと300行位ですから3倍に膨れ上がっています。

Javaは波カッコが苦手で敬遠していましたが、開きカッコ、閉じカッコともに行末に置くことで目立たなくしたのが効果的でした。カッコの色がペアによって異なるようにするVScodeの拡張機能”Bracket Pair Colorizer”のおかげです。

一連の学習で初心者から初級者レベルになれたかと思います。

このまま予定通りGUIアプリ作成に進むか、余勢を駆ってJavaScriptの学習に取り組むか考えているところです。

import java.util.Date;
import java.text.SimpleDateFormat;

// 現在日時を作成
Date dateObj = new Date();
SimpleDateFormat dFormat = new SimpleDateFormat( "yyMMddHHmm" );
String now = dFormat.format(dateObj);

// ファイル名を作成
String filename = String.format("%s_%s.html",now,name);

[Java] 37 戻り値を複数にしたい場合の対応

戻り値は複数にできないためデータ型が同じであればArrayListにまとめます。

今回は競走馬名と競走馬IDを引き継ぎたかったので、ArrayListにまとめて戻り値としました。

第1メソッドで作成したnameとIDをmainメソッドで共有し、IDは第2メソッド、
nameは第4メソッドで使用する

<importは省略>

public class AppTest {
    static String url = "jdbc:mysql://localhost:3306";
    static String user = "ユーザ名";
    static String password = "パスワード";

    public static void main(String args[]) {
        ArrayList<String> nameAndID = IDSearch();
        ArrayList<ArrayList<String>> raceList = raceSearch(nameAndID);
        ArrayList<ArrayList<String>> raceListCon = raceSearch2(raceList);
        // 第4メソッドで競走馬名を使うためnameAndIDを引数に追加
        toHTML(raceListCon,nameAndID);}

    // 第1メソッド
    public static ArrayList<String> IDSearch() {
        
        <中略>

        if (IDList.size() >= 2){
            System.out.println("該当する馬が複数います。番号を入力してください。");
            int i = 1;
            for (String s : IDList) {
                System.out.println(i + " " + s);
                i = i + 1;}

            InputStreamReader isr2 = new InputStreamReader(System.in);
            BufferedReader br2 = new BufferedReader(isr2);

            String num = null;
            try {
                num = br2.readLine();}
            catch (IOException e) {
                e.printStackTrace();}

            Integer numInt = Integer.parseInt(num);
            String ID = IDList.get(numInt - 1);
            System.out.println("該当馬のIDは" + ID + "です");
            
            // 競走馬名と競走馬IDをArrayListにまとめる
            ArrayList<String> nameAndID = new ArrayList<String>();
            nameAndID.add(name);
            nameAndID.add(ID);

            return nameAndID;}

        else if (IDList.size() == 0){
            System.out.println("該当する馬はいません");
            ArrayList<String> nameAndID = null;
            System.exit(0);

            return nameAndID;}

        else {
            String ID = IDList.get(0);
            System.out.println("該当馬のIDは" + ID + "です");
            ArrayList<String> nameAndID = new ArrayList<String>();
            nameAndID.add(name);
            nameAndID.add(ID);

            return nameAndID;}}

    // 第2メソッド
    public static ArrayList<ArrayList<String>> raceSearch(ArrayList<String> nameAndID) {
        System.out.println("第2メソッド内出力 " + nameAndID);

        // 引き継いだArrayListから競走馬IDを取り出す
        String ID = nameAndID.get(1);
        System.out.println("ID " + ID);

        String birthYear = ID.substring(0,4);
        Integer birthYearInt = Integer.parseInt(birthYear);
        Integer startYear = birthYearInt + 2;

        System.out.println("最速デビュー年 " + startYear);

        <中略>

    // 第4メソッド
    public static void toHTML(ArrayList<ArrayList<String>> raceListCon,\
    ArrayList<String> nameAndID) {
        System.out.println("第4メソッド内出力1 " + raceListCon + \
        "第4メソッド内出力2 " + nameAndID);

[Java] 36 ArrayListからHTMLファイルを作成

[macOS Catalina 10.15.7]

[Java] 33の続きです。

Pythonにおけるpandasのような気の利いたライブラリはないようなので、HTMLコードを1行にベタ書きさせました。

作成したHTMLファイルはVScodeで開き、以下のショートカットで自動整形できます。
⌘ + K → ⌘ + F

StringBuilderは文字列にスペースが含まれていると結合できないため、文字とスペースを分割して結合させます。

<途中から>

StringBuilder HTMLcode = new StringBuilder();

Integer count = 1;
HTMLcode.append("<table" + " " + "border='1'" + " " + "class='dataframe'>" + "<thead><tr>");
for (ArrayList<String> raceList: raceListConSorts){
    // 1行目はヘッダ
    if (count == 1){
        for (int i = 0 ; i < raceList.size() ; i++){
            HTMLcode.append("<th>" + raceList.get(i) + "</th>");}
        HTMLcode.append("</tr></thead><tbody>");
        HTMLcode.append("<tr>");
        count = count + 1;
    else{
        for (int i2 = 0 ; i2 < raceList.size() ; i2++){
            HTMLcode.append("<td>" + raceList.get(i2) + "</td>");}
        HTMLcode.append("</tr>");}}
HTMLcode.append("</tbody></table>");

try{
    File file = new File("test.html");
    FileWriter filewriter = new FileWriter(file);
    filewriter.write(HTMLcode.toString());
    filewriter.close();}
catch(IOException e){
    System.out.println(e);}}}
--------------------------------------------------

HTMLコードの内容
--------------------------------------------------
<table border='1' class='dataframe'><thead><tr><th>日付</th><th>開催</th>\
<th>天候</th><th>レース</th><th>レース名</th><th>枠番</th><th>馬番</th>\
<th>単勝</th><th>人気</th><th>着順</th><th>騎手</th><th>斤量</th><th>コース</th>\
<th>馬場状態</th><th>タイム</th><th>通過</th><th>上り</th><th>馬体重</th>\
<th>1着馬</th><th>賞金</th><th>raceID</th></tr></thead><tbody><tr>\
<td>2020-10-25</td><td>4回京都6日目</td><td>天候 : 晴</td><td>5 R</td>\
<td>2歳新馬</td><td>7</td><td>13</td><td>2.3</td><td>1</td><td>1</td>\
<td>福永祐一</td><td>55.0</td><td>芝右 外1800m</td><td>芝 : 良</td><td>1:49.9</td>\
<td>6-4</td><td>34.6</td><td>450(0)</td><td>シャフリヤール</td><td>700.0</td>\
<td>r202008040605</td></tr><td>2021-02-14</td><td>1回東京6日目</td><td>天候 : 晴</td>\
<td>11 R</td><td>第55回共同通信杯(G3)</td><td>8</td><td>11</td><td>4.9</td>\
<td>2</td><td>3</td><td>福永祐一</td><td>56.0</td><td>芝左1800m</td><td>芝 : 良</td>\
<td>1:48.0</td><td>7-8-8</td><td>33.4</td><td>448(-2)</td><td>エフフォーリア</td>\
<td>956.1</td><td>r202105010611</td></tr><td>2021-03-27</td><td>2回阪神1日目</td>\
<td>天候 : 晴</td><td>11 R</td><td>第68回毎日杯(G3)</td><td>6</td><td>6</td>\
<td>2.9</td><td>2</td><td>1</td><td>川田将雅</td><td>56.0</td><td>芝右 外1800m</td>\
<td>芝 : 良</td><td>1:43.9</td><td>4-4</td><td>34.1</td><td>448(0)</td>\
<td>シャフリヤール</td><td>3,833.6</td><td>r202109020111</td></tr></tbody></table>