Java 1.1 и последующие версии предлагают несколько
простых методов печати, которые можно вызвать из Java-приложений. Пока
еще модели безопасности Java не позволяют вывести на принтер апплеты
Web-страниц, однако, будущие версии Java, возможно, снимут это
ограничение.
Сначала мы рассмотрим относительно примитивные
методы печати, которые предлагает Java, а затем изучим технологию
объектно-ориентированного программирования. Знание данной технологии
позволит вам построить более полезный и надежный объект Printer.
Основы
печати на Java
Java 1.1
представляет класс PrintJob и дополнительный метод print(Graphics
g) класса Container, которые образуют систему печати. Вы получаете
объект класса PrintJob из класса Toolkit, взятого из класса Frame.
PrintJob pj = getToolkit().getPrintJob(frm,
"printing", null);
Первым аргументом является фрейм приложения,
вторым -- любое имя задания для печати, а третьим -- либо объект класса
Properties, либо null. Поскольку система печати на Java не поддерживает
никакие свойства, поэтому в качестве объекта вполне можно использовать
null.
Класс PrintJob включает следующие методы:
end() |
Заканчивает процесс печати и выполняет
необходимую чистку. |
finalize() |
Заканчивает процесс печати, как только
к нему перестают поступать обращения. |
getGraphics() |
Получает объект Graphics, который изображается на следующей
странице. |
getPageDimension() |
Возвращает размеры страницы в
пикселах. |
getPageResolution() |
Возвращает разрешение страницы в
пикселах на дюйм. |
lastPageFirst() |
Возращает "true"
("истинно"), при условии что последняя
страница печатается первой. |
Простая программа
печати
Самой простой программой печати является та,
которая:
Создает фрейм;
Получает объект PrintJob;
Получает объект принтера Graphics;
Передаёт данные для печати объекту
принтера Graphics;
Удаляет объект принтера Graphics, что
приводит к выбросу страницы;
Завершает процесс печати.
Пример подобной программы приводится далее:
import java.awt.*; class helloPrinter
extends Frame { public helloPrinter() { //get the print
job PrintJob pjob = getToolkit().getPrintJob(this, "Printer",
null); Graphics pg =
pjob.getGraphics(); //get the
graphics print(pg);
//print something pg.dispose();
//end the page pjob.end();
//end the job System.exit(0);
//and quit
} //----------------------------------------- public void
print(Graphics g) { g.setFont(new Font("SansSerif",
Font.PLAIN, 12)); g.drawString("Hello Printer", 100,
100); } //----------------------------------------- static
public void main(String argv[]) { new
helloPrinter(); } }
Данная программа поможет нам создать больше
общих классов печати:
Задание на печать должно создаваться во
фрейме. Поэтому задания на печать почти всегда являются визуальными
приложениями.
Когда вы создаете класс PrintJob,
появляется системный диалог принтера; после чего вы можете выбрать тип
принтера и желаемое количество копий.
Хотя задание на печать создается во
фрейме, необязательно чтобы данный фрейм был визуальным или имел
ненулевой размер.
Прежде чем отправлять текст на принтер, вы
должны выбрать шрифт.
Процесс печати происходит путем передачи
данных объекту принтера Graphics.
Каждая страница распечатывается сразу и
имеет свой собственный объект Graphics. А метод dispose() позволяет
распечатать страницу до конца.
Недостатки простых способов
печати
Данная простая программа печати выглядит
достаточно примитивно. Мы должны печатать каждую строку текста, в конце
каждой строки ставить символ переноса, сами задавать шрифт, а также
задавать каждую страницу отдельно. Мы бы предпочли создать более
функциональный объект Printer, который мог бы сам задавать, хотя бы,
типы шрифтов и конец строки. К тому же, хотелось бы, чтобы этот процесс
был от нас скрыт.
Класс Printer
Даже если мы будем создавать свой класс
Printer, он должен находиться во фрейме. Им может быть любой контейнер,
который мы можем добавить во фрейм, а не столько фрейм сам по себе. Мы
будем создавать объект printer путем наследования его от класса Canvas.
Как правило, данный объект Canvas будет невизуальным и минимального
размера: 1 x 1 пиксел. Так как мы собираемся выводить наши данные в
объект Graphics, содержащийся внутри данного объекта canvas, у нас
появляется возможность создать метод предварительного просмотра страницы
(практически без дополнительного кодирования) путем отображения объекта
Canvas и вызова метода paint.
Создание класса Printer Итак,
давайте рассмотрим, что мы хотим, чтобы данный класс выполнял. Мы хотели
бы иметь возможность печатать обычные строки, строки с возвратом
каретки, изменять шрифты, печатать колонки, которые организуются с
помощью символов табуляции. Кроме того, было бы неплохо иметь
возможность предварительного просмотра текста, выводимого на печать.
В идеале, мы хотели бы, чтобы объект Printer
выполнял больше функций, к примеру, организация текста в более, чем одну
колонку, даже если мы будем печатать его последовательно. Чтобы
пользоваться данной возможностью, наша модель должна предусматривать
хранение в объекте Printer элементов печатаемой страницы, до тех пор
пока данная страница не будет закончена; а затем распечатать их все
сразу. Те же требования предъявляются и к возможности предварительного
просмотра страницы.
Методы класса Printer Нам нужно
иметь возможность печатать и переходить на новую строку. Поэтому нам
нужен как метод print, так и метод println:
printer = new Printer(.. .); //create object
printer.print("Hello "); //print text
printer.println(" printer); //print text and newline
Кроме того, мы хотели бы задавать новые
шрифты:
printer.setFont(font); //set a new font
и иметь возможность перемещаться по символам
табуляции:
printer.tab(35); //move to column 35
Кроме того, нам нужно переходить на новые
страницы, не создавая новый объект для каждой страницы:
printer.newPage(); //page eject
И, конечно же, нам нужно, чтобы процесс
печати закончился там, где мы его закончили:
printer.endJob(); //end printer job
Теперь давайте рассмотрим, как мы можем
реализовать эти методы так, чтобы можно было хранить различные комманды
внутри объекта printer и "проигрывать их", когда мы выводим их на печать
или предварительно просматриваем на экране.
Далее, если мы будем продолжать расширять
этот объект printer, мы хотели бы иметь возможность представлять
графические линии и изображения.
Вектор распечатываемых объектов
Каждый раз когда нам нужно сохранить неопределённое количество
объектов, первое, что приходит в голову, -- это использовать класс
Vector.
Но какие объекты мы будем сохранять и как мы
можем проверить, который из них будет печататься следующим? Предположим,
что у нас есть объекты String, Font и NewLine, которые хранятся в классе
Vector.
Если бы нам нужно было их распечатать, нам бы
пришлось определить тип объекта, а затем вызвать для этого типа
соответствующий метод:
for (int i =0; i<objects.size; i++)
{
obj = objects.elementAt(i);
if (obj instanceof String)
print((String)obj);
if (obj instanceof Font)
//etc..
Это не только плохо читаемо, но более
отвечает процедурному программированию, чем объектно-ориентированному.
Вместо нескончаемой цепочки if-блоков, которые у нас имеются, давайте
попытаемся создать серию объектов, которые сами знают, как себя
распечатать или нарисовать.
Класс printerObject Если мы хотим
иметь возможность изобразить каждый объект соответствующим ему образом,
мы должны создать класс для распечатываемого объекта, в котором есть
метод draw. Мы сообщаем ему, на поверхности какого объекта Graphics
нужно рисовать и с каких координат начать рисование:
abstract class printerObject
{
abstract void draw(Graphics g, Point p);
}
Затем мы наследуем конкретные объекты данного
класса от абстрактного класса, каждый из которых будет выполнять метод
draw по-разному. Преимущество данного приема заключается в том, что нам
больше не нужно будет проверять тип объекта: каждый объект (String, Font
или NewLine) уже имеет соответствующий ему метод draw.
Принцип моделей проектирования В
принципе, если мы рассмотрим всё это с точки зрения Моделей
Проектирования, мы обнаружим, что каждый из этих объектов, в
действительности, является классом Command. Мы просто назвали данный
метод draw, а не Execute, хотя принципиальной разницы это не имеет.
К тому же, так как мы создаем и добавляем
различные распечатываемые объекты, мы, фактически, используем объект
Printer как класс Factory, генерирующий один из трех различных типов
классов printerObject в зависимости от действия, которое нам нужно
выполнить.
После этого мы можем распечатать весь список
объектов путем перемешения по вектору и вызова метода draw
соответствующего printObject.
public void print(Graphics g)
{
printerObject p;
f.setFont(fnt); //always start with some font
for (int i = 0; i < objects.size(); i ++)
{
p = (printerObject)objects.elementAt(i);
p.draw(g, pt);
} }
Нужно отметить, что при получении общих
объектов из класса Vector и преобразовании их к типу printerObjects, нам
не обязательно знать, какой объект мы в данный момент печатаем: у
каждого из них есть свой метод draw.
Классы printObject
Каждый из этих классов содержит метод draw,
работающий с объектами Graphics и Point, которые мы ему передаём.
Каждый из этих классов может иметь свой
собственный конструктор для передачи различной информации, поскольку мы
не определили конструктор при описании нашего абстрактного класса.
Класс printString Данный класс
печатает строку с точки, определённой координатами "x" и "y", а затем
устанавливает координату "x" за окончание строки:
class printString extends printerObject
{
String st;
public printString(String s)
{
st = s; //save the string
}
//---------------------------------------------------
public void draw(Graphics g, Point p)
{
g.drawString(st, p.x, p.y); //draw it
//add in the width of the string
p.x +=
g.getFontMetrics(g.getFont()).stringWidth(st);
}
}
Отметим, что поскольку точка начала
отображения передаётся как объект Point, а не парой координат "x", "y",
она передаётся ссылкой, и любое изменение координаты "x" приводит к
изменению объекта Рoint в вызывающей программе.
Класс newLine Чтобы переместиться
на следующую строку, нам нужно определить высоту текущего шрифта,
увеличить координату "y" на эту величину и установить координату "x" в
левое крайнее положение или в ноль.
class newLine extends printerObject
{
//---------------------------------------------------
public void draw(Graphics g, Point p)
{
p.x = 0; //reset x
//advance y
p.y += g.getFontMetrics(g.getFont()).getHeight();
}
}
Класс setFont Возможность
установки нового шрифта при "проигрывании" класса Vector означает, что
мы выполняем метод draw, используя простой метод setFont.
class printFont extends printerObject
{
Font fnt;
public printFont(Font f)
{
fnt = f; //save the font
}
//------------------------------------
public void draw(Graphics g, Point p)
{
g.setFont(fnt); //set the font
if (p.y <= 0)
{ p.y = g.getFontMetrics(fnt).getHeight();
}
}
}
В дополнение к этому, если данный шрифт
находится в верхней части страницы, где координата "y" равняется 0, мы
продвигаем эту координату на высоту шрифта, поскольку шрифты рисуются
снизу вверх.
Класс printTab Данный класс
продвигает координату "x" на расстояние, равное установленной нами
ширине символов. Возникает вопрос: какие символы и какой размер шрифта
мы будем использовать? Вот ответ: пока мы последовательно выполняем
задание на печать, то, как мы определяем ширину одного символа
табуляции, особого значения не имеет.
Чтобы убедиться в том, что используется
только один размер во всех объектах класса printTab, мы делаем
переменную tab_width статической переменной.
Только таким образом все копии данного класса
будут автоматически ссылаться на одно и то же значение. Мы устанавливаем
его, если оно равно нулю, по умолчанию, равным ширине символа "W" в
шрифте, который мы задаём в конструкторе. Как только данное значение
установлено, этот код больше не выполняется.
class printTab extends printerObject
{
static int tab_width = 0;
int tab_dist;
Font tabFnt;
//---------------------------------------
public printTab(Font tbFont, int tabdist)
{
tabFnt = tbFont;
tab_dist = tabdist;
}
//---------------------------------------
public void draw(Graphics g, Point p)
{
if (tab_width == 0)
tab_width =
g.getFontMetrics(tabFnt).stringWidth("W");
if (p.x < (tab_dist*tab_width))
{
p.x = tab_dist * tab_width;
}
}
}
Распечатка "n" символов табуляции равносильно
перемещению координаты "x" на расстояние "n", умноженное на ширину
табуляции.
Если координата "x" прошла эту точку, мы
ничего не делаем. С другой стороны, мы можем продвинуться на одну строку
и переместиться к положению табуляции на новой строке.
Методы класса Printer
Теперь, когда мы определили printerObjects,
которые мы будем применять, легко проследить, как объект Printer будет
их использовать. Мы просто создаём новый экземпляр каждого типа
printerObject и добавляем его в vector:
public void setFont(Font f)
{
objects.addElement(new printFont(f));
}
//--------------------------------------
public void print(String s)
{
objects.addElement(new printString(s));
}
//---------------------------------------------------
public void println(String s)
{
print(s);
objects.addElement(new newLine());
}
//--------------------------------------
public void tab(int tabstop)
{
objects.addElement(new printTab(tabFont, tabstop));
}
Кроме данных методов печати, нам нужны методы
newPage и endJob. Метод newPage фактически выполняет всю работу. Он
вызывает метод print(g), который проходит через весь список
printerObjects и вызывает их методы draw().
public void newPage()
{
if (pjob == null)
pjob = getToolkit().getPrintJob(f, "Printer", null);
pg = pjob.getGraphics();
print(pg); //print the whole vector
pg.dispose(); //eject the page
pt =new Point( 0, 0); //reinitialize print posn
objects = new Vector(); //and print objects
}
//-----------------------------------
public void print(Graphics g)
{
printerObject p;
f.setFont(fnt); //always start with some font
for (int i = 0; i < objects.size(); i ++)
{
p = (printerObject)objects.elementAt(i);
p.draw(g, pt); //draw each object
}
}
Метод endJob() завершает PrintJob. Если вы
игнорируете этот метод, вы можете также закончить PrintJob, вызвав его в
методе finalize().
public void finalize()
{
if (objects.size() > 0) //make sure all printed
newPage();
endJob();
}
//--------------------------------------
public void endJob()
{
pjob.end();
}
Предварительный просмотр печатаемой
страницы
С помощью объекта Printer, который мы только
что создали, отобразить всю страницу на экране не представляет труда:
просто нам нужно вызвать тот же метод print(g), используя объект
Graphics экрана вместо объекта Graphics принтера. Откуда мы получаем
объект graphics экрана? Мы берем его из стандартного метода paint(),
который мы, в свою очередь, используем для вызова метода печати.
public void paint(Graphics g)
{
pt = new Point(0, 0); //always start at top
print(g); //displays text as preview
}
Программа печати
Данная программа создаёт маленькое окно 100 x
100 с двумя кнопками: Show и Plot. Также создаётся объект Printer и
добавляется во фрейм. Затем печатаются три строки текста тремя
различными шрифтами с помощью табуляции в объекте Printer. Когда вы
нажимаете на кнопку Print, он отправляет эти строки на принтер, который
вначале вызывает системный диалог принтера (Windows или другой
операционной системы); а затем печатает страницу.
Когда вы нажимаете на кнопку Show, окно
расширяется до размеров страницы, полученных от PrintJob, и отображает
текст.
Установка размеров страницы Метод
getPageDimension() класса PrintJob может работать только при
условии, что объект класса PrintJob уже создан. Так как для этого вы
можете нажать кнопку "Show", мы предлагаем обходной путь, когда не надо
создавать класс PrintJob, если таковой еще не существует, поскольку
всякий раз при создании этого класса будет вызываться системный диалог
печати. Это вызывает неудобства, если вы хотите просто просмотреть
текст. Для страницы 8 x 11 дюймов по умолчанию метод getPageDimension()
возвращает размер 620 x 790 пикселов. Заметьте, что размер в пикселах,
не имеет ничего общего с разрешением принтера. Он просто возвращает пару
чисел, пропорциональных размерам страницы, чтобы дать вам возможность
разместить текст внутри данной страницы. Предлагаемый нами подход
предназначен для получения этих значений напрямую, пока не создан класс
PrintJob:
public Dimension pageSize()
{
if (pjob == null)
return new Dimension(620, 790);
else
{
pjob = getToolkit().getPrintJob(f, "Printer", null);
return pjob.getPageDimension();
}
}
Законченная программа печати
Мы уже обсудили некоторые части программы
печати и сам класс Printer. Законченная программа приводится ниже. Она
действительно довольно проста по сравнению с теми объяснениями, которыми
она сопровождалась.
import java.awt.*;
import java.awt.event.*;
import java.util.*;
public class printing extends Frame
implements ActionListener, WindowListener
{
Button b, show;
Printer printer;
//---------------------------------------------------
public printing()
{
super("print test"); //frame title;
addWindowListener(this);
Panel p = new Panel(); //spaces buttons
add("South", p);
b = new Button("Print"); //2 buttons
p.add("South", b);
b.addActionListener(this);
show = new Button("Show");
p.add(show);
show.addActionListener(this);
printer = new Printer(this); //printer added
setBounds(100,100,100,100);
setVisible(true);
loadStrings(); //do printing here
}
//---------------------------------------------------
private void loadStrings()
{
//puts the text into the Printer object
int i = 1;
printer.setFont(new Font("SanSerif", Font.PLAIN, 12));
printer.print(i++ +"."); printer.tab(10);
printer.println("Hello printer");
printer.print(i++ +"."); printer.tab(10);
printer.setFont(new Font("SanSerif", Font.BOLD, 12));
printer.println("A bold stroke");
printer.print(i++ +"."); printer.tab(10);
printer.setFont(new Font("SanSerif", Font.ITALIC, 18));
printer.println("but emphatic");
}
//---------------------------------------------------
public void actionPerformed(ActionEvent ev)
{
Object obj = ev.getSource();
if (obj == b)
{
printer.newPage(); //print on page
printer.endJob();
}
if (obj == show)
{
showPreview(); //or on screen
}
}
//---------------------------------------------------
private void showPreview()
{
Dimension d = printer.pageSize();
setBounds(0 , 0, d.width, d.height);
printer.setBounds(0, 0, d.width, d.height);
printer.setVisible(true);
}
//---------------------------------------------------
static public void main(String argv[])
{
new printing();
}
//-------------------------------------------------
public void windowClosing(WindowEvent wEvt)
{
System.exit(0); //exit on System exit box clicked
}
public void windowClosed(WindowEvent wEvt){}
public void windowOpened(WindowEvent wEvt){}
public void windowIconified(WindowEvent wEvt){}
public void windowDeiconified(WindowEvent wEvt){}
public void windowActivated(WindowEvent wEvt){}
public void windowDeactivated(WindowEvent wEvt){}
}
//===============================================
class Printer extends Canvas
{
Frame f; //parent frame
PrintJob pjob; //printjob object
Graphics pg; //printer graphics handle
Vector objects; //array of printer instructions
Point pt; //current printer position
Font fnt; //current font
Font tabFont; //font to use in tab calcns
public Printer(Frame frm)
{
f = frm; //save form
f.add(this); //add this object to form
setVisible(false);//but do not show it
pjob = null; //no print job yet
pt =new Point( 0, 0); //initialize print posn
objects = new Vector(); //and print objects
tabFont = new Font("MonoSpaced", Font.PLAIN, 12);
fnt = new Font("SansSerif", Font.PLAIN, 12);
}
//--------------------------------------
public void setFont(Font f)
{
objects.addElement(new printFont(f));
}
//--------------------------------------
public void print(String s)
{
objects.addElement(new printString(s));
}
//---------------------------------------------------
public void println(String s)
{
print(s);
objects.addElement(new newLine());
}
//--------------------------------------
public void newPage()
{
if (pjob == null)
pjob = getToolkit().getPrintJob(f, "Printer", null);
pg = pjob.getGraphics();
print(pg);
pg.dispose();
pt =new Point( 0, 0); //initialize print posn
objects = new Vector(); //and print objects
}
//--------------------------------------
public void finalize()
{
if (objects.size() > 0)
newPage();
endJob();
}
//--------------------------------------
public void endJob()
{
pjob.end();
}
//--------------------------------------
public void tab(int tabstop)
{
objects.addElement(new printTab(tabFont, tabstop));
}
//--------------------------------------
public Dimension pageSize()
{
if (pjob == null)
return new Dimension(620, 790);
else
{
pjob = getToolkit().getPrintJob(f, "Printer", null);
return pjob.getPageDimension();
}
}
//---------------------------------------------------
public void paint(Graphics g)
{
pt = new Point(0, 0);
print(g); //displays text as preview
}
//---------------------------------------------------
public void print(Graphics g)
{
printerObject p;
f.setFont(fnt); //always start with some font
for (int i = 0; i < objects.size(); i ++)
{
p = (printerObject)objects.elementAt(i);
p.draw(g, pt);
}
}
} //end class
//===================================================
abstract class printerObject
{
abstract void draw(Graphics g, Point p);
}
//===================================================
class newLine extends printerObject
{
//---------------------------------------------------
public void draw(Graphics g, Point p)
{
p.x = 0;
p.y += g.getFontMetrics(g.getFont()).getHeight();
}
}
//=====================================================
class printString extends printerObject
{
String st;
public printString(String s)
{
st = s;
}
//-------------------------------------------------
public void draw(Graphics g, Point p)
{
g.drawString(st, p.x, p.y);
p.x += g.getFontMetrics(g.getFont()).stringWidth(st);
}
}
//===================================================
class printFont extends printerObject
{
Font fnt;
public printFont(Font f)
{
fnt = f;
}
//--------------------------------------------------
public void draw(Graphics g, Point p)
{
g.setFont(fnt);
if (p.y <= 0)
{
p.y = g.getFontMetrics(fnt).getHeight();
}
}
}
//===================================================
class printTab extends printerObject
{
static int tab_width = 0;
int tab_dist;
Font tabFnt;
public printTab(Font tbFont, int tabdist)
{
tabFnt = tbFont;
tab_dist = tabdist;
}
public void draw(Graphics g, Point p)
{
if (tab_width == 0)
tab_width =
g.getFontMetrics(tabFnt).stringWidth("W");
if (p.x < (tab_dist*tab_width))
{
p.x = tab_dist * tab_width;
}
}
}
Резюме
В данном документе мы рассмотрели различные
методы печати на Java. Сначала мы ознакомились с самой простой
программой печати, затем создали класс печати, который использует как
модель Command, так и модель Factory. Эти модели дают возможность
создать список printerObjects, который содержит указания для печати
строк, шрифтов, символов новых строк и меток табуляции. Данный тип
объектно-ориентированного программирования подчеркивает важность
использования основных принципов проектирования при создании простых,
легких в применении программ.
|