SHOEISHA iD

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

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

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

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

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

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

AddressListクラス

 住所、宛名のデータが複数になるので、それをどのように用意するかというのが問題になります。記事の中心は帳票印刷なので、なるべく簡単にしかも独立させて、お好みのものと差し替えるのを楽にしたいと思います。

 元々のデータをはタブ区切りのUTF-8テキストファイルとします。

 ファイルの保存場所はカレントディレクトリとします(kakuujusho.txt)。

#郵便番号   住所  氏名
252-0314	神奈川県相模原市南区南台6-8-10 三上ハイツ304	齋藤梨花
037-0202	青森県五所川原市金木町朝日丘66番地67	赤石美緒
038-0212	青森県南津軽郡大鰐町大字角館字山中七番地の7	荒川諭子
037-0024	青森県五所川原市みどり野三丁目60番地	井澤麻美
036-0351	青森県黒石市大字黒石字十面沢154番地9	工藤亜紗美
...以下略

 ファイルの指定は、次のstaticメソッドを使いました。API仕様ではあまりお勧めでないようではあります。

java.nio.file.Paths.get(Stringfirst,
                       String... more)

 ファイルのopenもstaticメソッドです。try−with−resources文でcloseを省略します。

java.nio.file.Files.newBufferedReader(Path path)

 読み込んだ後、split()でフィールドを分けてPersonクラスのインスタンスを生成してArrayListに追加しています。先頭が#なものはコメントとして、項目数が3に満たないものは不正データとして読み飛ばします。ファイル内の空行などで不具合が生ずるのを防ぎます。

 Presonクラスはここで定義しています。あえてgetter、setterを作りません。

AddressList.java
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import java.util.ArrayList;

class Person {
    String zip;
    String addr;
    String name;
    public Person(String zip, String addr, String name){
        this.zip = zip;
        this.addr = addr;
        this.name = name;
    }
}
public class AddressList {
    public static List<Person> readTextFile(String fileName) {
        List<Person> list = new ArrayList<>();
        try(BufferedReader br = Files.newBufferedReader(Paths.get(fileName))){
            String line;
            while ((line = br.readLine()) != null) {
                if (line.startsWith("#")) continue;
                String[] tmp = line.split("\t");
                if (3>tmp.length) continue;
                list.add(new Person(tmp[0],tmp[1],tmp[2]));
            }
        }catch(IOException e){
            System.err.println( e);
        }
        return list;
    }
    public static void main( String[] args ) {
        List<Person> ls = AddressList.readTextFile("kakuujusho.txt");
        for( Person hito:ls ){
            System.out.println(hito.addr);
        }
    }
}

複数の宛名プログラム本体(横書き)

 MultiPageViewableインターフェースを持った、「PrintableWithPreview04.java」の発展版です。縦書きも作る計画なので、若干の手直しもあります。名前も大きく変えました。

PostcardsJushoHor.java
/** PostcardsJushoHor.java
 宛名を縦書きにし、1行に入り切らない時は複数行に分けます。
 プレビューはMultiPageDialogを適用
 枠に合わせて描画も拡大・縮小、JScrollPaneも使用します。
 AddressListを使って複数の宛名を扱います。
 2019-11-06
 @auther Adachi Junichi
*/
import java.awt.*;
import java.awt.print.*;
import javax.print.*;
import javax.print.attribute.*;
import javax.print.attribute.standard.*;
import javax.swing.*;
import java.util.List;

public class PostcardsJushoHor
               extends JPanel
               implements Printable,MultiPageViewable { //①
    List<Person> hitos;
    int previewpage = 0;
    double wZh;
    Dimension motodims  = new Dimension();
    Dimension scaledims = new Dimension();
    float mm2pt = 72/25.4f;
    double trdx = 0;
    double trdy = 0;
    PrintRequestAttributeSet rqset ;
    double ratio = 1.0d; //用紙サイズに応じてsetPanelSize()で再設定

    public PostcardsJushoHor(List<Person> hitos){ //②
        this.hitos = hitos;
        setBackground(Color.white);
        rqset = new HashPrintRequestAttributeSet();
        MediaSizeName msname = MediaSizeName.JAPANESE_POSTCARD;
        rqset.add(msname);
        MediaSize msize = MediaSize.getMediaSizeForName(msname);
        float mwidth  = msize.getX(MediaPrintableArea.MM);
        float mheight = msize.getY(MediaPrintableArea.MM);
        float L = 5.1f;
        float R = 5.2f;
        float T = 5.3f;
        float B = 5.4f;
        rqset.add(new MediaPrintableArea(
            L, T, (mwidth - L - R), (mheight - T - B),
            MediaPrintableArea.MM));
        setPanelSize(mwidth,mheight);
    }
    public void setPanelSize(float w,float h){
        motodims.setSize(w*mm2pt,h*mm2pt); 
        setPreferredSize(motodims);
        wZh = motodims.getWidth()/motodims.getHeight();
        ratio = 2.0d; //葉書は小さいので2倍で表示
    }
    @Override
    public double getRatio(){
        return ratio;
    }
    @Override //①
    public int incPreviewPage(){
        previewpage++;
        if (previewpage>=hitos.size()) previewpage=0;
        repaint();
        return previewpage;
    }
    @Override
    public int decPreviewPage(){
        previewpage--;
        if (0>previewpage) previewpage = hitos.size()-1;
        repaint();
        return previewpage;
    }
    @Override
    public int number(){
        return hitos.size();
    }
    @Override
    public String getStatus(){
        String retv;
        if(hitos.size()==0){
            retv ="....";
        }
        else{
             retv = (previewpage+1)+"/"+hitos.size();
        }
        return retv;
    }
    @Override
    public String getCurrentData(){
        String retv;
        Person hito = hitos.get(previewpage);
        retv = hito.zip+" "+hito.addr+"/"+ hito.name;
        return retv;
    }
    //spinner-bidou
    @Override
    public void setTrdx(double x){
        trdx = x*mm2pt;
    }
    @Override
    public void setTrdy(double y){
        trdy = -y*mm2pt;
    }

    @Override
    public void paintComponent(Graphics g){ //③
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g;
        int viewpw = getParent().getWidth();
        int viewph = getParent().getHeight();
        JScrollPane sp = (JScrollPane) getParent().getParent();
        int hbarh = sp.getHorizontalScrollBar().getHeight();
        int vbarw = sp.getVerticalScrollBar().getWidth();
        boolean hbarAri = sp.getHorizontalScrollBar().isVisible();
        boolean vbarAri = sp.getVerticalScrollBar().isVisible();
        if (vbarAri) viewpw += vbarw;
        if (hbarAri) viewph += hbarh;
        double scale;
        if((viewpw-vbarw)/(double)viewph > wZh){
            scaledims.setSize(viewpw-vbarw, (viewpw-vbarw)/wZh);
            scale = (viewpw-vbarw)/motodims.getWidth();
        }else if( wZh > (double)viewpw / (viewph-hbarh)  ){
            scaledims.setSize((viewph-hbarh)*wZh, viewph-hbarh);
            scale = (viewph-hbarh)/motodims.getHeight();
        }else{
            if ( (double)viewpw/viewph > wZh ) {
                scaledims.setSize(viewph*wZh, viewph);
                scale = viewph/motodims.getHeight();
            }else{
                scaledims.setSize(viewpw, viewpw/wZh);
                scale = viewpw/motodims.getWidth();
            }
        }
        g2.scale(scale,scale);
        setPreferredSize(scaledims);
        drawPage(g2,previewpage); //③
    }
    @Override
    public int print(Graphics g, PageFormat pf, int pageIndex) { //④
        if (pageIndex >= hitos.size()) return NO_SUCH_PAGE;
        Graphics2D g2 = (Graphics2D) g;
        drawPage(g2,pageIndex);  //④
        return PAGE_EXISTS;
    }
    public void drawPage(Graphics2D g2,int idx){  //⑤
        g2.translate(trdx,trdy); 
        String zip = hitos.get(idx).zip;
        String addr = hitos.get(idx).addr;
        String name = hitos.get(idx).name;

        g2.setColor(Color.BLACK);
        //w:width ; v:virtical width ; pch:pitch
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                            RenderingHints.VALUE_ANTIALIAS_ON);
        float xzip1 = 44.0f;
        float xzip2 = 66.0f;
        float xpch1 = 13.9f/2;
        float xpch2 = 20.5f/3;
        float wzbox = 5.7f;
        float yzip  = 20.3f;
        float vzip  = 8.3f;
        g2.setFont(new Font("SansSerif", Font.PLAIN, 16));

        float padb = 1.5f; //yzipそのままだと下線に接するのでもどす値(mm)
        AdjustString ads;
        for(int i=0; 3>i; i++){
            ads = new AdjustString(g2, zip.substring(i,i+1),wzbox);
            ads.drawCenter(xzip1 + xpch1*i, yzip - padb);
        }
        for(int i=0; 4>i; i++){
            ads = new AdjustString(g2, zip.substring(i+4,i+1+4),wzbox);
            ads.drawCenter(xzip2 + xpch2*i, yzip - padb);
        }
        //float charh = g2.getFontMetrics().getFont().getSize()/mm2pt;
        float xaddr = xzip1-2.0f;
        float yaddr = yzip +vzip*1.5f;
        float ypch  = vzip;
        float waddr = xpch1*3+xpch2*4;
        g2.setFont(new Font("SansSerif", Font.PLAIN, 14));
        AfureString afs;
        afs = new AfureString(g2,addr,waddr);
        afs.drawLeft(xaddr, yaddr);
        int ct = 0;
        while(afs.hasNext()){
            afs.nextLine();
            afs.drawRight(xaddr, yaddr + ypch*(++ct));
        }
        g2.setFont(new Font("SansSerif", Font.PLAIN, 16));
        ads = new AdjustString(g2,name+"様",waddr);
        ads.drawKintou(xaddr, yaddr + ypch*(++ct + 0.5f));
    }
    public void doPrint() { //⑥
        PrinterJob pj = PrinterJob.getPrinterJob();
        pj.setPrintable(this);
        if (pj.printDialog(rqset)) {
            try { pj.print(rqset); }
            catch (PrinterException e) {
                System.err.println(e);
            }
        }
    }

    public static void main(String[] args) { //⑦
        javax.swing.SwingUtilities.invokeLater(new Runnable(){
            public void run() {
                createAndShowGUI();
            }
        });
    }
    public static void createAndShowGUI() { //⑧
        List<Person> ls = AddressList.readTextFile("kakuujusho.txt");
        PostcardsJushoHor pable = new PostcardsJushoHor(ls);
        MultiPageDialog dialog = new MultiPageDialog(null,true,pable);
        dialog.showDialog();
        while (dialog.getValue()==1){
            System.out.println("印刷を開始");
            pable.doPrint();
            System.out.println("印刷を終了");
            dialog.showDialog();
        }
        System.out.println("Canseled:"+dialog.getValue());
        dialog.dispose();
    }
}

PostcardsJushoHor.javaの解説

 「PostcardsJushoHor.java」の「PrintableWithPreview04.java」からの変更点。

① implements MultiPageViewable

 MultiPageViewableインターフェースで定められたメソッドを持っています。特に、現在のページがpreviewpageというフィールド値に格納されて使われます。

② コンストラクタ

 引数がPersonクラスのListに変わります。

 main()にあった印刷用紙の指定などPrintRequestAttributeSetまわりの設定をここに移しました。文字や罫線の描画も用紙サイズと不可分ですら、ここにあるのはふさわしいことです。

③ paintComponent()はほとんど変わらない

 drawPageを呼び出す時の引数に現在のページpreviewpageを加えます。printから呼ばれるときにはプリント用のページ構成があるので、フィールド変数ではありますが、引数にします。

④ print()もほとんど同じ

 もともと呼ばれる時にページ数が入ってきます。それをそのままdrawPageを呼び出す時の引数にするだけです。

⑤ drawPage()は最初の部分が二項目だけ変わります
  1. ページ番号を引数として受け取って、そのページに書くべき宛名を読み出します。
  2. スピナーによる微調整値trdx,trdyでg2.translate(trdx,trdy)します。ありがたいことにこれだけで全体がシフトしてくれます。
⑥ doPrint()

 main()にあったものを移動し1つのメソッドとしました。main()から呼び出します。

⑦ main()

 swingのスレッドポリシーに従って、invokeLater()で起動するようにしました。

⑧ createAndShowGUI()

 これもswingのスレッドポリシーのためにstaticメソッドとしていますが、本来はmain()の中です。

 ここから複数の宛名を読み(AddressListクラス)、Printableなインスタンスを生成し(PostcardsJushoHorクラス)、ダイアログ(MultiPageDialog)を出します。

 ダイアログから戻ったらgetValue()で値を調べます。1は、印刷を選択したということです。その場合doPrint()を呼び出して印刷し、再びダイアログを表示します。getValue()が1でなくなるまで繰り返します。

 繰り返しになっているのは、JSpinnerで印刷位置微調整するために1枚印刷してみて変更するという繰り返しが必要だからです。1枚だけ印刷というのは、doPrint()で出す印刷ダイアログで描画ページを指定することで行います。

PostcardsJushoHorの実行結果

PostcardsJushoHorの実行結果
PostcardsJushoHorの実行結果

まとめとプログラム一覧

今回のまとめ

 印刷に使うprintableなクラスにextens JPanelを加え、描画部分を共通にすることでプレビューが可能であることを示しました。

 住所の自動区切りプログラムAfureStringを導入してプレビューも拡大できるように改良しました。

 複数人数の印刷のためにページを切り替えるプレビューができる独自ダイアログを作成しました。このダイアログを使いまわすためにMultiPageViewableというインターフェースを定義しました。

 複数の宛名を渡すためにPersonというクラスを定義し、タブ区切りのファイルを読んでそのList返すプログラムを作りました。

 MultiPageViewableを実装した最初のプログラムであるPostcardsJushoHor.javaを作り、住所録ファイルから全員の印刷プレビューで確認し、一括で印刷できるようにしました。

 次回はこれを縦書きにします。

 この回で掲載したプログラムリストの一覧を示します。*印をつけて強調しているのは、今後の連載で部品として使う可能性のあるものや改良・追加・発展が考えられるプログラムです。

プログラム一覧

  • PrintableWithPreview01.java:
    横書き+AdjustStringでプレビュー簡易版導入。
  • AfureString.java*:
    住所の自動区切りプログラム。
  • PrintableWithPreview04.java:
    AfureString.javaを使用し、プレビューも拡大できるようにする。
  • MultiPageDialog.java*:
    プレビューのための独自ダイアログ。複数のベージを切り替えて確認でき、印刷位置の微調整もする。
  • MultiPageViewable.java*:
    MultiPageDialogクラスのコンストラクタ引数に使用される描画データを持つクラスが準拠すべきインターフェースの定義。
  • AddressList.java*:
    引数文字列のファイルから住所録のデータが入ったPersonクラスのListを作成する。Personクラスの定義も含まれる。
  • kakuujusho.txt:
    架空の住所録(の一部)。タブ区切り。
  • PostcardsJushoHor.java*:
    複数の宛名プログラム本体(横書き)

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

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

もっと読む

この記事の著者

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

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

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

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

この記事をシェア

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

おすすめ

アクセスランキング

アクセスランキング

イベント

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

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

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

メールバックナンバー

アクセスランキング

アクセスランキング