GUI tutorial for numerical simulation (3)

シミュレーション本体の描画画面を開く

スライダなどを動かしたとき、それに応じた動作の定義は「リスナー」で行う。

以下のプログラムでは、

2つのウィンドウともアクティブにする

シミュレーションにおいて、開始ボタンをおして時間発展の描画を始めたとする。逐次実行のプログラムでは時間発展画面だけがアクティブになり、コントロールフレーム内のボタンを押しても反応してくれない。

2つのフレームを同時にアクティブにするには、時間発展計算用に新しいスレッド(thread, 櫛)を作成し(つまり処理を枝分かれさせ)、計算が進んでいる間も、メインのGUIのリスナーも活性状態にしておくという手法が取られる。

描画のためのクラスを定義する

以降のプログラムでは、

  1. GUI
  2. シミュレーションモデルの本体
  3. シミュレーション可視化(車の配置パターン描画)機能 の3つを独立なクラスとして作成する。

ここでは描画フレームの練習として、3.を新規にdrawFigure.javaという名で準備する。

プログラム例

GUI本体

/**
 *  ボタン、スライダなどでパラメータ設定するサンプル
 *  step2: ボタンやスライダにリスナーをつける
 *  step3: シミュレーション動画用ウィンドウを開く(別スレッド)
 **/

import java.awt.*;
import javax.swing.*;

import java.awt.event.*;       // リスナー用
import javax.swing.event.*;

// Runnableインターフェイスを装着(インプリメント)step3
public class sample_gui extends JPanel implements Runnable {

    /******* step3:    for thread ***************/
    private Thread myTh;
    private boolean halt_;

    /** グラフィック関係変数 */
    JButton startBtn, stopBtn, exitBtn;       // ボタン
    JLabel label_1, label_2;                  // ラベル
    JSlider slider_1, slider_2;                // スライダ

    /** 各種グローバル */
    static int maxSize = 600; // width of window in pixels
    static double scale_2 = 100;  // スライダ2の値の単位

    /** step3: シミュレーション描画用 中身はdrawFigure.javaで定義 */
    drawFigure simulationFrame;


    /************* コンストラクタ ************************/
    sample_gui() {
        /** step3: シミュレーションフレームの生成 */
        simulationFrame = new drawFigure(500); // サイズを引数に

        /** パラメータ初期値 */
        int  ini_1 = 500, ini_2 = 10;

        /** ボタン生成 */
        startBtn = new JButton("start");
        stopBtn = new JButton("stop");
        exitBtn = new JButton("終了");
        /** スライドバー生成 (値は整数)*/
        slider_1 = new JSlider(JSlider.HORIZONTAL, 0, 1000, ini_1);
        slider_2 = new JSlider(JSlider.HORIZONTAL, 0, 100, ini_2);

        /** ラベル生成 */
        label_1 = new JLabel(String.valueOf(ini_1));
        label_2 = new JLabel(String.valueOf(ini_2/scale_2));


        /** スライダーやボタンにリスナーをつける */
        // step3
        startBtn.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent ae) {
                    /**
                     * startボタンが押されたら、ゲームオブジェクトを作成し、
                     * スレッドをスタートさせる
                     */
                    thread_start();
                }
            });

        exitBtn.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent ae) {
                    System.exit(0);
                }
            });

        slider_1.addChangeListener(new ChangeListener() {
                public void stateChanged(ChangeEvent e) {
                    label_1.setText(Integer.toString(slider_1.getValue()));
                }
            });
        slider_2.addChangeListener(new ChangeListener() {
                public void stateChanged(ChangeEvent e) {
                    label_2.setText(Double.toString(slider_2.getValue()/scale_2));
                }
            });

        /***********************************************/

        /** パネルにスライドバー、ラベルを貼る */
        JPanel panel_operation = new JPanel(); //ボタン3つを一つのJPanelに
        panel_operation.add(startBtn);
        panel_operation.add(stopBtn);
        panel_operation.add(exitBtn);

        JPanel panel_1 = new JPanel(); // ラベル、スライダ、値表示を一つのJpanelに
        panel_1.add(new JLabel("パラメータ1"));
        panel_1.add(slider_1);
        panel_1.add(label_1);

        JPanel panel_2 = new JPanel();
        panel_2.add(new JLabel("パラメータ2"));
        panel_2.add(slider_2);
        panel_2.add(label_2);

        /** 親パネルにボタンと、スライドバー・ラベル を並べる */
        JPanel p = new JPanel();
        p.setLayout(new GridLayout(2, 2)); // 2行2列に設定
        p.add(panel_operation);            // 上で作ったJPanelを並べる
        p.add(panel_1);
        p.add(panel_2);

        add(p, BorderLayout.NORTH);       // 親フレームに置く

    }

    /** main */
    public static void main(String[] args) {

        // パラメータセットのGUIを作成し、表示
        JFrame setParamFrame = new JFrame("パラメータセット"); // 親フレーム
        sample_gui ctr = new sample_gui();  // このクラスの実体を作る(実体名: ctr)
        setParamFrame.getContentPane().add(ctr, BorderLayout.CENTER); // 親フレームに「実体」を貼る
        setParamFrame.setSize(750, 100);    // フレームの大きさ設定
        setParamFrame.setLocation(600, 20); // フレームを置く位置
        setParamFrame.setVisible(true);     // 見えるようにする
    }

    /*********************step3での追加 *****************************/
    /** GUIで操作できるようにrunnableインターフェイスに基づいてスレッドを操作をする */
    public void thread_start() { /** startボタンにより呼び出す */
        halt_ = false;
        myTh = new Thread(this);
        myTh.start(); /** start()によりrun()が実行される */
    }

    public void stop() { /** stopボタンで呼び出す */
        halt_ = true;
        myTh.interrupt();
    }

    /** 新しいスレッドの実体 */
    public void run() {

        this.simulationFrame.drawCircles();

    }
}

/**
  step4での課題:

  シミュレーションの本体部分のクラスを作成し、
  それを描画用オブジェクトとつなげて、スナップショットを描く

*/

描画用クラス定義 drawFigure.java

// /* シミュレーションのスナップショット描画 */
import java.io.*;
import java.awt.*; // uses the abstract windowing toolkit
import javax.swing.*;
import javax.imageio.ImageIO;         // スナップショットのセーブなどに利用
import java.awt.image.BufferedImage;

public class drawFigure extends JPanel {

    Graphics g;              // 車の配置などを書き込むカンバス
    JFrame gWin;
    double screenWidth;
    int frameTop, frameLeft; // platform-dependent width of frame's top and left
    int marginX=20, marginY=20;

    /** コンストラクタ */
    drawFigure(int fSize) {
    	screenWidth = (double) fSize;

    	// シミュレーション本体画面の初期化
    	gWin = new JFrame("Trafic Flow Model");
    	gWin.setLocation(20, 150); // screen coordinates of top left corner
    	gWin.setResizable(false);
    	gWin.setVisible(true); // show it!

	/** 下30ピクセルは文字出力用 inset**/
    	Insets theInsets = gWin.getInsets();
    	gWin.setSize(fSize + theInsets.left + theInsets.right + marginX*2, fSize
		     + theInsets.top + marginY*2+ theInsets.bottom+30);
    	this.frameTop = theInsets.top;
    	this.frameLeft = theInsets.left;
    	this.g = gWin.getGraphics(); // 画像をここに書き込む
    }

    public void drawCircles() {

	// 画面を白で塗りつぶす
    	g.setColor(new Color(255,255,255));
    	g.fillRect(0, 0, gWin.getWidth(),gWin.getHeight());

	// 適当に丸を書く
    	for(int n = 0; n<3;n++){
	    double centerX = Math.random();
	    double centerY = Math.random();
	    this.g.setColor(new Color(255,0,0)); // 線の色
	    double nX =  screenWidth * centerX +this.frameLeft+this.marginX;
	    double nY =  screenWidth * centerY +this.frameTop + this.marginY;
	    this.g.fillOval((int) nX, (int) nY, 30, 30);
	}
    }
}

グラフィックスメソッド

Graphicsクラスの中に、基本的な図形(線、楕円、長方形など)の描画メソッドが用意されている。 詳しくは、マニュアルを参照してほしい。 https://docs.oracle.com/javase/jp/7/api/java/awt/Graphics.html

など。