複数の宛名をまとめて確認し、一括で印刷したい
複数の宛名を印刷する場合に1枚ずつの確認と印刷では手間が増えます。全ページを確認して一度のプリンタ選択で全部を印刷したくなります。
複数の宛名の確認のためにダイアログに[次][前]そして[印刷開始]のボタンが必要です。
前のプログラムではJOptionPaneをアレンジして、そこからJDialogを生成しました。これでもボタンは自由に追加できますが、ボタンクリックで呼び出し元に戻ってしまいます。そこで今度はJDialogを拡張したクラスを作り、呼び出し元に戻らずにダイアログ内で宛名を切り替えるようにします。
今後のためにJDialogを継承したクラスとして作成します。
ボタンが4つ。[次]、[前]はダイアログを閉じずに、ベージを移動します。[印刷]と[戻る]はダイアログを閉じ、JDialogのようにgetValue()の値で印刷指示であることを伝えるものとします。
vspinnerとhspinnerはJSpinnerのインスタンスで、文字や罫線の描かれる位置を0.1mm単位で微調整します。調整のためには実際に1枚印刷してみて変更するという繰り返しが必要ですが、一応画面でも動いて調整の方向が確認できるようになっています。1枚だけ印刷するには、印刷ダイアログで描画ページの範囲を指定することで行います。
NORTHに配置したcurrentlabelはJPanelに描画する前のデータを示し、正しく描画されていることを確認するため設けたものです。
/** 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とします。
/** 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); }