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を作りません。
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 宛名を縦書きにし、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()は最初の部分が二項目だけ変わります
- ページ番号を引数として受け取って、そのページに書くべき宛名を読み出します。
- スピナーによる微調整値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の実行結果
まとめとプログラム一覧
今回のまとめ
印刷に使う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*:
複数の宛名プログラム本体(横書き)