プレビューのための独自ダイアログを作る
上の例ではプレビューをstaticメソッドのJOptionPane.showOptionDialog()を使って簡単に済ませましたが、いくつか不満があるでしょう。
- 拡大、縮小、スクロールなどができない。
- 人数分をプレビューで確認してまとめて印刷したい。
これを解消するには独自のダイアログが必要です。具体的には、JOptionPaneのインスタンスを作り、さらにcreateDialog()でDialogを作って setResizable(true) します。(次のリスト中の//★)
pable.setPanelSize(mwidth,mheight);
String[] goOrNot = {"印刷","中止"};
JOptionPane optpane = new JOptionPane(//★
pable,
JOptionPane.PLAIN_MESSAGE,
JOptionPane.DEFAULT_OPTION,
null,goOrNot,goOrNot[0]
);
JDialog dialog = optpane.createDialog("拡大可印刷プレビュー" );//★
dialog.pack();
dialog.setResizable(true);//★
dialog.setVisible(true);
//retval:"印刷","中止",null
String retval = (String)optpane.getValue();
if (retval==null || retval.equals("中止")) System.exit(0);
PrinterJob pj = PrinterJob.getPrinterJob();
これで、ダイアログはマウスドラッグで拡大縮小できますが、内容は大きさが変化しません。そのためには次の改変(//☆)も必要です。
Dimension viewdim;
....
public void setPanelSize(float w,float h){
viewdim = new Dimension();
viewdim.setSize(w*mm2pt,h*mm2pt);
setPreferredSize(viewdim);
}
@Override
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
double scale = getWidth()/viewdim.getWidth();//☆
g2.scale(scale,scale);//☆
drawPage(g2);
}
Graphics2Dクラスのscale()メソッドで拡大率を指定します。このメソッドはGraphicsクラスにはありませんから、paintComponent()の中で引数のgをキャストしてGraphics2Dとしてscale()を適用してからdrawPage()を呼び出します。drawPage()の引数もこれに合わせてGraphics2Dに変えています。
拡大率は、とりあえず幅だけで計算してみましょう。マウスドラッグで変化した新しい幅を元の幅で割れば、新しい幅いっぱいに広がる拡大率になります。縦も同じ比率にします。getWidth()は自分自身の幅ですからマウスドラッグで変化した新しい幅にあたります。
元の幅はsetPanelSize()メソッドで使ったDimensionのインスタンスをフィールド変数としてアクセスできるようにしておきます。インスタンス名をdからviewdimに変えておきました。
DimensionはGraphics時代のクラスで、幅と高さがintでフィールドに格納されています。setSize(double width,double height)メソッドがversion1.2の時代に追加されましたが、doubleの値が小数部分を切り上げてintになってフィールドに格納されます。getWidth()メソッドなどはdoubleの値が返りますがintの値をキャストして出しているだけで、設定時の小数部分の情報は失われています。
このプログラムのプレビューです。
マウスのドラッグで枠が横に伸ばされると、内容もそれに合わせて拡大されます。はみ出した下部は見ることができません。スクロールして見えるようにするために、次はJScrollPaneを追加します。
プレビューにスクロールを付けて高さからも拡大可にする
ここでは、JScrollPaneの中に入れることで、スクロールできるようにします。横幅に合わせて拡大・縮小をしていましたが、縦幅でも操作できるようにします。つまり横幅・縦幅の描画が大きくなる方に合わせて拡大・縮小し、他方はスクロールするようにします。
今回も、上に掲載した「PrintableWithPreview01.java」を元に変更しましたが、プログラム全体を掲載し直します。一番大きな変更はpaintComponent()メソッドの部分。つぎはJScrollPaneを加えているmain()メソッドの部分です。
解説は、プログラムリスト掲載の後にします。
/** PrintableWithPreview04.java
JDialog scrole
AfureString.javaを使い、1行に入り切らない時は2行に分けます。
プレビューをJPanelとJOptionPaneにsetResizable()を適用
枠に合わせて描画も拡大・縮小、JScrollPaneも使用します。
2019-11-06 Adachi Junichi
*/
import java.awt.*;
import java.awt.print.*;
import javax.print.*;
import javax.print.attribute.*;
import javax.print.attribute.standard.*;
import javax.swing.*;
public class PrintableWithPreview04 extends JPanel implements Printable {
String zip;
String addr;
String name;
double wZh; //①
Dimension motodims = new Dimension();
Dimension scaledims = new Dimension();
float mm2pt = 72/25.4f;
public PrintableWithPreview04(String zip,String addr,String name){
this.zip = zip;
this.addr = addr;
this.name = name;
setBackground(Color.white);
}
public void setPanelSize(float w,float h){
motodims.setSize(w*mm2pt,h*mm2pt); //②
setPreferredSize(motodims);
wZh = motodims.getWidth()/motodims.getHeight();
}
@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){ //条件1
scaledims.setSize(viewpw-vbarw, (viewpw-vbarw)/wZh);
scale = (viewpw-vbarw)/motodims.getWidth();
}else if( wZh > (double)viewpw / (viewph-hbarh) ){ //条件2
scaledims.setSize((viewph-hbarh)*wZh, viewph-hbarh);
scale = (viewph-hbarh)/motodims.getHeight();
}else{ //条件3
if ( (double)viewpw/viewph > wZh ) { //条件3-1
scaledims.setSize(viewph*wZh, viewph);
scale = viewph/motodims.getHeight();
}else{ //条件3-2
scaledims.setSize(viewpw, viewpw/wZh);
scale = viewpw/motodims.getWidth();
}
}
g2.scale(scale,scale);
setPreferredSize(scaledims);
drawPage(g2);
}
@Override
public int print(Graphics g, PageFormat pf, int pageIndex) {
if (pageIndex != 0) return NO_SUCH_PAGE;
Graphics2D g2 = (Graphics2D) g; //④
drawPage(g2);
return PAGE_EXISTS;
}
public void drawPage(Graphics2D g2){
g2.setColor(Color.BLACK);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
//w:width ; v:virtical width ; pch:pitch
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 static void main(String[] args) {
String zip = "036-1234";
String addr = "神奈川県相模原市南区南台6-8-10 三上ハイツ304";
String name = "齋藤梨花";
PrintableWithPreview04 pable = new PrintableWithPreview04(zip,addr,name);
PrintRequestAttributeSet rqset = new HashPrintRequestAttributeSet();
//MediaSizeName msname = MediaSizeName.ISO_A4; //A4用紙
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));
pable.setPanelSize(mwidth,mheight);
JScrollPane scrollPane = new JScrollPane(pable); //⑤
String[] goOrNot = {"印刷","中止"};
JOptionPane optpane = new JOptionPane(
scrollPane, //⑥
JOptionPane.PLAIN_MESSAGE,
JOptionPane.DEFAULT_OPTION,
null,goOrNot,goOrNot[0]
);
JDialog dialog = optpane.createDialog("拡大可印刷プレビュー" );
dialog.pack();
dialog.setResizable(true);
dialog.setVisible(true);
dialog.dispose();
//retval:"印刷","中止",null
String retval = (String)optpane.getValue();
if (retval==null || retval.equals("中止")) System.exit(0);
PrinterJob pj = PrinterJob.getPrinterJob();
pj.setPrintable(pable);
if (pj.printDialog(rqset)) {
try { pj.print(rqset); }
catch (PrinterException e) {
System.err.println(e);
}
}
}
}
プレビューの追加部分の詳しい解説
丸付き数字はプログラム中の部分を指します。
① フィールドに縦横比wZhとDimensionのインスタンスを2つ用意します
wZhはWidth/heightに見えるようにとの命名です。
motodimsは、印刷用紙の大きさをから求めたポイント単位の幅と高さの値です。明記していませんがfinalです。setPanelSize()メソッド内で、Dimension#setSize()を使って値を入れます。これは引数がDoubleですが、切り上げして整数として格納されます。この値は画面表示の横、縦のピクセル数になります。
scaledimsはプレビューが描画されるJPanelの大きさを格納するDimensionです。枠の大きさが変わるたびにpaintCommpnent()で変更されます。
② setPanelSize()
印刷用紙の大きさを引数(mm)で受けて、ポイントに換算してmotodimsに格納し、wZhを計算しておきます。
setPreferredSize()は初期値の設定です。枠の大きさが変えられるとpaintCommpnent()内で再設定されます。セットされるのはJPanel、正確にはJPanelを継承したPrintableWithPreview04のインスタンス、つまり自分自身です。
setPreferredSize()でセットされた大きさがJScrollPaneの現在の大きさより大きいとスクロールバーが自動で生成されます。
③ paintComponent()メソッドの大変更
ダイアログの構造は、JDialog-JScrollPane-JViewport-JPanelとなっています。
このpaintComponent()はJPanelのメソッドなので、getParent()で得られるのはJViewport、getParent().getParent()で得られるのはJScrollPaneです。
JViewportの枠からJPanelを覗いている構造になるので、JViewportの大きさをまず求めたいのですが、スクロールバーがあるとそれを除いた大きさになるので、スクロールバーの部分を含めた縦横の大きさを求めるために、長くなっています(注の部分)。
その後JViewportの大きさにどう合わせるかの場合分けの設定をします。JViewportの枠の縦横のどちらに合わせればより大きく表示できるかを計算して、拡大率(scale)を決定しJPanelのsetPreferredSize()をします。
条件1~3については、別に解説します。
④ GraphicsとGraphics2Dキャスト位置の変更
paintComponent()メソッド内でGraphic2Dの機能が必要になったので、Graphics2D g2 = (Graphics2D) g;を移して、drawPage(g2)メソッドの引数をGraphicsからGraphics2Dに変更しています。
⑤ JPanelを格納したJScrollPaneをメッセージObjectとするJOptionPaneのインスタンスを作ります
ユーザーが選択可能な項目はObjectの配列で与えます。今回は"印刷"、"中止"の文字列の配列を使います。
JScrollPaneはJPanelのような部品なので、表示のためにJDialogに入れてsetVisible(true)することで表示します。オプションの選択後も不可視になるだけで残りますから、dispose()しておきます。
dispase()後もJOptionPaneのインスタンスは残りますから、getValue()で選択したものを取り出します。ユーザーが選択せずに[☓]ボタンで閉じた場合はnullが返ります。
プレビューの拡大余話
プレビューの拡大を横幅だけでなく高さにも連動させたことで、複雑になってしまいました。その説明をしておきます。目的からすると横幅にだけ連動してスクロールを付けるとか、マウスのホイールで拡大するということにしたほうが良かったかもしれません。ちょっと面倒なことに対処しなければならなかったからです。
これを作っているうちに元の用紙の縦横比と微妙に異なる大きさにした時に、ウインドウ内の表示が拡大・縮小を繰り返して振動する現象が現れました。
JScrollPaneの窓部分をViewportといいます。これにはスクロールバーの幅が含まれません。つまり、JViewport#getWidth()で得られる横幅は縦スクロールバーがある場合その幅を除いた値になります。それにJPanelを合わせて表示した時に、すっぽり入って縦スクロールバーが不要になることがあります。するとこれにJPanelの幅を合わせようとして拡大して今度は縦スクロールバーが必要になるので縮小される...というような循環が起こっていると考えられます。
これに対処するために条件を3つに分類しました。
条件1 縦スクロールバーの幅を引いても縦横比がwZhより大きい場合
図で紫色がViewportの枠です。viewpwはスクロールバーがない場合の横幅になっています。それから縦スクロールバーの幅を引いたものが図のwです。JPanelとある長方形は印刷用紙の縦横比を保ったまま横幅をwに合わせたものです。Viewportの幅/高さがwZhより大きい時はJPanelが下にはみ出します。
条件2 横スクロールバーの高さを引いても縦横比がwZhより小さい場合
図で紫色がViewportの枠です。viewphはスクロールバーがない場合の高さになっています。それから横スクロールバーの高さ引いたものが図のhです。JPanelとある長方形は印刷用紙の縦横比を保ったまま高さをhに合わせたものです。Viewportの幅/高さがwZhより小さい時はJPanelが右にはみ出します。
条件3 上記のどちらでもない場合
どちらでもない場合は縦横比が近い場合です。この場合、スクロールバーが出現しない方に合わせます。
条件3-1 こちらはViewportの幅/高さがwZhより大きい場合です。
条件3-2 こちらはViewportの幅/高さがwZhより小さい場合です。
