How to set node attribute dynamically using NodeBuilder pattern in groovy
Preferably it would be nice to reference the current element in the conditional statement since the condition might appear deep down in the structure.
asked Aug 26, 2011 at 12:56
10.3k 6 6 gold badges 46 46 silver badges 71 71 bronze badges
1 Answer 1
The easiest way to do this is to evaluate the condition for the dynamic attribute outside the node closure. For Example:
if (condition) < b.div(attribute: "value", dynamicAttribute: true) < . >> else < b.div(attribute: "value") < . >>
Alternatively, you can create a map of the attributes beforehand:
def attributes = [attribute: "value"] if (condition) < attributes['dynamicAttribute'] = true >b.div(attributes)
Работа с XML в Groovy
Groovy предоставляет значительное количество методов, предназначенных для просмотра и управления содержимым XML.
В этом руководстве мы покажем, как добавлять, редактировать или удалять элементы из XML в Groovy, используя различные подходы. Мы также покажем, как создать структуру XML с нуля .
2. Определение модели
Давайте определим структуру XML в нашем каталоге ресурсов, которую мы будем использовать в наших примерах:
articles> article> title>First steps in Javatitle> author id="1"> firstname>Sienafirstname> lastname>Kerrlastname> author> release-date>2018-12-01release-date> article> article> title>Dockerize your SpringBoot applicationtitle> author id="2"> firstname>Jonasfirstname> lastname>Lugolastname> author> release-date>2018-12-01release-date> article> article> title>SpringBoot tutorialtitle> author id="3"> firstname>Danielefirstname> lastname>Fergusonlastname> author> release-date>2018-06-12release-date> article> article> title>Java 12 insightstitle> author id="1"> firstname>Sienafirstname> lastname>Kerrlastname> author> release-date>2018-07-22release-date> article> articles>
И прочитайте его в переменную InputStream :
def xmlFile = getClass().getResourceAsStream("articles.xml")
3. XML-парсер
Начнем изучение этого потока с класса XmlParser .
3.1. Чтение
Чтение и анализ XML-файла, вероятно, является наиболее распространенной XML-операцией, которую приходится выполнять разработчику. XmlParser предоставляет очень простой интерфейс, предназначенный именно для этого:
def articles = new XmlParser().parse(xmlFile)
На этом этапе мы можем получить доступ к атрибутам и значениям структуры XML, используя выражения GPath.
Давайте теперь реализуем простой тест, используя Spock , чтобы проверить правильность нашего объекта article:
def "Should read XML file properly"() given: "XML file" when: "Using XmlParser to read file" def articles = new XmlParser().parse(xmlFile) then: "Xml is loaded properly" articles.'*'.size() == 4 articles.article[0].author.firstname.text() == "Siena" articles.article[2].'release-date'.text() == "2018-06-12" articles.article[3].title.text() == "Java 12 insights" articles.article.find it.author.'@id'.text() == "3" >.author.firstname.text() == "Daniele" >
Чтобы понять, как получить доступ к значениям XML и как использовать выражения GPath, давайте сосредоточимся на внутренней структуре результата операции XmlParser#parse .
Объект article является экземпляром groovy.util.Node . Каждый узел состоит из имени, карты атрибутов, значения и родителя (который может быть нулевым или другим узлом) .
В нашем случае значением article является экземпляр groovy.util.NodeList , который является классом-оболочкой для коллекции Node s. NodeList расширяет класс java.util.ArrayList , который обеспечивает извлечение элементов по индексу. Чтобы получить строковое значение узла, мы используем groovy.util.Node#text().
В приведенном выше примере мы ввели несколько выражений GPath:
- article.article[0].author.firstname — получить имя автора первой статьи — article.article [n] будет напрямую обращаться к n -й статье .
- ‘*’ — получить список дочерних элементов статьи — это аналог groovy.util.Node#children()
- author.’@id’ — получить атрибут id элемента author . author.’@attributeName’ обращается к значению атрибута по его имени (эквиваленты: author[‘@id’] и author.@id )
3.2. Добавление узла
Как и в предыдущем примере, давайте сначала прочитаем содержимое XML в переменную. Это позволит нам определить новый узел и добавить его в наш список статей, используя groovy.util.Node#append.
Давайте теперь реализуем тест, который доказывает нашу точку зрения:
def "Should add node to existing xml using NodeBuilder"() given: "XML object" def articles = new XmlParser().parse(xmlFile) when: "Adding node to xml" def articleNode = new NodeBuilder().article(id: '5') title('Traversing XML in the nutshell') author firstname('Martin') lastname('Schmidt') > 'release-date'('2019-05-18') > articles.append(articleNode) then: "Node is added to xml properly" articles.'*'.size() == 5 articles.article[4].title.text() == "Traversing XML in the nutshell" >
Как видно из приведенного выше примера, процесс довольно прост.
Заметим также, что мы использовали groovy.util.NodeBuilder, который является хорошей альтернативой использованию конструктора узла для нашего определения узла .
3.3. Изменение узла
Мы также можем изменить значения узлов с помощью XmlParser . Для этого давайте еще раз проанализируем содержимое XML-файла. Далее мы можем отредактировать узел содержимого, изменив поле значения объекта Node .
Давайте помнить, что хотя XmlParser использует выражения GPath, мы всегда извлекаем экземпляр NodeList, поэтому для изменения первого (и единственного) элемента мы должны получить к нему доступ, используя его индекс.
Давайте проверим наши предположения, написав быстрый тест:
def "Should modify node"() given: "XML object" def articles = new XmlParser().parse(xmlFile) when: "Changing value of one of the nodes" articles.article.each it.'release-date'[0].value = "2019-05-18" > then: "XML is updated" articles.article.findAll it.'release-date'.text() != "2019-05-18" >.isEmpty() >
В приведенном выше примере мы также использовали Groovy Collections API для обхода NodeList .
3.4. Замена узла
Далее давайте посмотрим, как заменить весь узел, а не просто изменить одно из его значений.
Аналогично добавлению нового элемента, мы будем использовать NodeBuilder для определения узла , а затем заменим в нем один из существующих узлов с помощью groovy.util.Node#replaceNode :
def "Should replace node"() given: "XML object" def articles = new XmlParser().parse(xmlFile) when: "Adding node to xml" def articleNode = new NodeBuilder().article(id: '5') title('Traversing XML in the nutshell') author firstname('Martin') lastname('Schmidt') > 'release-date'('2019-05-18') > articles.article[0].replaceNode(articleNode) then: "Node is added to xml properly" articles.'*'.size() == 4 articles.article[0].title.text() == "Traversing XML in the nutshell" >
3.5. Удаление узла
Удаление узла с помощью XmlParser довольно сложно. Хотя класс Node предоставляет метод remove(Node child) , в большинстве случаев мы не стали бы использовать его сам по себе.
Вместо этого мы покажем, как удалить узел, значение которого удовлетворяет заданному условию.
По умолчанию доступ к вложенным элементам с помощью цепочки ссылок Node.NodeList возвращает копию соответствующих дочерних узлов. Из-за этого мы не можем использовать метод java.util.NodeList#removeAll непосредственно в нашей коллекции статей .
Чтобы удалить узел по предикату, мы должны сначала найти все узлы, соответствующие нашему условию, а затем пройтись по ним и каждый раз вызывать метод java.util.Node#remove для родителя .
Давайте реализуем тест, удаляющий все статьи, id автора которых отличается от 3 :
def "Should remove article from xml"() given: "XML object" def articles = new XmlParser().parse(xmlFile) when: "Removing all articles but the ones with > articles.article .findAll it.author.'@id'.text() != "3" > .each articles.remove(it) > then: "There is only one article left" articles.children().size() == 1 articles.article[0].author.'@id'.text() == "3" >
Как мы видим, в результате нашей операции удаления мы получили XML-структуру только с одной статьей, а ее id равен 3 .
4. XmlSlurper
Groovy также предоставляет еще один класс, предназначенный для работы с XML. В этом разделе мы покажем, как читать XML-структуру и управлять ею с помощью XmlSlurper.
4.1. Чтение
Как и в наших предыдущих примерах, начнем с разбора структуры XML из файла:
def "Should read XML file properly"() given: "XML file" when: "Using XmlSlurper to read file" def articles = new XmlSlurper().parse(xmlFile) then: "Xml is loaded properly" articles.'*'.size() == 4 articles.article[0].author.firstname == "Siena" articles.article[2].'release-date' == "2018-06-12" articles.article[3].title == "Java 12 insights" articles.article.find it.author.'@id' == "3" >.author.firstname == "Daniele" >
Как мы видим, интерфейс идентичен интерфейсу XmlParser . Однако структура вывода использует groovy.util.slurpersupport.GPathResult , который является классом-оболочкой для Node . GPathResult предоставляет упрощенные определения таких методов, как equals() и toString() , обертывая Node#text(). В результате мы можем читать поля и параметры напрямую, используя только их имена.
4.2. Добавление узла
Добавление узла также очень похоже на использование XmlParser . Однако в этом случае groovy.util.slurpersupport. GPathResult#appendNode предоставляет метод, который принимает экземпляр java.lang.Object в качестве аргумента. В результате мы можем упростить новые определения узлов , следуя тому же соглашению, введенному Node Builder :
def "Should add node to existing xml"() given: "XML object" def articles = new XmlSlurper().parse(xmlFile) when: "Adding node to xml" articles.appendNode article(id: '5') title('Traversing XML in the nutshell') author firstname('Martin') lastname('Schmidt') > 'release-date'('2019-05-18') > > articles = new XmlSlurper().parseText(XmlUtil.serialize(articles)) then: "Node is added to xml properly" articles.'*'.size() == 5 articles.article[4].title == "Traversing XML in the nutshell" >
Если нам нужно изменить структуру нашего XML с помощью XmlSlurper, мы должны повторно инициализировать наш объект article, чтобы увидеть результаты. Мы можем добиться этого, используя комбинацию методов groovy.util.XmlSlurper #parseText и groovy.xmlXmlUtil#serialize .
4.3. Изменение узла
Как мы упоминали ранее, GPathResult представляет упрощенный подход к манипулированию данными. При этом, в отличие от XmlSlurper, мы можем изменять значения напрямую, используя имя узла или имя параметра:
def "Should modify node"() given: "XML object" def articles = new XmlSlurper().parse(xmlFile) when: "Changing value of one of the nodes" articles.article.each it.'release-date' = "2019-05-18" > then: "XML is updated" articles.article.findAll it.'release-date' != "2019-05-18" >.isEmpty() >
Заметим, что когда мы изменяем только значения объекта XML, нам не нужно снова анализировать всю структуру.
4.4. Замена узла
Теперь приступим к замене всего узла. И снова на помощь приходит GPathResult . Мы можем легко заменить узел, используя groovy.util.slurpersupport.NodeChild#replaceNode , который расширяет GPathResult и следует тому же соглашению об использовании значений Object в качестве аргументов:
def "Should replace node"() given: "XML object" def articles = new XmlSlurper().parse(xmlFile) when: "Replacing node" articles.article[0].replaceNode article(id: '5') title('Traversing XML in the nutshell') author firstname('Martin') lastname('Schmidt') > 'release-date'('2019-05-18') > > articles = new XmlSlurper().parseText(XmlUtil.serialize(articles)) then: "Node is replaced properly" articles.'*'.size() == 4 articles.article[0].title == "Traversing XML in the nutshell" >
Как и в случае добавления узла, мы модифицируем структуру XML, поэтому нам нужно снова его разобрать.
4.5. Удаление узла
Чтобы удалить узел с помощью XmlSlurper, мы можем повторно использовать метод groovy.util.slurpersupport.NodeChild#replaceNode , просто указав пустое определение узла :
def "Should remove article from xml"() given: "XML object" def articles = new XmlSlurper().parse(xmlFile) when: "Removing all articles but the ones with > articles.article .findAll it.author.'@id' != "3" > .replaceNode > articles = new XmlSlurper().parseText(XmlUtil.serialize(articles)) then: "There is only one article left" articles.children().size() == 1 articles.article[0].author.'@id' == "3" >
Опять же, изменение структуры XML требует повторной инициализации нашего объекта article .
5. XmlParser против XmlSlurper
Как мы показали в наших примерах, использование XmlParser и XmlSlurper очень похоже. Мы можем более или менее достичь одинаковых результатов с обоими. Однако некоторые различия между ними могут склонить чашу весов в сторону одного или другого.
Во-первых, XmlParser всегда анализирует весь документ в виде DOM-структуры. Благодаря этому мы можем одновременно читать и писать в него . Мы не можем сделать то же самое с XmlSlurper , так как он оценивает пути более лениво. В результате XmlParser может потреблять больше памяти. «
С другой стороны, XmlSlurper использует более простые определения, что упрощает работу с ним. Мы также должны помнить, что любые структурные изменения, внесенные в XML с помощью XmlSlurper , требуют повторной инициализации, что может привести к неприемлемому снижению производительности в случае внесения множества изменений друг за другом.
Решение о том, какой инструмент использовать, должно приниматься с осторожностью и полностью зависит от варианта использования.
6. Разметчик
Помимо чтения XML-дерева и управления им, Groovy также предоставляет инструменты для создания XML-документа с нуля. Давайте теперь создадим документ, состоящий из первых двух статей из нашего первого примера, используя groovy.xml.MarkupBuilder :
def "Should create XML properly"() given: "Node structures" when: "Using MarkupBuilderTest to create xml structure" def writer = new StringWriter() new MarkupBuilder(writer).articles article title('First steps in Java') author(id: '1') firstname('Siena') lastname('Kerr') > 'release-date'('2018-12-01') > article title('Dockerize your SpringBoot application') author(id: '2') firstname('Jonas') lastname('Lugo') > 'release-date'('2018-12-01') > > then: "Xml is created properly" XmlUtil.serialize(writer.toString()) == XmlUtil.serialize(xmlFile.text) >
В приведенном выше примере мы видим, что MarkupBuilder использует тот же подход для определений узлов , который мы использовали ранее с NodeBuilder и GPathResult .
Чтобы сравнить выходные данные MarkupBuilder с ожидаемой структурой XML, мы использовали метод groovy.xml.XmlUtil#serialize .
7. Заключение
В этой статье мы рассмотрели несколько способов управления XML-структурами с помощью Groovy.
Мы рассмотрели примеры разбора, добавления, редактирования, замены и удаления узлов с помощью двух классов, предоставляемых Groovy: XmlParser и XmlSlurper . Мы также обсудили различия между ними и показали, как можно с нуля построить дерево XML с помощью MarkupBuilder .
Как всегда, полный код, использованный в этой статье, доступен на GitHub .
Вызов JavaScript-кода из JavaFX-апплета
Вызов JavaScript-кода из JavaFX-апплета обеспечивает метод getHostServices() класса javafx.application.Application , возвращающий объект javafx.application. HostServices , который позволяет взаимодействовать с Web-страницей, содержащей JavaFX-апплет. Для взаимодействия с Web-страницей JavaFX-апплета класс HostServices предос- тавляет методы getCodeBase() , getDocumentBase() , resolveURI() , showDocument() и getWebContext() . Метод getWebContext() возвращает объект netscape.javascript.JSObject , обеспечи- вающий доступ к JavaScript-коду с помощью методов: public Object getMember(String name) — эквивалент this.name языка JavaScript; public Object getSlot(int index) — эквивалент this[index] языка JavaScript;
| 272 | Глава 7 |
public void setMember(String name, Object value) — эквивалент this.name = value языка JavaScript; public void setSlot(int index, Object value) — эквивалент this[index] = value языка JavaScript; public void removeMember(String name) — удаляет именованный член; public Object call(String methodName, Object args[]) — эквивалент this.methodName (args[0], args[1], . ) языка JavaScript; public Object eval(String s) — вычисляет JavaScript-выражение. В качестве примера использования JavaScript-кода в Java-коде дополним проект JavaFXApplicationLiveConnect Web-страницей, из которой можно будет загрузить Web-страницу, содержащую набор ссылок на изображения и JavaFX-апплет, демонстрирующий изображения. Для этого откроем среду NetBeans с поддержкой платформы JavaFX 2.0 и в меню Файл выберем Создать проект | JavaFX | JavaFX Application , нажмем кнопку Далее и введем имя проекта JavaFXApplicationJSObject, отметим флажок Create Main Class , введем имя класса javafxapplication.JavaFXApplication и нажмем кнопку Готово . В окне редактора среды NetBeans дополним код сгенерированного класса JavaFXApplication согласно листингу 7.3. Листинг 7.3. Код класса JavaFXApplication проекта JavaFXApplicationJSObject package javafxapplication; import javafx.application.Application; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.scene.Cursor; import javafx.scene.Group; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.paint.Color; import javafx.stage.Stage; import netscape.javascript.JSObject; public class JavaFXApplication extends Application < public static void main(String[] args) < Application.launch(args); >@Override public void start(Stage primaryStage) < primaryStage.setTitle("Hello World"); Group root = new Group(); Scene scene = new Scene(root, 300, 200, Color.BEIGE); Button btn = new Button(); btn.setLayoutX(35); btn.setLayoutY(80); btn.setText("Открыть изображения"); btn.setCursor(Cursor.CLOSED_HAND);
| Совместное использование JavaScript и JavaFX | 273 |
btn.setStyle(«-fx-font: bold 16pt Georgia;-fx-text-fill: white; -fx-background-color: #a0522d;-fx-border-width: 3px; -fx-border-color:#f4a460 #800000 #800000 #f4a460;» ); btn.setOnAction(new EventHandler() < @Override public void handle(ActionEvent e) < JSObject js = getHostServices().getWebContext(); String baseURI=getHostServices().getCodeBase(); if (js != null) < try < js.eval("window.location.replace ('"+baseURI+"JavaFXApplicationLiveConnect.html');"); >catch (Exception ex) < >>> >); root.getChildren().add(btn); primaryStage.setScene(scene); primaryStage.show(); >> В методе start() класса JavaFXApplication создается кнопка Button с обработчиком событий, в котором с помощью метода getCodeBase() класса HostServices получается URI-адрес каталога, содержащего JAR-файл JavaFX-приложения JavaFXApplicationJSObject и Web-страницу JavaFX-приложения JavaFXApplicationLiveConnect. Далее на основе полученного базового URI-адреса и с помощью объекта JSObject текущая Web-страница заменяется Web-страницей JavaFX-приложения JavaFXApplicationLiveConnect. Выбрав опцию Очистить и построить для проекта JavaFXApplicationJSObject, скопируем файлы папки dist проекта JavaFXApplicationJSObject в папку dist проек- та JavaFXApplicationLiveConnect. Теперь, открыв Web-страницу JavaFXApplicationJSObject.html в Web-браузере, можно будет увидеть кнопку Открыть изображения , при нажатии которой загру- зится Web-страница JavaFXApplicationLiveConnect.html. П РИМЕЧАНИЕ Проекты JavaFXApplicationJSObject и JavaFXApplicationLiveConnect с примером совместно- го использования JavaScript и JavaFX находятся в папке Примеры\Глава7 в электронном архиве (см. приложение в конце книги).
Использование JavaScript в WebView
Для JavaFX-компонента WebView прямое использование JavaScript-кода обеспечива- ет метод executeScript(java.lang.String script) класса javafx.scene.web.WebEngine , принимающий в качестве аргумента строку JavaScript-кода и исполняющий его в контексте текущей Web-страницы.
| 274 | Глава 7 |
В качестве примера использования JavaScript-кода узлом WebView модифицируем проект JavaFXApplicationWebView из разд. «Отображение Web-контента» главы 2 . Дополним код метода start() класса JavaFXApplication проекта JavaFXApplicationWebView: private Double zoom=1.0; Button btnP = new Button(); btnP.setText(«+»); btnP.setCursor(Cursor.CLOSED_HAND); btnP.setStyle(«-fx-font: bold 16pt Georgia;-fx-text-fill: white;-fx-background- color: #a0522d;-fx-border-width: 3px; -fx-border-color:#f4a460 #800000 #800000 #f4a460;» ); btnP.setOnAction(new EventHandler() < @Override public void handle(ActionEvent e) < zoom=zoom*1.1; webView.getEngine().executeScript("document.body.style.zoom p1339 ft50">Button btnM = new Button(); btnM.setText(«-«); btnM.setCursor(Cursor.CLOSED_HAND); btnM.setStyle(«-fx-font: bold 16pt Georgia;-fx-text-fill: white;-fx-background- color: #a0522d;-fx-border-width: 3px; -fx-border-color:#f4a460 #800000 #800000 #f4a460;» ); btnM.setOnAction(new EventHandler() < @Override public void handle(ActionEvent e) < zoom=zoom/1.2; webView.getEngine().executeScript("document.body.style.zoom p102 ft48">>>); hbox.getChildren().addAll(textField, btn, btnP,btnM); В граф сцены JavaFX-приложения с компонентом WebView добавлены две кнопки, при нажатии которых Web-страница, отображаемая узлом WebView , увеличивается или уменьшается. Достигается этот эффект с помощью изменения свойств объекта BODY методом executeScript() класса WebEngine . После выбора пункта Очистить и построить в окне Проекты среды NetBeans для измененного проекта JavaFX-приложения и пункта Выполнить можно будет увидеть окно JavaFX-приложения с панелью инструментов, содержащей три кнопки и текстовое поле, после ввода в которое URL-адреса Web-страницы узел WebView отобразит ее, а при нажатии на кнопки + и – отображаемая Web-страница будет увеличиваться и уменьшаться (рис. 7.2). П РИМЕЧАНИЕ Проект JavaFXApplicationWebViewWithJavaScript с примером совместного использования JavaScript и WebView находится в папке Примеры\Глава7 в электронном архиве (см. прило- жение в конце книги) .
| Совместное использование JavaScript и JavaFX | 275 | |
Рис. 7.2. JavaFX-приложение с узлом WebView и использованием JavaScript-кода
| 276 | Глава 7 |
Г Л АВ А 8 Выполнение фоновых задач Для выполнения определенной работы, занимающей значительное время процессора, в отдельном потоке от потока JavaFX Application Thread, для того чтобы не блокировать GUI-интерфейс JavaFX-приложения, платформа JavaFX 2.0 предлагает использовать пакет javafx.concurrent. Работу с фоновыми потоками пакет javafx.concurrent обеспечивает с помощью двух абстрактных классов — Task
278 Глава 8 Запуск объекта Task
| Выполнение фоновых задач | 279 |
primaryStage.setScene(scene); primaryStage.show(); 3. Создается экземпляр индикатора ProgressBar , который помещается в левый верхний угол основного окна JavaFX-приложения и для которого определяются такие свойства, как курсор мыши, визуальный эффект, всплывающая подсказка, предпочтительные размеры и начальное значение свойства progress : final ProgressBar pb=new ProgressBar(); pb.setLayoutX(20); pb.setLayoutY(50); pb.setCursor(Cursor.TEXT); DropShadow effect=new DropShadow(); effect.setOffsetX(8); effect.setOffsetY(8); pb.setEffect(effect); pb.setTooltip(new Tooltip(«Индикатор выполнения задачи»)); pb.setPrefSize(200,30); pb.setProgress(0.0); 4. Создаются две Button -кнопки — Start и Reset , которые размещаются под индикатором ProgressBar . Для кнопки Start определяется обработчик событий, в котором создается экземпляр анонимного класса, расширяющего класс Task с переопределением методов call() и updateProgress() . В методе call() имитируются продолжительные вычисления и вызывается метод updateProgress() с передачей ему в качестве аргумента текущего значения выполнения вычислений. В методе updateProgress() на основе текущего значения выполнения вычислений устанавливается прогресс индикатора ProgressBar . Также в обработчике событий кнопки Start демонстрируются три способа запуска выполнения фоновой задачи с помощью объектов Thread , ExecutorService и Service . Для кнопки Reset определяется обработчик событий, в котором индикатор ProgressBar устанавливается в начальное состояние: Button btnS=new Button(«Start»); btnS.setLayoutX(20); btnS.setLayoutY(100); btnS.setStyle(«-fx-font: 16pt Arial;»); btnS.setOnAction(new EventHandler() < @Override public void handle(ActionEvent e) < final Task task = new Task() < @Override protected Void call() throws Exception < int max = 10; for (int i = 1; i
| 280 | Глава 8 |
return null; > @Override protected void updateProgress(long workDone, long max)< pb.setProgress((double)workDone/(double)max); >>; //new Thread(task).start(); ExecutorService es = Executors.newSingleThreadExecutor (); // es.submit (task); // es.shutdown (); Service service=new Service() < @Override protected Task createTask() < return task; >>; service.start(); >>); Button btnR=new Button(«Reset»); btnR.setLayoutX(100); btnR.setLayoutY(100); btnR.setStyle(«-fx-font: 16pt Arial;»); btnR.setOnAction(new EventHandler() < @Override public void handle(ActionEvent e) < pb.setProgress(0.0); >>); 5. И в заключение индикатор ProgressBar и кнопки Button добавляются в корневой узел графа сцены: root.getChildren().add(pb); root.getChildren().add(btnS); root.getChildren().add(btnR); Запустить созданное JavaFX-приложение можно, щелкнув в окне Проекты среды NetBeans правой кнопкой мыши на узле проекта JavaFXApplicationTask и выбрав пункт Очистить и построить , а затем — Выполнить . В результате можно будет увидеть индикатор и две кнопки — Start и Reset . При нажатии кнопки Start изменяется прогресс индикатора, а при нажатии кнопки Reset индикатор устанавливается в исходное состояние.
Г Л АВ А 9 Совместное использование Swing и JavaFX Пакет javafx.embed.swing платформы JavaFX 2.0 обеспечивает встраивание JavaFXсцены в Swing-приложение. Для этого пакет javafx.embed.swing предоставляет ком- понент JFXPanel . Класс JFXPanel расширяет класс javax.swing.JComponent , являющийся базовым классом для всех Swing-компонентов, помещаемых в Swing-контейнеры верхнего уров- ня JFrame , JDialog и JApplet . Класс JFXPanel имеет, помимо унаследованных от класса JComponent методов, пуб- личные методы getScene() , setScene() , setOpaque() , isOpaque() , getPreferredSize() , addNotify() и removeNotify() . Экземпляр класса JFXPanel может быть создан с помощью конструктора public JFXPanel() или посредством класса-фабрики javafx.embed.swing.JFXPanelBuilder . При использовании компонента JFXPanel в Swing-приложении необходимо учитывать, что Swing-компоненты обрабатываются в потоке Event Dispatch Thread, а JavaFX-компоненты — в потоке JavaFX Application Thread. Поэтому для инициализации и изменения графа JavaFX-сцены в Swing-приложении используется шаблон программирования: Platform.runLater(new Runnable() < @Override public void run() < doSomethingWithJavaFX(); >>); А для инициализации и изменения Swing-интерфейса применяется шаблон программирования: SwingUtilities.invokeLater(new Runnable() < @Override public void run() < doSomethingWithSwing(); >>);
| 282 | Глава 9 |
В качестве примера использования компонента JFXPanel в Swing-приложении рассмотрим встраивание узла MediaView , воспроизводящего медиаконтент. При этом основой будет служить проект JavaFXApplicationMediaView из разд. «Воспроиз- ведение аудио и видео» главы 2 . Для создания Swing-приложения откроем среду NetBeans и в меню Файл выберем Создать проект | Java | Java Application , введем имя проекта JavaApplicationSwing, сбросим флажок Создать главный класс и нажмем кнопку Готово . В окне Проекты среды NetBeans щелкнем правой кнопкой мыши на узле проекта JavaApplicationSwing и выберем Создать | Другое | Формы Swing GUI | Форма Примера Приложения , нажмем кнопку Далее , введем имя класса MainFrame и имя пакета main и нажмем кнопку Готово . В результате средой NetBeans будет сгенерирован код класса MainFrame , который расширяет класс javax.swing.JFrame , имеет метод main() — точку входа в Swingприложение и представляет главное окно приложения с панелью меню File , Edit и Help . В окне Проекты среды NetBeans выбором Добавить архив JAR или папку для узла Библиотеки проекта JavaApplicationSwing добавим в путь приложения биб- лиотеку jfxrt.jar платформы JavaFX 2.0. В представлении Design среды NetBeans, в окне [JFrame]-свойства , изменим размеры preferredSize главного окна приложения на [600, 600] . В представлении Source среды NetBeans дополним код класса MainFrame методом initFX() (лис- тинг 9.1). Листинг 9.1. Код метода initFX() класса MainFrame private void initFX(javafx.embed.swing.JFXPanel fxPanel) < // This method is invoked on JavaFX thread final Group root = new Group(); Scene scene = new Scene(root, 400, 400, Color.BLACK); fxPanel.setScene(scene); Media video = new Media(this.getClass(). getResource("video.flv").toString()); final MediaPlayer playerVideo=new MediaPlayer(video); final MediaView media=new MediaView(playerVideo); media.setFitHeight(300); media.setFitWidth(300); media.setPreserveRatio(true); Media audio = new Media(this.getClass().getResource("audio.mp3").toString()); final MediaPlayer playerAudio=new MediaPlayer(audio); playerAudio.setCycleCount(MediaPlayer.INDEFINITE); Button btnPlay=new Button(); btnPlay.setTooltip(new Tooltip("Play"));
| Совместное использование Swing и JavaFX | 283 |
Image imPlay=new Image(this.getClass(). getResource(«player_play.png»).toString()); ImageView imvPlay=new ImageView(imPlay); imvPlay.setFitHeight(20); imvPlay.setFitWidth(20); btnPlay.setGraphic(imvPlay); btnPlay.setOnAction(new EventHandler() < @Override public void handle(ActionEvent e) < playerVideo.play(); playerAudio.play(); >>); Button btnPause=new Button(); btnPause.setTooltip(new Tooltip(«Pause»)); Image imPause=new Image(this.getClass(). getResource(«player_pause.png»).toString()); ImageView imvPause=new ImageView(imPause); imvPause.setFitHeight(20); imvPause.setFitWidth(20); btnPause.setGraphic(imvPause); btnPause.setOnAction(new EventHandler() < @Override public void handle(ActionEvent e) < playerVideo.pause(); playerAudio.pause(); >>); Button btnStop=new Button(); btnStop.setTooltip(new Tooltip(«Stop»)); Image imStop=new Image(this.getClass(). getResource(«player_stop.png»).toString()); ImageView imvStop=new ImageView(imStop); imvStop.setFitHeight(20); imvStop.setFitWidth(20); btnStop.setGraphic(imvStop); btnStop.setOnAction(new EventHandler() < @Override public void handle(ActionEvent e) < playerVideo.stop(); playerAudio.stop(); >>); final Slider sliderT=new Slider(); sliderT.setCursor(Cursor.CROSSHAIR); sliderT.setMax(31200); sliderT.setMin(0); sliderT.setOrientation(Orientation.HORIZONTAL); sliderT.setShowTickLabels(false); sliderT.setShowTickMarks(false);
| 284 | Глава 9 |
sliderT.setOnMousePressed(new EventHandler() < @Override public void handle(MouseEvent event) < playerVideo.pause(); playerAudio.pause(); >>); sliderT.setOnMouseReleased(new EventHandler() < @Override public void handle(MouseEvent event) < playerVideo.seek(new Duration(sliderT.getValue())); >>); playerVideo.currentTimeProperty().addListener(new ChangeListener() < @Override public void changed(ObservableValueov, Duration old_value, Duration new_value) < if(new_value.toMillis()>31200) < playerVideo.stop(); playerAudio.stop(); playerVideo.play(); playerAudio.play(); >sliderT.setValue(new_value.toMillis()); >>); final Slider sliderV=new Slider(); sliderV.setCursor(Cursor.CROSSHAIR); sliderV.setTooltip(new Tooltip(«Volume»)); sliderV.setMaxWidth(70); sliderV.setMax(1.0); sliderV.setMin(0.0); sliderV.setValue(0.5); sliderV.setOrientation(Orientation.HORIZONTAL); sliderV.setShowTickLabels(false); sliderV.setShowTickMarks(false); sliderV.valueProperty().addListener(new ChangeListener() < @Override public void changed(ObservableValueov, Number old_val, Number new_val) < playerAudio.setVolume(sliderV.getValue()); >>); Label volume=new Label(); Image imVolume=new Image(this.getClass(). getResource(«volume.jpeg»).toString()); ImageView imvVolume=new ImageView(imVolume); imvVolume.setFitHeight(20); imvVolume.setFitWidth(20); volume.setGraphic(imvVolume);
| Совместное использование Swing и JavaFX | 285 |
HBox hbox=new HBox(); hbox.setSpacing(5); hbox.setAlignment(Pos.CENTER); hbox.getChildren().addAll(btnPlay,btnPause,btnStop,sliderV,volume); VBox vbox=new VBox(); vbox.setSpacing(20); vbox.setAlignment(Pos.CENTER); VBox.setMargin(sliderT, new Insets(0,30,0,30)); HBox.setMargin(sliderV, new Insets(0,0,0,30)); vbox.getChildren().addAll(media,sliderT,hbox); StackPane pane=new StackPane(); pane.setAlignment(Pos.CENTER); pane.setCursor(Cursor.HAND); pane.setLayoutX(20); pane.setLayoutY(20); Rectangle rec=new Rectangle(350,350); rec.setFill(Color.BLACK); rec.setStroke(Color.NAVY); rec.setStrokeWidth(5); rec.setStrokeType(StrokeType.OUTSIDE); rec.setArcHeight(10); rec.setArcWidth(20); pane.getChildren().addAll(rec,vbox); root.getChildren().addAll(pane); > Код метода initFX() класса MainFrame основывается на коде метода start() класса JavaFXApplication проекта JavaFXApplicationMediaView из разд. «Воспроизведение аудио и видео» главы 2 . Также изменим метод main() класса MainFrame (листинг 9.2). Листинг 9.2. Код метода main() класса MainFrame public static void main(String args[]) < final MainFrame mainFrame=new MainFrame(); final javafx.embed.swing.JFXPanel fxPanel = new javafx.embed.swing.JFXPanel(); mainFrame.add(fxPanel,null); fxPanel.setBounds(50,30,400, 400); javafx.application.Platform.runLater(new Runnable() < @Override public void run() < mainFrame.initFX(fxPanel); >>); java.awt.EventQueue.invokeLater(new Runnable() < @Override
| 286 | Глава 9 |
public void run() < mainFrame.setVisible(true); >>); > В коде метода main() класса MainFrame создается экземпляр класса MainFrame и экземпляр компонента JFXPanel , который добавляется в JFrame -окно MainFrame . При этом сцена компонента JFXPanel формируется в потоке JavaFX Application Thread с помощью метода initFX() , а JFrame -окно MainFrame становится видимым в потоке Event Dispatch Thread. После выбора в окне Проекты среды NetBeans пункта Очистить и построить для проекта JavaApplicationSwing и затем пункта Выполнить можно будет увидеть окно Swing-приложения со встроенным видеоплеером (рис. 9.1). Рис. 9.1. Swing-приложение со встроенным JavaFX-плеером
Для развертывания созданного Swing-приложения в качестве JWS-приложения с подключением среды выполнения платформы JavaFX 2.0 в окне Проекты среды NetBeans выберем опцию Свойства для проекта JavaApplicationSwing. Затем в по-
| Совместное использование Swing и JavaFX | 287 |
явившемся диалоговом окне выберем категорию Web Start , отметим флажки Включить Web Start и Разрешить выполнение в автономном режиме и нажмем кнопку OK . После выбора в окне Проекты среды NetBeans пункта Очистить и построить для проекта JavaApplicationSwing необходимо дополнить в редакторе кода JNLP-файл launch.jnlp из папки dist каталога проекта (листинг 9.3). Листинг 9.3. Код JNLP-файла проекта JavaApplicationSwing
Проект JavaApplicationSwing с примером Swing-приложения, содержащего JavaFXкомпоненты, находится в папке Примеры\Глава9 в электронном архиве (см. приложение в конце книги).
| 288 | Глава 9 |
Для встраивания узла MediaView в апплет javax.swing.JApplet откроем среду NetBeans и в меню Файл выберем Создать проект | Java | Java Application , вве- дем имя проекта JavaApplicationSwingApplet, сбросим флажок Создать главный класс и нажмем кнопку Готово . В окне Проекты среды NetBeans щелкнем правой кнопкой мыши на узле проекта JavaApplicationSwingApplet и выберем Создать | Другое | Формы Swing GUI | Форма JApplet , нажмем кнопку Далее , введем имя класса апплета JAppletWithJavaFX и имя пакета main и нажмем кнопку Готово . В результате средой NetBeans будет сгенерирован код класса JAppletWithJavaFX , расширяющего класс javax.swing.JApplet . В окне Проекты среды NetBeans выбором Добавить архив JAR или папку для узла Библиотеки проекта JavaApplicationSwingApplet добавим в путь приложения библиотеку jfxrt.jar платформы JavaFX 2.0. В представлении Design среды NetBeans, в окне [JApplet]-свойства , изменим размеры preferredSize окна апплета на [600, 600] . В представлении Source среды NetBeans дополним код класса JAppletWithJavaFX методом initFX() (см. листинг 9.1). Также изменим метод init() класса JAppletWithJavaFX (листинга 9.4).
Листинг 9.4. Код метода init() класса JAppletWithJavaFX @Override public void init() < try < for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) < if ("Nimbus".equals(info.getName())) < javax.swing.UIManager.setLookAndFeel(info.getClassName()); break; >> > catch (ClassNotFoundException ex) < java.util.logging.Logger.getLogger(SwingJApplet.class.getName()). log(java.util.logging.Level.SEVERE, null, ex); >catch (InstantiationException ex) < java.util.logging.Logger.getLogger(SwingJApplet.class.getName()). log(java.util.logging.Level.SEVERE, null, ex); >catch (IllegalAccessException ex) < java.util.logging.Logger.getLogger(SwingJApplet.class.getName()). log(java.util.logging.Level.SEVERE, null, ex); >catch (javax.swing.UnsupportedLookAndFeelException ex) < java.util.logging.Logger.getLogger(SwingJApplet.class.getName()). log(java.util.logging.Level.SEVERE, null, ex); final javafx.embed.swing.JFXPanel fxPanel = new javafx.embed.swing.JFXPanel();
| Совместное использование Swing и JavaFX | 289 |
add(fxPanel,null); fxPanel.setBounds(50,30,400, 400); javafx.application.Platform.runLater(new Runnable() < @Override public void run() < initFX(fxPanel); >>); try < java.awt.EventQueue.invokeAndWait(new Runnable() < public void run() < initComponents(); >>); > catch (Exception ex) < ex.printStackTrace(); >> В коде метода init() класса JAppletWithJavaFX создается экземпляр компонента JFXPanel , который добавляется в апплет JAppletWithJavaFX. При этом сцена компонента JFXPanel формируется в потоке JavaFX Application Thread с помощью метода initFX() , а инициализация Swing-компонентов апплета JAppletWithJavaFX произво- дится в потоке Event Dispatch Thread. Для развертывания созданного JApplet-апплета с помощью JWS с подключением среды выполнения платформы JavaFX 2.0, в окне Проекты среды NetBeans выберем пункт Свойства для проекта JavaApplicationSwingApplet. Затем в появившемся диалоговом окне выберем категорию Web Start , отметим флажки Включить Web Start и Разрешить выполнение в автономном режиме , выберем переключатель Дескриптор апплета , кнопкой Параметры апплета установим ширину и высоту апплета со значениями (600, 600) и нажмем кнопку OK . После выбора в окне Проекты среды NetBeans пункта Очистить и построить для проекта JavaApplicationSwingApplet необходимо дополнить в редакторе кода JNLPфайл launch.jnlp из папки dist каталога проекта (листинг 9.5). Листинг 9.5. Код JNLP-файла проекта JavaApplicationSwingApplet
| 290 | Глава 9 |
Г Л АВ А 10 Компоненты JavaFX Beans и связывание данных Платформа Java определяет JavaBeans-компоненты, которые используются для передачи данных. JavaBeans-компоненты представлены Java-классами, созданными по определенным правилам. Классы JavaBeans-компонентов имеют публичный конструктор без параметров, свойства класса доступны через методы get() и set() , классы имеют методы add() и remove() добавления и удаления слушателей событий, а также классы обладают свойством сериализуемости (преобразования в последовательность байтов и обратно) путем реализации интерфейса java.io.Serializable . Кроме того, платформа Java содержит программный интерфейс JavaBeans API для создания и обработки JavaBeans-компонентов. Платформа JavaFX 2.0 расширяет JavaBeans-модель, определяя JavaFX-свойства. JavaFX-свойства технологии JavaFX 2.0 реализованы пакетами javafx.beans, javafx.beans.binding, javafx.beans.property и javafx.beans.value программного интерфейса JavaFX 2.0 API. Конечной реализацией JavaFX-свойств являются классы SimpleBooleanProperty , SimpleDoubleProperty , SimpleFloatProperty , SimpleIntegerProperty , SimpleLongProperty , SimpleObjectProperty
| 292 | Глава 10 |
Кроме того, данные классы расширяют классы XXXExpression пакета javafx.beans.binding, обеспечивающие для JavaFX-свойств методы создания объектов XXXBinding , представляющих выражения, результат которых синхронизирован со значением данного свойства и со значением объекта, выступающим в качестве аргумента вышеупомянутых методов. Таким образом, JavaFX Beans-компонент представлен классом, имеющим: публичный конструктор без параметров; методы get() и set() доступа к свойствам класса; методы xxxProperty() , возвращающие JavaFX-свойства. В качестве примера рассмотрим класс javafx.scene.control.Button программного интерфейса JavaFX API. Класс Button имеет: унаследованные от класса javafx.scene.Node свойства: blendMode , boundsInLocal , boundsInParent , cacheHint , cache , clip , cursor , depthTest , disabled , disable , effect , eventDispatcher , focused , focusTraversable , hover , id , inputMethodRequests , layoutBounds , layoutX , layoutY , managed , mouseTransparent , onDragDetected , onDragDone , onDragDropped , onDragEntered , onDragExited , onDragOver , onInputMethodTextChanged , onKeyPressed , onKeyReleased , onKeyTyped , onMouseClicked , onMouseDragged , onMouseEntered , onMouseExited , onMouseMoved , onMousePressed , onMouseReleased , opacity , parent , pickOnBounds , pressed , rotate , rotationAxis , scaleX , scaleY , scaleZ , scene , style , translateX , translateY , translateZ , visible ; унаследованное от класса javafx.scene.Parent свойство needsLayout ; унаследованные от класса javafx.scene.control.Control свойства: contextMenu , height , maxHeight , maxWidth , minHeight , minWidth , prefHeight , prefWidth , skin , tooltip , width ; унаследованные от класса javafx.scene.control.Labeled свойства: alignment , contentDisplay , font , graphic , graphicTextGap , labelPadding , mnemonicParsing , textAlignment , textFill , textOverrun , text , underline , wrapText ; унаследованные от класса javafx.scene.control.ButtonBase свойства armed и onAction ; собственные свойства cancelButton и defaultButton . Какие-то из этих свойств доступны для чтения и записи, а какие-то — только для чтения. Для каждого свойства чтения и записи класс Button предоставляет методы доступа get() и set() , для свойства только чтения — метод get() , а также метод xxxProperty() , возвращающий JavaFX-свойство ХХХProperty или ReadOnlyХХХProperty . Рассмотрим свойство text класса Button . Метод textProperty() Button -объекта возвращает JavaFX-свойство javafx.beans. property.StringProperty , определяющее значение свойства text . Установить значение данного JavaFX-свойства и соответственно значение свойства text кнопки
| Компоненты JavaFX Beans и связывание данных | 293 |
Button можно методом set() интерфейса javafx.beans.value.WritableObjectValue или методом setValue() класса javafx.beans.property.StringProperty . Возвращает значение свойства text кнопки Button метод get() интерфейса javafx.beans.value. ObservableObjectValue или метод getValue() класса javafx.beans.binding. StringExpression : Button btn = new Button(); btn.textProperty().set(«Hello1»); System.out.println(btn.textProperty().get()); btn.textProperty().setValue(«Hello2»); System.out.println(btn.textProperty().getValue()); При этом методы getBean() и getName() вернут объект javafx.scene.control.Button и имя свойства text : System.out.println(btn.textProperty().getBean().getClass().getName()); //Вывод: javafx.scene.control.Button System.out.println(btn.textProperty().getName()); //Вывод: text Применяя метод bind() интерфейса javafx.beans.property.Property к JavaFX-свой- ству текста кнопки Button , можно связать его значение, например, со значением JavaFX-свойства текста поля javafx.scene.control.TextField так, что, набирая текст в поле TextField , он будет автоматически становиться текстом кнопки Button : TextField field=new TextField(); Button btn = new Button(); btn.textProperty().bind(field.textProperty()); Применяя метод bindBidirectional() класса javafx.beans.property.StringProperty к JavaFX-свойству текста кнопки Button , можно связать его значение, например, со значением JavaFX-свойства текста поля TextField так, что в нем будет автоматически отображаться измененный текст кнопки Button : TextField field=new TextField(); final Button btn = new Button(); btn.setOnAction(new EventHandler() < public void handle(ActionEvent event) < btn.textProperty().set("Hello"); >>); btn.textProperty().bindBidirectional(field.textProperty()); Организовать прослушивание изменения значения JavaFX-свойства текста кнопки Button позволяет метод addListener(ChangeListener listener) интерфейса javafx.beans.value.ObservableValue : TextField field1=new TextField(); final TextField field2=new TextField(); Button btn = new Button(); btn.textProperty().bind(field1.textProperty());
| 294 | Глава 10 |
btn.textProperty().addListener(new ChangeListener() < @Override public void changed(ObservableValueo, String oldVal, String newVal)< field2.setText(newVal); >>); Так как JavaFX-свойства поддерживают отложенные вычисления своих значений, т. е. значение JavaFX-свойства пересчитывается не сразу после своего изменения, а только тогда, когда оно запрашивается методом get() , и в промежутке между изменением значения и его пересчетом генерируется событие недействительности зна- чения, метод addListener(InvalidationListener listener) интерфейса javafx.beans. Observable позволяет организовать прослушивание недействительности значения JavaFX-свойства текста кнопки Button : final Button btn = new Button(); btn.setOnAction(new EventHandler() < public void handle(ActionEvent event) < btn.textProperty().set("Hello"); >>); btn.textProperty().addListener(new InvalidationListener()< @Override public void invalidated(Observable observable) < System.out.println("Button text value invalidated"); >>); Здесь при нажатии кнопки Button возникнет событие недействительности значения JavaFX-свойства текста. Так как JavaFX-свойство StringProperty текста кнопки Button расширяет класс javafx.beans.binding.StringExpression , его методы позволяют организовать различ- ного рода связанные вычисления: TextField field1=new TextField(); TextField field2=new TextField(); Button btn = new Button(); btn.textProperty().set(«Hello «); StringBinding sb = (StringBinding)btn.textProperty().concat(field1.textProperty()); field2.textProperty().bind(sb); Здесь текст при вводе в поле field1 соединяется с текстом кнопки и результат отображается в поле field2 . В JavaFX-коде также можно использовать самостоятельные JavaFX-свойства, безотносительно к JavaFX-компонентам. Для этого необходимо создавать экземпляры классов SimpleBooleanProperty , SimpleDoubleProperty , SimpleFloatProperty , SimpleIntegerProperty , SimpleLongProperty , SimpleObjectProperty и SimpleStringProperty пакета javafx.beans.property . Так как данные классы расширяют классы XXXExpression пакета javafx.beans.binding, при этом можно создавать связанные вычисления. Для создания связанных вычислений платформа JavaFX 2.0 предлагает два типа программного интерфейса — High-Level Binding API и Low-Level Binding API.
| Компоненты JavaFX Beans и связывание данных | 295 |
Программный интерфейс High-Level Binding API также содержит Fluent API и предусматривает применение класса Bindings . Пример использования Fluent API: TextField field=new TextField(); SimpleDoubleProperty sp1=new SimpleDoubleProperty(); SimpleDoubleProperty sp2=new SimpleDoubleProperty(); NumberBinding sum=sp2.add(sp1); field.textProperty().bind(sum.asString()); sp1.set(4); sp2.set(3); //Вывод: 7.0 Здесь в поле TextField отобразится сумма значений двух JavaFX-свойств SimpleDoubleProperty . Пример использования класса Bindings : TextField field=new TextField(); SimpleDoubleProperty sp1=new SimpleDoubleProperty(); SimpleDoubleProperty sp2=new SimpleDoubleProperty(); NumberBinding sum=Bindings.add(sp1,sp2); field.textProperty().bind(sum.asString()); sp1.set(4); sp2.set(3); //Вывод: 7.0 Здесь в поле TextField также отобразится сумма значений двух JavaFX-свойств SimpleDoubleProperty . Пример использования Low-Level Binding API: TextField field=new TextField(); field.setPrefWidth(100); final SimpleDoubleProperty sp1=new SimpleDoubleProperty(); final SimpleDoubleProperty sp2=new SimpleDoubleProperty(); DoubleBinding db = new DoubleBinding() < < super.bind(sp1,sp2); >@Override protected double computeValue() < return (sp1.get()+sp2.get()); >>; NumberBinding sum=db; field.textProperty().bind(sum.asString()); sp1.set(4); sp2.set(3); //Вывод: 7.0 Для применения Low-Level Binding API необходимо расширять классы ХХХBinding пакета javafx.beans.binding с переопределением их метода computeValue() .
| 296 | Глава 10 |
Г Л АВ А 11 Заставка запуска JavaFX-приложения От начала своего запуска до отображения главного окна JavaFX-приложение проходит четыре фазы процесса запуска и загрузки: 1. Инициализация среды выполнения. 2. Загрузка ресурсов приложения. 3. Инициализация приложения. 4. Выполнение кода приложения. Набор файлов папки dist проекта JavaFX-приложения обеспечивает заставки по умолчанию для первых двух фаз запуска и загрузки. Особенно это актуально для запуска JavaFX-апплета, встроенного в Web-страницу, и JWS-запуска настольного JavaFX-приложения, т. к. запуск отдельного JavaFX-приложения происходит достаточно быстро и не требует заставок. Улучшить отображение и обработку процесса запуска и загрузки JavaFXприложения можно несколькими способами. Первый способ — это использование обработчика onGetSplash программного интерфейса Deployment Toolkit API для создания окна запуска JavaFX-апплета, встроенного в Web-страницу. Другой способ — это применение CSS-стилей к Preloader-предзагрузчику по умолчанию. И наконец, можно создать свой класс предзагрузчика, расширяющий абстрактный класс javafx.application.Preloader . Обработчик onGetSplash возвращает HTML-код, отображаемый во время инициализации среды выполнения JavaFX-апплета. Для создания своей заставки запуска JavaFX-апплета откроем в редакторе кода HTML-страницу из папки dist проекта JavaFX-приложения, предварительно сгенерированную с помощью выбора пункта Очистить и построить среды NetBeans, и добавим следующий код:
| 298 | Глава 11 |
var im=document.createElement(«img»); im.setAttribute(‘src’, ‘web-files/image.gif’); im.setAttribute(‘hspace’, ‘350’); im.setAttribute(‘vspace’, ‘100’); p.appendChild(im); return p; > function javafxEmbed_fxApp() < dtjava.embed( < id : 'fxApp', url : 'JavaFXApplicationWithSplash.jnlp', placeholder : 'javafx-app-placeholder', width : 800, height : 600, jnlp_content : '. . .' >, < javafx : '2.0+' >, < onGetSplash: getSplash >); > dtjava.addOnloadCallback(javafxEmbed_fxApp); Не забудем при этом добавить в папку web-files каталога dist проекта JavaFXприложения файл изображения image.gif. В результате при открытии Web-страницы JavaFX-приложения можно будет увидеть созданную заставку (рис. 11.1).
Рис. 11.1. JavaFX-приложение с пользовательской заставкой инициализации среды выполнения JavaFX-апплета
| Заставка запуска JavaFX-приложения | 299 |
П РИМЕЧАНИЕ Проект JavaFXApplicationWithSplash с примером создания заставки инициализации среды выполнения JavaFX-апплета находится в папке Примеры\Глава11 в электронном архиве (см. приложение в конце книги) . Preloader-предзагрузчик по умолчанию отвечает за визуализацию второй фазы процесса запуска JavaFX-приложения — загрузки ресурсов приложения. По умолчанию среда выполнения платформы JavaFX 2.0 использует Preloader-предзагрузчик, отображающий пользователю заставку с JavaFX-компонентом ProgressBar . При этом CSS-стиль предзагрузчика по умолчанию определяется в файле deploydialogs.css библиотеки jfxrt.jar платформы JavaFX 2.0. CSS-селекторы и CSS- свойства, определяемые в файле deploydialogs.css для предзагрузчика по умолчанию, перечислены в табл. 11.1.
| Таблица 11.1. CSS-селекторы и CSS-свойства, | |
| установленные для предзагрузчика по умолчанию | |
| Селекторы | Свойства |
| .default-preloader | -fx-background-color , |
| -fx-text-fill | |
| .default-preloader .progress-bar | -fx-skin , |
| -fx-background-color | |
| .default-preloader .progress-bar .bar | -fx-background-color , |
| -fx-background-insets , | |
| -fx-padding | |
| .default-preloader .progress-bar .track | -fx-background-color , |
| -fx-border-color , | |
| -fx-opacity | |
Для изменения CSS-стиля предзагрузчика по умолчанию откроем файл jfx-impl.xml из папки nbproject каталога NetBeans-проекта JavaFX-приложения и внесем следующий код: » mainClass=»$» fallbackClass=»$»> -fx-background-color: #00bfff; -fx-preloader-text: ‘Loading Application!’; -fx-font: bold 20pt Arial; >»/> ¶meters-impl;
| 300 | Глава 11 |
Таким образом, изменяя параметр javafx.default.preloader.stylesheet приложения, можно изменять CSS-стиль предзагрузчика по умолчанию. Выбрав в среде NetBeans опцию проекта Очистить и построить и открыв HTML-страницу из папки dist каталога проекта, можно увидеть заставку загрузки кода JavaFX-приложения (рис. 11.2). Рис. 11.2. JavaFX-приложение с измененным CSS-стилем предзагрузчика по умолчанию П РИМЕЧАНИЕ Проект JavaFXApplicationWithCSSPreloader с примером предзагрузчика по умолчанию с измененным CSS-стилем находится в папке Примеры\Глава11 в электронном архиве (см. приложение в конце книги) . Для того чтобы создать свой Preloader-предзагрузчик, откроем среду NetBeans и в меню Файл выберем Создать проект | JavaFX | JavaFX Preloader , нажмем кнопку Далее , введем имя проекта и нажмем кнопку Готово . NetBeans-шаблон JavaFX Preloader создает класс, расширяющий абстрактный класс javafx.application.Preloader , с переопределением методов start() , handleStateChangeNotification() и handleProgressNotification() . В методе start() можно создать сцену, представляющую заставку, в методе handleStateChangeNotification() при переходе процесса запуска приложения в состояние BEFORE_INIT , BEFORE_LOAD или BEFORE_START можно закрыть окно заставки, а в методе handleProgressNotification() — передать значение прогресса запуска приложения, например, в индикатор сцены.
| Заставка запуска JavaFX-приложения | 301 |
Кроме того, в расширении класса Preloader можно переопределить методы init() , stop() , handleApplicationNotification() и handleErrorNotification() для инициализа- ции предзагрузчика, освобождения его ресурсов, обработки уведомлений от приложения и обработки ошибок запуска приложения. В окне редактора среды NetBeans изменим код сгенерированной основы класса предзагрузчика (листинг 11.1). Листинг 11.1. Код класса предзагрузчика package javafxpreloader; import javafx.application.Preloader; import javafx.application.Preloader.ProgressNotification; import javafx.application.Preloader.StateChangeNotification; import javafx.scene.Group; import javafx.scene.Scene; import javafx.scene.control.ProgressIndicator; import javafx.scene.layout.BorderPane; import javafx.scene.paint.Color; import javafx.stage.Stage; public class JavaFXPreloader extends Preloader < private Stage stage; private ProgressIndicator progress; @Override public void start(Stage stage) throws Exception < this.stage = stage; Group root = new Group(); root.setTranslateX(200); root.setTranslateY(200); Scene scene = new Scene(root, 800, 600, Color.BEIGE); BorderPane pane = new BorderPane(); pane.setPrefSize(200,200); progress=new ProgressIndicator(); progress.setStyle("-fx-font:bold 20pt Arial;"); pane.setCenter(progress); root.getChildren().addAll(pane); stage.setScene(scene); stage.show(); >@Override public void handleStateChangeNotification(StateChangeNotification scn) < /*if (scn.getType() == StateChangeNotification.Type.BEFORE_START) < stage.hide(); >*/ > @Override public void handleProgressNotification(ProgressNotification pn) < progress.setProgress(pn.getProgress()); >
| 302 | Глава 11 |
@Override public void handleApplicationNotification(Preloader.PreloaderNotification info)< Preloader.ProgressNotification ntf=(Preloader.ProgressNotification) info; if (ntf.getProgress()==1.0) stage.hide(); else progress.setProgress(-1); >> В методе start() класса предзагрузчика создается сцена с круглым индикатором ProgressIndicator , в методе handleProgressNotification() значение прогресса загруз- ки ресурсов приложения передается в индикатор сцены ProgressIndicator , в методе handleApplicationNotification() обрабатывается значение прогресса инициализации самого приложения, передаваемое из приложения, при этом по завершении инициализации приложения окно заставки закрывается. Таким образом, созданный предзагрузчик отвечает за визуализацию не только второй фазы процесса запуска JavaFX-приложения — загрузки ресурсов приложения, но и за визуализацию третьей фазы — инициализации приложения. Для передачи предзагрузчику значения прогресса инициализации приложения в главный класс JavaFX-приложения включим код: @Override public void init() throws Exception < this.notifyPreloader(new Preloader.ProgressNotification(0.0)); Thread.sleep(2000); this.notifyPreloader(new Preloader.ProgressNotification(1.0)); >Для того чтобы связать созданный предзагрузчик с JavaFX-приложением, в окне Проекты среды NetBeans щелкнем правой кнопкой мыши на узле проекта и выберем Свойства | Run . В строке Use Preloader нажмем кнопку Browse , затем кнопку Далее и выберем проект предзагрузчика, после чего нажмем кнопку Готово . Закроем окно свойств проекта кнопкой OK и выберем для проекта JavaFX- приложения опцию Очистить и построить . В результате среда NetBeans создаст в каталоге dist проекта папку lib с JAR-файлом предзагрузчика и включит в манифест JAR-файла JavaFX-приложения строки: Manifest-Version: 1.0 JavaFX-Version: 2.0 implementation-vendor: implementation-title: JavaFXApplicationWithPreloader implementation-version: 1.0 JavaFX-Preloader-Class: javafxpreloader.JavaFXPreloader JavaFX-Application-Class: javafxapplication.JavaFXApplication JavaFX-Class-Path: lib/JavaFXPreloader.jar JavaFX-Fallback-Class: com.javafx.main.NoJavaFXFallback Created-By: JavaFX Packager Main-Class: com/javafx/main/Main
| Заставка запуска JavaFX-приложения | 303 |
Кроме того, JNLP-файл каталога dist также дополнится строками:
| 304 | Глава 11 |
Г Л АВ А 12 Язык FXML Платформа JavaFX 2.0 представляет альтернативный Java-коду способ создания графа сцены JavaFX-приложения с помощью языка FXML, являющегося декларативным языком, основанным на языке XML. Применение языка FXML находится в русле традиций использования декларативных языков RIA-платформ для описания GUI-интерфейсов. Для платформы Flex — это язык MXML, для платформы Silverlight — это язык XAML. Использование языка FXML заключается в создании FXML-файла, содержащего описание дочерних узлов корневого узла графа сцены и загрузки его с помощью статического метода load() класса javafx.fxml.FXMLLoader , возвращающего корневой узел графа сцены: Parent root = FXMLLoader.load(getClass().getResource(«application.fxml»)); stage.setScene(new Scene(root)); stage.show(); Преимущество такой модели создания GUI-интерфейса состоит в том, что изменение графа сцены JavaFX-приложения не требует перекомпиляции Java-кода — FXML-описание GUI-интерфейса загружается при запуске JavaFX-приложения. Среда разработки NetBeans c поддержкой платформы JavaFX 2.0 содержит NetBeans-шаблон JavaFX FXML Application для создания JavaFX-приложений, GUI-интерфейс которых основан на FXML-описании. Для того чтобы воспользоваться данным NetBeans-шаблоном, необходимо открыть среду NetBeans c поддержкой платформы JavaFX 2.0 и в меню Файл выбрать Соз- дать проект | JavaFX | JavaFX FXML Application , нажать кнопку Далее , ввести имя проекта, отметить флажок Create Application Class и нажать кнопку Готово . В результате средой NetBeans будет сгенерирован проект, содержащий в каталоге src папку пакета главного класса приложения с тремя файлами — Java-файлом главного класса приложения, Sample.fxml и Sample.java. Главный класс приложения расширяет класс javafx.application.Application с переопределением его метода start() и имеет метод main() — точку входа в приложение. При этом в методе start() производится загрузка FXML-файла Sample.fxml,
javafx.fxml.Initializable ,
| 306 | Глава 12 |
содержащего описание графа сцены приложения, с созданием корневого узла графа сцены с помощью статического метода load() класса javafx.fxml.FXMLLoader . FXML-файл Sample.fxml содержит описание корневой JavaFX-панели AnchorPane с дочерними JavaFX-компонентами Button и Label . Атрибут onAction компонента Button ссылается на метод handleButtonAction() , обрабатывающий нажатие кнопки Button , класса Sample , который определен в качестве контроллера атрибутом fx:controller компонента AnchorPane . Язык FXML поддерживает шаблон проектирования Model-View-Controller (MVC), разделяющий бизнес-логику, представление данных и взаимодействие с пользователем. В MVC-терминологии FXML-описание является представлением View, модель Model представляет компонент JavaFX Beans, обеспечивающий данные JavaFXприложения, а контроллером Controller является Java-класс, реализующий код взаимодействия с пользователем и связывания компонента JavaFX Beans с FXMLописанием GUI-интерфейса JavaFX-приложения. Для включения такого контроллера в JavaFX-приложение его необходимо указать в качестве значения атрибута fx:controller FXML-описания. FXML-контроллер может реализовывать интерфейс имеющий единственный метод void initialize(java.net.URL location, java.util. ResourceBundle resources) , вызываемый средой выполнения для инициализации класса FXML-контроллера и используемый, например, для связывания компонента данных JavaFX Beans с FXML-описанием: private Person person = new Person(); @FXML private TextField firstName; @Override public void initialize(URL url, ResourceBundle rb) < firstName.textProperty().bindBidirectional(person.firstNameProperty());>Методу initialize() , в качестве аргументов, среда выполнения передает URL-адрес FXML-описания GUI-интерфейса JavaFX-приложения и ResourceBundle-ресурсы JavaFX-приложения. URL-адрес FXML-описания и ResourceBundle-ресурсы определяются при вызове в главном классе JavaFX-приложения статического метода load(java.net.URL location, java.util.ResourceBundle resources) класса javafx.fxml.FXMLLoader . Файл ResourceBundle-ресурсов решает задачу локализации строк GUI-интерфейса и передается как ResourceBundle -объект методу load() с помощью статического метода getBundle() класса java.util.ResourceBundle : Parent root = FXMLLoader.load(getClass().getResource(«[имя файла FXMLописания].fxml»), ResourceBundle.getBundle(«[имя пакета].[имя properties-файла]»));
Для создания файла ResourceBundle-ресурсов в среде NetBeans щелкнем правой кнопкой мыши на узле пакета главного класса JavaFX FXML-приложения и выбе-
| Язык FXML | 307 |
рем Создать | Другое | Другое | Файл свойств , нажмем кнопку Далее , введем имя файла и нажмем кнопку Готово . В результате средой NetBeans будет сгенерирован properties-файл, имя которого без расширения, но предваряя именем пакета, нужно указать в аргументе метода ResourceBundle.getBundle() . Заполнив файл ResourceBundle-ресурсов парами «ключ — значение», получить доступ к любому значению можно с помощью метода getString(String key) класса java.util.ResourceBundle в Java-коде или указанием префикса % и имени ключа в качестве значения атрибута компонента FXML-описания: properties-файл: labelText=Hello файл FXML-описания: Помимо интерфейса javafx.fxml.Initializable FXML-контроллер может использовать аннотацию @FXML пакета javafx.fxml, которая маркирует protected — или private — поля и методы FXML-контроллера, доступные из FXML-описания: файл FXML-описания: #handleButtonAction » fx:id=»button» /> класс FXML-контроллера: @FXML private void handleButtonAction (ActionEvent event) < System.out.println("You clicked me!");>Чтобы не применять аннотацию @FXML , поля и методы FXML-контроллера, доступные из FXML-описания, делаются публичными: файл FXML-описания: #handleButtonAction » fx:id=»button» /> класс FXML-контроллера: public void handleButtonAction (ActionEvent event) < System.out.println("You clicked me!");>FXML-описание GUI-интерфейса JavaFX-приложения имеет структуру XMLдокумента, однако не имеет XML-схемы. Для использования специфических XMLэлементов пространства имен языка FXML в FXML-описание должно быть вклю- чено объявление пространства имен xmlns:fx=»http://javafx.com/fxml». Экземпляр JavaFX-компонента создается в FXML-описании с помощью тега импорта и тега, начинающегося с имени класса JavaFX-компонента:
| 308 | Глава 12 |
FXML-описание позволяет создавать не только экземпляры JavaFX-компонентов, но и другие Java-объекты. Экземпляр хэш-таблицы java.util.HashMap создается с помощью тега: Экземпляр Java-класса, имеющего статический метод valueOf() , создается с помощью указания атрибута fx:value : Java-объект, возвращаемый методом класса-фабрики, создается указанием атрибута fx:factory :
| Язык FXML | 309 |
префикс @ представляет URL-адрес:
| 310 | Глава 12 |
П Р И Л О Ж Е Н И Е Описание электронного архива Электронный архив к книге выложен на FTP-сервере издательства по адресу: ftp://85.249.45.166/9785977508209.zip . Ссылка доступна и со страницы книги на сайте www.bhv.ru . В архиве находятся следующие папки: папка Приложения содержит файл с описанием пакетов программного интер- фейса JavaFX 2.0 API;
| папка Примеры содержит | рассмотренные в книге проекты, перечисленные | |
| в табл. П1. | ||
| Таблица П1. Содержимое папки Примеры | ||
| Папка | Описание | |
| \Глава2 | ||
| JavaFXApplicationAccordion | Пример создания панели Accordion | |
| JavaFXApplicationAnchorPane | Пример создания панели AnchorPane | |
| JavaFXApplicationAreaChart | Пример создания диаграммы AreaChart | |
| JavaFXApplicationAudioClip | Пример звукового сопровождения | |
| JavaFXApplicationBarChart | Пример создания диаграммы BarChart | |
| JavaFXApplicationBorderPane | Пример создания панели BorderPane | |
| JavaFXApplicationBoxPane | Пример создания панелей VBox и HBox | |
| JavaFXApplicationBubbleChart | Пример создания диаграммы BubbleChart | |
| JavaFXApplicationButton | Пример создания кнопки Button | |
| JavaFXApplicationCheckBox | Пример создания флажка CheckBox | |
| JavaFXApplicationChoiceBox | Пример создания списка ChoiceBox | |
| JavaFXApplicationContextMenu | Пример создания контекстного меню ContextMenu | |
| JavaFXApplicationFileChooser | Пример создания окна FileChooser | |
| JavaFXApplicationFlowPane | Пример создания панели FlowPane | |
| 312 | Приложение | |
| Таблица П1 (продолжение) | ||
| Папка | Описание | |
| JavaFXApplicationGridPane | Пример создания панели GridPane | |
| JavaFXApplicationGroup | Пример создания узла Group | |
| JavaFXApplicationHTMLEditor | Пример создания редактора HTML-контента | |
| HTMLEditor | ||
| JavaFXApplicationHyperlink | Пример создания гиперссылки Hyperlink | |
| JavaFXApplicationImageView | Пример создания узла изображения ImageView | |
| JavaFXApplicationLabel | Пример создания метки Label | |
| JavaFXApplicationLineChart | Пример создания диаграммы LineChart | |
| JavaFXApplicationListView | Пример создания списка ListView | |
| JavaFXApplicationMediaView | Пример воспроизведения медиаконтента | |
| JavaFXApplicationMenuBar | Пример создания панели MenuBar | |
| JavaFXApplicationMenuButton | Пример создания кнопки MenuButton | |
| JavaFXApplicationPasswordField | Пример создания поля PasswordField | |
| JavaFXApplicationPieChart | Пример создания диаграммы PieChart | |
| JavaFXApplicationPopup | Пример создания окна Popup | |
| JavaFXApplicationProgress | Пример создания индикаторов ProgressBar | |
| и ProgressIndicator | ||
| JavaFXApplicationRadioButton | Пример создания переключателя RadioButton | |
| JavaFXApplicationScatterChart | Пример создания диаграммы ScatterChart | |
| JavaFXApplicationScene | Пример создания сцены Scene | |
| JavaFXApplicationScrollPane | Пример создания панели ScrollPane | |
| JavaFXApplicationSeparator | Пример создания разделителя Separator | |
| JavaFXApplicationShape | Пример создания геометрических форм Arc , Circle , | |
| CubicCurve , Ellipse , Line , Path , Polygon , Polyline , | ||
| QuadCurve , Rectangle , SVGPath | ||
| JavaFXApplicationSlider | Пример создания ползунка Slider | |
| JavaFXApplicationSplitMenuButton | Пример создания кнопки SplitMenuButton | |
| JavaFXApplicationSplitPane | Пример создания панели SplitPane | |
| JavaFXApplicationStackPane | Пример создания панели StackPane | |
| JavaFXApplicationStage | Пример создания окна Stage | |
| JavaFXApplicationTableView | Пример создания таблицы TableView | |
| JavaFXApplicationTabPane | Пример создания панели TabPane | |
| JavaFXApplicationText | Пример создания узла Text | |
| JavaFXApplicationTextArea | Пример создания поля TextArea | |
| JavaFXApplicationTextField | Пример создания поля TextField | |
| Описание электронного архива | 313 |
| Таблица П1 (продолжение) | |
| Папка | Описание |
| JavaFXApplicationTilePane | Пример создания панели TilePane |
| JavaFXApplicationTitledPane | Пример создания панели TitledPane |
| JavaFXApplicationToggleButton | Пример создания кнопки ToggleButton |
| JavaFXApplicationToolBar | Пример создания панели ToolBar |
| JavaFXApplicationTooltip | Пример создания подсказки Tooltip |
| JavaFXApplicationTreeView | Пример создания дерева TreeView |
| JavaFXApplicationWebView | Пример создания встроенного Web-браузера |
| WebView | |
| \Глава4 | |
| JavaFXApplicationBlend | Пример эффекта Blend |
| JavaFXApplicationBloom | Пример эффекта Bloom |
| JavaFXApplicationBoxBlur | Пример эффекта BoxBlur |
| JavaFXApplicationColorAdjust | Пример эффекта ColorAdjust |
| JavaFXApplicationDisplacementMap | Пример эффекта DisplacementMap |
| JavaFXApplicationDropShadow | Пример эффекта DropShadow |
| JavaFXApplicationGaussianBlur | Пример эффекта GaussianBlur |
| JavaFXApplicationGlow | Пример эффекта Glow |
| JavaFXApplicationInnerShadow | Пример эффекта InnerShadow |
| JavaFXApplicationLighting | Пример эффекта Lighting |
| JavaFXApplicationMotionBlur | Пример эффекта MotionBlur |
| JavaFXApplicationPerspective | Пример эффекта Perspective |
| JavaFXApplicationReflection | Пример эффекта Reflection |
| JavaFXApplicationSepiaTone | Пример эффекта SepiaTone |
| JavaFXApplicationShadow | Пример эффекта Shadow |
| \Глава5 | |
| JavaFXApplicationAnimation | Пример создания Transition — и Timeline -анимации |
| \Глава7 | |
| JavaFXApplicationJSObject, | Примеры совместного использования JavaScript |
| JavaFXApplicationLiveConnect | и JavaFX |
| JavaFXApplicationWebViewWithJavaScript | Пример совместного использования JavaScript |
| и WebView | |
| \Глава8 | |
| JavaFXApplicationTask | Пример выполнения фоновой задачи |
| в JavaFX-приложении |
| 314 | Приложение | |
| Таблица П1 (окончание) | ||
| Папка | Описание | |
| \Глава9 | ||
| JavaApplicationSwing | Пример Swing-приложения, содержащего | |
| JavaFX-компоненты | ||
| JavaApplicationSwingApplet | Пример JApplet-апплета, содержащего | |
| JavaFX-компоненты | ||
| \Глава11 | ||
| JavaFXApplicationWithCSSPreloader | Пример предзагрузчика по умолчанию с изменен- | |
| ным CSS-стилем | ||
| JavaFXApplicationWithSplash | Пример создания заставки инициализации среды | |
| выполнения JavaFX-апплета | ||
| JavaFXApplicationWithPreloader, | Пример JavaFX-приложения и его предзагрузчика | |
| JavaFXPreloader | ||