文枫 深圳全通数码技术ȝ
在我们的实际工作中,l常需要实现打印功能。但׃历史原因QJava提供的打印功能一直都比较弱。实际上最初的jdkҎ(gu)不支持打华ͼ直到jdk1.1才引入了很轻量的打印支持。所以,在以前用Java/Applet/JSP/Servlet设计的程序中Q较复杂的打印都是通过调用ActiveX/OCX控g或者VB/VCE序来实现的Q非帔R烦。实际上QSUN公司也一直致力于Java打印功能的完善,而Java2q_则终于有了一个健壮的打印模式的开端,该打印模式与Java2D囑Ş包充分结合成一体。更令h鼓舞的是Q新发布的jdk1.4则提供了一套完整的"Java 打印服务 API" QJava Print Service APIQ,它对已有的打印功能是U极的补充。利用它Q我们可以实现大部分实际应用需求,包括打印文字、图形、文件及打印预览{等。本文将通过一个具体的E序实例来说明如何设计Java打印E序以实现这些功能,q对不同版本的实现方法进行分析比较。希望大家能从中获取一些有益的提示?
2.1 Java的打印API
Java的打印API主要存在于java.awt.print包中。而jdk1.4新增的类则主要存在于javax.print包及其相应的子包javax.print.event和javax.print.attribute中。其中javax.print包中主要包含打印服务的相关类Q而javax.print.event则包含打C件的相关定义Qjavax.print.attribute则包括打印服务的可用属性列表等?
2.2 如何实现打印
要生一个打华ͼ臛_需要考虑两条Q?
2.3 打印机对话框
2.3.1 Printable的打印对话框
开始打印工作之前,可以通过PrinterJob.printDialog来显CZ个打印对话框。它l用户一个机会以选择应该打印的页码范_q可供用h变打印设|。它是一个本地对话框?
事实上,当从一个Printable对象q行一个打印工作时Q打印对象ƈ不知道需要打印多页。它只是不停地调用printҎ(gu)。只要printҎ(gu)q回Printable.PAGE_EXISTS|打印工作׃停地产生打印,直到printҎ(gu)q回Printable.NO_SUCH_PAGEӞ打印工作才停止?
׃打印工作只有在打印完成后才进行准的|计算Q所以在对话框上的页码范围是未初始化的[1,9999]。我们可以通过构徏一个java.awt.print.Book对象传递给打印对象Q也可以通过指定的格式计需要打印的|q传递给打印对象Q其准地知道要打印多页?
2.3.2 ServiceUI的打印对话框
与Printable的对话框不同的是Q在jdk1.4提供ServiceUI的打印机对话框的~省行ؓ已经用新?API 更改了:~省情况下对话框不显C。我们必M用ServiceUIc调用printDialogҎ(gu)创徏如下所C的打印对话框?/p>
3.1 打印文本
3.1.1 应用场景
假设我们需要打C个窗体的某个文本~辑域(可能只有几行Q也可能包含多页Q的内容Qƈ且每|多打?4行,如何实现呢?
3.1.2 解决Ҏ(gu)
基本思\如下Q首先我们需要实现Printable接口Q然后按照每|?4行的格式计算共需要打印多页Q当打印文本的按钮被点击Ӟ执行相应的打印动作。打印文本的具体操作可通过Graphics2D的drawStringҎ(gu)来实现?/p>
1、实现Printable接口
/*Graphic指明打印的图形环境;PageFormat指明打印|式(面大小以点量单位,
1点ؓ1英才?/72Q?英寸?5.4毫米。A4U大致ؓ595×842点)Qpage指明号*/
public int print(Graphics g, PageFormat pf, int page) throws PrinterException
{
Graphics2D g2 = (Graphics2D)g;
g2.setPaint(Color.black); //讄打印颜色为黑?br>if (page >= PAGES) //当打印页号大于需要打印的总页数时Q打印工作结?br>return Printable.NO_SUCH_PAGE;
g2.translate(pf.getImageableX(), pf.getImageableY());//转换坐标Q确定打印边?br>drawCurrentPageText(g2, pf, page); //打印当前|?br>return Printable.PAGE_EXISTS; //存在打印|Ql打印工?br>}
/*打印指定号的具体文本内?/
private void drawCurrentPageText(Graphics2D g2, PageFormat pf, int page)
{
String s = getDrawText(printStr)[page];//获取当前늚待打印文本内?br>//获取默认字体及相应的寸
FontRenderContext context = g2.getFontRenderContext();
Font f = area.getFont();
String drawText;
float ascent = 16; //l定字符炚w
int k, i = f.getSize(), lines = 0;
while(s.length() > 0 && lines < 54) //每页限定?4行以?br>{
k = s.indexOf('\n'); //获取每一个回车符的位|?br>if (k != -1) //存在回RW?br>{
lines += 1; //计算行数
drawText = s.substring(0, k); //获取每一行文?br>g2.drawString(drawText, 0, ascent); //具体打印每一行文本,同时走纸UM
if (s.substring(k + 1).length() > 0)
{
s = s.substring(k + 1); //截取未打印的文?br>ascent += i;
}
}
else //不存在回车符
{
lines += 1; //计算行数
drawText = s; //获取每一行文?br>g2.drawString(drawText, 0, ascent); //具体打印每一行文本,同时走纸UM
s = ""; //文本已结?br>}
}
}
/*打印目标文本按存放ؓ字符串数l?/
public String[] getDrawText(String s)
{
String[] drawText = new String[PAGES];//Ҏ(gu)|初始化数l?br>for (int i = 0; i < PAGES; i++)
drawText[i] = ""; //数组元素初始化ؓI字W串
int k, suffix = 0, lines = 0;
while(s.length() > 0)
{
if(lines < 54) //不够一|
{
k = s.indexOf('\n');
if (k != -1) //存在回RW?br>{
lines += 1; //行数累加
//计算该页的具体文本内容,存放到相应下标的数组元素
drawText[suffix] = drawText[suffix] + s.substring(0, k + 1);
if (s.substring(k + 1).length() > 0)
s = s.substring(k + 1);
}
else
{
lines += 1; //行数累加
//文本内容存攑ֈ相应的数l元?br>drawText[suffix] = drawText[suffix] + s;
s = "";
}
}
else //已满一|
{
lines = 0; //行数l计清零
suffix++; //数组下标?
}
}
return drawText;
}
2、计需要打印的总页?/p>
public int getPagesCount(String curStr)
{
int page = 0;
int position, count = 0;
String str = curStr;
while(str.length() > 0) //文本未计算完毕
{
position = str.indexOf('\n'); //计算回RW的位置
count += 1; //l计行数
if (position != -1)
str = str.substring(position + 1); //截取未计算的文?br>else
str = ""; //文本已计完?br>}
if (count > 0)
page = count / 54 + 1; //以总行数除?4获取总页?/p>
return page; //q回需打印的总页?br>}
3.1、以jdk1.4以前的版本实现打印动作按钮监听,q完成具体的打印操作
private void printTextAction()
{
printStr = area.getText().trim(); //获取需要打印的目标文本
if (printStr != null && printStr.length() > 0) //当打印内容不为空?br>{
PAGES = getPagesCount(printStr); //获取打印总页?br>PrinterJob myPrtJob = PrinterJob.getPrinterJob(); //获取默认打印作业
PageFormat pageFormat = myPrtJob.defaultPage(); //获取默认打印面格式
myPrtJob.setPrintable(this, pageFormat); //讄打印工作
if (myPrtJob.printDialog()) //昄打印对话?br>{
try
{
myPrtJob.print(); //q行每一늚具体打印操作
}
catch(PrinterException pe)
{
pe.printStackTrace();
}
}
}
else
{
//如果打印内容为空Ӟ提示用户打印取?br>JOptionPane.showConfirmDialog
(null, "Sorry, Printer Job is Empty, Print Cancelled!", "Empty",
JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE);
}
}
3.2、以jdk1.4新版本提供的API实现打印动作按钮监听Qƈ完成具体的打印操?/p>
private void printText2Action()
{
printFlag = 0; //打印标志清零
printStr = area.getText().trim();//获取需要打印的目标文本
if (printStr != null && printStr.length() > 0) //当打印内容不为空?br>{
PAGES = getPagesCount(printStr); //获取打印总页?br>//指定打印输出格式
DocFlavor flavor = DocFlavor.SERVICE_FORMATTED.PRINTABLE;
//定位默认的打印服?br>PrintService printService = PrintServiceLookup.lookupDefaultPrintService();
//创徏打印作业
DocPrintJob job = printService.createPrintJob();
//讄打印属?br>PrintRequestAttributeSet pras = new HashPrintRequestAttributeSet();
DocAttributeSet das = new HashDocAttributeSet();
//指定打印内容
Doc doc = new SimpleDoc(this, flavor, das);
//不显C打印对话框Q直接进行打印工?br>try
{
job.print(doc, pras); //q行每一늚具体打印操作
}
catch(PrintException pe)
{
pe.printStackTrace();
}
}
else
{
//如果打印内容为空Ӟ提示用户打印取?br>JOptionPane.showConfirmDialog(null, "Sorry, Printer Job is Empty, Print Cancelled!", "Empty", JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE);
}
}
3.2 打印预览
3.2.1应用场景
大多商业应用都需要提供打印预览机Ӟ它可以让我们在屏q上看到面Q这样就不会因ؓ不喜Ƣ的打印l果而浪费纸张。假设我们在打印上一节所说的文本之前Q需要先q行打印预览。那么该怎么实现呢?
界面实现囄如下Q(Next预览下一,Preview预览前一,Close则关闭预览)
3.2.2解决Ҏ(gu)
基本思\Q虽然Java2q_的打印APIq不提供标准的打印预览对话框Q但是自己来q行设计也ƈ不复杂。正常情况下QprintҎ(gu)页面环境绘制到一个打印机囑Ş环境上,从而实现打印。而事实上QprintҎ(gu)q不能真正生打印页面,它只是将待打印内容绘制到囑Ş环境上。所以,我们可以忽略掉屏q图形环境,l过适当的羃放比例,使整个打印页容纳在一个屏q矩形里Q从而实现精的打印预览?/p>
在打印预览的设计实现中,主要需要解决两个问题。第一Q如何将打印内容按合适的比例l制到屏q;W二Q如何实现前后翻c下面我l出q两个问题的具体实现Ҏ(gu)Q完整的实现请参看附件中的PrintPreviewDialog.java文g?/p>
/*待打印内容按比例绘制到屏幕*/
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
PageFormat pf = PrinterJob.getPrinterJob().defaultPage(); //获取面格式
double xoff; //在屏q上面初始位置的水q_U?br>double yoff; //在屏q上面初始位置的垂直偏U?br>double scale; //在屏q上适合面的比?br>double px = pf.getWidth(); //面宽度
double py = pf.getHeight(); //面高度
double sx = getWidth() - 1;
double sy = getHeight() - 1;
if (px / py < sx / sy)
{
scale = sy / py; //计算比例
xoff = 0.5 * (sx - scale * px); //水^偏移?br>yoff = 0;
}
else
{
scale = sx / px; //计算比例
xoff = 0;
yoff = 0.5 * (sy - scale * py); //垂直偏移?br>}
g2.translate((float)xoff, (float)yoff); //转换坐标
g2.scale((float)scale, (float)scale);
Rectangle2D page = new Rectangle2D.Double(0, 0, px, py); //l制面矩Ş
g2.setPaint(Color.white); //讄面背景为白?br>g2.fill(page);
g2.setPaint(Color.black);//讄面文字为黑?br>g2.draw(page);
try
{
preview.print(g2, pf, currentPage); //昄指定的预览页?br>}
catch(PrinterException pe)
{
g2.draw(new Line2D.Double(0, 0, px, py));
g2.draw(new Line2D.Double(0, px, 0, py));
}
}
/*预览指定的页?/
public void viewPage(int pos)
{
int newPage = currentPage + pos;
//指定面在实际的范围?br>if (0 <= newPage && newPage < preview.getPagesCount(printStr))
{
currentPage = newPage; //指定页面赋gؓ当前?br>repaint();
}
}
q样Q在按下"Next"按钮Ӟ只需要调用canvas.viewPage(1)Q而在按下"Preview"按钮Ӟ只需要调用canvas.viewPage(-1)卛_实现预览的前后翻c?/p>
3.3 打印囑Ş
3.3.1应用场景
在实际应用中Q我们还需要打印图形。譬如,我们有时需要将一个Java Applet的完整界面或一个应用程序窗体及其所包含的全部组仉打印出来Q又应该如何实现呢?
3.3.2解决Ҏ(gu)
基本思\如下Q在Java的Componentcd其派生类中都提供了print和printAllҎ(gu)Q只要设|好属性就可以直接调用q两个方法,从而实现对lg及图形的打印?/p>
/*打印指定的窗体及其包含的lg*/
private void printFrameAction()
{
Toolkit kit = Toolkit.getDefaultToolkit(); //获取工具?br>Properties props = new Properties();
props.put("awt.print.printer", "durango");//讄打印属?br>props.put("awt.print.numCopies", "2");
if(kit != null)
{
//获取工具p带的打印对象
PrintJob printJob = kit.getPrintJob(this, "Print Frame", props);
if(printJob != null)
{
Graphics pg = printJob.getGraphics();//获取打印对象的图形环?br>if(pg != null)
{
try
{
this.printAll(pg);//打印该窗体及其所有的lg
}
finally
{
pg.dispose();//注销囑Ş环境
}
}
printJob.end();//l束打印作业
}
}
}
3.4 打印文g
3.4.1应用场景
在很多实际应用情况下Q我们可能都需要打印用h定的某一个文件。该文g可能是图形文Ӟ如GIF、JPEG{等Q也可能是文本文Ӟ如TXT、Java文g{等Q还可能是复杂的PDF、DOC文g{等。那么对于这L打印需求,我们又应该如何实现呢Q?/p>
3.4.2解决Ҏ(gu)
基本思\Q在jdk1.4以前的版本,要实现这L打印功能非帔R烦和复杂Q甚x难以惌的。但q运的是Qjdk1.4的打印服务API提供了一整套的打印文件流的类和方法。利用它们,我们可以非常方便快捷地实现各式各样不同类型文件的打印功能。下面给Z个通用的处理方法?/p>
/*打印指定的文?/
private void printFileAction()
{
//构造一个文仉择器,默认为当前目?br>JFileChooser fileChooser = new JFileChooser(SystemProperties.USER_DIR);
int state = fileChooser.showOpenDialog(this);//弹出文g选择对话?br>if (state == fileChooser.APPROVE_OPTION)//如果用户选定了文?br>{
File file = fileChooser.getSelectedFile();//获取选择的文?br>//构徏打印h属性集
PrintRequestAttributeSet pras = new HashPrintRequestAttributeSet();
//讄打印格式Q因为未定文gcdQ这里选择AUTOSENSE
DocFlavor flavor = DocFlavor.INPUT_STREAM.AUTOSENSE;
//查找所有的可用打印服务
PrintService printService[] = PrintServiceLookup.lookupPrintServices(flavor, pras);
//定位默认的打印服?br>PrintService defaultService = PrintServiceLookup.lookupDefaultPrintService();
//昄打印对话?br>PrintService service = ServiceUI.printDialog(null, 200, 200, printService
, defaultService, flavor, pras);
if (service != null)
{
try
{
DocPrintJob job = service.createPrintJob();//创徏打印作业
FileInputStream fis = new FileInputStream(file);//构造待打印的文件流
DocAttributeSet das = new HashDocAttributeSet();
Doc doc = new SimpleDoc(fis, flavor, das);//建立打印文g格式
job.print(doc, pras);//q行文g的打?br>}
catch(Exception e)
{
e.printStackTrace();
}
}
}
}
在上面的CZ中,因尚未确定文件的cdQ所以将指定文g的打印格式定义ؓDocFlavor.INPUT_STREAM.AUTOSENSE。事实上Q如果在q行打印之前Q就已确定地知道文g的格式,如ؓGIFQ就应定义ؓDocFlavor.INPUT_STREAM.GIF Q如为PDFQ就应该定义为DocFlavor.INPUT_STREAM.PDFQ如为纯ASCII文gQ就可以定义?DocFlavor.INPUT_STREAM.TEXT_HTML_US_ASCII。等{。jdk1.4的javax.print.DocFlavor提供了极Z富的文g类型,你可以根据具体的应用需求进行合适的选择。具体的API参考文档可见本文的参考资??/p>
4 l束?/p>
以上是本人在两年多J2EE应用开发中Qȝ的关于用Javaq行打印E序设计的一些经验,希望能给大家一些启C和裨益。尽目前用Java来实现打印功能与用Microsoft的MFC API相比实有更多的ȝ。但jdk1.4的推出,对Java以前较弱的打印功能是一个极好的补充。相信大家如果能够很好地理解前文所q的打印E序设计实例Qƈ加以应用和拓展,应该可以解决目前大部分应用的实际~程问题。而随着Java的进一步发展和完善Q必更好地充实其基cd及打印APIQ相信用Java实现高打印功能也将来不成ؓ我们q些Java痴迷者头痛的问题?/p>
5 参考资?/p>
《Java2核心技?卷ⅡQ高U特性?机械工业出版C?
Java打印服务参考文档:http://java.sun.com/j2se/1.4/docs/guide/jps/
jdk1.4 API参考文档:http://java.sun.com/j2se/1.4/docs/api/
6 例程源码
PrintSrc.zip包含下列java源代码和Class代码Q?/p>
PrintTest.java包含了本文所描述的所有打印功能的实现源代码。相应的打印文本功能通过Print Text和PrintText2Qjdk1.4实现Q按钮调用;打印文g通过Print File按钮调用Q打印图形通过Print Frame按钮调用Q而Print Preview则进行打印预览?
PrintPreviewDialog.java包含打印预览源代码,你可以通过PrintTestH体中的Print Preview按钮来调用?
关于作?/p>
文枫Q深圛_通数码技术ȝ。目前专注于J2EE应用与开发。休闲时间喜Ƣ旅游、踢球。你可以通过 wenfb@sina.com 与我联系?/p>