JavaFX в действии: только практика

После небольшого перерыва мы с вами возвращаемся к платформе создания RIA-приложений JavaFX, о которой мы говорили фактически весь май. Эта статья будет посвящена практике работы с JavaFX, и поэтому в ней будет много программного кода и мало теории.


Рисунки на песке

Люди с детства любят рисовать. Некоторые даже становятся профессиональными дизайнерами или известными художниками, но большая часть всё-таки рисует не ради славы или денег, а просто потому, что хочется. Именно поэтому в любой уважающей себя социальной сети есть небольшое web-приложение, позволяющее пользователю быстро создать и отправить рисунок другому пользователю. В подавляющем большинстве случаев такие приложения, конечно же, написаны на Flash'е. Мы же с вами знаем, что Flash использовать не обязательно - это ведь отличная возможность попрактиковаться в знании JavaFX. Поэтому давайте посмотрим, как подобную "рисовалку" можно создать на базе этой платформы, и вы собственными глазами сможете убедиться, что получится у нас приложение, не слишком по своим возможностям отличающееся от инструмента, предлагаемого пользователям социальной сети "В контакте".

Итак, внимание: листинг, в котором и расположился полный код нашего с вами приложения.

import javafx.stage.*;
import javafx.scene.*;
import javafx.scene.paint.*;
import javafx.scene.input.*;
import javafx.scene.shape.*;
var pathelements: PathElement[];
Stage {
 title: "Simple drawing example"
 width: 250
 height: 80
 scene: Scene {
  content: Group{
   content: [
    Rectangle{
     width: 250
     height: 250
     fill: Color.WHITE
     onMousePressed: function( e: MouseEvent ):Void {
      insert MoveTo {x : e.x, y : e.y } into pathelements;
     }
     onMouseDragged: function( e: MouseEvent ):Void {
      insert LineTo {x : e.x, y : e.y } into pathelements;
     }
    }
    Path{
     stroke: Color.BLACK
     elements: bind pathelements
    }
   ]
  }
 }
}
 

Ознакомились с листингом? Замечательно. Теперь давайте разберёмся, что же в нём, всё-таки, написано. Первые пять строчек, полагаю, ни у кого совершенно никаких вопросов не вызовут - это список пакетов, которые необходимы для того, чтобы мы смогли скомпилировать и запустить предложенный вашему вниманию пример. Следующая строчка содержит описание переменной, которая и будет, в общем-то, содержать в себе рисунок. Дальше идут знакомые вам объявления объектов Stage и Scene с описанием некоторых их свойств. В общем-то, здесь тоже, думаю, всё более чем прозрачно. Внутри сцены вы видите описание белого квадрата размером 250 на 250 пикселов - даже не запуская этот пример на выполнение, совсем не сложно догадаться, что именно этот квадрат будет рабочей областью нашего демонстрационного приложения, на которой в дальнейшем и будет размещаться рисунок. Поклонники творчества Казимира Малевича могут заменить белый квадрат на чёрный - но тогда и цвет "кисти" нужно будет подобрать какой-то другой.

Вся работа приложения сосредоточена в двух обработчиках событий - onMousePressed и onMouseDragged. Первый запускается, когда пользователь отпускает нажатую кнопку мыши, а второй - когда он делает её курсором какое-либо движение при нажатой кнопке. Как видите, оба эти обработчика добавляют элементы в набор элементов кривой, которая входит в состав нашей сцены и связана с объявлённой после всех импортов переменной pathelements. Весь фокус в том, как и что именно добавляется. Как видите, при нажатой кнопке в фигуру добавляются линии, а когда кнопку отпускаем, то и перемещение осуществляется без отрисовки линий.

Как видите, написать собственную "рисовалку" на JavaFX - дело действительно буквально считанных минут. А сейчас мы с вами переходим к следующему примеру, не менее интересному и полезному и при этом дополняющему уже рассмотренный нами.


Сохраняемся!

Рисовать - это, безусловно, здорово, скажете вы, однако грош цена такому рисованию, если его результаты нельзя нигде сохранить и отобразить в дальнейшем. И здесь я с вами соглашусь, поскольку, действительно, сохранять результаты работы пользователя - важнейшая задача любого приложения, пусть даже такого слабого в плане изобразительных средств, как наша "рисовалка".

Впрочем, здесь, перефразируя классика, у меня есть для вас пренеприятнейшее известие: Sun Microsystems с нами не согласна. По крайней мере, найти в документации достаточно простой способ сохранения содержимого сцены в файл мне, к сожалению, не удалось. Тем не менее, на форумах по JavaFX на sun.com, можно отыскать разные варианты обходных путей, позволяющих всё-таки осуществить подобное сохранение. Сразу предупрежу: код достаточно сложен для тех, кто только начинает изучать JavaFX, и потребует от вас некоторых знаний не только в области JavaFX, но и в самом языке Java.

Код примера с сохранением файлов я разделил на два листинга. Во втором листинге вы можете увидеть код функции, непосредственно отвечающей за такое сохранение. Нужные импорты выглядят следующим образом: "import javafx.reflect.*; import javafx.scene.*; import java.awt.image.BufferedImage; import javafx.scene.image.*;" (кавычки в настоящем программном коде, сами понимаете, неуместны).

function saveImageToFile(node:Node, width:Integer, height:Integer)
{
 var context = FXLocal.getContext();
 var nodeClass = context.findClass("javafx.scene.Node");
 var getFXNode = nodeClass.getFunction("impl_getFXNode");
 var sgNode = (getFXNode.invoke(context.mirrorOf(node))
  as FXLocal.ObjectValue).asObject();
 var g2dClass = (context.findClass("java.awt.Graphics2D")
  as FXLocal.ClassType).getJavaImplementationClass();
 var paintMethod = sgNode.getClass().getMethod("render", g2dClass);
 var img = new java.awt.image.BufferedImage(width, height,
  java.awt.image.BufferedImage.TYPE_INT_ARGB);
 var g2 = img.createGraphics();
 paintMethod.invoke(sgNode, g2);
 g2.dispose();
 var savefile = new java.io.File("d:\\Examlpe.png");
 javax.imageio.ImageIO.write(img, "png", savefile);
}

Для начала - о параметрах этой функции. Первый из них - это экземпляр того графического объекта, который мы собираемся "увековечить". От класса Node унаследованы визуальные классы JavaFX, в том числе и класс Path, с которым будем иметь дело мы. Второй и третий параметры - это размеры изображения, то есть, соответственно, его ширина и высота.

Сам код функции становится понятным тому, кто работал с отражениями (reflections) в Java. Я расскажу о том, что происходит здесь, в общих чертах, чтобы не объяснять слишком много о Reflection API, а то статья грозит получиться просто огромной. С помощью механизма отражений мы получаем информацию о классе, изображение экземпляра которого сохраняем в файл, ищем метод, который будет "рендерить" сохраняемое изображение, затем создаём объект, который его (изображение) содержит. Ну а затем уже идёт, собственно, запись в файл формата PNG. Естественно, что вместо "d:\\Example.png" может стоять и любое другое имя файла, в том числе и генерируемое динамически.

Дальше нам требуется наш Path внутри сцены объявить как переменную, чтобы с этим объектом можно было работать - думаю, с этим вы вполне уже сможете справиться самостоятельно. Кроме того, добавим на сцену ещё один объект - с ним вы можете ознакомиться в третьем листинге (не забудьте при этом добавить в список импортов следующую строчку: "import javafx.ext.swing.SwingButton;" (без кавычек).

SwingButton {
 impl_layoutX: 0
 impl_layoutY: 0
 text: "Save"
 action: function() {
  saveImageToFile(path, 250, 250)
 }
}

Здесь, как видите, мы добавили на сцену кнопку из пакета элементов управления для создания на Java пользовательских интерфейсов, который называется Swing. Думаю, многие их тех, кто читает эту статью, совсем не понаслышке знакомы с ним. То, что в JavaFX-приложениях можно использовать Swing, - это, на самом деле, очень даже хорошо, потому что это позволяет придать им привычный для пользователя вид. Например, можно с помощью Swing добавить диалог сохранения файла, который позволит выбрать и его имя, и формат. Кнопка эта размещается в точке с координатами (0; 0), то есть, в верхнем левом углу окна нашего приложения. При нажатии на неё мы и сохраняем содержимое переменной path с помощью функции saveImageToFile, которую уже рассмотрели во втором листинге.

В целом, как видите, код сохранения картинки получился сложнее, чем код, позволяющий нам её рисовать. Думаю, что этот минус, довольно-таки существенный, разработчики JavaFX исправят в ближайших версиях своего программного продукта, которые выйдут в свет. Если вам не нравится предложенный вариант сохранения картинки, то с помощью Google вы сможете найти альтернативные предложения - например, одна из довольно оригинальных методик предусматривает создание снимка экрана и сохранения в файл уже его части.


Резюме

Что ж, давайте подведём краткие итоги всему тому, что мы с вами успели обсудить. С одной стороны, мы в очередной раз убедились в том, что JavaFX - мощная и комплексная платформа, вполне подходящая для создания полезных, с практической точки зрения, RIA-приложений. С другой стороны, узнали, что и у этой медали есть обратная сторона. Впрочем, как видите, родство с Java всё-таки очень сильно выручает, когда речь заходит о каких-то не вполне тривиальных задачах. Так что JavaFX - отличный выбор для всех, кто желает создавать RIA-приложения.

(Продолжение следует)

Вадим СТАНКЕВИЧ,
dreamdrusch@tut.by

Версия для печатиВерсия для печати

Номер: 

22 за 2009 год

Рубрика: 

Software
Заметили ошибку? Выделите ее мышкой и нажмите Ctrl+Enter!