Pull to refresh

JavaFX, HelloWorld + CSS + FXML. Окончание

Reading time9 min
Views34K
HelloWorld из примера, предложенного Oracle в «Getting Started with JavaFX», на ПК с Windows. Преображение окна ввода логина и пароля с помощью CSS, создание формы на FXML, а также использование CSS в FXML'овой форме. Снова командная строка и тонкости, о которых нам не рассказали в туториале.



В первом разделе Getting Started with JavaFX описывается создание простейшего приложения JavaFX, которое получилось завести с помощью командной строки здесь. Во втором разделе предлагается сделать окно ввода логина и пароля. Оно сделано из первого приложения тут. В результате код несколько вырос в размерах.
HelloWorld.java
package helloworld;

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.scene.layout.GridPane;
import javafx.geometry.*;
import javafx.scene.text.*;
import javafx.scene.control.*;
import javafx.scene.paint.*;
import javafx.scene.layout.HBox;

public class HelloWorld extends Application {
	public static void main(String[] args) {
		launch(args);
	}
	@Override
	public void start(Stage primaryStage) {
		primaryStage.setTitle("JavaFX Welcome");
		GridPane grid = new GridPane();
		grid.setAlignment(Pos.CENTER);
		grid.setHgap(10);
		grid.setVgap(10);
		grid.setPadding(new Insets(25, 25, 25, 25));
//		grid.setGridLinesVisible(true);

		Text scenetitle = new Text("Welcome");
		scenetitle.setFont(Font.font("Tahoma", FontWeight.NORMAL, 20));

		grid.add(scenetitle, 0, 0, 2, 1);
		Label userName = new Label("User Name:");
		grid.add(userName, 0, 1);
		TextField userTextField = new TextField();
		grid.add(userTextField, 1, 1);
		Label pw = new Label("Password:");
		grid.add(pw, 0, 2);
		PasswordField pwBox = new PasswordField();
		grid.add(pwBox, 1, 2);

		Button btn = new Button("Sign in");
		HBox hbBtn = new HBox(10);
		hbBtn.setAlignment(Pos.BOTTOM_RIGHT);
		hbBtn.getChildren().add(btn);
		grid.add(hbBtn, 1, 4);

		final Text actiontarget = new Text();
			grid.add(actiontarget, 1, 6);

		btn.setOnAction(new EventHandler<ActionEvent>() {
		 
		    @Override
		    public void handle(ActionEvent e) {
			actiontarget.setFill(Color.FIREBRICK);

			actiontarget.setText("Sign in button pressed");
		    }
		});
		
		Scene scene = new Scene(grid, 300, 275);
		primaryStage.setScene(scene);

		scene.getStylesheets().add
		     (HelloWorld.class.getResource("HelloWorld.css").toExternalForm());

		primaryStage.show();
	}
}

Команды компиляции, запуска, упаковки в jar и запуска jar, каждая в своем CMD-файле, остались прежними.
Командная строка
@"C:\Program Files\Java\jdk1.7.0_40\bin\javac" -d out -classpath "C:\Program Files\Java\jre7\lib\jfxrt.jar" src\helloworld\HelloWorld.java
@"C:\Program Files\Java\jdk1.7.0_40\bin\java" -classpath "C:\Program Files\Java\jre7\lib\jfxrt.jar;.\out" helloworld.HelloWorld
@"C:\Program Files\Java\jdk1.7.0_40\bin\javafxpackager" -createjar -appclass helloworld.HelloWorld -srcdir .\out -outfile HelloWorld -v
@"C:\Program Files\Java\jre7\bin\java.exe" -jar HelloWorld.jar
@pause

Займемся третьим разделом. Туториал предлагает воспользоваться заготовкой из второго раздела. Так и сделаем, только он у нас не Login.java, а HelloWorld.java. Пришло время создавать CSS-файл.

Проигнорируем те пункты инструкции (в туториале), которые касаются работы с IDE. В четвертом пункте предлагается создать CSS-файл в папке src\login, в нашем случае получается src\helloworld. Ну, пока что так и сделаем, только обзовем его, раз уж продолжаем здороваться с миром, HelloWorld.css. Хоть это и не принципиально.

Шестой пункт рассказывает, какие изменения надо внести в код java: после строк
Scene scene = new Scene(grid, 300, 275);
primaryStage.setScene(scene);
надо вставить
scene.getStylesheets().add
 (HelloWorld.class.getResource("HelloWorld.css").toExternalForm());
(с поправкой на названия файлов и классов), аккурат перед
primaryStage.show();

Кроме того, чтобы текстовые объекты могли как-то управляться CSS-файлом, надо этим объектам дать соответствующие идентификаторы. Для этого надо найти строки
scenetitle.setFont(Font.font(“Tahoma”, FontWeight.NORMAL, 20));  
и
actiontarget.setFill(Color.FIREBRICK);
и заменить их на
scenetitle.setId("welcome-text");
и
actiontarget.setId("actiontarget");
соответственно. А лучше на всякий случай не заменять, а закрыть комментарием, а исправление записать строчкой ниже.

И, конечно, надо наполнить файл с таблицами стилей.
HelloWorld.css
.root {
     -fx-background-image: url("background.jpg");
}
.label {
    -fx-font-size: 12px;
    -fx-font-weight: bold;
    -fx-text-fill: #333333;
    -fx-effect: dropshadow( gaussian , rgba(255,255,255,0.5) , 0,0,0,1 );
}
#welcome-text {
   -fx-font-size: 32px;
   -fx-font-family: "Arial Black";
   -fx-fill: #818181;
   -fx-effect: innershadow( three-pass-box , rgba(0,0,0,0.7) , 6, 0.0 , 0 , 2 );
}
#actiontarget {
  -fx-fill: FIREBRICK;
  -fx-font-weight: bold;
  -fx-effect: dropshadow( gaussian , rgba(255,255,255,0.5) , 0,0,0,1 );  
}
.button {
    -fx-text-fill: white;
    -fx-font-family: "Arial Narrow";
    -fx-font-weight: bold;
    -fx-background-color: linear-gradient(#61a2b1, #2A5058);
    -fx-effect: dropshadow( three-pass-box , rgba(0,0,0,0.6) , 5, 0.0 , 0 , 1 );
}
.button:hover {
    -fx-background-color: linear-gradient(#2A5058, #61a2b1);
}

Наполнил сразу всем, что по тексту третьего раздела предлагается втыкать постепенно, любуясь результатом — фоновая картинка, эффекты текста, эффекты кнопки.

Ах да, надо же еще картинку для фонового изображения… Ну, я не стал скачивать или рисовать, а скопировал первую попавшуюся. Первой попалась фоновая (иронично, как раз к месту) из папки «Мои рисунки\Образцы рисунков» — Закат.jpg. Переименовал в background.jpg и положил рядом с CSS-файлом.

Компилируем. Успешно. Запускаем. Не работает! Изучаем вывод ошибок. Настораживает часто встречающееся Unknown source, стало быть, неизвестный источник. Плюс находятся знакомые буквы среди всего этого безобразия:
at helloworld.HelloWorld.start(HelloWorld.java:68)

А в шестьдесят восьмой строке у нас как раз выковыривание стилей из CSS-файла. Который положен по инструкции в src\helloworld. В контесте данного «исследования» я даже не хочу рассматривать, почему для NetBeans этот файл требуется именно в папке исходников (хотя и догадываюсь). Но, раз мы нигде не указывали путь к файлу стилей, он должен лежать непосредственно возле того, кто им пользуется. А запускается неупакованная программа из файла HelloWorld.class, который в out\helloworld\. Давайте туда и перебросим, вместе с картинкой.

Теперь компилируется и запускается. Правда, картинка фоновая сильно отличается от туториальной, и краснокирпичное сообщение о нажатой кнопке на красном фоне плохо видно, но это мелочи, это можно и поменять. Забавно другое — давайте теперь дадим команду упаковки в jar и попробуем jar-файл запустить.

Не запускается! А если запускали консольной командой, а не щелчком по самому файлу, то видно ошибки, и какие-то они знакомые… Давайте-ка залезем в jar, как будто это архив. Там лежит структура папок com\javafx\main, наличие которой обеспечивается утилитой javafxpackager, и META-INF с манифестом, это не очень интересно сейчас. А интересна папка helloworld, в которой нет HelloWorld.css, зато есть HelloWorld.bss! Выходит, упаковщик нахально, без нашей команды, переупаковал CSS в бинарный вид. Проверим? Давайте заменим расширение .css на .bss в шестьдесят восьмой строке, откомпилируем, но не будем сразу запускать, а упакуем и запустим сразу jar.

Теперь запускается. Но, какая досада, не запускается из HelloWorld.class! Не держать же два комплекта исходников для разных случаев. Тем более, bss как раз рекомендованы для ускорения загрузки. Добавим команду, которую запишем в файл css2bss.cmd:
@"C:\Program Files\Java\jdk1.7.0_40\bin\javafxpackager" -createbss -srcdir .\src\helloworld -outdir .\out\helloworld -srcfiles HelloWorld.css -v
@pause

Файлы CSS и фоновой картинки перекинем в src\helloworld\ (как от нас с самого начала и требовали), в файле исходника оставим в строке, где getStylesheets(), расширение .bss. Компилируем. Делаем bss. Упаковываем в jar. Теперь работают и запуск из HelloWorld.class, и из HelloWorld.jar. А разобраться в том, какие стили за что отвечают, и как их можно всяко-разно менять, предлагаю самостоятельно. Эта тема непосредственно к JavaFX не относится.

Ну и четвертый раздел туториала — FXML. Код программы изменяется настолько сильно, что проще переписать его заново. И даже создать копию проекта в соседней папке, например, GetStartFXML. Туда надо скопировать уже пять командных файлов и создать папки out и src\helloworld, а в последнюю положить исходник.
HelloWorld.java
package helloworld;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.*;
import javafx.fxml.*;

public class HelloWorld extends Application {
	public static void main(String[] args) {
		launch(args);
	}
	@Override
	public void start(Stage stage) throws Exception {
	    Parent root = FXMLLoader.load(getClass().getResource("fxml_example.fxml"));

	    Scene scene = new Scene(root, 300, 275);
	    stage.setTitle("FXML Welcome");
	    stage.setScene(scene); 
	    stage.show();
	
	}
}

Далее надо собрать файл fxml_example.fxml, разбросанный по четвертому разделу. Так как данные из него будут подхватываться на лету, как и из CSS-файла, стоит сделать его сразу же рядом с использующим его файлом HelloWorld.class, которого еще не существует. То есть, в out\helloworld, поэтому создадим вложенную в out папку самостоятельно, до первой компиляции.
fxml_example.fxml
<?xml version="1.0" encoding="UTF-8"?>

<?import java.net.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>

<GridPane fx:controller="helloworld.FXMLExampleController" xmlns:fx="http://javafx.com/fxml" alignment="center" hgap="10" vgap="10" styleClass="root">

    <padding><Insets top="25" right="25" bottom="10" left="25"/></padding> 

<Text id="welcome-text" text="Welcome" GridPane.columnIndex="0" GridPane.rowIndex="0" GridPane.columnSpan="2"/>

<Label text="User Name:" GridPane.columnIndex="0" GridPane.rowIndex="1"/>
<TextField GridPane.columnIndex="1" GridPane.rowIndex="1"/>
<Label text="Password:" GridPane.columnIndex="0" GridPane.rowIndex="2"/>
<PasswordField fx:id="passwordField" GridPane.columnIndex="1" GridPane.rowIndex="2"/>

<HBox spacing="10" alignment="bottom_right" GridPane.columnIndex="1" GridPane.rowIndex="4">
<Button text="Sign In" onAction="#handleSubmitButtonAction"/>
</HBox>
<Text fx:id="actiontarget" GridPane.columnIndex="1" GridPane.rowIndex="6"/>

<stylesheets>
<URL value="@HelloWorld.css" />
</stylesheets>

</GridPane>

Этот файл сразу включает в себя и работу с CSS, а чего тянуть-то? Поэтому положим рядом недавно созданные HelloWorld.css и background.jpg. Не хватает еще обработки событий кнопки. В четвертом разделе предлагается для этого использовать отдельный файл, упомянутый в FXML: GridPane fx:controller=«helloworld.FXMLExampleController».
FXMLExampleController.java
package helloworld;

import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.text.Text;

public class FXMLExampleController {
@FXML private Text actiontarget;
@FXML protected void handleSubmitButtonAction(ActionEvent event) {
actiontarget.setText("Sign in button pressed");
}
}

И конечно не надо забывать его тоже откомпилировать, для чего добавим в compile.cmd дополнительную строчку. Кроме того, я убрал «собаку» перед командами компиляции, чтобы видеть, что обе запустились и отработали:
"C:\Program Files\Java\jdk1.7.0_25\bin\javac" -d out -classpath "C:\Program Files\Java\jre7\lib\jfxrt.jar" src\helloworld\HelloWorld.java 
"C:\Program Files\Java\jdk1.7.0_40\bin\javac" -d out -classpath "C:\Program Files\Java\jre7\lib\jfxrt.jar" src\helloworld\FXMLExampleController.java
@pause

Компилируем. Собираем jar. Запускаем class или запускаем jar. Работает, отличий внешнего вида и поведения от предыдущего примера не видно. Но получилось разделить приложение на кучу модулей — отдельно главный класс, создающий сцену, отдельно описание формы, отдельно обработчик событий, отдельно стили элементов. Причем, таблицы стилей и описание формы можно менять без перекомпиляции класса, например, переставить местами элементы в ячейках таблицы GridPane или сменить фоновую картинку.

Однако, хоть и работает, но давайте заглянем в архив jar. Там, между прочим, нету файла HelloWorld.css, а есть .bss, как и в примере из третьего раздела. В fxml_example.fxml упоминается HelloWorld.css. Однако работает и class, и jar. Давайте, как в предыдущем примере, перебросим HelloWorld.css и background.jpg из out\helloworld в src\helloworld (FXML-файл оставим на месте). Компилируем. Делаем bss из css. Собираем jar. Запускаем class, а потом запускаем jar. Опять работает! Да что ж такое — ей вообще, что ли, без разницы? А давайте, раз расширение файла таблицы стилей не играет роли, в fxml_example.fxml сотрем его начисто? Вместо
<stylesheets>
<URL value="@HelloWorld.css" />
</stylesheets>
сделаем
<stylesheets>
<URL value="@HelloWorld" />
</stylesheets>

Но программе на самом деле наплевать на расширение файла стилей — ей только направление пальцем покажи, а там сама разберется. Забавно, правда?

Теперь есть полигон для издевательства — можно попробовать всякого рода подмены стилей, оформления, обработки событий и все такое. Можно попробовать запустить эту заготовку не на десктопе, а в браузере — немного об этом упомянуто в шестом разделе туториала. Но там немного и неинтересно, о разворачивании (deployment) приложений есть отдельное руководство. Пятый раздел тоже ворошить не хочу, на Хабре уже было неплохо сказано о визуальных эффектах JavaFX вот здесь, а приведенные в туториале примеры совершенно не лезут в рамки заготовок первых четырех разделов.

Тему HelloWorld в JavaFX на базе jfxpub-overview считаю исчерпанной.
Tags:
Hubs:
+5
Comments0

Articles