yamaken1343’s blog

技術ブログもどき

JavaFXで時間管理タイマーを作る [メモリリーク編]

yamaken1343.hatenablog.jp

f:id:yamaken1343:20180501131610p:plain

(上から2つ目のプロセスです)

しばらく使ってたんですけど、なんか妙にメモリ食うんですよねこれ。起動時から100MBくらい使うし3時間位でこうなるし。

というわけで原因を探してたんですけど、こんな記事を見つけました。

d.hatena.ne.jp

ドキュメントにもしっかりその旨が書いてあるらしく

Timeline (JavaFX 8)

警告: 実行中のTimelineはFXランタイムから参照されます。Timelineを無限にした場合、適切に停止しないと、メモリー・リークが発生する可能性があります。アニメーション化されるプロパティを持つすべてのオブジェクトがガベージ・コレクションの対象となるわけではありません。

だそうです。

コード

追加部分を抜粋すると

public class Controller implements Initializable {
    private int c = 0;

    private Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(1), //時間経過をトリガにするのはTimelineクラスを使う
                new EventHandler<ActionEvent>() {
                    public void handle(ActionEvent actionEvent) { //ここに書いた処理がDuration.seconds(1)で示した感覚で実行される
                        c++;
                        if (c > 60){
                            c = 0;
                            timeline.stop();
                            timeline.play();
                        }
                    }
                }
        )
    );

    public void mainButtonClick(ActionEvent actionEvent) {
        if (mainTimer.getStatus()){ //タイマーが動作中のクリック

        }else { //タイマーが動作していない

            timeline.setCycleCount(100); //何回繰り返すか指定する

        }

}

何回呼ばれたか数えるカウンタを用意して、一定回数(60回)呼ばれたら停止して再開すると言った形にしました。

非常に頭の悪い感じになりましたが、これで治るのか楽しみですね

JavaFXで時間管理タイマーを作る [fxml & css編]

yamaken1343.hatenablog.jp

見た目があまりにもダサくて使いたくなかったため、fxmlとcssを用いて見た目をいい感じにします。

ネットにある情報がfxmlじゃなくてjavaコードで構造を定義してるものが多くて正直わかりませんでした。英語で探せばよかったかな。

コード

<BorderPane fx:controller="Controller" xmlns:fx="http://javafx.com/fxml">
    <stylesheets>
        <URL value="@labTimer.css"/>
    </stylesheets>
    <top>
        <VBox alignment="CENTER">
            <HBox>
                <Label text="らぼいん"/>
                <Region HBox.hgrow="ALWAYS"/>
                <Label fx:id="nowTimeLabel"/>
            </HBox>
            <HBox>
                <Label fx:id="inDate" text="00/00 00:00:00"/>
                <Region HBox.hgrow="ALWAYS"/>
                <Label fx:id="nowTime" text="00/00 00:00:00"/>
            </HBox>
            <Label text=" "/>
            <Label text="らぼ時間"/>
            <Label fx:id="allTimerLabel"/>
        </VBox>
    </top>
    <center>
        <GridPane alignment="center" hgap="10" vgap="10">
            <Label text="研究時間: " GridPane.rowIndex="0" GridPane.columnIndex="0"/>
            <Label fx:id="childTimerLabel1" GridPane.rowIndex="0" GridPane.columnIndex="1"/>
            <Label text="休憩時間: " GridPane.rowIndex="1" GridPane.columnIndex="0"/>
            <Label fx:id="childTimerLabel2" GridPane.rowIndex="1" GridPane.columnIndex="1"/>
            <Label text="その他時間: " GridPane.rowIndex="2" GridPane.columnIndex="0"/>
            <Label fx:id="childTimerLabel3" GridPane.rowIndex="2" GridPane.columnIndex="1"/>

        </GridPane>
    </center>
    <bottom>
        <VBox alignment="CENTER">
            <Label fx:id="statusLabel" />
            <Label fx:id="mainTimerLabel" />
            <HBox>
                <Region HBox.hgrow="ALWAYS"/>
                <Button fx:id="mainButton" onAction="#mainButtonClick"/>
                <Region HBox.hgrow="ALWAYS"/>
                <Button fx:id="childButton1" text="研究" onAction="#button1Click"/>
                <Region HBox.hgrow="ALWAYS"/>
                <Button fx:id="childButton2" text="休憩" onAction="#button2Click"/>
                <Region HBox.hgrow="ALWAYS"/>
                <Button fx:id="childButton3" text="その他" onAction="#button3Click"/>
                <Region HBox.hgrow="ALWAYS"/>
            </HBox>
        </VBox>
    </bottom>
</BorderPane>
Label#mainTimerLabel{
    -fx-font-size:28pt;
}
Label#allTimerLabel{
    -fx-font-size:28pt;
}

解説

BorderPone

top, left, center, right, bottomの5つの構造をもち、それぞれに配置できる

stylesheets

URLタグでスタイルシートの場所を指定できる。Pone要素の中に書かないとダメっぽい?直書きもだめでした。

HBox, VBox

それぞれ要素を横に並べる、縦に並べるときに使う

Region HBox.hgrow="ALWAYS"

空白の指定。左右均等配置になる。ウィンドウ自体を拡大したらこの部分が伸びる

GridPone

GridPane.hogeIndexに従って格子状に配置できる

CSS

クラス#fx:id{CSSのタグに-fx-をつけたもの}で修飾できる

結果

f:id:yamaken1343:20180426122839p:plain

参考

totomo.net

qiita.com

JavaFXで時間管理タイマーを作る [まとめ]

まとめです

yamaken1343.hatenablog.jp

yamaken1343.hatenablog.jp

yamaken1343.hatenablog.jp

yamaken1343.hatenablog.jp

yamaken1343.hatenablog.jp

yamaken1343.hatenablog.jp

yamaken1343.hatenablog.jp

github.com

今後の予定

  • 見た目を何とかする - 達成
  • ログの出力とか実装
  • ステータスバーに情報を出したい
  • ダイアログとかどうやって出すか勉強したい
  • twitterにつぶやきたい

感想

  • Qtにくらべると随分素直で書きやすい
  • 情報が少ない
  • IntelliJ IDEAは神。これの補完ですべてを書いた

JavaFXで時間管理タイマーを作る [時刻編]

yamaken1343.hatenablog.jp

Java日付時刻APIメモ(Hishidama's Java8 Date and Time API Memo)

qiita.com

このサイトを参考に時刻関連の実装を行う

仕様変更

  • らぼりだ時刻は普段使われないので現在時刻を表示する。

コード(追加部分)

    private LocalDateTime dt;
    private DateTimeFormatter dtf = DateTimeFormatter.ofPattern("MM/dd HH:mm:ss");

    private Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(1), //時間経過をトリガにするのはTimelineクラスを使う
                new EventHandler<ActionEvent>() {
                    public void handle(ActionEvent actionEvent) { //ここに書いた処理がDuration.seconds(1)で示した感覚で実行される

                        dt = LocalDateTime.now();
                        nowTime.setText(dt.format(dtf));

                    }
                }
        )
    );

    public void mainButtonClick(ActionEvent actionEvent) {
        if (mainTimer.getStatus()){ //タイマーが動作中のクリック

            nowTimeLabel.setText("らぼりだ: ");

            dt = LocalDateTime.now();
            nowTime.setText(dt.format(dtf));


        }else { //タイマーが動作していない

            dt = LocalDateTime.now();
            inDate.setText(dt.format(dtf));
            nowTimeLabel.setText("現在時刻: ");

        }

    }

成果物

f:id:yamaken1343:20180424125125p:plain f:id:yamaken1343:20180424125143p:plain f:id:yamaken1343:20180424125153p:plain

github.com

一応完成ということにします

yamaken1343.hatenablog.jp

JavaFXで時間管理タイマーを作る [ボタン編]

yamaken1343.hatenablog.jp

ボタンを押したら実際に動作するところまで作ります。

コード

Controller クラス

import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.util.Duration;
import javafx.scene.control.Label;

import java.net.URL;
import java.util.ResourceBundle;

import static javafx.animation.Animation.INDEFINITE;

public class Controller implements Initializable {
    private LabT mainTimer = new LabT();
    private LabT allTimer = new LabT();
    private LabT researchTimer = new LabT();
    private LabT restTimer = new LabT();
    private LabT otherTimer = new LabT();

    private Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(1), //時間経過をトリガにするのはTimelineクラスを使う
                new EventHandler<ActionEvent>() {
                    public void handle(ActionEvent actionEvent) { //ここに書いた処理がDuration.seconds(1)で示した感覚で実行される
                        mainTimer.timeCount();
                        allTimer.timeCount();
                        researchTimer.timeCount();
                        restTimer.timeCount();
                        otherTimer.timeCount();

                        mainTimerLabel.setText(mainTimer.formatPrint());
                        allTimerLabel.setText(allTimer.formatPrint());
                        childTimerLabel1.setText(researchTimer.formatPrint());
                        childTimerLabel2.setText(restTimer.formatPrint());
                        childTimerLabel3.setText(otherTimer.formatPrint());
                    }
                }
        )
    );

    @FXML
    private Label mainTimerLabel;
    @FXML
    private Label allTimerLabel;
    @FXML
    public Label childTimerLabel1;
    @FXML
    public Label childTimerLabel2;
    @FXML
    public Label childTimerLabel3;
    @FXML
    public Button mainButton;
    @FXML
    public Label statusLabel;

    public Controller() {

    }

    public void button1Click(ActionEvent actionEvent) {
        researchTimer.active();
        restTimer.inactive();
        otherTimer.inactive();

        mainTimer.reset();
        mainTimerLabel.setText(mainTimer.formatPrint()); //0に戻ったことを表示
        statusLabel.setText("研究中");

    }

    public void button2Click(ActionEvent actionEvent) {
        researchTimer.inactive();
        restTimer.active();
        otherTimer.inactive();

        mainTimer.reset();
        mainTimerLabel.setText(mainTimer.formatPrint());
        statusLabel.setText("休憩中");

    }

    public void button3Click(ActionEvent actionEvent) {
        researchTimer.inactive();
        restTimer.inactive();
        otherTimer.active();

        mainTimer.reset();
        mainTimerLabel.setText(mainTimer.formatPrint());
        statusLabel.setText("その他動作中");

    }

    public void mainButtonClick(ActionEvent actionEvent) {
        if (mainTimer.getStatus()){ //タイマーが動作中のクリック

            researchTimer.inactive(); //タイマーがstop時、全部非アクティブ前提
            restTimer.inactive();
            otherTimer.inactive();
            allTimer.inactive();
            mainTimer.inactive();

            timeline.stop();

            mainButton.setText("らぼいん");
            statusLabel.setText("らぼりだ中");


        }else { //タイマーが動作していない
            mainButton.setText("らぼりだ");

            researchTimer.reset();
            restTimer.reset();
            otherTimer.reset();
            allTimer.reset();
            mainTimer.reset();

            mainTimerLabel.setText(mainTimer.formatPrint()); //0に戻ったことを表示
            allTimerLabel.setText(allTimer.formatPrint());
            childTimerLabel1.setText(researchTimer.formatPrint());
            childTimerLabel2.setText(restTimer.formatPrint());
            childTimerLabel3.setText(otherTimer.formatPrint());

            mainTimer.active();
            allTimer.active();
            button1Click(null); //らぼいん初回は研究中とする

            timeline.setCycleCount(INDEFINITE); //何回繰り返すか指定する
            timeline.play();

        }

    }
    @Override
    public void initialize(URL url, ResourceBundle resourceBundle) {
        mainTimerLabel.setText(mainTimer.formatPrint());
        allTimerLabel.setText(allTimer.formatPrint());
        childTimerLabel1.setText(researchTimer.formatPrint());
        childTimerLabel2.setText(restTimer.formatPrint());
        childTimerLabel3.setText(otherTimer.formatPrint());
        mainButton.setText("らぼいん");
        statusLabel.setText("らぼりだ中");
    }

}

fxml

<?import javafx.geometry.Insets?>
<?import javafx.scene.layout.GridPane?>

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<GridPane fx:controller="Controller"
          xmlns:fx="http://javafx.com/fxml" alignment="center" hgap="10" vgap="10">
    <Label text="らぼいん: " GridPane.rowIndex="0" GridPane.columnIndex="0"/>
    <Label fx:id="inDate" text="dummy" GridPane.rowIndex="0" GridPane.columnIndex="1"/>
    <Label text="らぼりだ: " GridPane.rowIndex="0" GridPane.columnIndex="2"/>
    <Label fx:id="outDate" text="dummy" GridPane.rowIndex="0" GridPane.columnIndex="3"/>
    <Label text="らぼ時間" GridPane.rowIndex="1" GridPane.columnIndex="0"/>
    <Label fx:id="allTimerLabel" GridPane.rowIndex="2" GridPane.columnIndex="1"/>
    <Label text="研究時間: " GridPane.rowIndex="3" GridPane.columnIndex="0"/>
    <Label fx:id="childTimerLabel1" GridPane.rowIndex="3" GridPane.columnIndex="1"/>
    <Label text="休憩時間: " GridPane.rowIndex="4" GridPane.columnIndex="0"/>
    <Label fx:id="childTimerLabel2" GridPane.rowIndex="4" GridPane.columnIndex="1"/>
    <Label text="その他時間: " GridPane.rowIndex="5" GridPane.columnIndex="0"/>
    <Label fx:id="childTimerLabel3" GridPane.rowIndex="5" GridPane.columnIndex="1"/>
    <Label text="状態: " GridPane.rowIndex="6" GridPane.columnIndex="0"/>
    <Label fx:id="statusLabel" text="dummy" GridPane.rowIndex="6" GridPane.columnIndex="1"/>
    <Label fx:id="mainTimerLabel" GridPane.rowIndex="7" GridPane.columnIndex="1"/>
    <Button fx:id="mainButton" GridPane.rowIndex="8" GridPane.columnIndex="0" onAction="#mainButtonClick"/>
    <Button fx:id="childButton1" text="研究" GridPane.rowIndex="8" GridPane.columnIndex="1" onAction="#button1Click"/>
    <Button fx:id="childButton2" text="休憩" GridPane.rowIndex="8" GridPane.columnIndex="2" onAction="#button2Click"/>
    <Button fx:id="childButton3" text="その他" GridPane.rowIndex="8" GridPane.columnIndex="3" onAction="#button3Click"/>


</GridPane>

解説

前回からの変更点

  • コンストラクタに処理を書いていたのを対応したボタンとイニシャライザに割り当てた
  • ボタンにonActionを追加

ボタンのクリックと関数について

  • fxmlで作成したボタンをクリックするとonActionに対応した関数が実行される
  • なのでボタンクリック時に実行したい処理を対応した関数内に書いていく
  • 引数には何が入るのかは勉強不足により不明

mainButtonClick

  • トグル的な動作をさせたいのでif文で動作を切り替えるようにしている
  • ボタンと対応させた関数も普通に呼び出すことができる

initializeのoverride

  • initializeをオーバーライドすることで起動時に行われる処理を新たに記述することができる
  • 使用にはInitializableの継承が必要

成果画面

f:id:yamaken1343:20180419161645p:plainf:id:yamaken1343:20180419161657p:plainf:id:yamaken1343:20180419161702p:plain

次回は日付部分実装したい

JavaFXで時間管理タイマーを作る [タイマー編]

yamaken1343.hatenablog.jp

時間経過に応じてカウントがインクリメントされて表示されるためのコア部分を作成する

コード(抜粋)

private LabT mainTimer = new LabT();
private LabT allTimer = new LabT();
private LabT researchTimer = new LabT();
private LabT restTimer = new LabT();
private LabT otherTimer = new LabT();

@FXML
private Label mainTimerLabel;
//略

private Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(1), //時間経過をトリガにするのはTimelineクラスを使う
         new EventHandler<ActionEvent>() {
            public void handle(ActionEvent actionEvent) { //ここのブロック内に書いた処理がDuration.seconds()で示した感覚で実行される。今回は1秒
                mainTimer.timeCount(); //一括でタイマーのカウントアップを命令する
                allTimer.timeCount();
                researchTimer.timeCount();
                restTimer.timeCount();
                otherTimer.timeCount();

                mainTimerLabel.setText(mainTimer.formatPrint()); //表示の更新
                allTimerLabel.setText(allTimer.formatPrint());
                childTimerLabel1.setText(researchTimer.formatPrint());
                childTimerLabel2.setText(restTimer.formatPrint());
                childTimerLabel3.setText(otherTimer.formatPrint());
            }
        }
));

public Controller() { //コンストラクタ
        mainTimer.active(); //カウントアップしたいタイマーのみをアクティブにする
        allTimer.active();
        researchTimer.active();

        timeline.setCycleCount(INDEFINITE); //何回繰り返すか指定する。INDEFINITEで制限なし
        timeline.play(); //動作開始
}

解説

  • コンストラクタでカウントアップしたいタイマーのみをアクティブにしLabT.statusをTrueにする、時間経過で周期的な処理を行うtimelineインスタンスを起動する
  • timelineインスタンス内で各タイマーのカウントアップ命令を行う。このとき、LabT.statusがTrueのもののみ実際にカウントアップされる。
  • 指定したラベルにタイマーの値がセットされ、表示される
  • 1秒毎に繰り返される

yamaken1343.hatenablog.jp