« Обучающая машина mysql (обновления от 03.01.2008) | Про java swing - часть 2 » |
Про java swing - часть 1
Введение в разработку приложений с графическим интерфейсом для java
Для разработки приложений с графическим интерфейсом под java существует несколько библиотек, лишь две из них: awt и swing были официально признаны и по сути разработаны авторами java – компанией sun. Еще известна библиотека swt – разработка IBM, также ведутся разработки множеством крупных компаний, а также существуют общественные движения, наиболее известен проект java desktop.
Данная часть методического пособия будет посвящен использованию библиотеки swing, ее философии (а она есть и довольно отлична от знакомой вам по MFC), также будет уделено достаточное внимание созданию собственных элементов управления.
JFC – это сокращение от Java Foundation Classes, впервые JFC появился в 1997 и некоторое время поставлялся отдельным пакетом от java core language api. В версиях jdk начиная от 1.2 jfc является уже интегрированным.
Врезка: история развития графической подсистемы java: В ранних - 1.0.x - версиях Java Development Kit активно использовались "тяжелые" компоненты AWT, тесно связанные конкретной аппаратной платформой на которой выполнялось приложение. Дальнейшее развертывание концепции "write once, run everywhere" (написать однажды, запускать везде) привело к тому, что в версии 1.1.x наметился переход к универсальным компонентам. Тогда появились "легкие" интерфейсные классы, в которых любой компонент на экране создается средствами Java с помощью графических классов и методов. Такого рода классы компонентов были объединены в библиотеку под названием Swing и доступны разработчикам в составе как JDK, так и отдельного продукта JFC (Java Foundation Classes). Причем для совместимости со старыми версиями JDK старые компоненты из AWT остались нетронутыми, хотя компания JavaSoft -, отвечающая за выпуск JDK, - рекомендует не смешивать в одной и той же программе старые и новые компоненты.
Основные возможности и концепция JFC
Возможность | Описание |
Swing GUI Components | Для построения интерфейса разработано достаточно большое количество элементов управления: кнопки, списки, наборы закладок … |
Pluggable Look-and-Feel Support | Приложения, которые используют Swing component-ы предлагают возможные стратегии того как будет выглять кнопка или текстовое поле а также то как она будет себя вести во взаимодействии с пользователем. В стандартной поставке jdk1.3 были стили для отображения интерфейса под windows/motif/metal и вы сами можете разработать стиль отображения: например в 10 версии borland jbuilder разработчики создали собственный стиль. |
MVC – Model View Controller | Многие компоненты Swing реализованы по технологии MVC. Это означает, что компонент условно разбит на три части: модель (model), вид (view) и контроллер (controller). Каждая из этих частей строго и только свою задачу. Модель хранит важные данные компонента и обеспечивает программный интерфейс к ним. Вид занимается внешним видом изображения компонента и тесно связан с системой настройки интерфейса L&F. Контроллер же управляет компонентом в целом, получая сигналы от вида и уведомляя об изменениях модель компонента. Такое разграничение важно при смене L&F. Реализуя свой компонент для Swing, естественно будет сделать его пригодным для разных интерфейсов L&F. Технология MVC - это все, что вам нужно. Достаточно написать различные виды для Metal L&F, Motif L&F, Windows L&F и т. п. При этом модель и контроллер компонента меняются. |
Java 2D API | Средства для рисования 2d графики с возможностью отправки изображения на печать, сохранения в файле. |
Drag-and-Drop Support | Поддержка D&D как внутри java приложения так и между несколькими приложениями: java и java, java и созданные с помощью иных языков программы. |
Internationalization | Возможность создавать приложения с поддержкой нескольких языков. |
import javax.swing.*;
import java.awt.*;
java 2d api
Начнем с простого: создадим приложение использующего средства 2d графики. Прежде всего необходимо создать основу – “холст” для рисования для этого следует создать диалоговое окно. Вам следует использовать либо класс Jframe (для немодальных окон) или Jdialog (соответствнно для модальных).
public static void main(String[] args) {
JFrame jf = new JFrame("Пример диалогового окна");
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.setSize(300 , 300); или так jf.setSize(new Dimension (300 , 300));
jf.setVisible(true);
}
• DO_NOTHING_ON_CLOSE - ничего не делать. Окно не закроется и ничего не произойдет.
• HIDE_ON_CLOSE – окно будет спрятано.
• DISPOSE_ON_CLOSE – окно как и в предыдущем случае будет спрятано, но при этом и уничтожено (не насовсем, просто часть ресурсов операционной системы, которые использовались для отображения окна будут "освобождены"). Повторно окно можно показать с помощью вызова setVisible (хотя это может занять чуть больше времени чем "просто спрятать и не освобождать никаких ресурсов" - HIDE_ON_CLOSE).
• EXIT_ON_CLOSE - по закрытию формы приложение завершается используя вызов System.exit().
Затем я установил размеры, используя при вызове setSize либо явно заданные размеры – ширина или высота или обертку в виде объекта Dimension (запомните этот класс, многие методы графической подсистемы указывают размеры чего-либо с помощью объектов этого класса).
Начнем с простого: на форме должна рисоваться некая геометрическая «композиция» при создании которой будет использовано максимальное количество методов.
Важно: модель обработки событий в стиле MFC с картами событий в swing не используется – вместо этого вы должны либо перекрывать методы классов-предков, либо использовать концепцию “слушателей” (начнем с первого способа).
JFrame jf = new JFrame("Пример диалогового окна"){
public void paint(Graphics g) {
super.paint(g);
g.setColor(Color.BLUE); // устанавливаем текущий цвет
g.drawLine(0,0 ,getSize().width , getSize().height);// рисуем линию через весь размер экрана
g.setColor(new Color (128,128,128));
g.drawOval(getSize().width/2 , getSize().height/2, 100, 50);
g.setColor(new Color (0xFFFF00A0, true));
g.drawPolygon(
new Polygon(new int []{0,100,200,300}, new int []{0 , 200, 300, 100}, 4)
);
g.drawRoundRect(60 , 60 , 210 , 210 , 50 , 10);
g.drawString("Hello From Java 2D", 50 , 50);
g.fillRect(20 , 20 , 60 , 60);
}
};
• В примере используется метод getSize, возвращающий объект Dimension хранящий размеры окна.
• При задании цвета с помощью вызова
g.setColor(new Color (0xFFFF00A0, true));
Color(int r, int g, int b, int a).
• При вызове метода fillRect выполняется закраска площади прямоугольника текущим цветом – фактически и рамка объекта и фон одинаковы.
Далее приводится справочное пособие из методов класса Graphics:
abstract void clearRect(int x, int y, int width, int height)
abstract void clipRect(int x, int y, int width, int height)
abstract void copyArea(int x, int y, int width, int height, int dx, int dy)
abstract Graphics create()
abstract void dispose()
void draw3DRect(int x, int y, int width, int height, boolean raised)
abstract void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle)
void drawChars(char[] data, int offset, int length, int x, int y)
abstract boolean drawImage(Image img, int x, int y, Color bgcolor, ImageObserver observer)
abstract boolean drawImage(Image img, int x, int y, ImageObserver observer)
abstract boolean drawImage(Image img, int x, int y, int width, int height, Color bgcolor, ImageObserver observer)
abstract boolean drawImage(Image img, int x, int y, int width, int height, ImageObserver observer)
abstract boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1,
int sx2, int sy2, Color bgcolor, ImageObserver observer)
abstract void drawLine(int x1, int y1, int x2, int y2)
abstract void drawOval(int x, int y, int width, int height)
abstract void drawPolygon(int[] xPoints, int[] yPoints, int nPoints)
void drawPolygon(Polygon p)
abstract void drawPolyline(int[] xPoints, int[] yPoints, int nPoints)
void drawRect(int x, int y, int width, int height)
abstract void drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight)

abstract void drawString(AttributedCharacterIterator iterator, int x, int y)
abstract void drawString(String str, int x, int y)
void fill3DRect(int x, int y, int width, int height, boolean raised)
Далее должен был бы идти длинный перечень методов, которые делают тоже, что и вышеперичисленные рисующие примитивы, но с закраской. Я их пропущу, надеясь что вы сами догадаетесь какие у них имена либо наконец-то откроете мануал.
abstract Shape getClip()
Дело в том, что когда необходима перерисовка некоторого элемента управления (и форма Jframe –это всего лишь частный случай), то для оптимизации, при вызове метода update (данный метод служит для стирования фона области) и paint (непосредственно рисование), в передаваемом объекте Graphics g уже задана область отсечения и все попытки рисования вне это области не будут отображаться.
abstract Rectangle getClipBounds()
Получаем область отсечения, но при этом берем ее не истинный размер, а “описывающий” прямоугольник.
abstract Color getColor()
abstract Font getFont()
FontMetrics getFontMetrics()
abstract FontMetrics getFontMetrics(Font f)
abstract void setClip(int x, int y, int width, int height)
abstract void setClip(Shape clip)
abstract void setColor(Color c)
abstract void setFont(Font font)
abstract void setPaintMode()
abstract void setXORMode(Color c1)
abstract void translate(int x, int y)
g.drawRect(10 , 10 , 100 , 100); // рисуем квадрат со стороной 100 px.
g.translate(10 , 10); // перемещаем систему координат в точку 10, 10
g.drawRect(0,0,100,100);// рисуем квадрат совпадающий с ранее нарисованным
public void Paint (Graphics g) {
Graphics2D g2 = (Graphics2D) g;
// что-то рисуем
}
• setStroke • setPaint • setComposite • setTransform • setClip • setFont • setRenderingHintsПримеры: в которых рисуются различные параметры для атрибутов рисования:

Третий параметр – тип линии используемой при соединении двух фрагментов.

Пятый параметр - здесь вы должны указать стиль чередования штриховки – элементы массива задают, то сплошную линию, то пропуск.
Следующий параметр - начальное состояние линии штриховки.
Теперь попробуем нарисовать заливку прямоугольника. В первом случае используется сплошная красная заливка, во втором же градиент, переходящий от красного (в левом верхнем углу) до белого (в правом нижнем):
g2.setPaint(Color.red);
g2.fill(new Rectangle2D.Double(20, 20,100, 100));
// и второй – с градиентом
GradientPaint redtowhite = new GradientPaint(20,20,Color.RED,20+100, 20+100,Color.WHITE);
g2.setPaint(redtowhite);
g2.fill(new Rectangle2D.Double(20, 20,100, 100));
BufferedImage bum = new BufferedImage(50, 50, BufferedImage.TYPE_INT_RGB);
Image img = getToolkit().getImage("C:\\CA_LIC\\boat.gif");
MediaTracker tracker = new MediaTracker(this);
tracker.addImage(img, 0);
try {
tracker.waitForID(0 , 2000);
}
catch (InterruptedException e) {
e.printStackTrace();
}
Image img2 = img.getScaledInstance(100 , -100 , Image.SCALE_SMOOTH) ;
tracker.addImage(img2 , 1);
try {
tracker.waitForID(1 , 2000);
}
catch (InterruptedException e) {
e.printStackTrace();
}
bum.getGraphics().drawImage(img2, 0 , 0 , this );
TexturePaint tpaint = new TexturePaint(bum , new Rectangle2D.Double(10, 10, 20 , 20));
Graphics2D g2 = (Graphics2D )g;
g2.setPaint(tpaint);
g2.fill(new RoundRectangle2D.Double(50, 50, 200, 200, 10, 10));
Так как исторически java разрабатывалась для того, чтобы работать в среде intenet, то предполагалось, что загрузка изображения может занимать значительное время и даже быть неуспешной. Поэтому, когда мы работаем с изображениями возникает вопрос о том, загружено оно или нет.
Базовым классом для всех изображений служит класс Image. Для изображений которые уже загружены используется класс BufferedImage. Однако перед тем, как создать данное изображение, необходимо дождаться его загрузки. Для этого используется объект типа MediaTracker. При создании которого в качестве параметра конструтора следует указать любой визуальный элемент управления. Затем необходимо добавить изображение в список ожидающих загрузки, давая при этому ему порядковый учетный номер, и затем ожидаем загрузки. Следующий шаг (более для демонстрации, чем для необходимости) – масштабирование изображения. Для этого используем вызов:
img.getScaledInstance(100 , -100 , Image.SCALE_SMOOTH);
Следующим шагом будет управление параметрами рисования – рендеринга. Все использованные параметры (константы) задаются в классе RenderingHints.
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, VALUE_INTERPOLATION_BICUBIC);
TextLayout textTl = new TextLayout("Hello From J",
new Font("Helvetica", Font.ITALIC, 48),
g2.getFontRenderContext());
textTl.draw(g2 , 150 , 150);
Arc2D | Ellipse2D | QuadCurve2D |
Area | GeneralPath | Rectangle2D |
CubicCurve2D | Line2D | RectangularShape |
Dimension2D | Point2D | RoundRectangle2D |
Для представления фигур похожих на прямоугольник служат классы: Rectangle2D, RoundRectangle2D, Arc2D, и Ellipse2D.

Класс QuadCurve2D позволяет создать кривую, описываемую (тавтология ...) кривой второго порядка (определяется, такая кривая двумя точками на ее краях и контрольной точкой). Соответственно, CubicCurve2D задает кривую третьего порядка, для ее задания вам нужны две конечные точки и уже две управляющие точки.

Особый интерес представляет класс GeneralPath. Который позволяет задать фигуру по опорным точкам.

g.setColor(Color.BLUE);
Shape s = new QuadCurve2D.Double (50,50, 25, 78, 120 , 80);
((Graphics2D) g).draw(s);
GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD,12);
path.reset();
double alp30 = Math.PI / 6;
path.moveTo(0,0);
int cx = getSize().width / 2;
int cy = getSize().height / 2;
path.moveTo((float)(cx + 100), cy);
for (int i = 1; i <= 12; i++){
float x = (float)(cx + 100 * Math.cos (i*alp30));
float y = (float)(cy + 100 * Math.sin (i*alp30));
path.lineTo(x,y);
}
((Graphics2D) g).draw(path);
Area ar = new Area(path);
ar.exclusiveOr(new Area (new Ellipse2D.Double(100 , 100 , 50 , 50)));
((Graphics2D) g).draw(ar);
Area ar = new Area(path);
Area rhs = new Area (new Ellipse2D.Double(100 , 100 , 50 , 50));
ar.exclusiveOr(rhs); // операция XOR исключающего ИЛИ
ar.add(rhs); // операция объединения фигур
ar.intersect(rhs); // пересечение фигур
ar.subtract(rhs); // разница фигур
Трансформация фигур, текста и изображений
Возвращаясь к параметрам управляющими Graphics2D, рассмотрим как можно трансформировать объекты. Вы можете выполнять над графическими объектами операции: перемещение, вращение, масштабирование, усечение объектов на стадии рендеринга. Все эти преобразования задаются с помощью объектов производных от AffineTransform.
Важно: все афинные преобразования не приводят к переходу к новой системе координат, в которой, например, линии, которые были не параллельны стали бы таковыми или наоборот.
Класс Graphics2D предоставляет несколько методов для трансформации. Вам нужно создать объект AffineTransform и затем вызвать метод transform в составе Graphics2D.
Для создания объекта AffineTransform необходимо воспользоваться фабричными методами в составе AffineTransform:
Матрицы преобразования для всех этих операций:

// создаем матрицу вращения
AffineTransform rot = AffineTransform.getRotateInstance (Math.PI / 12 , 100,100);
// масштабирования
AffineTransform scal = AffineTransform.getScaleInstance (0.5 , 0.33);
// урезания
AffineTransform shear = AffineTransform.getShearInstance (0.5 , 0.33);
// перемещения
AffineTransform transl = AffineTransform.getTranslateInstance(50 , -70);
// применяем матрицу к графическому контексту
g2.setTransform(rot);
// и чего-то рисуем
textTl.draw(g2 , 150 , 150);
AffineTransform trans = AffineTransform.getRotateInstance(0);
trans.rotate(Math.PI / 6, 100 , 100);
trans.scale(1.5, 0.33);
trans.translate(15 , 40);
g2.setTransform(trans);
textTl.draw(g2 , 150 , 150);
Обработка изображений
Примечание: часто объекты, которые необходимо отрисовать на окне имеют уникальные размеры и форму. Такую, что ее трудно воспроизвести с помощью Shape и Clip-отсечений для Graphics2D. Тогда мы создаем изображение в виде GIF с прозрачным цветом (в java поддержка прозрачности GIF есть по-умолчанию, просто загрузите GIF и используйте его при вызове Graphics.drawImage).
Наблюдатели: как я говорил ранее загрузка изображения может занимать некоторое время в общем случае для ожидания используется класс MediaTracker мы его уже использовали ранее, но он является слишком тяжелым инструментом не позволяющим отслеживать процесс загрузки изображения. Для этого используются классы наблюдателей: Observer.
ImageObserver obs = new ImageObserver() {
public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {
String mes ="";
switch (infoflags){
case ImageObserver.ABORT: mes = " Создание изображения завершилось неудачно";break;
case ImageObserver.ALLBITS: mes = " Изображение полностью получено";break;
case ImageObserver.ERROR: mes = " Ошибка";break;
case ImageObserver.FRAMEBITS: mes = " Получен еще один кадр изображения";break;
case ImageObserver.HEIGHT: mes = " Получена высота изображения: height = " + height;break;
case ImageObserver.PROPERTIES: mes = " Получены свойства изображения ";break;
case ImageObserver.SOMEBITS: mes = " Получен следующий фрагмент изображения";break;
case ImageObserver.WIDTH: mes = " Получена ширина изображения: width = " + width;break;
}
System.out.println ("Img: " + img + " = " + mes);
return true;
}
};
Graphics2D g2 = (Graphics2D )g;
g2.drawImage(getToolkit().getImage("C:\\CA_LIC\\boat.gif") , AffineTransform.getRotateInstance(Math.PI / 4), obs);
Image i;
i.getWidth(new ImageProducer (){… код метода ….});
i.getHeight(new ImageProducer (){… код метода ….});
getToolkit().getImage("C:\\CA_LIC\\boat.gif");

Для представления производителя изображения используется интерфейс ImageProducer. Учитывайте, что в качестве источника изображения может быть не только файл, а все что угодно (лишь бы оно поддерживало данный интерфейс). В примере далее используется класс MemoryImageSource, который позволяет создать изображение по содержимому массива чисел. Если у вас есть объект Image, то для получения ссылки на его производителя воспользуйтесь методом getSource.
MemoryImageSource msource = new MemoryImageSource(
4,7,
new int []{
0xFFFF0000,0xFFFF0000,0xFFFF0000,0xFFFF0000,
0xFFFF0000,0xFF00FF00,0xFF00FF00,0xFFFF0000,
0xFFFF0000,0xFF00FF00,0xFF00FF00,0xFFFF0000,
0xFFFF0000,0xFF0000FF,0xFF0000FF,0xFFFF0000,
0xFFFF0000,0xFF00FF00,0xFF00FF00,0xFFFF0000,
0xFFFF0000,0xFF0000FF,0xFF0000FF,0xFFFF0000,
0xFFFF0000,0xFFFF0000,0xFFFF0000,0xFFFF0000
}
, 0, 4
);
Image img = getToolkit().createImage(msource);
И где-то в коде метода paint (Graphics g) вызываем метод:
Graphics2D g2 = (Graphics2D )g;
g2.drawImage(img, 20, 20 , 50*4, 50*7, this);

Врезка: общее представление о цветовых моделях. Модель HSB (Hue Saturation Brightness = Тон Насыщенность Яркость) построена на основе субъективного восприятия цвета человеком. Предложена в 1978 году. Эта модель тоже основана на цветах модели RGB, но любой цвет в ней определяется своим цветом (тоном), насыщенностью (то есть добавлением к нему белой краски) и яркостью ( то есть добавлением к нему черной краски). Фактически любой цвет получается из спектрального добавлением серой краски. Эта модель аппаратно-зависимая и не соответствует восприятию человеческого глаза, так как глаз воспринимает спектральные цвета как цвета с разной яркостью (синий кажется более темным, чем красный), а в модели HSB им всем приписывается яркость 100%. Модель является аппаратно-зависимой. H определяет частоту света и принимает значение от 0 до 360 градусов. V или B: V - значение (принимает значения от 0 до 1) или B - яркость, определяющая уровень белого света (принимает значения от 0 до 100%). Являются высотой конуса. S - определяет насыщенность цвета. Значение ее является радиусом конуса.
В составе класса Color есть методы для преобразования между цветовой моделью CMYK и RGB: HSBtoRGB и RGBtoHSB:
Graphics2D g2 = (Graphics2D) g;
Image img_orig = getToolkit().getImage("C:\\CA_LIC\\boat.gif");
final float value_correct = (float) Math.random();
ImageProducer prod = new FilteredImageSource(img_orig.getSource(),
new RGBImageFilter() {
public int filterRGB(int x, int y, int rgba) {
int alpha = rgba & 0xFF000000;
Color c = new Color(rgba);
float[] hsb = new float[3];
Color.RGBtoHSB(c.getRed(), c.getGreen(), c.getBlue(), hsb);
hsb[2] *= value_correct;
hsb[2] = Math.max(0.0f, Math.min(1.0f, hsb[2]));
return alpha | Color.HSBtoRGB(hsb[0], hsb[1], hsb[2]);
//return c.getAlpha() | Color.HSBtoRGB(hsb[0], hsb[1], hsb[2]);
}
});
g2.drawImage(getToolkit().createImage(prod), AffineTransform.getRotateInstance(Math.PI / 4), obs);
« Обучающая машина mysql (обновления от 03.01.2008) | Про java swing - часть 2 » |