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*:
複数の宛名プログラム本体(横書き)
