javaで帳票印刷 選択して印刷

選択して印刷とは

通知表が複数ページ印刷できるようになりました。でも特定の生徒のものだけ先に印刷したいとか、逆に2,3後回しにしたいということもあるでしょう。訂正があった生徒だけ印刷し直しという場面も考えられます。

下図のように選択して印刷したいというのが、このページのテーマです。

印刷する生徒をチェックボックスで選択する

一般的にはJListの使用を思いつくかもしれません。一覧からShift,Ctrlキーを併用して追加選択、範囲選択が可能です。しかし、2つの理由からcheckboxを使うことにしました。

理由1: 右側の4つのボタンの機能の実現が難しそうであった

理由2: ユーザーにCtrlを併用する選択が思いの外不評であった

右側の4つのボタンの機能は次の通り

全選択
全員にチェックを入れる
全解除
全員のチェックをはずす
反転
全員のチェックのON,OFFを逆にする。
逆順
表示の順番を逆にする。印刷順を変えることを目的としている。

これを実現するためにSelectIdNo.javaを作ります。これは選択のためだけのクラスで、これまでの帳票印刷のプログラムから呼び出して使います。単体ではテスト用データを使って動作テストを行います。

SelectIdNo.javaの動作の流れ

1. 番号をキーにした名前のマップであるnamemapを引数にしてSelectIdNo.javaのインスタンスを作る

2. それをダイアログに乗せて表示をし、ユーザーにチェックボックスで選択させる

3. getSelectedAsList()メソッドを使って選択された番号をリストとして受け取る

LinkedHashMapはputした順序が変わることはありません。生徒の番号に抜けがなければどちらを使っても同じです。0から始まるか1から始まるかの違いはありますが、換算は簡単です。しかし、今回mapで管理することにした関係でchekboxのtextに書いた番号を読み取って返しています。mapで何番目ではなく、番号を返します

画面設計

ボックスレイアウトの箱を組み合わせて配置しています。その中にチェックボックスの配列を15ずつ配置します。チェックされた項目のテキストから番号を取得しますので、チェックボックスの配置は動いても構いません。

Boxlayoutを使ったのは楽そうだったからですが、なかなか癖があって、GridBagの方がよかったかもしれません。

Boxの組み合わせ方

プログラム(SelectIdNo.java)

動作テストのため、main()(から呼ばれるcreateAndShowGUI())に名前のデータを直に書いています。

SelectIdNo.java

package print01;

import java.awt.Dimension;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JOptionPane;

//2017-5-6 from SelectHito Maximum-Minimum, add repaint() in reArrCboxes()
public class SelectIdNo extends Box implements ActionListener {
    ArrayList<JCheckBox> cboxes;
    LinkedHashMap<Integer,String> namemap;
    Box[] c15boxs;
    JButton allButton;
    JButton nonButton;
    JButton revButton;
    JButton gykButton;
    boolean gyakujun;
    //int nmax;
    public String ver = "5.1-for print01";
    /** id->SeiMei のMapを使って選択パネルを作成する */
    public SelectIdNo(LinkedHashMap<Integer,String> namemap) {
        super( BoxLayout.X_AXIS);
        this.namemap = namemap;
        cboxes = new ArrayList<JCheckBox>();
        c15boxs = new Box[3];
        for (int i=0 ;c15boxs.length>i; i++){
            c15boxs[i] = createVerticalBox();
            c15boxs[i].setAlignmentY(JComponent.TOP_ALIGNMENT );
        }
        //nmax = namemap.size(); -->> cboxes.size()
        gyakujun=false;
        int n = 0;
        for (int key : namemap.keySet()) {
            String ctxt = String.format("%2d\uu00A0%s",key,namemap.get(key)); //U+00A0 uは2つでも可
            cboxes.add(new JCheckBox(ctxt));
            c15boxs[n/15].add(cboxes.get(n));
            n++;
        }
       int cwidth = 0;
        for (int i=0 ;c15boxs.length>i; i++){
            cwidth = Math.max(c15boxs[i].getPreferredSize().width,cwidth);
            add(c15boxs[i]);
        }
        Dimension csize = new Dimension(cwidth,c15boxs[0].getPreferredSize().height);
        for (int i=0 ;c15boxs.length>i; i++){
            c15boxs[i].setPreferredSize(csize);
        }

        add(Box.createHorizontalStrut(20));

        Box buttonbox = new Box(BoxLayout.Y_AXIS);
        buttonbox.setAlignmentY(JComponent.TOP_ALIGNMENT );
        allButton = new JButton("全選択");
        nonButton = new JButton("全解除");
        revButton = new JButton("反転");
        gykButton = new JButton("逆順");
        buttonbox.add(allButton);
        buttonbox.add(Box.createVerticalStrut(8));
        buttonbox.add(nonButton);
        buttonbox.add(Box.createVerticalStrut(8));
        buttonbox.add(revButton);
        buttonbox.add(Box.createVerticalStrut(8));
        buttonbox.add(gykButton);
        allButton.addActionListener(this);
        nonButton.addActionListener(this);
        revButton.addActionListener(this);
        gykButton.addActionListener(this);
        Dimension btnsize = allButton.getPreferredSize();
        nonButton.setMaximumSize(btnsize);
        revButton.setMaximumSize(btnsize);
        gykButton.setMaximumSize(btnsize);

        add(buttonbox);
    }
    /** checkboxの並びを逆にする。actionPerformed()からのみ呼ばれる */
     private void reArrCboxes(){
        //cboxes(n):seitos(n) still
        for (int i=0 ;c15boxs.length>i; i++){
            c15boxs[i].removeAll();
        }
        for ( int n=0; cboxes.size()>n; n++ ){
            int cb = 0;
            if (gyakujun) {
                cb = cboxes.size() - n-1;
            }else {
                cb = n;
            }
            c15boxs[n/15].add(cboxes.get(cb));
        }
        validate();
        for (int i=0 ;c15boxs.length>i; i++){
            c15boxs[i].repaint();
        }
    }
    @Override
    public void actionPerformed(ActionEvent e) {
        if (e.getSource() == gykButton) {
            gyakujun = ! gyakujun;
            reArrCboxes();
        }
        else if (e.getSource() == allButton) {
            for ( int cb=0; cboxes.size()>cb; cb++ ){
                cboxes.get(cb).setSelected(true);
            }
        }
        else if (e.getSource() == nonButton) {
            for ( int cb=0; cboxes.size()>cb; cb++ ){
                cboxes.get(cb).setSelected(false);
            }
        }
        else if (e.getSource() == revButton) {
            for ( int cb=0; cboxes.size()>cb; cb++ ){
                if (cboxes.get(cb).isSelected()==true){
                    cboxes.get(cb).setSelected(false);
                }else{
                    cboxes.get(cb).setSelected(true);
                }
            }
        }
    }
    /** checkの付いたものだけのMapを返す */
    public LinkedHashMap<Integer,String> getSelected(){
        LinkedHashMap<Integer,String> temp = new LinkedHashMap<>();
        if (gyakujun) {
            for (int i=cboxes.size()-1; i>=0; i--){
                if (cboxes.get(i).isSelected()){
                    String[] tmp = cboxes.get(i).getText().split("\uu00a0",2); //U+00A0
                    //System.out.println("/"+tmp[0]+"/"+tmp[1]+"/");//test
                    temp.put(Integer.parseInt(tmp[0].trim()),tmp[1]);
                }
            }
        }else {
            for (int i=0; cboxes.size()>i; i++){
                if (cboxes.get(i).isSelected()){
                    String[] tmp = cboxes.get(i).getText().split("\uu00a0",2); //U+00A0
                    //System.out.println("/"+tmp[0]+"/"+tmp[1]+"/");//test
                    temp.put(Integer.parseInt(tmp[0].trim()),tmp[1]);
                }
            }
        }
        return temp;
    }
    /** checkの付いた番号(mapのkey)をlistにして返す */
    public LinkedList<Integer> getSelectedAsList(){
        LinkedList<Integer> temp = new LinkedList<>();
        if (gyakujun) {
            for (int i=cboxes.size()-1; i>=0; i--){
                if (cboxes.get(i).isSelected()){
                    String[] tmp = cboxes.get(i).getText().split("\uu00a0",2); //U+00A0
                    temp.add(Integer.parseInt(tmp[0].trim()));
                }
            }
        }else {
            for (int i=0; cboxes.size()>i; i++){
                if (cboxes.get(i).isSelected()){
                    String[] tmp = cboxes.get(i).getText().split("\uu00a0",2); //U+00A0
                    temp.add(Integer.parseInt(tmp[0].trim()));
                }
            }
        }
        return temp;
    }

    private static void createAndShowGUI() {
        JFrame frame = new JFrame("CheckBoxDemo");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize( 600, 400 );
        frame.setVisible( true );
        LinkedHashMap<Integer,String> namemap = new LinkedHashMap<>();
        namemap.put(1,"赤石 美緒");
        namemap.put(2,"荒川 早織");
        namemap.put(3,"石村 千尋");
        namemap.put(4,"岩崎 桃子");
        namemap.put(5,"太田 舞");
        namemap.put(6,"小山内 真紀子");
        namemap.put(7,"小山 美奈子");
        namemap.put(8,"兼平 綾乃");
        namemap.put(9,"北村 香奈");
        namemap.put(10,"工藤 彩香");
        namemap.put(11,"工藤 由佳");
        namemap.put(12,"古川 香");
        namemap.put(13,"小柳 真衣");
        namemap.put(14,"齋藤 弥生");
        namemap.put(15,"佐々木 詔子");
        namemap.put(16,"佐藤 亜希");
        namemap.put(17,"ショパン フレデリック");
        namemap.put(18,"ファインマン リチャード");
        namemap.put(19,"壱弐三 四五六七八九");
        namemap.put(20,"壱弐三 四五六七八");
        namemap.put(21,"壱弐三 四五六七");
        namemap.put(22,"相馬 静香");
        namemap.put(23,"髙谷 由紀子");
        namemap.put(24,"田中 恵美子");
        namemap.put(25,"對馬 由希子");
        namemap.put(26,"中田 範子");
        namemap.put(27,"八木橋 美沙子");
        namemap.put(28,"荒木 健吾");
        namemap.put(29,"石川 和茂");
        namemap.put(30,"葛西 俊則");
        namemap.put(31,"舘山 健太郎");
        namemap.put(32,"田中 誠");
        namemap.put(33,"鳴海 裕也");
        namemap.put(34,"西沢 和樹");
        namemap.put(35,"長谷川 弘樹");
        namemap.put(36,"原子 啓太");
        namemap.put(37,"平山 拓斗");
        namemap.put(38,"三浦 大樹");

        SelectIdNo shbox = new SelectIdNo(namemap);

        String[] printOrNot = {"印刷開始","中止"};
        int rev = JOptionPane.showOptionDialog(
                frame, shbox,
                "印刷選択 ver"+shbox.ver,
                JOptionPane.OK_CANCEL_OPTION,
                JOptionPane.PLAIN_MESSAGE,
                null,printOrNot,printOrNot[0]
        );
       if (rev==1) {
            System.out.println("Canceled");
        }
        else if (rev==0 ){
            LinkedHashMap<Integer,String> selnamemap;
            selnamemap = shbox.getSelected();
            for (int key:selnamemap.keySet()){
                System.out.println(key+" "+selnamemap.get(key));
            }
            System.out.println("-------------------- ");
            for (int no:shbox.getSelectedAsList()){
                System.out.println(no+" "+namemap.get(no));
            }
        }
        System.exit(0);
    }
    public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });
   }
}

ボタンの幅を合わせる(setMaximumSizeのみ有効)

ボタンの全選択、全解除、反転、逆順 の文字数が異なります。BorderLayoutやGridLayoutでは自動で広げて幅を揃えてくれますが、Boxでは本来の大きさらしく幅が違ってしまいます。合わせるために[全選択]の幅をgetPreferredSize().widthで取得し、setPreferredSize()で指定してみましたが機能してくれません。MinimumSize, MaximumSize も加えて実験してみます。

MinimumSize, MaximumSize, PreferredSize を指定の前後に調べます。[全選択]以外のボタンは全部2文字分とし、全部の効果を一度に比較しています

SelectIdNo.javaのコンストラクタ

int height = allButton.getPreferredSize().height;
int width  = allButton.getPreferredSize().width;
//allButton.setPreferredSize(Dimension(width, height));
//[解除]はsetPreferredSize()の効果を見ます
    System.out.println("no-mi "+nonButton.getMinimumSize().width+":"+height);//test
    System.out.println("no-ma "+nonButton.getMaximumSize().width+":"+height);//test
    System.out.println("no-pr "+nonButton.getPreferredSize().width+":"+height +"/");//test
nonButton.setPreferredSize(new Dimension(width, height));
    System.out.println("no-mi "+nonButton.getMinimumSize().width+":"+height);//test
    System.out.println("no-ma "+nonButton.getMaximumSize().width+":"+height);//test
    System.out.println("no-pr "+nonButton.getPreferredSize().width+":"+height +"/");//test
//[反転]はsetMinimumSize()の効果を見ます
    System.out.println("rv-mi "+revButton.getMinimumSize().width+":"+height);//test
    System.out.println("rv-ma "+revButton.getMaximumSize().width+":"+height);//test
    System.out.println("rv-pr "+revButton.getPreferredSize().width+":"+height +"/");//test
revButton.setMinimumSize(new Dimension(width, height));
    System.out.println("rv-mi "+revButton.getMinimumSize().width+":"+height);//test
    System.out.println("rv-ma "+revButton.getMaximumSize().width+":"+height);//test
    System.out.println("rv-pr "+revButton.getPreferredSize().width+":"+height +"/");//test
//[逆順]はsetMaximumSize()の効果を見ます
    System.out.println("gy-mi "+gykButton.getMinimumSize().width+":"+height);//test
    System.out.println("gy-ma "+gykButton.getMaximumSize().width+":"+height);//test
    System.out.println("gy-pr "+gykButton.getPreferredSize().width+":"+height +"/");//test
gykButton.setMaximumSize(new Dimension(width, height));
    System.out.println("gy-mi "+gykButton.getMinimumSize().width+":"+height);//test
    System.out.println("gy-ma "+gykButton.getMaximumSize().width+":"+height);//test
    System.out.println("gy-pr "+gykButton.getPreferredSize().width+":"+height +"/");//test
[全選択]のPreferredSizeから他の3つにSetする値を取得します
[解除]はsetPreferredSize()効果がありません
[反転]はsetMinimumSize()効果がありません
[逆順]はsetMaximumSize()効果があります

ボタンの幅を合わせる(System.out.println()で得られる値)

System.out.println()で得られる値は、それぞれ設定前は等しくなっていて、設定後はsetの通りにgetできます。ただ、効果として現れるのはsetMaximumSize()だけです。

adachi@debian64:/media/S1T/basicaka/akajava/javadabiyone$ java box.SelectIdNo 
no-mi 60:26
no-ma 60:26
no-pr 60:26/
no-mi 60:26
no-ma 60:26
no-pr 73:26/

rv-mi 60:26
rv-ma 60:26
rv-pr 60:26/
rv-mi 73:26
rv-ma 60:26
rv-pr 60:26/

gy-mi 60:26
gy-ma 60:26
gy-pr 60:26/
gy-mi 60:26
gy-ma 73:26
gy-pr 60:26/

Boxの幅を合わせる(setPreferredSizeのみ有効)

checkboxの入る3つのBoxの幅を揃えたい。見栄えもありますが、逆順にした時に幅が動くのは困るのです。

そこでgetPreferredSize()で一番の広いものを調べておき、全部その幅に設定します。これは、setPreferredSize()でうまく行きます

SelectIdNo.javaのコンストラクタ

int cwidth = 0;
for (int i=0 ;c15boxs.length>i; i++){
    System.out.println(c15boxs[i].getMinimumSize().width+":"+cwidth);//test
    System.out.println(c15boxs[i].getMaximumSize().width+":"+cwidth);//test
    System.out.println(c15boxs[i].getPreferredSize().width+":"+cwidth);//test
    cwidth = Math.max(c15boxs[i].getPreferredSize().width,cwidth);
    //if (c15boxs[i].getComponentCount()>0) add(c15boxs[i]);
    add(c15boxs[i]);
}
Dimension csize = new Dimension(cwidth,c15boxs[0].getPreferredSize().height);
for (int i=0 ;c15boxs.length>i; i++){
    System.out.println("-----"+i+"-----");
    //System.out.printf( "%2d : %4d\n",i,c15boxs[i].getComponentCount() ); //test
    System.out.println("bf-mi "+c15boxs[i].getMinimumSize().width+":"+cwidth);//test
    System.out.println("bf-ma "+c15boxs[i].getMaximumSize().width+":"+cwidth);//test
    System.out.println("bf-pr "+c15boxs[i].getPreferredSize().width+":"+cwidth);//test
    //c15boxs[i].setMinimumSize(csize);
    //c15boxs[i].setMaximumSize(csize);
    c15boxs[i].setPreferredSize(csize);
    System.out.println("af-mi "+c15boxs[i].getMinimumSize().width+":"+cwidth);//test
    System.out.println("af-ma "+c15boxs[i].getMaximumSize().width+":"+cwidth);//test
    System.out.println("af-pr "+c15boxs[i].getPreferredSize().width+":"+cwidth +"/");//test
}
setMinimumSize()使用効果がありません
setMaximumSize()使用効果がありません
setPreferredSize()使用効果があります

Boxの幅を合わせる(System.out.println()で得られる値)

最初の9行は最大幅を見つける時のもの。131,196,118が初期値で、196が最大。Minimum,Maximum,Preferredのどれも同じ値。

そのあとは設定の前後。以下は効果のあった最後のsetPreferredSize()のもので、それだけが196になり、Minimum,Maximumは変化していない。

131:0
131:0
131:0
196:131
196:131
196:131
118:196
118:196
118:196
-----0-----
bf-mi 131:196
bf-ma 131:196
bf-pr 131:196
af-mi 131:196
af-ma 131:196
af-pr 196:196/
-----1-----
bf-mi 196:196
bf-ma 196:196
bf-pr 196:196
af-mi 196:196
af-ma 196:196
af-pr 196:196/
-----2-----
bf-mi 118:196
bf-ma 118:196
bf-pr 118:196
af-mi 118:196
af-ma 118:196
af-pr 196:196/

Minimum,Maximumについても、指定したものだけが196になり他は変わらないのは同様。ただし効果がない。

幅指定のまとめ

ボタンの幅はsetMaximumSize()でなければなりません

Dimension btnsize = allButton.getPreferredSize();
nonButton.setMaximumSize(btnsize);
revButton.setMaximumSize(btnsize);
gykButton.setMaximumSize(btnsize);

Boxの幅はsetPreferredSize()でなければなりません

int cwidth = 0;
for (int i=0 ;c15boxs.length>i; i++){
    cwidth = Math.max(c15boxs[i].getPreferredSize().width,cwidth);
    add(c15boxs[i]);
}
Dimension csize = new Dimension(cwidth, c15boxs[0].getPreferredSize().height);
for (int i=0 ;c15boxs.length>i; i++){
    c15boxs[i].setPreferredSize(csize);
}

呼び出し方

各mapから選択されたものだけを抽出していままで通りのTsuuchiHRのインスタンスを作ります。

Windowsのダイアログにはcloseボタンがあります。これを使った時にも例外を起こさないようにする必要があります。

TsuuchiHR.javaのcreateAndShowGUI()内

        reqset.add(OrientationRequested.PORTRAIT);  //この下に挿入

        SelectIdNo shbox = new SelectIdNo(namemap);  //ここから追加

        String[] printOrNot = {"画面で確認","印刷開始","中止"};
        int rev = JOptionPane.showOptionDialog(
                null, shbox,
                "印刷選択 ver"+shbox.ver,
                JOptionPane.OK_CANCEL_OPTION,
                JOptionPane.PLAIN_MESSAGE,
                null,printOrNot,printOrNot[0]
        );
        LinkedList<Integer> selectedno = shbox.getSelectedAsList();
        if (selectedno.size()==0) rev = 2;
        if (rev==2 || 0>rev) { //中止:2 close:-1
            System.out.println("Canceled");
        }
        else if (2>rev){ //"画面で確認"または"印刷開始"
            //選択した生徒だけのデータにする
            LinkedHashMap<Integer,String> selnamemap = new LinkedHashMap<>();
            LinkedHashMap<Integer,LinkedHashMap<String,int[]>> selseisekimap =  new LinkedHashMap<>();
            LinkedHashMap<Integer,int[][]> selshukketsumap =  new LinkedHashMap<>();
            for (int no:selectedno){
                selnamemap.put(no,namemap.get(no));
                selseisekimap.put(no,seisekimap.get(no));
                selshukketsumap.put(no,shukketsumap.get(no));
            }
            TsuuchiHR prevPrintable = new TsuuchiHR(
                                selnamemap,selseisekimap,selshukketsumap,
                                tanis,gakki);  //printable instance この先はほとんど同じ
            if (rev==0){//画面で確認(プレビュー)
                JScrollPane scrollPane = new JScrollPane(prevPrintable);
                JPanel sbPanel = new JPanel();
                sbPanel.setLayout(new BorderLayout());
                sbPanel.add(scrollPane,BorderLayout.CENTER);
                JPanel btnPanel = new JPanel();
                sbPanel.add(btnPanel,BorderLayout.SOUTH);
                JButton upbtn = new JButton("次");
                JButton dnbtn = new JButton("前");
                upbtn.addActionListener(prevPrintable);
                dnbtn.addActionListener(prevPrintable);
                JLabel statlabel = new JLabel(String.format("%3d/%-3d",1,selseisekimap.size())); //ラベルの初期値
                statlabel.setName("status");
                prevPrintable.setStatLabel(statlabel);
                btnPanel.add(dnbtn);
                btnPanel.add(statlabel);
                btnPanel.add(upbtn);
                String[] goOrNot = {"印刷","中止"};
                JOptionPane pane = new JOptionPane(
                                   sbPanel, 
                                   JOptionPane.PLAIN_MESSAGE, 
                                   JOptionPane.DEFAULT_OPTION,
                                   null,goOrNot,goOrNot[0]
                               );
                //JDialog dialog = pane.createDialog( null, "印刷プレビュー" );
                JDialog dialog = pane.createDialog("印刷プレビュー" );
                int dh = dialog.getPreferredSize().height;
                int dw = dialog.getPreferredSize().width;
                int scrh = Toolkit.getDefaultToolkit().getScreenSize().height;
                if (dh>scrh) {
                    dialog.setPreferredSize(new Dimension(dw,scrh-40));
                }
                dialog.pack();
                dialog.setResizable(true);
                dialog.setVisible(true);
                dialog.dispose(); //needs for ends /set HIDE_ON_CLOSE by default
                String svalue = (String)pane.getValue();
                rev = 1;//印刷へ
                if (svalue==null || svalue.equals("中止")) rev = 0; // System.exit(0);
            }
            if (rev ==1){//印刷
                pj.setPrintable(prevPrintable);  //printable instance
                System.out.println(pj.getPrintService().getName()); //test
                if (pj.printDialog( reqset )) {
                    try { pj.print( reqset ); }
                    catch (PrinterException e) {
                        System.err.println(e);
                    }
                }
            }
        } //end of (2>rev)
        //dialog.dispose();
    }
}