SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

Javaの標準機能だけで実現する帳票印刷

宛名印刷と印刷プレビュー

Javaの標準機能だけで実現する帳票印刷 第5回

  • X ポスト
  • このエントリーをはてなブックマークに追加

複数の宛名をまとめて確認し、一括で印刷したい

 複数の宛名を印刷する場合に1枚ずつの確認と印刷では手間が増えます。全ページを確認して一度のプリンタ選択で全部を印刷したくなります。

 複数の宛名の確認のためにダイアログに[次][前]そして[印刷開始]のボタンが必要です。

 前のプログラムではJOptionPaneをアレンジして、そこからJDialogを生成しました。これでもボタンは自由に追加できますが、ボタンクリックで呼び出し元に戻ってしまいます。そこで今度はJDialogを拡張したクラスを作り、呼び出し元に戻らずにダイアログ内で宛名を切り替えるようにします。

 今後のためにJDialogを継承したクラスとして作成します。

JDialogを継承するMultiPageDialogのレイアウト
JDialogを継承するMultiPageDialogのレイアウト

 ボタンが4つ。[次]、[前]はダイアログを閉じずに、ベージを移動します。[印刷]と[戻る]はダイアログを閉じ、JDialogのようにgetValue()の値で印刷指示であることを伝えるものとします。

 vspinnerとhspinnerはJSpinnerのインスタンスで、文字や罫線の描かれる位置を0.1mm単位で微調整します。調整のためには実際に1枚印刷してみて変更するという繰り返しが必要ですが、一応画面でも動いて調整の方向が確認できるようになっています。1枚だけ印刷するには、印刷ダイアログで描画ページの範囲を指定することで行います。

 NORTHに配置したcurrentlabelはJPanelに描画する前のデータを示し、正しく描画されていることを確認するため設けたものです。

MultiPageDialog.java
/**
MultiPageViewableな複数データのプレビューのためのダイアログ
ページをめくってレイアウトを確認できる
拡大・縮小・スクロールができる
印刷位置の微調整ができる
印刷の指示は一括でおこなう
2019-12-02
@author Adachi Junichi
@version 1.0
*/
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSpinner;
import javax.swing.SpinnerModel;
import javax.swing.SpinnerNumberModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.JSpinner.NumberEditor;

public class MultiPageDialog extends JDialog
                 implements ActionListener,ChangeListener{ //①
    int retvalue =0;
    MultiPageViewable pable; //②
    JLabel statlabel;
    JLabel currentlabel;
    JSpinner vspinner,hspinner;
    public MultiPageDialog(JFrame fr, boolean modal, MultiPageViewable pable) { //②
        super(fr,modal);
        //Dialog(Frame owner, String title, boolean modal)
        this.pable = pable;
        //set 3 components in Dialog
        //NORTH CENTER EAST
        setLayout(new BorderLayout());
        currentlabel = new JLabel(pable.getCurrentData()); //③
        add(currentlabel,BorderLayout.NORTH);
        JScrollPane scrollPane = new JScrollPane((JPanel)pable); //②
        double enlg = pable.getRatio();
        if(enlg>0 && enlg!=1d){
            double pw = ((JPanel)pable).getPreferredSize().getWidth();
            double ph = ((JPanel)pable).getPreferredSize().getHeight();
            Dimension tmpd = new Dimension();
            tmpd.setSize(pw*enlg,ph*enlg);
            scrollPane.setPreferredSize(tmpd);
        }
        add(scrollPane,BorderLayout.CENTER);
        JPanel btnpanel = new JPanel();
        add(btnpanel,BorderLayout.EAST);

        //in EAST set buttons etc. for btnpanel 
        statlabel = new JLabel(pable.getStatus());
        JButton nextbtn = new JButton("次");
        nextbtn.addActionListener(this);
        nextbtn.setActionCommand("next");
        JButton prevbtn = new JButton("前");
        prevbtn.addActionListener(this);
        prevbtn.setActionCommand("prev");
        JButton printbtn = new JButton("印刷");
        printbtn.addActionListener(this);
        printbtn.setActionCommand("print");
        JButton quitbtn = new JButton("戻る");
        quitbtn.addActionListener(this);
        quitbtn.setActionCommand("quit");
        int pgs = pable.number(); //④
        if (2>pgs) {
            nextbtn.setEnabled(false);
            prevbtn.setEnabled(false);
        }
        //in EAST set spinners for btnpanel 
        JLabel vlabel = new JLabel("上へ(mm)");
        JLabel hlabel = new JLabel("右へ(mm)");
        SpinnerModel vmodel =
                 new SpinnerNumberModel(0d, -10d, +10d, 0.1d);
        SpinnerModel hmodel =
                 new SpinnerNumberModel(0d, -10d, +10d, 0.1d);
        vspinner = new JSpinner(vmodel);
        hspinner = new JSpinner(hmodel);
        NumberEditor veditor = new NumberEditor(vspinner, "##0.0");
        NumberEditor heditor = new NumberEditor(hspinner, "##0.0");
        veditor.getTextField().setColumns(5);
        heditor.getTextField().setColumns(5);
        vspinner.setEditor(veditor);
        hspinner.setEditor(heditor);
        vspinner.addChangeListener(this);
        hspinner.addChangeListener(this);
        //in EAST add buttons,spinners, label to btnpanel
        GridBagLayout gridbag = new GridBagLayout();
        btnpanel.setLayout(gridbag);
        btnpanel.add(statlabel);
        btnpanel.add(nextbtn);
        btnpanel.add(prevbtn);
        btnpanel.add(vlabel);
        btnpanel.add(vspinner);
        btnpanel.add(hlabel);
        btnpanel.add(hspinner);
        btnpanel.add(printbtn);
        btnpanel.add(quitbtn);
        GridBagConstraints c = new GridBagConstraints();
        c.gridx = 0 ;
        c.anchor=GridBagConstraints.NORTH;
        c.insets = new Insets(2, 4, 2, 4);
        c.fill = GridBagConstraints.HORIZONTAL;
        gridbag.setConstraints(statlabel, c);
        gridbag.setConstraints(nextbtn, c);
        gridbag.setConstraints(prevbtn, c);
        gridbag.setConstraints(vlabel, c);
        gridbag.setConstraints(vspinner, c);
        gridbag.setConstraints(hlabel, c);
        gridbag.setConstraints(hspinner, c);
        gridbag.setConstraints(printbtn, c);
        c.weighty = 1.0;
        gridbag.setConstraints(quitbtn, c);

        //ajust to screen size 
        int h = getPreferredSize().height; //⑤
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        int scrw = screenSize.width;
        int scrh = screenSize.height;
        if (h>scrh) {
            setPreferredSize(new Dimension(scrw*2/3,scrh*2/3));
        }
        pack();
        setResizable(true);
    }
    @Override
    public void stateChanged(ChangeEvent e){ //⑥
        pable.setTrdx((double)hspinner.getValue());
        pable.setTrdy((double)vspinner.getValue());
        //repaint();
        ((JPanel)pable).repaint();
    }
    public void showDialog(){ //⑦
        retvalue = -1;
        setVisible(true);
    }
    public int getValue(){ //⑧
        return retvalue;
    }
    @Override
    public void actionPerformed(ActionEvent e){
        String command = e.getActionCommand();
        if (command.equals("next")){ //⑨
            int page = pable.incPreviewPage();
            statlabel.setText(pable.getStatus());
            currentlabel.setText(pable.getCurrentData());
        }
         else if (command.equals("prev")){
            int page = pable.decPreviewPage();
            statlabel.setText(pable.getStatus());
            currentlabel.setText(pable.getCurrentData());
        }
        else if (command.equals("print")) { //⑩
            retvalue = 1;
            setVisible(false);
        }
        else if (command.equals("quit")) {
            retvalue = 0;
            setVisible(false);
        }
    }
}

MultiPageDialog.javaの詳しい解説

 丸付き数字はプログラム中の部分を指します。

① ChangeListener

 ChangeListenerはJSpinnerの値が変化した時を検知するためのイベントリスナです

② MultiPageViewableインターフェース

 ここは従来PrintableWithPreview04などのJPanelの拡張でPrintableなクラスのインスタンスにでしたが、結局は自分自身でした。今回はDialog部分を独立させたので異なるクラスのインスタンスを組み合わせることになります。そして今後縦書きの宛名も考えていきますから、ここをインターフェースにしておいて、異なるクラスでも同じインターフェースを持つように設計すれば、MultiPageDialogはそのまま使えるということにできます。MultiPageViewableはインターフェース名です。

 インターフェースにして、1つだけ不都合があります。それはJScrollPaneに格納する時にJPanelのようにComponentでなければならないということです。そこで(JPanel)でキャストしています。

③ getCurrentData()

 MultiPageViewableなインスタンスにはデータを提供するメソッドがいくつかあります。getCurrentData()は現在のページの文字列データです。

④ number()

 2つめのデータ提供メソッドです。

 number()はページ数を返します。2ページ以下の場合[次][前]のボタンを使用不可にします。

⑤ getPreferredSize()

 ダイアログの大きさを調整します。

 ダイアログの大きさが画面を越えると扱いが面倒になるので、調整です。プレビュー部分はスクロールで対応するはずです。

⑥ stateChanged()

 JSpinnerの値が変化するとこのメソッドが呼び出されます。

 JButtonとactionPerformed()の関係と同じです。内容はMultiPageViewableなインスタンスにセットします。MultiPageViewable側ではJPanelの再描画時にこの値を使うので、repaint()で再描画を促します。repaint()だけでも、JDialogとその中のすべての部品に再描画の要求が届きます。しかし、一度(JPanel)のキャストを使っているので、ここでも((JPanel)pable).repaint()として必要なものだけ狙い撃ちします。.repaint()の前に( )が必要です。

⑦ showDialog()

 このダイアログを表示させます。

 表示させると、setVisible(false)になるまで処理は止まります。retvalue = -1 はダイアログのクローズボタン[×]で処理が呼び出し側に戻った時に、それが分かるようにするためのものです。

⑧ getValue()

 retvalueの値を返します。ダイアログが生成された時点で0、可視化で-1、[印刷]ボタンで1、[戻る]ボタンで0となります。ダイアログが不可視になって制御がもどってから呼び出されて、印刷に進むかどうかを判断するためです。

⑨ incPreviewPage()など

 [次]ボタンを押されたときには、MultiPageViewableなインスタンスにそれを知らせ、新しいページ位置(3/24など)と文字列データを得て、それぞれのラベルにセットします。

 [前]でも同様にします。

⑩ setVisible(false)

 retvalueの値を[印刷]ボタンで1、[戻る]ボタンで0にしてsetVisible(false)で不可視にし、制御を呼び出し側に戻します。

MultiPageViewableインターフェース

 いままで作ってきたクラスは extends JPanel implements Printable なものでした。ここからDialog部分を分離独立させ MultiPageDialogクラスを作ったわけです。

 このMultiPageDialogクラスをいろいろな帳票に使いまわしするために、インターフェースを定義することにします。名前をMultiPageViewableとします。

MultiPageViewable.java
/** MultiPageDialogクラスのコンストラクタ引数に
使用される描画データを持つクラスが準拠すべきインターフェース。
これに加えて、extends JPanel でなければならない。
2019-11-28
@auther Adachi Junichi
*/
interface MultiPageViewable {
    //extends にはインターフェースしか書けない
    /** 表示するページの初期拡大率。*/
    public double getRatio();
    /** 表示するページ数を次のページにする。*/
    public int incPreviewPage();
    /** 表示するページ数を前のページにする。*/
    public int decPreviewPage();
    /** ページ数を返す。1ページしかなければ、次・前を無効にする*/
    public int number();
    /** 2/30 など現在のページの位置を表す文字列を返す */
    public String getStatus();
    /** 現在のページ関連付けられた文字列を返す
     元々、描画されたものが正しいかを確認するためのものであった */
    public String getCurrentData();
    /** 描画位置をx方向にずらす値(単位はmm)を受け取る*/
    public void setTrdx(double x);
    /** 描画位置をy方向にずらす値(単位はmm)を受け取る*/
    public void setTrdy(double y);
}

次のページ
AddressListクラス

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
Javaの標準機能だけで実現する帳票印刷連載記事一覧

もっと読む

この記事の著者

安達 順一(アダチ ジュンイチ)

私立高校に理科・情報の教員として勤めていました。Linuxサーバー/クライアントの授業システムを作り、移動プロファイルで運用していました。教員用にもLinuxサーバーを用意し成績処理プログラムを書きました。情報の学校設定科目ではウェブページ制作とjavaのプログラミングの初歩の授業を作りました。情報...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/11894 2020/07/08 11:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング